@compass-labs/widgets 0.1.39 → 0.1.40

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.mjs CHANGED
@@ -1,9 +1,10 @@
1
- import { createContext, forwardRef, useState, useCallback, useImperativeHandle, useContext, useEffect, useMemo, useRef } from 'react';
1
+ import { createContext, forwardRef, useState, useCallback, useImperativeHandle, useContext, useRef, useEffect, useMemo } from 'react';
2
2
  import { useQueryClient, useQuery, QueryClient, QueryClientProvider } from '@tanstack/react-query';
3
3
  import { CompassApiSDK } from '@compass-labs/api-sdk';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
5
  import { arbitrum, base, mainnet } from 'viem/chains';
6
- import { Wallet, Loader2, ArrowDownLeft, ArrowUpRight, X, Check, Copy, LogOut, ChevronDown, AlertCircle, ArrowRight, ExternalLink, ArrowDownUp, ArrowLeftRight, CheckCircle, TrendingUp, Plus, Minus, Circle, AlertTriangle, XCircle, TrendingDown, ChevronRight, ArrowUpCircle, ArrowDownCircle, RotateCcw, Equal, ChevronUp, Inbox, Percent, Clock, Calendar } from 'lucide-react';
6
+ import { Wallet, Loader2, ArrowDownLeft, ArrowUpRight, Send, ArrowRight, X, CheckCircle, AlertCircle, CreditCard, ExternalLink, XCircle, Check, Copy, LogOut, ChevronDown, ArrowDownUp, ArrowLeftRight, TrendingUp, Plus, Minus, Circle, AlertTriangle, TrendingDown, ChevronRight, ArrowUpCircle, ArrowDownCircle, RotateCcw, Equal, ChevronUp, Inbox, Percent, Clock, Calendar } from 'lucide-react';
7
+ import { isAddress, encodeFunctionData, parseUnits } from 'viem';
7
8
 
8
9
  // src/provider/CompassProvider.tsx
9
10
  var ApiContext = createContext(null);
@@ -90,7 +91,10 @@ var disconnectedWallet = {
90
91
  },
91
92
  switchChain: null,
92
93
  login: null,
93
- logout: null
94
+ logout: null,
95
+ fundWallet: null,
96
+ hasExternalWallet: true,
97
+ sendTransaction: null
94
98
  };
95
99
  function WalletProvider({ children, wallet }) {
96
100
  const value = wallet ? {
@@ -100,7 +104,10 @@ function WalletProvider({ children, wallet }) {
100
104
  signTypedData: wallet.signTypedData,
101
105
  switchChain: wallet.switchChain ?? null,
102
106
  login: wallet.login ?? null,
103
- logout: wallet.logout ?? null
107
+ logout: wallet.logout ?? null,
108
+ fundWallet: wallet.fundWallet ?? null,
109
+ hasExternalWallet: wallet.hasExternalWallet ?? true,
110
+ sendTransaction: wallet.sendTransaction ?? null
104
111
  } : disconnectedWallet;
105
112
  return /* @__PURE__ */ jsx(WalletContext.Provider, { value, children });
106
113
  }
@@ -1269,25 +1276,29 @@ function WalletStatus({
1269
1276
  );
1270
1277
  }
1271
1278
  function ActionModal({ isOpen, onClose, title, children }) {
1279
+ const modalRef = useRef(null);
1272
1280
  useEffect(() => {
1273
1281
  const handleEscape = (e) => {
1274
1282
  if (e.key === "Escape") onClose();
1275
1283
  };
1276
1284
  if (isOpen) {
1277
1285
  document.addEventListener("keydown", handleEscape);
1278
- document.body.style.overflow = "hidden";
1279
1286
  }
1280
1287
  return () => {
1281
1288
  document.removeEventListener("keydown", handleEscape);
1282
- document.body.style.overflow = "";
1283
1289
  };
1284
1290
  }, [isOpen, onClose]);
1291
+ const handleOverlayWheel = useCallback((e) => {
1292
+ if (modalRef.current && !modalRef.current.contains(e.target)) {
1293
+ window.scrollBy(0, e.deltaY);
1294
+ }
1295
+ }, []);
1285
1296
  if (!isOpen) return null;
1286
1297
  return /* @__PURE__ */ jsxs(
1287
1298
  "div",
1288
1299
  {
1289
1300
  className: "fixed inset-0 z-50 flex items-center justify-center p-4",
1290
- style: { overflowY: "auto", scrollbarWidth: "none" },
1301
+ onWheel: handleOverlayWheel,
1291
1302
  children: [
1292
1303
  /* @__PURE__ */ jsx(
1293
1304
  "div",
@@ -1300,21 +1311,27 @@ function ActionModal({ isOpen, onClose, title, children }) {
1300
1311
  /* @__PURE__ */ jsxs(
1301
1312
  "div",
1302
1313
  {
1314
+ ref: modalRef,
1303
1315
  className: "relative w-full max-w-md",
1304
1316
  style: {
1305
1317
  backgroundColor: "var(--compass-color-surface)",
1306
1318
  boxShadow: "var(--compass-shadow-lg)",
1307
1319
  borderRadius: "var(--compass-border-radius-xl)",
1308
- fontFamily: "var(--compass-font-family)"
1320
+ fontFamily: "var(--compass-font-family)",
1321
+ maxHeight: "85vh",
1322
+ overflowY: "auto",
1323
+ overscrollBehavior: "contain",
1324
+ scrollbarWidth: "none"
1309
1325
  },
1310
1326
  children: [
1311
1327
  /* @__PURE__ */ jsxs(
1312
1328
  "div",
1313
1329
  {
1314
- className: "flex items-center justify-between border-b",
1330
+ className: "flex items-center justify-between border-b sticky top-0 z-10",
1315
1331
  style: {
1316
1332
  borderColor: "var(--compass-color-border)",
1317
- padding: "calc(var(--compass-spacing-card) * 0.75) var(--compass-spacing-card)"
1333
+ padding: "calc(var(--compass-spacing-card) * 0.75) var(--compass-spacing-card)",
1334
+ backgroundColor: "var(--compass-color-surface)"
1318
1335
  },
1319
1336
  children: [
1320
1337
  /* @__PURE__ */ jsx(
@@ -2571,114 +2588,731 @@ function CreditAccountGuard({
2571
2588
  }
2572
2589
  );
2573
2590
  }
2574
- function AccountBalancesModal({
2575
- isOpen,
2576
- onClose,
2577
- balances,
2578
- totalUsdValue,
2579
- isLoading = false,
2580
- earnAccountAddress,
2581
- walletAddress
2582
- }) {
2583
- return /* @__PURE__ */ jsx(ActionModal, { isOpen, onClose, title: "Balance Breakdown", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
2584
- (walletAddress || earnAccountAddress) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
2585
- walletAddress && /* @__PURE__ */ jsx(CopyableAddress, { address: walletAddress, label: "Wallet" }),
2586
- earnAccountAddress && /* @__PURE__ */ jsx(CopyableAddress, { address: earnAccountAddress, label: "Product Account" })
2587
- ] }),
2588
- isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-4", children: /* @__PURE__ */ jsx(
2589
- Loader2,
2590
- {
2591
- size: 24,
2592
- className: "animate-spin",
2593
- style: { color: "var(--compass-color-primary)" }
2594
- }
2595
- ) }) : balances.length === 0 ? /* @__PURE__ */ jsx(
2596
- "div",
2591
+ var EVM_CHAIN_IDS = {
2592
+ ethereum: 1,
2593
+ base: 8453,
2594
+ arbitrum: 42161
2595
+ };
2596
+ function BuyForm({ targetAddress, defaultAsset = "USDC", onComplete }) {
2597
+ const { fundWallet } = useEmbeddableWallet();
2598
+ const { chainId } = useChain();
2599
+ const [amount, setAmount] = useState("");
2600
+ const [state, setState] = useState("idle");
2601
+ const [error, setError] = useState(null);
2602
+ const numericChainId = EVM_CHAIN_IDS[chainId] || 8453;
2603
+ const handleBuy = useCallback(async () => {
2604
+ if (!fundWallet || !amount || parseFloat(amount) <= 0) {
2605
+ return;
2606
+ }
2607
+ setState("buying");
2608
+ setError(null);
2609
+ try {
2610
+ await fundWallet({
2611
+ address: targetAddress,
2612
+ chainId: numericChainId,
2613
+ asset: defaultAsset,
2614
+ amount
2615
+ });
2616
+ setState("success");
2617
+ onComplete?.();
2618
+ setTimeout(() => setState("idle"), 3e3);
2619
+ } catch (err) {
2620
+ setState("error");
2621
+ setError(err instanceof Error ? err.message : "Purchase failed. Please try again.");
2622
+ }
2623
+ }, [fundWallet, amount, targetAddress, numericChainId, defaultAsset, onComplete]);
2624
+ const chainName = chainId.charAt(0).toUpperCase() + chainId.slice(1);
2625
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
2626
+ /* @__PURE__ */ jsxs(
2627
+ "p",
2597
2628
  {
2598
- className: "text-center py-4",
2599
- style: { color: "var(--compass-color-text-tertiary)" },
2600
- children: "No tokens in account"
2629
+ className: "text-sm font-medium",
2630
+ style: { color: "var(--compass-color-text)", fontFamily: "var(--compass-font-family)" },
2631
+ children: [
2632
+ "Buy ",
2633
+ defaultAsset,
2634
+ " on ",
2635
+ chainName
2636
+ ]
2601
2637
  }
2602
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
2603
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
2638
+ ),
2639
+ /* @__PURE__ */ jsxs("div", { children: [
2640
+ /* @__PURE__ */ jsx(
2641
+ "label",
2642
+ {
2643
+ className: "block text-sm mb-1.5",
2644
+ style: { color: "var(--compass-color-text-secondary)", fontFamily: "var(--compass-font-family)" },
2645
+ children: "Amount (USD)"
2646
+ }
2647
+ ),
2648
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2604
2649
  /* @__PURE__ */ jsx(
2605
2650
  "span",
2606
2651
  {
2607
- className: "text-xs font-medium uppercase tracking-wide",
2652
+ className: "absolute left-3 top-1/2 -translate-y-1/2 text-sm",
2608
2653
  style: { color: "var(--compass-color-text-tertiary)" },
2609
- children: "Available in Account"
2654
+ children: "$"
2610
2655
  }
2611
2656
  ),
2612
2657
  /* @__PURE__ */ jsx(
2613
- "div",
2658
+ "input",
2614
2659
  {
2615
- className: "flex flex-col gap-2",
2616
- style: {
2617
- maxHeight: "50vh",
2618
- overflowY: "auto",
2619
- scrollbarWidth: "none"
2660
+ type: "number",
2661
+ value: amount,
2662
+ onChange: (e) => {
2663
+ setAmount(e.target.value);
2664
+ if (state === "error") {
2665
+ setState("idle");
2666
+ setError(null);
2667
+ }
2620
2668
  },
2621
- children: balances.filter((token) => parseFloat(token.balance) >= 5e-3).map((token) => /* @__PURE__ */ jsxs(
2622
- "div",
2669
+ disabled: state === "buying",
2670
+ placeholder: "0.00",
2671
+ className: "w-full border px-3 py-2.5 pl-7 text-sm focus:outline-none disabled:opacity-50",
2672
+ style: {
2673
+ backgroundColor: "var(--compass-color-surface)",
2674
+ borderColor: "var(--compass-color-border)",
2675
+ borderRadius: "var(--compass-border-radius-lg)",
2676
+ color: "var(--compass-color-text)",
2677
+ fontFamily: "var(--compass-font-family)"
2678
+ }
2679
+ }
2680
+ )
2681
+ ] })
2682
+ ] }),
2683
+ /* @__PURE__ */ jsx(
2684
+ "p",
2685
+ {
2686
+ className: "text-xs",
2687
+ style: { color: "var(--compass-color-text-tertiary)", fontFamily: "var(--compass-font-family)" },
2688
+ children: "Buy crypto with card or bank transfer. Funds are sent directly to your account."
2689
+ }
2690
+ ),
2691
+ state === "success" && /* @__PURE__ */ jsxs(
2692
+ "div",
2693
+ {
2694
+ className: "flex items-center gap-2 p-3 text-sm",
2695
+ style: {
2696
+ backgroundColor: "var(--compass-color-success-muted, rgba(34,197,94,0.1))",
2697
+ color: "var(--compass-color-success)",
2698
+ borderRadius: "var(--compass-border-radius-lg)",
2699
+ fontFamily: "var(--compass-font-family)"
2700
+ },
2701
+ children: [
2702
+ /* @__PURE__ */ jsx(CheckCircle, { size: 16 }),
2703
+ "Purchase initiated. Funds may take a few minutes to arrive."
2704
+ ]
2705
+ }
2706
+ ),
2707
+ state === "error" && error && /* @__PURE__ */ jsxs(
2708
+ "div",
2709
+ {
2710
+ className: "flex items-center gap-2 p-3 text-sm",
2711
+ style: {
2712
+ backgroundColor: "var(--compass-color-error-muted, rgba(239,68,68,0.1))",
2713
+ color: "var(--compass-color-error)",
2714
+ borderRadius: "var(--compass-border-radius-lg)",
2715
+ fontFamily: "var(--compass-font-family)"
2716
+ },
2717
+ children: [
2718
+ /* @__PURE__ */ jsx(AlertCircle, { size: 16 }),
2719
+ error
2720
+ ]
2721
+ }
2722
+ ),
2723
+ /* @__PURE__ */ jsx(
2724
+ "button",
2725
+ {
2726
+ onClick: handleBuy,
2727
+ disabled: state === "buying" || !amount || parseFloat(amount) <= 0,
2728
+ className: "w-full py-3 px-4 text-sm font-medium flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
2729
+ style: {
2730
+ backgroundColor: "var(--compass-color-primary)",
2731
+ color: "white",
2732
+ borderRadius: "var(--compass-border-radius-xl)",
2733
+ fontFamily: "var(--compass-font-family)",
2734
+ transition: "var(--compass-transition-normal)"
2735
+ },
2736
+ children: state === "buying" ? /* @__PURE__ */ jsxs(Fragment, { children: [
2737
+ /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
2738
+ "Processing..."
2739
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2740
+ /* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4" }),
2741
+ "Buy with card / bank"
2742
+ ] })
2743
+ }
2744
+ )
2745
+ ] });
2746
+ }
2747
+ var BLOCK_EXPLORERS = {
2748
+ ethereum: "https://etherscan.io",
2749
+ base: "https://basescan.org",
2750
+ arbitrum: "https://arbiscan.io"
2751
+ };
2752
+ function TxStatus({ state }) {
2753
+ const { chainId } = useChain();
2754
+ if (state.status === "idle") return null;
2755
+ const explorer = BLOCK_EXPLORERS[chainId] || BLOCK_EXPLORERS.ethereum;
2756
+ return /* @__PURE__ */ jsxs(
2757
+ "div",
2758
+ {
2759
+ className: "mt-4 p-4 border",
2760
+ style: {
2761
+ borderColor: "var(--compass-color-border)",
2762
+ borderRadius: "var(--compass-border-radius-xl)",
2763
+ fontFamily: "var(--compass-font-family)"
2764
+ },
2765
+ children: [
2766
+ state.status === "preparing" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm", children: [
2767
+ /* @__PURE__ */ jsx(
2768
+ Loader2,
2769
+ {
2770
+ className: "h-4 w-4 animate-spin",
2771
+ style: { color: "var(--compass-color-primary)" }
2772
+ }
2773
+ ),
2774
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text-secondary)" }, children: "Preparing transaction..." })
2775
+ ] }),
2776
+ state.status === "signing" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm", children: [
2777
+ /* @__PURE__ */ jsx(
2778
+ Loader2,
2779
+ {
2780
+ className: "h-4 w-4 animate-spin",
2781
+ style: { color: "var(--compass-color-warning)" }
2782
+ }
2783
+ ),
2784
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text-secondary)" }, children: "Waiting for signature..." })
2785
+ ] }),
2786
+ state.status === "broadcasting" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm", children: [
2787
+ /* @__PURE__ */ jsx(
2788
+ Loader2,
2789
+ {
2790
+ className: "h-4 w-4 animate-spin",
2791
+ style: { color: "var(--compass-color-primary)" }
2792
+ }
2793
+ ),
2794
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text-secondary)" }, children: "Broadcasting transaction..." })
2795
+ ] }),
2796
+ state.status === "submitted" && /* @__PURE__ */ jsxs("div", { children: [
2797
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2798
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm", children: [
2799
+ /* @__PURE__ */ jsx(
2800
+ Loader2,
2801
+ {
2802
+ className: "h-4 w-4 animate-spin",
2803
+ style: { color: "var(--compass-color-primary)" }
2804
+ }
2805
+ ),
2806
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text)" }, children: "Transaction submitted" })
2807
+ ] }),
2808
+ /* @__PURE__ */ jsxs(
2809
+ "a",
2623
2810
  {
2624
- className: "flex items-center justify-between p-3 rounded-lg",
2625
- style: {
2626
- backgroundColor: "var(--compass-color-surface)",
2627
- border: "1px solid var(--compass-color-border)",
2628
- flexShrink: 0
2629
- },
2811
+ href: `${explorer}/tx/${state.txHash}`,
2812
+ target: "_blank",
2813
+ rel: "noopener noreferrer",
2814
+ className: "flex items-center gap-1.5 text-xs hover:opacity-70 transition-opacity",
2815
+ style: { color: "var(--compass-color-text-secondary)" },
2630
2816
  children: [
2631
- /* @__PURE__ */ jsx("span", { className: "font-medium", style: { color: "var(--compass-color-text)" }, children: token.symbol }),
2632
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end", children: [
2633
- /* @__PURE__ */ jsx("span", { className: "font-mono font-medium", style: { color: "var(--compass-color-text)" }, children: formatAmount(token.balance) }),
2634
- /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: formatUSD(token.usdValue) })
2635
- ] })
2817
+ "View on explorer",
2818
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
2636
2819
  ]
2637
- },
2638
- token.symbol
2639
- ))
2640
- }
2641
- )
2642
- ] }),
2643
- /* @__PURE__ */ jsxs(
2644
- "div",
2645
- {
2646
- className: "flex items-center justify-between pt-3 mt-2",
2647
- style: { borderTop: "2px solid var(--compass-color-border)" },
2648
- children: [
2649
- /* @__PURE__ */ jsx("span", { className: "font-semibold", style: { color: "var(--compass-color-text)" }, children: "Total" }),
2820
+ }
2821
+ )
2822
+ ] }),
2823
+ /* @__PURE__ */ jsx(
2824
+ "p",
2825
+ {
2826
+ className: "text-xs mt-2",
2827
+ style: { color: "var(--compass-color-text-tertiary)" },
2828
+ children: "Waiting for on-chain confirmation. Balances will update automatically."
2829
+ }
2830
+ )
2831
+ ] }),
2832
+ state.status === "confirmed" && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2833
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm", children: [
2650
2834
  /* @__PURE__ */ jsx(
2651
- "span",
2835
+ CheckCircle,
2652
2836
  {
2653
- className: "font-bold text-xl",
2654
- style: { color: "var(--compass-color-text)" },
2655
- children: formatUSD(totalUsdValue)
2837
+ className: "h-4 w-4",
2838
+ style: { color: "var(--compass-color-success)" }
2656
2839
  }
2657
- )
2658
- ]
2659
- }
2660
- )
2661
- ] })
2662
- ] }) });
2663
- }
2664
- var TRANSFER_TOKEN = "USDC";
2665
- var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
2666
- compact = false,
2667
- hideVisual = false,
2840
+ ),
2841
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-success)" }, children: "Transaction confirmed" })
2842
+ ] }),
2843
+ /* @__PURE__ */ jsxs(
2844
+ "a",
2845
+ {
2846
+ href: `${explorer}/tx/${state.txHash}`,
2847
+ target: "_blank",
2848
+ rel: "noopener noreferrer",
2849
+ className: "flex items-center gap-1.5 text-xs hover:opacity-70 transition-opacity",
2850
+ style: { color: "var(--compass-color-text-secondary)" },
2851
+ children: [
2852
+ "View on explorer",
2853
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
2854
+ ]
2855
+ }
2856
+ )
2857
+ ] }),
2858
+ state.status === "failed" && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 text-sm", children: [
2859
+ /* @__PURE__ */ jsx(
2860
+ XCircle,
2861
+ {
2862
+ className: "h-4 w-4 mt-0.5 shrink-0",
2863
+ style: { color: "var(--compass-color-error)" }
2864
+ }
2865
+ ),
2866
+ /* @__PURE__ */ jsx(
2867
+ "span",
2868
+ {
2869
+ className: "break-all",
2870
+ style: { color: "var(--compass-color-error)" },
2871
+ children: state.error
2872
+ }
2873
+ )
2874
+ ] })
2875
+ ]
2876
+ }
2877
+ );
2878
+ }
2879
+ function useTxPolling(options = {}) {
2880
+ const { chainId } = useChain();
2881
+ const queryClient = useQueryClient();
2882
+ const pollIntervalRef = useRef(null);
2883
+ const clearPolling = useCallback(() => {
2884
+ if (pollIntervalRef.current) {
2885
+ clearInterval(pollIntervalRef.current);
2886
+ pollIntervalRef.current = null;
2887
+ }
2888
+ }, []);
2889
+ useEffect(() => {
2890
+ return () => clearPolling();
2891
+ }, [clearPolling]);
2892
+ const startPolling = useCallback(
2893
+ (txHash, setTxState) => {
2894
+ clearPolling();
2895
+ let polls = 0;
2896
+ pollIntervalRef.current = setInterval(async () => {
2897
+ polls++;
2898
+ for (const key of options.queryKeysToInvalidate ?? []) {
2899
+ queryClient.invalidateQueries({ queryKey: key });
2900
+ }
2901
+ try {
2902
+ const res = await fetch(
2903
+ `/api/compass/tx/receipt?hash=${txHash}&chain=${chainId}`
2904
+ );
2905
+ const data = await res.json();
2906
+ if (data.status === "success") {
2907
+ clearPolling();
2908
+ setTxState({ status: "confirmed", txHash });
2909
+ return;
2910
+ } else if (data.status === "reverted") {
2911
+ clearPolling();
2912
+ setTxState({ status: "failed", error: "Transaction reverted" });
2913
+ return;
2914
+ }
2915
+ } catch (err) {
2916
+ if (err instanceof TypeError && err.message.includes("fetch")) ; else {
2917
+ console.warn("[useTxPolling] Unexpected error polling tx receipt:", err);
2918
+ }
2919
+ }
2920
+ if (polls >= 24) {
2921
+ clearPolling();
2922
+ setTxState({ status: "failed", error: "Unable to confirm transaction. Please check a block explorer to verify." });
2923
+ }
2924
+ }, 5e3);
2925
+ },
2926
+ [chainId, clearPolling, queryClient, options.queryKeysToInvalidate]
2927
+ );
2928
+ return { startPolling, clearPolling };
2929
+ }
2930
+ var USDC_ADDRESSES = {
2931
+ ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
2932
+ base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
2933
+ arbitrum: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
2934
+ };
2935
+ var ERC20_TRANSFER_ABI = [
2936
+ {
2937
+ type: "function",
2938
+ name: "transfer",
2939
+ inputs: [
2940
+ { name: "to", type: "address" },
2941
+ { name: "amount", type: "uint256" }
2942
+ ],
2943
+ outputs: [{ name: "", type: "bool" }],
2944
+ stateMutability: "nonpayable"
2945
+ }
2946
+ ];
2947
+ async function waitForReceipt(txHash, chainId) {
2948
+ const maxPolls = 60;
2949
+ for (let i = 0; i < maxPolls; i++) {
2950
+ await new Promise((resolve) => setTimeout(resolve, 3e3));
2951
+ try {
2952
+ const res = await fetch(`/api/compass/tx/receipt?hash=${txHash}&chain=${chainId}`);
2953
+ const data = await res.json();
2954
+ if (data.status === "success") return;
2955
+ if (data.status === "reverted") throw new Error("Withdrawal transaction reverted");
2956
+ } catch (err) {
2957
+ if (err instanceof Error && err.message.includes("reverted")) throw err;
2958
+ }
2959
+ }
2960
+ throw new Error("Unable to confirm withdrawal. Please check a block explorer to verify.");
2961
+ }
2962
+ function SendForm({ onComplete, product = "earn", productAccountAddress }) {
2963
+ const { sendTransaction, signTypedData, address } = useEmbeddableWallet();
2964
+ const { chainId } = useChain();
2965
+ const { earnAccountAddress, isDeployed } = useEarnAccount();
2966
+ const [recipient, setRecipient] = useState("");
2967
+ const [amount, setAmount] = useState("");
2968
+ const [txState, setTxState] = useState({ status: "idle" });
2969
+ const { startPolling } = useTxPolling({
2970
+ queryKeysToInvalidate: product === "credit" ? [["creditBalances"], ["creditPositions"], ["eoaBalances"]] : [["earnAccountBalances"], ["eoaBalances"]]
2971
+ });
2972
+ const { data: earnBalanceData } = useQuery({
2973
+ queryKey: ["earnAccountBalances", chainId, address],
2974
+ queryFn: async () => {
2975
+ if (!address) return null;
2976
+ const response = await fetch(
2977
+ `/api/compass/earn-account/balances?owner=${address}&chain=${chainId}`
2978
+ );
2979
+ if (!response.ok) return null;
2980
+ return response.json();
2981
+ },
2982
+ enabled: !!address && isDeployed && product === "earn",
2983
+ staleTime: 30 * 1e3
2984
+ });
2985
+ const { data: creditBalanceData } = useQuery({
2986
+ queryKey: ["creditBalances", productAccountAddress, chainId],
2987
+ queryFn: async () => {
2988
+ if (!productAccountAddress) return null;
2989
+ const response = await fetch(
2990
+ `/api/compass/credit/balances?owner=${productAccountAddress}&chain=${chainId}`
2991
+ );
2992
+ if (!response.ok) return null;
2993
+ return response.json();
2994
+ },
2995
+ enabled: !!productAccountAddress && product === "credit",
2996
+ staleTime: 30 * 1e3
2997
+ });
2998
+ const availableBalance = product === "credit" ? creditBalanceData?.find((b) => b.tokenSymbol === "USDC")?.amount || "0" : earnBalanceData?.balances?.["USDC"]?.balance || "0";
2999
+ const tokenAddress = USDC_ADDRESSES[chainId];
3000
+ const isValidAddress = recipient.length > 0 && isAddress(recipient);
3001
+ const isValidAmount = amount.length > 0 && parseFloat(amount) > 0;
3002
+ const exceedsBalance = isValidAmount && parseFloat(amount) > parseFloat(availableBalance);
3003
+ const isBusy = txState.status !== "idle" && txState.status !== "confirmed" && txState.status !== "failed";
3004
+ const canSend = isValidAddress && isValidAmount && !exceedsBalance && !!sendTransaction && !!signTypedData && !isBusy;
3005
+ const handleSend = useCallback(async () => {
3006
+ if (!sendTransaction || !signTypedData || !isValidAddress || !isValidAmount || !tokenAddress) return;
3007
+ try {
3008
+ setTxState({ status: "preparing" });
3009
+ const prepareRes = await fetch("/api/compass/transfer/prepare", {
3010
+ method: "POST",
3011
+ headers: { "Content-Type": "application/json" },
3012
+ body: JSON.stringify({
3013
+ owner: address,
3014
+ chain: chainId,
3015
+ token: "USDC",
3016
+ amount,
3017
+ action: "WITHDRAW",
3018
+ ...product === "credit" ? { product: "credit" } : {}
3019
+ })
3020
+ });
3021
+ if (!prepareRes.ok) {
3022
+ const errData = await prepareRes.json();
3023
+ throw new Error(errData.error || "Failed to prepare withdrawal");
3024
+ }
3025
+ const prepareData = await prepareRes.json();
3026
+ setTxState({ status: "signing" });
3027
+ const withdrawSig = await signTypedData({
3028
+ domain: prepareData.domain,
3029
+ types: prepareData.normalizedTypes,
3030
+ primaryType: prepareData.primaryType,
3031
+ message: prepareData.message
3032
+ });
3033
+ setTxState({ status: "broadcasting" });
3034
+ const executeRes = await fetch("/api/compass/transfer/execute", {
3035
+ method: "POST",
3036
+ headers: { "Content-Type": "application/json" },
3037
+ body: JSON.stringify({
3038
+ owner: address,
3039
+ chain: chainId,
3040
+ eip712: prepareData.eip712,
3041
+ signature: withdrawSig,
3042
+ ...product === "credit" ? { product: "credit" } : {}
3043
+ })
3044
+ });
3045
+ if (!executeRes.ok) {
3046
+ const errData = await executeRes.json();
3047
+ throw new Error(errData.error || "Withdrawal failed");
3048
+ }
3049
+ const { txHash: withdrawHash } = await executeRes.json();
3050
+ await waitForReceipt(withdrawHash, chainId);
3051
+ setTxState({ status: "signing" });
3052
+ const data = encodeFunctionData({
3053
+ abi: ERC20_TRANSFER_ABI,
3054
+ functionName: "transfer",
3055
+ args: [recipient, parseUnits(amount, 6)]
3056
+ });
3057
+ const sendHash = await sendTransaction({ to: tokenAddress, data });
3058
+ setTxState({ status: "submitted", txHash: sendHash });
3059
+ startPolling(sendHash, setTxState);
3060
+ setAmount("");
3061
+ setRecipient("");
3062
+ onComplete?.();
3063
+ } catch (err) {
3064
+ setTxState({ status: "failed", error: err instanceof Error ? err.message : "Transaction failed" });
3065
+ }
3066
+ }, [sendTransaction, signTypedData, recipient, amount, address, chainId, tokenAddress, isValidAddress, isValidAmount, onComplete, startPolling, product]);
3067
+ const chainName = chainId.charAt(0).toUpperCase() + chainId.slice(1);
3068
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
3069
+ /* @__PURE__ */ jsxs(
3070
+ "p",
3071
+ {
3072
+ className: "text-sm font-medium",
3073
+ style: { color: "var(--compass-color-text)", fontFamily: "var(--compass-font-family)" },
3074
+ children: [
3075
+ "Send USDC on ",
3076
+ chainName
3077
+ ]
3078
+ }
3079
+ ),
3080
+ /* @__PURE__ */ jsxs("div", { children: [
3081
+ /* @__PURE__ */ jsx(
3082
+ "label",
3083
+ {
3084
+ className: "block text-sm mb-1.5",
3085
+ style: { color: "var(--compass-color-text-secondary)", fontFamily: "var(--compass-font-family)" },
3086
+ children: "Recipient Address"
3087
+ }
3088
+ ),
3089
+ /* @__PURE__ */ jsx(
3090
+ "input",
3091
+ {
3092
+ type: "text",
3093
+ value: recipient,
3094
+ onChange: (e) => {
3095
+ setRecipient(e.target.value);
3096
+ if (txState.status === "failed") setTxState({ status: "idle" });
3097
+ },
3098
+ disabled: isBusy,
3099
+ placeholder: "0x...",
3100
+ className: "w-full border px-3 py-2.5 text-sm focus:outline-none disabled:opacity-50 font-mono",
3101
+ style: {
3102
+ backgroundColor: "var(--compass-color-surface)",
3103
+ borderColor: recipient && !isValidAddress ? "var(--compass-color-error)" : "var(--compass-color-border)",
3104
+ borderRadius: "var(--compass-border-radius-lg)",
3105
+ color: "var(--compass-color-text)",
3106
+ fontFamily: "var(--compass-font-family)"
3107
+ }
3108
+ }
3109
+ ),
3110
+ recipient && !isValidAddress && /* @__PURE__ */ jsx("p", { className: "text-xs mt-1", style: { color: "var(--compass-color-error)" }, children: "Invalid Ethereum address" }),
3111
+ isValidAddress && recipient.toLowerCase() === address?.toLowerCase() && /* @__PURE__ */ jsx("p", { className: "text-xs mt-1", style: { color: "var(--compass-color-warning, #f59e0b)" }, children: "This is your own wallet address" }),
3112
+ isValidAddress && earnAccountAddress && recipient.toLowerCase() === earnAccountAddress.toLowerCase() && /* @__PURE__ */ jsx("p", { className: "text-xs mt-1", style: { color: "var(--compass-color-warning, #f59e0b)" }, children: "This is your savings account address" })
3113
+ ] }),
3114
+ /* @__PURE__ */ jsxs("div", { children: [
3115
+ /* @__PURE__ */ jsx(
3116
+ "label",
3117
+ {
3118
+ className: "block text-sm mb-1.5",
3119
+ style: { color: "var(--compass-color-text-secondary)", fontFamily: "var(--compass-font-family)" },
3120
+ children: "Amount"
3121
+ }
3122
+ ),
3123
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3124
+ /* @__PURE__ */ jsx(
3125
+ "input",
3126
+ {
3127
+ type: "number",
3128
+ value: amount,
3129
+ onChange: (e) => {
3130
+ setAmount(e.target.value);
3131
+ if (txState.status === "failed") setTxState({ status: "idle" });
3132
+ },
3133
+ disabled: isBusy,
3134
+ placeholder: "0.00",
3135
+ className: "w-full border px-3 py-2.5 pr-14 text-sm focus:outline-none disabled:opacity-50",
3136
+ style: {
3137
+ backgroundColor: "var(--compass-color-surface)",
3138
+ borderColor: "var(--compass-color-border)",
3139
+ borderRadius: "var(--compass-border-radius-lg)",
3140
+ color: "var(--compass-color-text)",
3141
+ fontFamily: "var(--compass-font-family)"
3142
+ }
3143
+ }
3144
+ ),
3145
+ /* @__PURE__ */ jsx(
3146
+ "span",
3147
+ {
3148
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-xs",
3149
+ style: { color: "var(--compass-color-text-tertiary)" },
3150
+ children: "USDC"
3151
+ }
3152
+ )
3153
+ ] }),
3154
+ /* @__PURE__ */ jsxs(
3155
+ "button",
3156
+ {
3157
+ onClick: () => setAmount(availableBalance),
3158
+ className: "text-xs mt-1",
3159
+ style: { color: "var(--compass-color-primary)", cursor: "pointer", background: "none", border: "none", padding: 0 },
3160
+ children: [
3161
+ "Available: ",
3162
+ parseFloat(availableBalance).toFixed(2),
3163
+ " USDC"
3164
+ ]
3165
+ }
3166
+ ),
3167
+ exceedsBalance && /* @__PURE__ */ jsx("p", { className: "text-xs mt-1", style: { color: "var(--compass-color-error)" }, children: "Amount exceeds available balance" })
3168
+ ] }),
3169
+ /* @__PURE__ */ jsx(
3170
+ "p",
3171
+ {
3172
+ className: "text-xs",
3173
+ style: { color: "var(--compass-color-text-tertiary)", fontFamily: "var(--compass-font-family)" },
3174
+ children: "Send USDC from your account to any address. Requires 2 signatures. Gas fees are sponsored."
3175
+ }
3176
+ ),
3177
+ /* @__PURE__ */ jsx(TxStatus, { state: txState }),
3178
+ /* @__PURE__ */ jsx(
3179
+ "button",
3180
+ {
3181
+ onClick: handleSend,
3182
+ disabled: !canSend,
3183
+ className: "w-full py-3 px-4 text-sm font-medium flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
3184
+ style: {
3185
+ backgroundColor: "var(--compass-color-primary)",
3186
+ color: "white",
3187
+ borderRadius: "var(--compass-border-radius-xl)",
3188
+ fontFamily: "var(--compass-font-family)",
3189
+ transition: "var(--compass-transition-normal)"
3190
+ },
3191
+ children: isBusy ? /* @__PURE__ */ jsxs(Fragment, { children: [
3192
+ /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
3193
+ "Sending..."
3194
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3195
+ /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" }),
3196
+ "Send USDC"
3197
+ ] })
3198
+ }
3199
+ )
3200
+ ] });
3201
+ }
3202
+ function AccountBalancesModal({
3203
+ isOpen,
3204
+ onClose,
3205
+ balances,
3206
+ totalUsdValue,
3207
+ isLoading = false,
3208
+ earnAccountAddress,
3209
+ walletAddress
3210
+ }) {
3211
+ return /* @__PURE__ */ jsx(ActionModal, { isOpen, onClose, title: "Balance Breakdown", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
3212
+ (walletAddress || earnAccountAddress) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
3213
+ walletAddress && /* @__PURE__ */ jsx(CopyableAddress, { address: walletAddress, label: "Wallet" }),
3214
+ earnAccountAddress && /* @__PURE__ */ jsx(CopyableAddress, { address: earnAccountAddress, label: "Product Account" })
3215
+ ] }),
3216
+ isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-4", children: /* @__PURE__ */ jsx(
3217
+ Loader2,
3218
+ {
3219
+ size: 24,
3220
+ className: "animate-spin",
3221
+ style: { color: "var(--compass-color-primary)" }
3222
+ }
3223
+ ) }) : balances.length === 0 ? /* @__PURE__ */ jsx(
3224
+ "div",
3225
+ {
3226
+ className: "text-center py-4",
3227
+ style: { color: "var(--compass-color-text-tertiary)" },
3228
+ children: "No tokens in account"
3229
+ }
3230
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
3231
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
3232
+ /* @__PURE__ */ jsx(
3233
+ "span",
3234
+ {
3235
+ className: "text-xs font-medium uppercase tracking-wide",
3236
+ style: { color: "var(--compass-color-text-tertiary)" },
3237
+ children: "Available in Account"
3238
+ }
3239
+ ),
3240
+ /* @__PURE__ */ jsx(
3241
+ "div",
3242
+ {
3243
+ className: "flex flex-col gap-2",
3244
+ style: {
3245
+ maxHeight: "50vh",
3246
+ overflowY: "auto",
3247
+ scrollbarWidth: "none"
3248
+ },
3249
+ children: balances.filter((token) => parseFloat(token.balance) >= 5e-3).map((token) => /* @__PURE__ */ jsxs(
3250
+ "div",
3251
+ {
3252
+ className: "flex items-center justify-between p-3 rounded-lg",
3253
+ style: {
3254
+ backgroundColor: "var(--compass-color-surface)",
3255
+ border: "1px solid var(--compass-color-border)",
3256
+ flexShrink: 0
3257
+ },
3258
+ children: [
3259
+ /* @__PURE__ */ jsx("span", { className: "font-medium", style: { color: "var(--compass-color-text)" }, children: token.symbol }),
3260
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end", children: [
3261
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-medium", style: { color: "var(--compass-color-text)" }, children: formatAmount(token.balance) }),
3262
+ /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: formatUSD(token.usdValue) })
3263
+ ] })
3264
+ ]
3265
+ },
3266
+ token.symbol
3267
+ ))
3268
+ }
3269
+ )
3270
+ ] }),
3271
+ /* @__PURE__ */ jsxs(
3272
+ "div",
3273
+ {
3274
+ className: "flex items-center justify-between pt-3 mt-2",
3275
+ style: { borderTop: "2px solid var(--compass-color-border)" },
3276
+ children: [
3277
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", style: { color: "var(--compass-color-text)" }, children: "Total" }),
3278
+ /* @__PURE__ */ jsx(
3279
+ "span",
3280
+ {
3281
+ className: "font-bold text-xl",
3282
+ style: { color: "var(--compass-color-text)" },
3283
+ children: formatUSD(totalUsdValue)
3284
+ }
3285
+ )
3286
+ ]
3287
+ }
3288
+ )
3289
+ ] })
3290
+ ] }) });
3291
+ }
3292
+ var TRANSFER_TOKEN = "USDC";
3293
+ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
3294
+ compact = false,
3295
+ hideVisual = false,
2668
3296
  onTransferComplete
2669
3297
  }, ref) {
3298
+ const { address, isConnected, signTypedData, switchChain, walletChainId, fundWallet, hasExternalWallet, sendTransaction } = useEmbeddableWallet();
3299
+ const { chainId, chain } = useChain();
3300
+ const { earnAccountAddress, isDeployed } = useEarnAccount();
3301
+ const queryClient = useQueryClient();
3302
+ const showBuyTab = !!fundWallet;
3303
+ const showSendTab = !!sendTransaction && !hasExternalWallet;
3304
+ const buyOnly = !hasExternalWallet && showBuyTab && !sendTransaction;
2670
3305
  const [isModalOpen, setIsModalOpen] = useState(false);
2671
- const [activeAction, setActiveAction] = useState("deposit");
3306
+ const [activeAction, setActiveAction] = useState(
3307
+ !hasExternalWallet && fundWallet ? "buy" : "deposit"
3308
+ );
2672
3309
  const selectedToken = TRANSFER_TOKEN;
2673
3310
  const [amount, setAmount] = useState("");
2674
- const [transferState, setTransferState] = useState("idle");
2675
- const [statusMessage, setStatusMessage] = useState("");
2676
- const [error, setError] = useState(null);
3311
+ const [txState, setTxState] = useState({ status: "idle" });
2677
3312
  const [isBalancesModalOpen, setIsBalancesModalOpen] = useState(false);
2678
- const { address, isConnected, signTypedData, switchChain, walletChainId } = useEmbeddableWallet();
2679
- const { chainId, chain } = useChain();
2680
- const { earnAccountAddress, isDeployed } = useEarnAccount();
2681
- const queryClient = useQueryClient();
3313
+ const { startPolling, clearPolling } = useTxPolling({
3314
+ queryKeysToInvalidate: [["earnAccountBalances"], ["eoaBalances"]]
3315
+ });
2682
3316
  const { data: balanceData, isLoading: balancesLoading } = useQuery({
2683
3317
  queryKey: ["earnAccountBalances", chainId, address],
2684
3318
  queryFn: async () => {
@@ -2710,7 +3344,7 @@ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
2710
3344
  }
2711
3345
  return {};
2712
3346
  },
2713
- enabled: !!address && activeAction === "deposit",
3347
+ enabled: !!address && hasExternalWallet && activeAction === "deposit",
2714
3348
  staleTime: 30 * 1e3
2715
3349
  });
2716
3350
  const earnBalances = balanceData?.balances || {};
@@ -2729,10 +3363,9 @@ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
2729
3363
  }
2730
3364
  const resetForm = useCallback(() => {
2731
3365
  setAmount("");
2732
- setTransferState("idle");
2733
- setStatusMessage("");
2734
- setError(null);
2735
- }, []);
3366
+ setTxState({ status: "idle" });
3367
+ clearPolling();
3368
+ }, [clearPolling]);
2736
3369
  const handleOpenModal = () => {
2737
3370
  resetForm();
2738
3371
  setIsModalOpen(true);
@@ -2747,7 +3380,8 @@ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
2747
3380
  const handleActionChange = (action) => {
2748
3381
  setActiveAction(action);
2749
3382
  setAmount("");
2750
- setError(null);
3383
+ setTxState({ status: "idle" });
3384
+ clearPolling();
2751
3385
  };
2752
3386
  const getMaxBalance = () => {
2753
3387
  if (activeAction === "deposit") {
@@ -2757,30 +3391,26 @@ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
2757
3391
  };
2758
3392
  const handleTransfer = useCallback(async () => {
2759
3393
  if (!address || !amount || !signTypedData) return;
2760
- setError(null);
3394
+ setTxState({ status: "preparing" });
2761
3395
  try {
2762
3396
  const targetChainId = chain.viemChain.id;
2763
3397
  if (walletChainId !== void 0 && walletChainId !== targetChainId) {
2764
3398
  if (!switchChain) {
2765
3399
  throw new Error(`Please switch your wallet to ${chain.name} (chain ID ${targetChainId}) to continue. Your wallet is currently on chain ID ${walletChainId}.`);
2766
3400
  }
2767
- setStatusMessage(`Switching network to ${chain.name}...`);
2768
3401
  try {
2769
3402
  await switchChain(targetChainId);
2770
- } catch (switchError) {
3403
+ } catch {
2771
3404
  throw new Error(`Please switch your wallet to ${chain.name} to continue. Your wallet is on chain ID ${walletChainId}, but ${chain.name} requires chain ID ${targetChainId}.`);
2772
3405
  }
2773
3406
  } else if (switchChain && walletChainId === void 0) {
2774
- setStatusMessage("Switching network...");
2775
3407
  try {
2776
3408
  await switchChain(targetChainId);
2777
- } catch (switchError) {
3409
+ } catch {
2778
3410
  throw new Error(`Please switch your wallet to ${chain.name} to continue`);
2779
3411
  }
2780
3412
  }
2781
3413
  if (activeAction === "deposit") {
2782
- setTransferState("checking_approval");
2783
- setStatusMessage("Checking token approval...");
2784
3414
  const approveResponse = await fetch("/api/compass/transfer/approve", {
2785
3415
  method: "POST",
2786
3416
  headers: { "Content-Type": "application/json" },
@@ -2799,16 +3429,14 @@ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
2799
3429
  if (approvalData.requiresTransaction) {
2800
3430
  throw new Error("This token requires a transaction-based approval. Please approve manually.");
2801
3431
  }
2802
- setTransferState("awaiting_approval_signature");
2803
- setStatusMessage("Please sign the approval...");
3432
+ setTxState({ status: "signing" });
2804
3433
  const approvalSignature = await signTypedData({
2805
3434
  domain: approvalData.domain,
2806
3435
  types: approvalData.normalizedTypes,
2807
3436
  primaryType: "Permit",
2808
3437
  message: approvalData.message
2809
3438
  });
2810
- setTransferState("approving");
2811
- setStatusMessage("Executing approval...");
3439
+ setTxState({ status: "broadcasting" });
2812
3440
  const executeApprovalResponse = await fetch("/api/compass/transfer/execute", {
2813
3441
  method: "POST",
2814
3442
  headers: { "Content-Type": "application/json" },
@@ -2825,8 +3453,7 @@ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
2825
3453
  }
2826
3454
  }
2827
3455
  }
2828
- setTransferState("awaiting_transfer_signature");
2829
- setStatusMessage("Preparing transfer...");
3456
+ setTxState({ status: "signing" });
2830
3457
  const prepareResponse = await fetch("/api/compass/transfer/prepare", {
2831
3458
  method: "POST",
2832
3459
  headers: { "Content-Type": "application/json" },
@@ -2843,15 +3470,13 @@ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
2843
3470
  throw new Error(errData.error || "Failed to prepare transfer");
2844
3471
  }
2845
3472
  const prepareData = await prepareResponse.json();
2846
- setStatusMessage("Please sign the transfer...");
2847
3473
  const transferSignature = await signTypedData({
2848
3474
  domain: prepareData.domain,
2849
3475
  types: prepareData.normalizedTypes,
2850
3476
  primaryType: prepareData.primaryType,
2851
3477
  message: prepareData.message
2852
3478
  });
2853
- setTransferState("transferring");
2854
- setStatusMessage("Executing transfer...");
3479
+ setTxState({ status: "broadcasting" });
2855
3480
  const executeResponse = await fetch("/api/compass/transfer/execute", {
2856
3481
  method: "POST",
2857
3482
  headers: { "Content-Type": "application/json" },
@@ -2867,17 +3492,12 @@ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
2867
3492
  throw new Error(errData.error || "Transfer failed");
2868
3493
  }
2869
3494
  const { txHash } = await executeResponse.json();
2870
- setTransferState("success");
2871
- setStatusMessage("Transfer successful!");
3495
+ setTxState({ status: "submitted", txHash });
3496
+ startPolling(txHash, setTxState);
2872
3497
  onTransferComplete?.(activeAction, selectedToken, amount, txHash);
2873
- queryClient.invalidateQueries({ queryKey: ["earnAccountBalances"] });
2874
- queryClient.invalidateQueries({ queryKey: ["eoaBalances"] });
2875
- setTimeout(() => {
2876
- resetForm();
2877
- }, 2e3);
3498
+ setAmount("");
2878
3499
  } catch (err) {
2879
- setTransferState("error");
2880
- setError(err instanceof Error ? err.message : "Transfer failed");
3500
+ setTxState({ status: "failed", error: err instanceof Error ? err.message : "Transfer failed" });
2881
3501
  }
2882
3502
  }, [
2883
3503
  address,
@@ -2889,14 +3509,13 @@ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
2889
3509
  walletChainId,
2890
3510
  signTypedData,
2891
3511
  switchChain,
2892
- queryClient,
2893
3512
  onTransferComplete,
2894
- resetForm
3513
+ startPolling
2895
3514
  ]);
2896
3515
  if (!isConnected) {
2897
3516
  return null;
2898
3517
  }
2899
- const isProcessing = transferState !== "idle" && transferState !== "success" && transferState !== "error";
3518
+ const isProcessing = txState.status !== "idle" && txState.status !== "confirmed" && txState.status !== "failed";
2900
3519
  if (!isDeployed && !hideVisual) {
2901
3520
  return /* @__PURE__ */ jsxs(
2902
3521
  "div",
@@ -2980,9 +3599,9 @@ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
2980
3599
  {
2981
3600
  isOpen: isModalOpen,
2982
3601
  onClose: handleCloseModal,
2983
- title: "Transfer Funds",
3602
+ title: showSendTab ? "Manage Funds" : buyOnly ? "Add Funds" : "Transfer Funds",
2984
3603
  children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
2985
- /* @__PURE__ */ jsx(
3604
+ buyOnly ? null : /* @__PURE__ */ jsx(
2986
3605
  "div",
2987
3606
  {
2988
3607
  className: "flex gap-1 p-1",
@@ -2991,7 +3610,9 @@ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
2991
3610
  borderRadius: "var(--compass-border-radius-lg)",
2992
3611
  fontFamily: "var(--compass-font-family)"
2993
3612
  },
2994
- children: ["deposit", "withdraw"].map((action) => /* @__PURE__ */ jsxs(
3613
+ children: [
3614
+ ...showSendTab ? ["buy", "send"] : ["deposit", "withdraw", ...showBuyTab ? ["buy"] : []]
3615
+ ].map((action) => /* @__PURE__ */ jsxs(
2995
3616
  "button",
2996
3617
  {
2997
3618
  onClick: () => handleActionChange(action),
@@ -3003,7 +3624,7 @@ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
3003
3624
  borderRadius: "var(--compass-border-radius-md)"
3004
3625
  },
3005
3626
  children: [
3006
- action === "deposit" ? /* @__PURE__ */ jsx(ArrowDownLeft, { size: 14 }) : /* @__PURE__ */ jsx(ArrowUpRight, { size: 14 }),
3627
+ action === "deposit" ? /* @__PURE__ */ jsx(ArrowDownLeft, { size: 14 }) : action === "withdraw" ? /* @__PURE__ */ jsx(ArrowUpRight, { size: 14 }) : action === "send" ? /* @__PURE__ */ jsx(Send, { size: 14 }) : /* @__PURE__ */ jsx(ArrowRight, { size: 14 }),
3007
3628
  action
3008
3629
  ]
3009
3630
  },
@@ -3011,120 +3632,116 @@ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
3011
3632
  ))
3012
3633
  }
3013
3634
  ),
3014
- /* @__PURE__ */ jsxs("div", { children: [
3015
- /* @__PURE__ */ jsx(
3016
- "label",
3017
- {
3018
- className: "text-sm font-medium mb-1 block",
3019
- style: { color: "var(--compass-color-text-secondary)" },
3020
- children: "Amount"
3635
+ activeAction === "send" ? /* @__PURE__ */ jsx(
3636
+ SendForm,
3637
+ {
3638
+ onComplete: () => {
3639
+ queryClient.invalidateQueries({ queryKey: ["earnAccountBalances"] });
3640
+ queryClient.invalidateQueries({ queryKey: ["eoaBalances"] });
3021
3641
  }
3022
- ),
3023
- /* @__PURE__ */ jsxs(
3024
- "div",
3642
+ }
3643
+ ) : activeAction === "buy" || buyOnly ? /* @__PURE__ */ jsx(
3644
+ BuyForm,
3645
+ {
3646
+ targetAddress: earnAccountAddress || "",
3647
+ onComplete: () => {
3648
+ queryClient.invalidateQueries({ queryKey: ["earnAccountBalances"] });
3649
+ queryClient.invalidateQueries({ queryKey: ["eoaBalances"] });
3650
+ }
3651
+ }
3652
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
3653
+ /* @__PURE__ */ jsxs("div", { children: [
3654
+ /* @__PURE__ */ jsx(
3655
+ "label",
3656
+ {
3657
+ className: "text-sm font-medium mb-1 block",
3658
+ style: { color: "var(--compass-color-text-secondary)" },
3659
+ children: "Amount"
3660
+ }
3661
+ ),
3662
+ /* @__PURE__ */ jsxs(
3663
+ "div",
3664
+ {
3665
+ className: "flex items-center gap-2 p-3 border",
3666
+ style: {
3667
+ backgroundColor: "var(--compass-color-background)",
3668
+ borderColor: "var(--compass-color-border)",
3669
+ borderRadius: "var(--compass-border-radius-lg)"
3670
+ },
3671
+ children: [
3672
+ /* @__PURE__ */ jsx(
3673
+ "input",
3674
+ {
3675
+ type: "number",
3676
+ value: amount,
3677
+ onChange: (e) => {
3678
+ setAmount(e.target.value);
3679
+ if (txState.status === "failed") setTxState({ status: "idle" });
3680
+ },
3681
+ placeholder: "0.00",
3682
+ disabled: isProcessing,
3683
+ className: "flex-1 bg-transparent outline-none text-lg font-mono",
3684
+ style: { color: "var(--compass-color-text)" }
3685
+ }
3686
+ ),
3687
+ /* @__PURE__ */ jsx(
3688
+ "span",
3689
+ {
3690
+ className: "text-sm font-medium",
3691
+ style: { color: "var(--compass-color-text-secondary)" },
3692
+ children: "USDC"
3693
+ }
3694
+ )
3695
+ ]
3696
+ }
3697
+ ),
3698
+ /* @__PURE__ */ jsxs(
3699
+ "button",
3700
+ {
3701
+ onClick: () => setAmount(formatAmount(getMaxBalance())),
3702
+ className: "text-xs mt-1",
3703
+ style: { color: "var(--compass-color-primary)", cursor: "pointer", background: "none", border: "none", padding: 0 },
3704
+ children: [
3705
+ "Available: ",
3706
+ formatAmount(getMaxBalance()),
3707
+ " USDC"
3708
+ ]
3709
+ }
3710
+ )
3711
+ ] }),
3712
+ /* @__PURE__ */ jsx("p", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: activeAction === "deposit" ? `Approves the token transfer, then transfers USDC from your ${!hasExternalWallet ? "embedded " : ""}wallet into your savings account. Requires 2 signatures.${!hasExternalWallet ? " Note: deposit and withdraw use your embedded wallet, not your bank account." : ""}` : `Moves USDC from your savings account back to your ${!hasExternalWallet ? "embedded " : ""}wallet. Requires 1 signature.${!hasExternalWallet ? " Gas fees are sponsored." : ""}` }),
3713
+ /* @__PURE__ */ jsx(TxStatus, { state: txState }),
3714
+ /* @__PURE__ */ jsx(
3715
+ "button",
3025
3716
  {
3026
- className: "flex items-center gap-2 p-3 border",
3717
+ onClick: handleTransfer,
3718
+ disabled: isProcessing || !amount || parseFloat(amount) <= 0,
3719
+ className: "w-full py-3 font-medium transition-colors flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
3027
3720
  style: {
3028
- backgroundColor: "var(--compass-color-background)",
3029
- borderColor: "var(--compass-color-border)",
3030
- borderRadius: "var(--compass-border-radius-lg)"
3721
+ backgroundColor: "var(--compass-color-primary)",
3722
+ color: "var(--compass-color-primary-text)",
3723
+ borderRadius: "var(--compass-border-radius-lg)",
3724
+ fontFamily: "var(--compass-font-family)"
3031
3725
  },
3032
- children: [
3033
- /* @__PURE__ */ jsx(
3034
- "input",
3035
- {
3036
- type: "number",
3037
- value: amount,
3038
- onChange: (e) => {
3039
- setAmount(e.target.value);
3040
- setError(null);
3041
- },
3042
- placeholder: "0.00",
3043
- disabled: isProcessing,
3044
- className: "flex-1 bg-transparent outline-none text-lg font-mono",
3045
- style: { color: "var(--compass-color-text)" }
3046
- }
3047
- ),
3048
- /* @__PURE__ */ jsx(
3049
- "span",
3050
- {
3051
- className: "text-sm font-medium",
3052
- style: { color: "var(--compass-color-text-secondary)" },
3053
- children: "USDC"
3054
- }
3055
- )
3056
- ]
3726
+ children: isProcessing ? /* @__PURE__ */ jsxs(Fragment, { children: [
3727
+ /* @__PURE__ */ jsx(Loader2, { size: 18, className: "animate-spin" }),
3728
+ " Processing..."
3729
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3730
+ /* @__PURE__ */ jsx(ArrowUpRight, { size: 16 }),
3731
+ " ",
3732
+ activeAction === "deposit" ? "Transfer to Savings Account" : "Withdraw to Wallet"
3733
+ ] })
3057
3734
  }
3058
- ),
3059
- /* @__PURE__ */ jsxs(
3060
- "button",
3735
+ ),
3736
+ /* @__PURE__ */ jsx(
3737
+ "p",
3061
3738
  {
3062
- onClick: () => setAmount(formatAmount(getMaxBalance())),
3063
- className: "text-xs mt-1",
3064
- style: { color: "var(--compass-color-primary)", cursor: "pointer", background: "none", border: "none", padding: 0 },
3065
- children: [
3066
- "Available: ",
3067
- formatAmount(getMaxBalance()),
3068
- " USDC"
3069
- ]
3739
+ className: "text-xs text-center",
3740
+ style: { color: "var(--compass-color-text-tertiary)" },
3741
+ children: "Gas fees are sponsored by Compass"
3070
3742
  }
3071
3743
  )
3072
- ] }),
3073
- /* @__PURE__ */ jsx("p", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: activeAction === "deposit" ? "Approves the token transfer, then transfers USDC from your wallet into your savings account. Requires 2 signatures." : "Withdraws USDC from your savings account back to your wallet. Requires 1 signature." }),
3074
- error && /* @__PURE__ */ jsx(
3075
- "div",
3076
- {
3077
- className: "p-3 text-sm",
3078
- style: {
3079
- backgroundColor: "var(--compass-color-error-muted)",
3080
- color: "var(--compass-color-error)",
3081
- borderRadius: "var(--compass-border-radius-lg)"
3082
- },
3083
- children: error
3084
- }
3085
- ),
3086
- statusMessage && !error && /* @__PURE__ */ jsx(
3087
- "div",
3088
- {
3089
- className: "p-3 text-sm text-center",
3090
- style: {
3091
- backgroundColor: transferState === "success" ? "var(--compass-color-success-muted)" : "var(--compass-color-primary-muted)",
3092
- color: transferState === "success" ? "var(--compass-color-success)" : "var(--compass-color-primary)",
3093
- borderRadius: "var(--compass-border-radius-lg)"
3094
- },
3095
- children: statusMessage
3096
- }
3097
- ),
3098
- /* @__PURE__ */ jsx(
3099
- "button",
3100
- {
3101
- onClick: handleTransfer,
3102
- disabled: isProcessing || !amount || parseFloat(amount) <= 0,
3103
- className: "w-full py-3 font-medium transition-colors flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
3104
- style: {
3105
- backgroundColor: "var(--compass-color-primary)",
3106
- color: "var(--compass-color-primary-text)",
3107
- borderRadius: "var(--compass-border-radius-lg)",
3108
- fontFamily: "var(--compass-font-family)"
3109
- },
3110
- children: isProcessing ? /* @__PURE__ */ jsxs(Fragment, { children: [
3111
- /* @__PURE__ */ jsx(Loader2, { size: 18, className: "animate-spin" }),
3112
- " Processing..."
3113
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3114
- /* @__PURE__ */ jsx(ArrowUpRight, { size: 16 }),
3115
- " ",
3116
- activeAction === "deposit" ? "Transfer to Savings Account" : "Withdraw to Wallet"
3117
- ] })
3118
- }
3119
- ),
3120
- /* @__PURE__ */ jsx(
3121
- "p",
3122
- {
3123
- className: "text-xs text-center",
3124
- style: { color: "var(--compass-color-text-tertiary)" },
3125
- children: "Gas fees are sponsored by Compass"
3126
- }
3127
- )
3744
+ ] })
3128
3745
  ] })
3129
3746
  }
3130
3747
  )
@@ -3179,7 +3796,7 @@ function truncate(value, decimals) {
3179
3796
  const factor = Math.pow(10, decimals);
3180
3797
  return (Math.floor(value * factor) / factor).toFixed(decimals);
3181
3798
  }
3182
- var BLOCK_EXPLORERS = {
3799
+ var BLOCK_EXPLORERS2 = {
3183
3800
  ethereum: "https://etherscan.io",
3184
3801
  base: "https://basescan.org",
3185
3802
  arbitrum: "https://arbiscan.io"
@@ -3650,7 +4267,7 @@ function SwapForm({
3650
4267
  /* @__PURE__ */ jsxs(
3651
4268
  "a",
3652
4269
  {
3653
- href: `${BLOCK_EXPLORERS[chainId] || BLOCK_EXPLORERS.ethereum}/tx/${lastTxHash}`,
4270
+ href: `${BLOCK_EXPLORERS2[chainId] || BLOCK_EXPLORERS2.ethereum}/tx/${lastTxHash}`,
3654
4271
  target: "_blank",
3655
4272
  rel: "noopener noreferrer",
3656
4273
  style: { display: "flex", alignItems: "center", gap: "4px", color: "var(--compass-color-text-secondary)", fontSize: "0.75rem", textDecoration: "none" },
@@ -3840,8 +4457,7 @@ function MarketSelector({
3840
4457
  },
3841
4458
  children: [
3842
4459
  getTypeLabel(selectedMarket.type),
3843
- " \xB7 ",
3844
- formatTvl(selectedMarket.tvl)
4460
+ selectedMarket.type !== "aave" ? ` \xB7 ${formatTvl(selectedMarket.tvl)}` : ""
3845
4461
  ]
3846
4462
  }
3847
4463
  )
@@ -3947,8 +4563,7 @@ function MarketSelector({
3947
4563
  },
3948
4564
  children: [
3949
4565
  getTypeLabel(market.type),
3950
- " \xB7 ",
3951
- formatTvl(market.tvl)
4566
+ market.type !== "aave" ? ` \xB7 ${formatTvl(market.tvl)}` : ""
3952
4567
  ]
3953
4568
  }
3954
4569
  )
@@ -3977,7 +4592,7 @@ function MarketSelector({
3977
4592
  )
3978
4593
  ] });
3979
4594
  }
3980
- var BLOCK_EXPLORERS2 = {
4595
+ var BLOCK_EXPLORERS3 = {
3981
4596
  ethereum: "https://etherscan.io",
3982
4597
  base: "https://basescan.org",
3983
4598
  arbitrum: "https://arbiscan.io"
@@ -4139,7 +4754,7 @@ function PositionCard({ position, chainId }) {
4139
4754
  tx.txHash && /* @__PURE__ */ jsx(
4140
4755
  "a",
4141
4756
  {
4142
- href: `${BLOCK_EXPLORERS2[chainId || "ethereum"] || BLOCK_EXPLORERS2.ethereum}/tx/${tx.txHash}`,
4757
+ href: `${BLOCK_EXPLORERS3[chainId || "ethereum"] || BLOCK_EXPLORERS3.ethereum}/tx/${tx.txHash}`,
4143
4758
  target: "_blank",
4144
4759
  rel: "noopener noreferrer",
4145
4760
  style: { color: "var(--compass-color-text-tertiary)", lineHeight: 0 },
@@ -4214,220 +4829,88 @@ function EarningsModal({ isOpen, onClose, positions, totalEarned, isLoading, cha
4214
4829
  padding: "6px",
4215
4830
  color: "var(--compass-color-text-secondary)",
4216
4831
  borderRadius: "8px",
4217
- display: "flex",
4218
- alignItems: "center",
4219
- justifyContent: "center"
4220
- },
4221
- children: /* @__PURE__ */ jsx(X, { size: 16 })
4222
- }
4223
- )
4224
- ] }),
4225
- /* @__PURE__ */ jsxs("div", { style: {
4226
- padding: "16px 20px",
4227
- margin: "0 16px",
4228
- backgroundColor: "var(--compass-color-surface)",
4229
- borderRadius: "14px"
4230
- }, children: [
4231
- /* @__PURE__ */ jsxs("div", { style: {
4232
- display: "flex",
4233
- alignItems: "center",
4234
- gap: "8px",
4235
- marginBottom: "8px"
4236
- }, children: [
4237
- /* @__PURE__ */ jsx("div", { style: {
4238
- width: "28px",
4239
- height: "28px",
4240
- borderRadius: "50%",
4241
- backgroundColor: totalEarned >= 0 ? "var(--compass-color-success-muted)" : "var(--compass-color-error-muted)",
4242
- display: "flex",
4243
- alignItems: "center",
4244
- justifyContent: "center"
4245
- }, children: totalEarned >= 0 ? /* @__PURE__ */ jsx(TrendingUp, { size: 14, style: { color: "var(--compass-color-success)" } }) : /* @__PURE__ */ jsx(TrendingDown, { size: 14, style: { color: "var(--compass-color-error)" } }) }),
4246
- /* @__PURE__ */ jsxs("div", { children: [
4247
- /* @__PURE__ */ jsx("div", { style: { fontSize: "0.6875rem", color: "var(--compass-color-text-tertiary)" }, children: "Total P&L" }),
4248
- /* @__PURE__ */ jsxs("div", { style: {
4249
- fontSize: "1.25rem",
4250
- fontWeight: 700,
4251
- color: totalEarned >= 0 ? "var(--compass-color-success)" : "var(--compass-color-error)",
4252
- letterSpacing: "-0.02em"
4253
- }, children: [
4254
- totalEarned >= 0 ? "+" : "",
4255
- formatCurrency(totalEarned)
4256
- ] })
4257
- ] })
4258
- ] }),
4259
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "16px" }, children: [
4260
- /* @__PURE__ */ jsxs("div", { children: [
4261
- /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: "var(--compass-color-text-tertiary)" }, children: "Unrealised " }),
4262
- /* @__PURE__ */ jsxs("span", { style: {
4263
- fontSize: "0.8125rem",
4264
- fontWeight: 600,
4265
- color: totalUnrealized >= 0 ? "var(--compass-color-success)" : "var(--compass-color-error)"
4266
- }, children: [
4267
- totalUnrealized >= 0 ? "+" : "",
4268
- formatCurrency(totalUnrealized)
4269
- ] })
4270
- ] }),
4271
- /* @__PURE__ */ jsxs("div", { children: [
4272
- /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: "var(--compass-color-text-tertiary)" }, children: "Realised " }),
4273
- /* @__PURE__ */ jsxs("span", { style: {
4274
- fontSize: "0.8125rem",
4275
- fontWeight: 600,
4276
- color: totalRealized >= 0 ? "var(--compass-color-success)" : "var(--compass-color-error)"
4277
- }, children: [
4278
- totalRealized >= 0 ? "+" : "",
4279
- formatCurrency(totalRealized)
4280
- ] })
4281
- ] })
4282
- ] })
4283
- ] }),
4284
- /* @__PURE__ */ jsx("div", { style: {
4285
- flex: 1,
4286
- overflowY: "auto",
4287
- scrollbarWidth: "none",
4288
- padding: "16px",
4289
- display: "flex",
4290
- flexDirection: "column",
4291
- gap: "10px"
4292
- }, children: isLoading ? /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: "32px 0", color: "var(--compass-color-text-tertiary)", fontSize: "0.875rem" }, children: "Loading positions..." }) : positions.length === 0 ? /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: "32px 0" }, children: [
4293
- /* @__PURE__ */ jsx("p", { style: { color: "var(--compass-color-text-secondary)", fontSize: "0.9375rem", margin: "0 0 4px" }, children: "No active positions yet" }),
4294
- /* @__PURE__ */ jsx("p", { style: { color: "var(--compass-color-text-tertiary)", fontSize: "0.8125rem", margin: 0 }, children: "Deposit into a market to start earning" })
4295
- ] }) : positions.map((position) => /* @__PURE__ */ jsx(PositionCard, { position, chainId }, position.id)) })
4296
- ]
4297
- }
4298
- )
4299
- }
4300
- );
4301
- }
4302
- var BLOCK_EXPLORERS3 = {
4303
- ethereum: "https://etherscan.io",
4304
- base: "https://basescan.org",
4305
- arbitrum: "https://arbiscan.io"
4306
- };
4307
- function TxStatus({ state }) {
4308
- const { chainId } = useChain();
4309
- if (state.status === "idle") return null;
4310
- const explorer = BLOCK_EXPLORERS3[chainId] || BLOCK_EXPLORERS3.ethereum;
4311
- return /* @__PURE__ */ jsxs(
4312
- "div",
4313
- {
4314
- className: "mt-4 p-4 border",
4315
- style: {
4316
- borderColor: "var(--compass-color-border)",
4317
- borderRadius: "var(--compass-border-radius-xl)",
4318
- fontFamily: "var(--compass-font-family)"
4319
- },
4320
- children: [
4321
- state.status === "preparing" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm", children: [
4322
- /* @__PURE__ */ jsx(
4323
- Loader2,
4324
- {
4325
- className: "h-4 w-4 animate-spin",
4326
- style: { color: "var(--compass-color-primary)" }
4327
- }
4328
- ),
4329
- /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text-secondary)" }, children: "Preparing transaction..." })
4330
- ] }),
4331
- state.status === "signing" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm", children: [
4332
- /* @__PURE__ */ jsx(
4333
- Loader2,
4334
- {
4335
- className: "h-4 w-4 animate-spin",
4336
- style: { color: "var(--compass-color-warning)" }
4337
- }
4338
- ),
4339
- /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text-secondary)" }, children: "Waiting for signature..." })
4340
- ] }),
4341
- state.status === "broadcasting" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm", children: [
4342
- /* @__PURE__ */ jsx(
4343
- Loader2,
4344
- {
4345
- className: "h-4 w-4 animate-spin",
4346
- style: { color: "var(--compass-color-primary)" }
4347
- }
4348
- ),
4349
- /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text-secondary)" }, children: "Broadcasting transaction..." })
4350
- ] }),
4351
- state.status === "submitted" && /* @__PURE__ */ jsxs("div", { children: [
4352
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
4353
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm", children: [
4354
- /* @__PURE__ */ jsx(
4355
- Loader2,
4356
- {
4357
- className: "h-4 w-4 animate-spin",
4358
- style: { color: "var(--compass-color-primary)" }
4359
- }
4360
- ),
4361
- /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text)" }, children: "Transaction submitted" })
4362
- ] }),
4363
- /* @__PURE__ */ jsxs(
4364
- "a",
4365
- {
4366
- href: `${explorer}/tx/${state.txHash}`,
4367
- target: "_blank",
4368
- rel: "noopener noreferrer",
4369
- className: "flex items-center gap-1.5 text-xs hover:opacity-70 transition-opacity",
4370
- style: { color: "var(--compass-color-text-secondary)" },
4371
- children: [
4372
- "View on explorer",
4373
- /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
4374
- ]
4375
- }
4376
- )
4377
- ] }),
4378
- /* @__PURE__ */ jsx(
4379
- "p",
4380
- {
4381
- className: "text-xs mt-2",
4382
- style: { color: "var(--compass-color-text-tertiary)" },
4383
- children: "Waiting for on-chain confirmation. Balances will update automatically."
4384
- }
4385
- )
4386
- ] }),
4387
- state.status === "confirmed" && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
4388
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm", children: [
4389
- /* @__PURE__ */ jsx(
4390
- CheckCircle,
4391
- {
4392
- className: "h-4 w-4",
4393
- style: { color: "var(--compass-color-success)" }
4394
- }
4395
- ),
4396
- /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-success)" }, children: "Transaction confirmed" })
4397
- ] }),
4398
- /* @__PURE__ */ jsxs(
4399
- "a",
4400
- {
4401
- href: `${explorer}/tx/${state.txHash}`,
4402
- target: "_blank",
4403
- rel: "noopener noreferrer",
4404
- className: "flex items-center gap-1.5 text-xs hover:opacity-70 transition-opacity",
4405
- style: { color: "var(--compass-color-text-secondary)" },
4406
- children: [
4407
- "View on explorer",
4408
- /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
4409
- ]
4410
- }
4411
- )
4412
- ] }),
4413
- state.status === "failed" && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 text-sm", children: [
4414
- /* @__PURE__ */ jsx(
4415
- XCircle,
4416
- {
4417
- className: "h-4 w-4 mt-0.5 shrink-0",
4418
- style: { color: "var(--compass-color-error)" }
4419
- }
4420
- ),
4421
- /* @__PURE__ */ jsx(
4422
- "span",
4423
- {
4424
- className: "break-all",
4425
- style: { color: "var(--compass-color-error)" },
4426
- children: state.error
4427
- }
4428
- )
4429
- ] })
4430
- ]
4832
+ display: "flex",
4833
+ alignItems: "center",
4834
+ justifyContent: "center"
4835
+ },
4836
+ children: /* @__PURE__ */ jsx(X, { size: 16 })
4837
+ }
4838
+ )
4839
+ ] }),
4840
+ /* @__PURE__ */ jsxs("div", { style: {
4841
+ padding: "16px 20px",
4842
+ margin: "0 16px",
4843
+ backgroundColor: "var(--compass-color-surface)",
4844
+ borderRadius: "14px"
4845
+ }, children: [
4846
+ /* @__PURE__ */ jsxs("div", { style: {
4847
+ display: "flex",
4848
+ alignItems: "center",
4849
+ gap: "8px",
4850
+ marginBottom: "8px"
4851
+ }, children: [
4852
+ /* @__PURE__ */ jsx("div", { style: {
4853
+ width: "28px",
4854
+ height: "28px",
4855
+ borderRadius: "50%",
4856
+ backgroundColor: totalEarned >= 0 ? "var(--compass-color-success-muted)" : "var(--compass-color-error-muted)",
4857
+ display: "flex",
4858
+ alignItems: "center",
4859
+ justifyContent: "center"
4860
+ }, children: totalEarned >= 0 ? /* @__PURE__ */ jsx(TrendingUp, { size: 14, style: { color: "var(--compass-color-success)" } }) : /* @__PURE__ */ jsx(TrendingDown, { size: 14, style: { color: "var(--compass-color-error)" } }) }),
4861
+ /* @__PURE__ */ jsxs("div", { children: [
4862
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.6875rem", color: "var(--compass-color-text-tertiary)" }, children: "Total P&L" }),
4863
+ /* @__PURE__ */ jsxs("div", { style: {
4864
+ fontSize: "1.25rem",
4865
+ fontWeight: 700,
4866
+ color: totalEarned >= 0 ? "var(--compass-color-success)" : "var(--compass-color-error)",
4867
+ letterSpacing: "-0.02em"
4868
+ }, children: [
4869
+ totalEarned >= 0 ? "+" : "",
4870
+ formatCurrency(totalEarned)
4871
+ ] })
4872
+ ] })
4873
+ ] }),
4874
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "16px" }, children: [
4875
+ /* @__PURE__ */ jsxs("div", { children: [
4876
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: "var(--compass-color-text-tertiary)" }, children: "Unrealised " }),
4877
+ /* @__PURE__ */ jsxs("span", { style: {
4878
+ fontSize: "0.8125rem",
4879
+ fontWeight: 600,
4880
+ color: totalUnrealized >= 0 ? "var(--compass-color-success)" : "var(--compass-color-error)"
4881
+ }, children: [
4882
+ totalUnrealized >= 0 ? "+" : "",
4883
+ formatCurrency(totalUnrealized)
4884
+ ] })
4885
+ ] }),
4886
+ /* @__PURE__ */ jsxs("div", { children: [
4887
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: "var(--compass-color-text-tertiary)" }, children: "Realised " }),
4888
+ /* @__PURE__ */ jsxs("span", { style: {
4889
+ fontSize: "0.8125rem",
4890
+ fontWeight: 600,
4891
+ color: totalRealized >= 0 ? "var(--compass-color-success)" : "var(--compass-color-error)"
4892
+ }, children: [
4893
+ totalRealized >= 0 ? "+" : "",
4894
+ formatCurrency(totalRealized)
4895
+ ] })
4896
+ ] })
4897
+ ] })
4898
+ ] }),
4899
+ /* @__PURE__ */ jsx("div", { style: {
4900
+ flex: 1,
4901
+ overflowY: "auto",
4902
+ scrollbarWidth: "none",
4903
+ padding: "16px",
4904
+ display: "flex",
4905
+ flexDirection: "column",
4906
+ gap: "10px"
4907
+ }, children: isLoading ? /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: "32px 0", color: "var(--compass-color-text-tertiary)", fontSize: "0.875rem" }, children: "Loading positions..." }) : positions.length === 0 ? /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: "32px 0" }, children: [
4908
+ /* @__PURE__ */ jsx("p", { style: { color: "var(--compass-color-text-secondary)", fontSize: "0.9375rem", margin: "0 0 4px" }, children: "No active positions yet" }),
4909
+ /* @__PURE__ */ jsx("p", { style: { color: "var(--compass-color-text-tertiary)", fontSize: "0.8125rem", margin: 0 }, children: "Deposit into a market to start earning" })
4910
+ ] }) : positions.map((position) => /* @__PURE__ */ jsx(PositionCard, { position, chainId }, position.id)) })
4911
+ ]
4912
+ }
4913
+ )
4431
4914
  }
4432
4915
  );
4433
4916
  }
@@ -4435,7 +4918,7 @@ function truncate2(value, decimals) {
4435
4918
  const factor = Math.pow(10, decimals);
4436
4919
  return (Math.floor(value * factor) / factor).toFixed(decimals);
4437
4920
  }
4438
- var EVM_CHAIN_IDS = {
4921
+ var EVM_CHAIN_IDS2 = {
4439
4922
  ethereum: 1,
4440
4923
  base: 8453,
4441
4924
  arbitrum: 42161
@@ -4476,8 +4959,8 @@ function EarnAccount({
4476
4959
  chain: chainProp,
4477
4960
  height = "600px"
4478
4961
  }) {
4479
- const { address, isConnected, login, logout, signTypedData, switchChain, walletChainId } = useEmbeddableWallet();
4480
- const { isDeployed } = useEarnAccount();
4962
+ const { address, isConnected, login, logout, signTypedData, switchChain, walletChainId, fundWallet, hasExternalWallet, sendTransaction } = useEmbeddableWallet();
4963
+ const { isDeployed, earnAccountAddress } = useEarnAccount();
4481
4964
  const { chainId: contextChainId } = useChain();
4482
4965
  const CHAIN_ID = chainProp || contextChainId;
4483
4966
  const queryClient = useQueryClient();
@@ -4491,10 +4974,15 @@ function EarnAccount({
4491
4974
  const [isTokenDropdownOpen, setIsTokenDropdownOpen] = useState(false);
4492
4975
  const [isFundModalOpen, setIsFundModalOpen] = useState(false);
4493
4976
  const [fundAmount, setFundAmount] = useState("");
4494
- const [fundAction, setFundAction] = useState("deposit");
4977
+ const [fundAction, setFundAction] = useState(
4978
+ !hasExternalWallet && fundWallet ? "buy" : "deposit"
4979
+ );
4495
4980
  const [fundStep, setFundStep] = useState("idle");
4496
4981
  const [fundTxState, setFundTxState] = useState({ status: "idle" });
4497
4982
  const fundPollRef = useRef(null);
4983
+ const showBuyTab = !!fundWallet;
4984
+ const showSendTab = !!sendTransaction && !hasExternalWallet;
4985
+ const fundBuyOnly = !hasExternalWallet && showBuyTab && !sendTransaction;
4498
4986
  const actionPollRef = useRef(null);
4499
4987
  const actionTimeoutRef = useRef(null);
4500
4988
  const [isBalancesModalOpen, setIsBalancesModalOpen] = useState(false);
@@ -4534,7 +5022,7 @@ function EarnAccount({
4534
5022
  const fundIsValid = Number(fundAmount) > 0 && !!address && isDeployed;
4535
5023
  const fundPhase = getFundPhase(fundStep);
4536
5024
  const ensureCorrectChain = useCallback(async () => {
4537
- const targetChainId = EVM_CHAIN_IDS[CHAIN_ID];
5025
+ const targetChainId = EVM_CHAIN_IDS2[CHAIN_ID];
4538
5026
  if (!targetChainId) return;
4539
5027
  if (walletChainId !== void 0 && walletChainId !== targetChainId) {
4540
5028
  if (!switchChain) {
@@ -4558,7 +5046,7 @@ function EarnAccount({
4558
5046
  return null;
4559
5047
  }
4560
5048
  },
4561
- enabled: !!address && isFundModalOpen && fundAction === "deposit",
5049
+ enabled: !!address && hasExternalWallet && isFundModalOpen && fundAction === "deposit",
4562
5050
  staleTime: 15 * 1e3
4563
5051
  });
4564
5052
  const walletBalance = walletBalanceQuery.data?.balance || "0";
@@ -4842,7 +5330,10 @@ function EarnAccount({
4842
5330
  setFundTxState({ status: "failed", error: "Transaction reverted" });
4843
5331
  return;
4844
5332
  }
4845
- } catch {
5333
+ } catch (err) {
5334
+ if (err instanceof TypeError && err.message.includes("fetch")) ; else {
5335
+ console.warn("[EarnAccount] Unexpected error polling tx receipt:", err);
5336
+ }
4846
5337
  }
4847
5338
  if (polls >= 24) {
4848
5339
  clearFundPolling();
@@ -5733,13 +6224,13 @@ function EarnAccount({
5733
6224
  onClose: () => {
5734
6225
  setIsFundModalOpen(false);
5735
6226
  setFundAmount("");
5736
- setFundAction("deposit");
6227
+ setFundAction(fundBuyOnly ? "buy" : showSendTab ? "buy" : "deposit");
5737
6228
  setFundStep("idle");
5738
6229
  setFundTxState({ status: "idle" });
5739
6230
  },
5740
- title: "Transfer Funds",
6231
+ title: showSendTab ? "Manage Funds" : fundBuyOnly ? "Add Funds" : "Transfer Funds",
5741
6232
  children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
5742
- /* @__PURE__ */ jsx(
6233
+ fundBuyOnly ? null : /* @__PURE__ */ jsx(
5743
6234
  "div",
5744
6235
  {
5745
6236
  className: "flex gap-1 p-1",
@@ -5748,7 +6239,9 @@ function EarnAccount({
5748
6239
  borderRadius: "var(--compass-border-radius-lg)",
5749
6240
  fontFamily: "var(--compass-font-family)"
5750
6241
  },
5751
- children: ["deposit", "withdraw"].map((tab) => /* @__PURE__ */ jsxs(
6242
+ children: [
6243
+ ...showSendTab ? ["buy", "send"] : ["deposit", "withdraw", ...showBuyTab ? ["buy"] : []]
6244
+ ].map((tab) => /* @__PURE__ */ jsxs(
5752
6245
  "button",
5753
6246
  {
5754
6247
  onClick: () => handleFundActionChange(tab),
@@ -5761,7 +6254,7 @@ function EarnAccount({
5761
6254
  border: "none"
5762
6255
  },
5763
6256
  children: [
5764
- tab === "deposit" ? /* @__PURE__ */ jsx(ArrowDownLeft, { size: 14 }) : /* @__PURE__ */ jsx(ArrowUpRight, { size: 14 }),
6257
+ tab === "deposit" ? /* @__PURE__ */ jsx(ArrowDownLeft, { size: 14 }) : tab === "withdraw" ? /* @__PURE__ */ jsx(ArrowUpRight, { size: 14 }) : tab === "send" ? /* @__PURE__ */ jsx(Send, { size: 14 }) : /* @__PURE__ */ jsx(ArrowRight, { size: 14 }),
5765
6258
  tab
5766
6259
  ]
5767
6260
  },
@@ -5769,137 +6262,158 @@ function EarnAccount({
5769
6262
  ))
5770
6263
  }
5771
6264
  ),
5772
- (fundIsBusy || fundStep === "confirmed") && fundAction === "deposit" && /* @__PURE__ */ jsxs(
5773
- "div",
6265
+ fundAction === "send" ? /* @__PURE__ */ jsx(
6266
+ SendForm,
5774
6267
  {
5775
- className: "flex gap-3 p-3 text-xs",
5776
- style: {
5777
- backgroundColor: "var(--compass-color-surface)",
5778
- borderRadius: "var(--compass-border-radius-lg)",
5779
- fontFamily: "var(--compass-font-family)"
5780
- },
5781
- children: [
5782
- /* @__PURE__ */ jsx(
5783
- FundStepIndicator,
5784
- {
5785
- number: 1,
5786
- label: "Approve",
5787
- state: fundPhase > 1 ? "done" : fundPhase === 1 ? "active" : "pending"
5788
- }
5789
- ),
5790
- /* @__PURE__ */ jsx(
5791
- FundStepIndicator,
5792
- {
5793
- number: 2,
5794
- label: "Transfer",
5795
- state: fundStep === "confirmed" ? "done" : fundPhase > 1 ? "active" : "pending"
5796
- }
5797
- )
5798
- ]
6268
+ onComplete: () => {
6269
+ queryClient.invalidateQueries({ queryKey: ["earnAccountBalances"] });
6270
+ queryClient.invalidateQueries({ queryKey: ["walletBalance"] });
6271
+ queryClient.invalidateQueries({ queryKey: ["walletTokenBalance"] });
6272
+ }
5799
6273
  }
5800
- ),
5801
- /* @__PURE__ */ jsxs("div", { children: [
5802
- /* @__PURE__ */ jsx(
5803
- "label",
6274
+ ) : fundAction === "buy" || fundBuyOnly ? /* @__PURE__ */ jsx(
6275
+ BuyForm,
6276
+ {
6277
+ targetAddress: earnAccountAddress || "",
6278
+ onComplete: () => {
6279
+ queryClient.invalidateQueries({ queryKey: ["earnAccountBalances"] });
6280
+ queryClient.invalidateQueries({ queryKey: ["walletBalance"] });
6281
+ queryClient.invalidateQueries({ queryKey: ["walletTokenBalance"] });
6282
+ }
6283
+ }
6284
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
6285
+ (fundIsBusy || fundStep === "confirmed") && fundAction === "deposit" && /* @__PURE__ */ jsxs(
6286
+ "div",
5804
6287
  {
5805
- className: "block text-sm mb-1.5",
6288
+ className: "flex gap-3 p-3 text-xs",
5806
6289
  style: {
5807
- color: "var(--compass-color-text-secondary)",
6290
+ backgroundColor: "var(--compass-color-surface)",
6291
+ borderRadius: "var(--compass-border-radius-lg)",
5808
6292
  fontFamily: "var(--compass-font-family)"
5809
6293
  },
5810
- children: "Amount"
6294
+ children: [
6295
+ /* @__PURE__ */ jsx(
6296
+ FundStepIndicator,
6297
+ {
6298
+ number: 1,
6299
+ label: "Approve",
6300
+ state: fundPhase > 1 ? "done" : fundPhase === 1 ? "active" : "pending"
6301
+ }
6302
+ ),
6303
+ /* @__PURE__ */ jsx(
6304
+ FundStepIndicator,
6305
+ {
6306
+ number: 2,
6307
+ label: "Transfer",
6308
+ state: fundStep === "confirmed" ? "done" : fundPhase > 1 ? "active" : "pending"
6309
+ }
6310
+ )
6311
+ ]
5811
6312
  }
5812
6313
  ),
5813
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
6314
+ /* @__PURE__ */ jsxs("div", { children: [
5814
6315
  /* @__PURE__ */ jsx(
5815
- "input",
6316
+ "label",
5816
6317
  {
5817
- type: "number",
5818
- value: fundAmount,
5819
- onChange: (e) => {
5820
- setFundAmount(e.target.value);
5821
- setFundTxState({ status: "idle" });
5822
- },
5823
- disabled: fundIsBusy,
5824
- placeholder: "0.00",
5825
- className: "w-full border px-3 py-2.5 pr-14 text-sm focus:outline-none disabled:opacity-50",
6318
+ className: "block text-sm mb-1.5",
5826
6319
  style: {
5827
- backgroundColor: "var(--compass-color-surface)",
5828
- borderColor: "var(--compass-color-border)",
5829
- borderRadius: "var(--compass-border-radius-lg)",
5830
- color: "var(--compass-color-text)",
6320
+ color: "var(--compass-color-text-secondary)",
5831
6321
  fontFamily: "var(--compass-font-family)"
5832
- }
6322
+ },
6323
+ children: "Amount"
5833
6324
  }
5834
6325
  ),
5835
- /* @__PURE__ */ jsx(
5836
- "span",
6326
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
6327
+ /* @__PURE__ */ jsx(
6328
+ "input",
6329
+ {
6330
+ type: "number",
6331
+ value: fundAmount,
6332
+ onChange: (e) => {
6333
+ setFundAmount(e.target.value);
6334
+ setFundTxState({ status: "idle" });
6335
+ },
6336
+ disabled: fundIsBusy,
6337
+ placeholder: "0.00",
6338
+ className: "w-full border px-3 py-2.5 pr-14 text-sm focus:outline-none disabled:opacity-50",
6339
+ style: {
6340
+ backgroundColor: "var(--compass-color-surface)",
6341
+ borderColor: "var(--compass-color-border)",
6342
+ borderRadius: "var(--compass-border-radius-lg)",
6343
+ color: "var(--compass-color-text)",
6344
+ fontFamily: "var(--compass-font-family)"
6345
+ }
6346
+ }
6347
+ ),
6348
+ /* @__PURE__ */ jsx(
6349
+ "span",
6350
+ {
6351
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-xs",
6352
+ style: { color: "var(--compass-color-text-tertiary)" },
6353
+ children: "USDC"
6354
+ }
6355
+ )
6356
+ ] }),
6357
+ fundMaxBalance !== "0" && /* @__PURE__ */ jsxs(
6358
+ "p",
5837
6359
  {
5838
- className: "absolute right-3 top-1/2 -translate-y-1/2 text-xs",
6360
+ className: "text-xs mt-1",
5839
6361
  style: { color: "var(--compass-color-text-tertiary)" },
5840
- children: "USDC"
6362
+ children: [
6363
+ "Available:",
6364
+ " ",
6365
+ /* @__PURE__ */ jsxs(
6366
+ "button",
6367
+ {
6368
+ onClick: () => setFundAmount(truncate2(parseFloat(fundMaxBalance), 2)),
6369
+ disabled: fundIsBusy,
6370
+ style: { color: "var(--compass-color-primary)", background: "none", border: "none", padding: 0, cursor: "pointer", fontSize: "inherit" },
6371
+ children: [
6372
+ truncate2(parseFloat(fundMaxBalance), 2),
6373
+ " USDC"
6374
+ ]
6375
+ }
6376
+ )
6377
+ ]
5841
6378
  }
5842
6379
  )
5843
6380
  ] }),
5844
- fundMaxBalance !== "0" && /* @__PURE__ */ jsxs(
6381
+ !fundIsBusy && fundStep !== "confirmed" && fundStep !== "failed" && fundTxState.status === "idle" && /* @__PURE__ */ jsx(
5845
6382
  "p",
5846
6383
  {
5847
- className: "text-xs mt-1",
5848
- style: { color: "var(--compass-color-text-tertiary)" },
5849
- children: [
5850
- "Available:",
5851
- " ",
5852
- /* @__PURE__ */ jsxs(
5853
- "button",
5854
- {
5855
- onClick: () => setFundAmount(truncate2(parseFloat(fundMaxBalance), 2)),
5856
- disabled: fundIsBusy,
5857
- style: { color: "var(--compass-color-primary)", background: "none", border: "none", padding: 0, cursor: "pointer", fontSize: "inherit" },
5858
- children: [
5859
- truncate2(parseFloat(fundMaxBalance), 2),
5860
- " USDC"
5861
- ]
5862
- }
5863
- )
5864
- ]
6384
+ className: "text-xs",
6385
+ style: {
6386
+ color: "var(--compass-color-text-tertiary)",
6387
+ fontFamily: "var(--compass-font-family)"
6388
+ },
6389
+ children: fundAction === "deposit" ? `Approves the token transfer, then transfers USDC from your ${!hasExternalWallet ? "embedded " : ""}wallet into your savings account. Requires 2 signatures.${!hasExternalWallet ? " Note: deposit and withdraw use your embedded wallet, not your bank account." : ""}` : `Moves USDC from your savings account back to your ${!hasExternalWallet ? "embedded " : ""}wallet. Requires 1 signature.${!hasExternalWallet ? " Gas fees are sponsored." : ""}`
5865
6390
  }
5866
- )
5867
- ] }),
5868
- !fundIsBusy && fundStep !== "confirmed" && fundStep !== "failed" && fundTxState.status === "idle" && /* @__PURE__ */ jsx(
5869
- "p",
5870
- {
5871
- className: "text-xs",
5872
- style: {
5873
- color: "var(--compass-color-text-tertiary)",
5874
- fontFamily: "var(--compass-font-family)"
5875
- },
5876
- children: fundAction === "deposit" ? "Approves the token transfer, then transfers USDC from your wallet into your savings account. Requires 2 signatures." : "Transfers USDC from your savings account back to your wallet. Requires 1 signature."
5877
- }
5878
- ),
5879
- /* @__PURE__ */ jsx(
5880
- "button",
5881
- {
5882
- onClick: handleFundTransfer,
5883
- disabled: !fundIsValid || fundIsBusy,
5884
- className: "w-full py-3 px-4 text-sm font-medium flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
5885
- style: {
5886
- backgroundColor: "var(--compass-color-primary)",
5887
- color: "var(--compass-color-primary-text)",
5888
- borderRadius: "var(--compass-border-radius-xl)",
5889
- fontFamily: "var(--compass-font-family)",
5890
- transition: "all 200ms ease",
5891
- border: "none"
5892
- },
5893
- children: fundIsBusy ? /* @__PURE__ */ jsxs(Fragment, { children: [
5894
- /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
5895
- fundButtonLabel()
5896
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5897
- /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
5898
- fundAction === "deposit" ? "Transfer to Savings Account" : "Withdraw to Wallet"
5899
- ] })
5900
- }
5901
- ),
5902
- /* @__PURE__ */ jsx(TxStatus, { state: fundTxState })
6391
+ ),
6392
+ /* @__PURE__ */ jsx(
6393
+ "button",
6394
+ {
6395
+ onClick: handleFundTransfer,
6396
+ disabled: !fundIsValid || fundIsBusy,
6397
+ className: "w-full py-3 px-4 text-sm font-medium flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
6398
+ style: {
6399
+ backgroundColor: "var(--compass-color-primary)",
6400
+ color: "var(--compass-color-primary-text)",
6401
+ borderRadius: "var(--compass-border-radius-xl)",
6402
+ fontFamily: "var(--compass-font-family)",
6403
+ transition: "all 200ms ease",
6404
+ border: "none"
6405
+ },
6406
+ children: fundIsBusy ? /* @__PURE__ */ jsxs(Fragment, { children: [
6407
+ /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
6408
+ fundButtonLabel()
6409
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
6410
+ /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
6411
+ fundAction === "deposit" ? "Transfer to Savings Account" : "Withdraw to Wallet"
6412
+ ] })
6413
+ }
6414
+ ),
6415
+ /* @__PURE__ */ jsx(TxStatus, { state: fundTxState })
6416
+ ] })
5903
6417
  ] })
5904
6418
  }
5905
6419
  ),
@@ -7706,19 +8220,24 @@ function getPhase(step) {
7706
8220
  if (step === "approving" || step === "signing-approval") return 1;
7707
8221
  return 2;
7708
8222
  }
7709
- var EVM_CHAIN_IDS2 = {
8223
+ var EVM_CHAIN_IDS3 = {
7710
8224
  ethereum: 1,
7711
8225
  base: 8453,
7712
8226
  arbitrum: 42161
7713
8227
  };
7714
8228
  function TopUpModal({ isOpen, onClose }) {
7715
- const { address, signTypedData, switchChain, walletChainId } = useEmbeddableWallet();
8229
+ const { address, signTypedData, switchChain, walletChainId, fundWallet, hasExternalWallet, sendTransaction } = useEmbeddableWallet();
7716
8230
  const { chainId } = useChain();
7717
- const { isDeployed } = useCreditAccount();
8231
+ const { isDeployed, creditAccountAddress } = useCreditAccount();
7718
8232
  const queryClient = useQueryClient();
7719
8233
  const prices = useTokenPrices();
7720
8234
  const token = "USDC";
7721
- const [action, setAction] = useState("deposit");
8235
+ const [action, setAction] = useState(
8236
+ !hasExternalWallet && fundWallet ? "buy" : "deposit"
8237
+ );
8238
+ const showBuyTab = !!fundWallet;
8239
+ const showSendTab = !!sendTransaction && !hasExternalWallet;
8240
+ const buyOnly = !hasExternalWallet && showBuyTab && !sendTransaction;
7722
8241
  const [amount, setAmount] = useState("");
7723
8242
  const [step, setStep] = useState("idle");
7724
8243
  const [txState, setTxState] = useState({ status: "idle" });
@@ -7767,7 +8286,7 @@ function TopUpModal({ isOpen, onClose }) {
7767
8286
  const isValid = Number(amount) > 0 && !!address && isDeployed;
7768
8287
  const isBusy = step === "approving" || step === "signing-approval" || step === "preparing-transfer" || step === "signing-transfer" || step === "executing";
7769
8288
  const ensureCorrectChain = useCallback(async () => {
7770
- const targetChainId = EVM_CHAIN_IDS2[CHAIN_ID];
8289
+ const targetChainId = EVM_CHAIN_IDS3[CHAIN_ID];
7771
8290
  if (!targetChainId) return;
7772
8291
  if (walletChainId !== void 0 && walletChainId !== targetChainId) {
7773
8292
  if (!switchChain) {
@@ -7969,7 +8488,10 @@ function TopUpModal({ isOpen, onClose }) {
7969
8488
  setTxState({ status: "failed", error: "Transaction reverted" });
7970
8489
  return;
7971
8490
  }
7972
- } catch {
8491
+ } catch (err) {
8492
+ if (err instanceof TypeError && err.message.includes("fetch")) ; else {
8493
+ console.warn("[TopUpModal] Unexpected error polling tx receipt:", err);
8494
+ }
7973
8495
  }
7974
8496
  if (polls >= 24) {
7975
8497
  clearPolling();
@@ -8005,8 +8527,8 @@ function TopUpModal({ isOpen, onClose }) {
8005
8527
  color: "var(--compass-color-text-secondary)",
8006
8528
  fontFamily: "var(--compass-font-family)"
8007
8529
  };
8008
- return /* @__PURE__ */ jsx(ActionModal, { isOpen, onClose, title: "Transfer Funds", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
8009
- /* @__PURE__ */ jsx(
8530
+ return /* @__PURE__ */ jsx(ActionModal, { isOpen, onClose, title: showSendTab ? "Manage Funds" : buyOnly ? "Add Funds" : "Transfer Funds", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
8531
+ !buyOnly && /* @__PURE__ */ jsx(
8010
8532
  "div",
8011
8533
  {
8012
8534
  className: "flex gap-1 p-1",
@@ -8015,7 +8537,9 @@ function TopUpModal({ isOpen, onClose }) {
8015
8537
  borderRadius: "var(--compass-border-radius-lg)",
8016
8538
  fontFamily: "var(--compass-font-family)"
8017
8539
  },
8018
- children: ["deposit", "withdraw"].map((tab) => /* @__PURE__ */ jsxs(
8540
+ children: [
8541
+ ...showSendTab ? ["buy", "send"] : showBuyTab ? ["deposit", "withdraw", "buy"] : ["deposit", "withdraw"]
8542
+ ].map((tab) => /* @__PURE__ */ jsxs(
8019
8543
  "button",
8020
8544
  {
8021
8545
  onClick: () => handleActionChange(tab),
@@ -8027,7 +8551,7 @@ function TopUpModal({ isOpen, onClose }) {
8027
8551
  borderRadius: "var(--compass-border-radius-md)"
8028
8552
  },
8029
8553
  children: [
8030
- tab === "deposit" ? /* @__PURE__ */ jsx(ArrowDownLeft, { size: 14 }) : /* @__PURE__ */ jsx(ArrowUpRight, { size: 14 }),
8554
+ tab === "deposit" ? /* @__PURE__ */ jsx(ArrowDownLeft, { size: 14 }) : tab === "withdraw" ? /* @__PURE__ */ jsx(ArrowUpRight, { size: 14 }) : tab === "send" ? /* @__PURE__ */ jsx(Send, { size: 14 }) : /* @__PURE__ */ jsx(ArrowRight, { size: 14 }),
8031
8555
  tab
8032
8556
  ]
8033
8557
  },
@@ -8035,122 +8559,143 @@ function TopUpModal({ isOpen, onClose }) {
8035
8559
  ))
8036
8560
  }
8037
8561
  ),
8038
- (isBusy || step === "confirmed") && action === "deposit" && /* @__PURE__ */ jsxs(
8039
- "div",
8562
+ action === "send" ? /* @__PURE__ */ jsx(
8563
+ SendForm,
8040
8564
  {
8041
- className: "flex gap-3 p-3 text-xs",
8042
- style: {
8043
- backgroundColor: "var(--compass-color-surface)",
8044
- borderRadius: "var(--compass-border-radius-lg)",
8045
- fontFamily: "var(--compass-font-family)"
8046
- },
8047
- children: [
8565
+ product: "credit",
8566
+ productAccountAddress: creditAccountAddress || void 0,
8567
+ onComplete: () => {
8568
+ queryClient.invalidateQueries({ queryKey: ["creditBalances"] });
8569
+ queryClient.invalidateQueries({ queryKey: ["walletTokenBalance"] });
8570
+ }
8571
+ }
8572
+ ) : action === "buy" || buyOnly ? /* @__PURE__ */ jsx(
8573
+ BuyForm,
8574
+ {
8575
+ targetAddress: creditAccountAddress || "",
8576
+ onComplete: () => {
8577
+ queryClient.invalidateQueries({ queryKey: ["creditBalances"] });
8578
+ queryClient.invalidateQueries({ queryKey: ["walletTokenBalance"] });
8579
+ }
8580
+ }
8581
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
8582
+ (isBusy || step === "confirmed") && action === "deposit" && /* @__PURE__ */ jsxs(
8583
+ "div",
8584
+ {
8585
+ className: "flex gap-3 p-3 text-xs",
8586
+ style: {
8587
+ backgroundColor: "var(--compass-color-surface)",
8588
+ borderRadius: "var(--compass-border-radius-lg)",
8589
+ fontFamily: "var(--compass-font-family)"
8590
+ },
8591
+ children: [
8592
+ /* @__PURE__ */ jsx(
8593
+ StepIndicator,
8594
+ {
8595
+ number: 1,
8596
+ label: "Approve",
8597
+ state: phase > 1 ? "done" : phase === 1 ? "active" : "pending"
8598
+ }
8599
+ ),
8600
+ /* @__PURE__ */ jsx(
8601
+ StepIndicator,
8602
+ {
8603
+ number: 2,
8604
+ label: "Transfer",
8605
+ state: step === "confirmed" ? "done" : phase > 1 ? "active" : "pending"
8606
+ }
8607
+ )
8608
+ ]
8609
+ }
8610
+ ),
8611
+ /* @__PURE__ */ jsxs("div", { children: [
8612
+ /* @__PURE__ */ jsx("label", { className: "block text-sm mb-1.5", style: labelStyle, children: "Amount" }),
8613
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
8048
8614
  /* @__PURE__ */ jsx(
8049
- StepIndicator,
8615
+ "input",
8050
8616
  {
8051
- number: 1,
8052
- label: "Approve",
8053
- state: phase > 1 ? "done" : phase === 1 ? "active" : "pending"
8617
+ type: "number",
8618
+ value: amount,
8619
+ onChange: (e) => {
8620
+ setAmount(e.target.value);
8621
+ setTxState({ status: "idle" });
8622
+ },
8623
+ disabled: isBusy,
8624
+ placeholder: "0.00",
8625
+ className: "w-full border px-3 py-2.5 pr-14 text-sm focus:outline-none disabled:opacity-50",
8626
+ style: inputStyle
8054
8627
  }
8055
8628
  ),
8056
8629
  /* @__PURE__ */ jsx(
8057
- StepIndicator,
8630
+ "span",
8058
8631
  {
8059
- number: 2,
8060
- label: "Transfer",
8061
- state: step === "confirmed" ? "done" : phase > 1 ? "active" : "pending"
8632
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-xs",
8633
+ style: { color: "var(--compass-color-text-tertiary)" },
8634
+ children: token
8062
8635
  }
8063
8636
  )
8064
- ]
8065
- }
8066
- ),
8067
- /* @__PURE__ */ jsxs("div", { children: [
8068
- /* @__PURE__ */ jsx("label", { className: "block text-sm mb-1.5", style: labelStyle, children: "Amount" }),
8069
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
8070
- /* @__PURE__ */ jsx(
8071
- "input",
8637
+ ] }),
8638
+ formatUsdEstimate(amount, token, prices) && /* @__PURE__ */ jsxs(
8639
+ "p",
8072
8640
  {
8073
- type: "number",
8074
- value: amount,
8075
- onChange: (e) => {
8076
- setAmount(e.target.value);
8077
- setTxState({ status: "idle" });
8078
- },
8079
- disabled: isBusy,
8080
- placeholder: "0.00",
8081
- className: "w-full border px-3 py-2.5 pr-14 text-sm focus:outline-none disabled:opacity-50",
8082
- style: inputStyle
8641
+ className: "text-xs mt-1",
8642
+ style: { color: "var(--compass-color-text-tertiary)" },
8643
+ children: [
8644
+ "\u2248",
8645
+ " ",
8646
+ formatUsdEstimate(amount, token, prices)
8647
+ ]
8083
8648
  }
8084
8649
  ),
8085
- /* @__PURE__ */ jsx(
8086
- "span",
8650
+ maxBalance && maxBalance !== "0" && /* @__PURE__ */ jsxs(
8651
+ "p",
8087
8652
  {
8088
- className: "absolute right-3 top-1/2 -translate-y-1/2 text-xs",
8653
+ className: "text-xs mt-1",
8089
8654
  style: { color: "var(--compass-color-text-tertiary)" },
8090
- children: token
8655
+ children: [
8656
+ "Available: ",
8657
+ truncate6(parseFloat(maxBalance), 2),
8658
+ " ",
8659
+ token
8660
+ ]
8091
8661
  }
8092
8662
  )
8093
8663
  ] }),
8094
- formatUsdEstimate(amount, token, prices) && /* @__PURE__ */ jsxs(
8664
+ !isBusy && step !== "confirmed" && step !== "failed" && txState.status === "idle" && /* @__PURE__ */ jsx(
8095
8665
  "p",
8096
8666
  {
8097
- className: "text-xs mt-1",
8098
- style: { color: "var(--compass-color-text-tertiary)" },
8099
- children: [
8100
- "\u2248",
8101
- " ",
8102
- formatUsdEstimate(amount, token, prices)
8103
- ]
8667
+ className: "text-xs",
8668
+ style: {
8669
+ color: "var(--compass-color-text-tertiary)",
8670
+ fontFamily: "var(--compass-font-family)"
8671
+ },
8672
+ children: action === "deposit" ? `Approves the token transfer, then transfers tokens from your ${!hasExternalWallet ? "embedded " : ""}wallet into your credit account. Requires 2 signatures.${!hasExternalWallet ? " Note: deposit and withdraw use your embedded wallet, not your bank account." : ""}` : `Moves tokens from your credit account back to your ${!hasExternalWallet ? "embedded " : ""}wallet. Requires 1 signature.${!hasExternalWallet ? " Gas fees are sponsored." : ""}`
8104
8673
  }
8105
8674
  ),
8106
- maxBalance && maxBalance !== "0" && /* @__PURE__ */ jsxs(
8107
- "p",
8675
+ /* @__PURE__ */ jsx(
8676
+ "button",
8108
8677
  {
8109
- className: "text-xs mt-1",
8110
- style: { color: "var(--compass-color-text-tertiary)" },
8111
- children: [
8112
- "Available: ",
8113
- truncate6(parseFloat(maxBalance), 2),
8114
- " ",
8115
- token
8116
- ]
8678
+ onClick: handleTransfer,
8679
+ disabled: !isValid || isBusy,
8680
+ className: "w-full py-3 px-4 text-sm font-medium flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
8681
+ style: {
8682
+ backgroundColor: "var(--compass-color-primary)",
8683
+ color: "white",
8684
+ borderRadius: "var(--compass-border-radius-xl)",
8685
+ fontFamily: "var(--compass-font-family)",
8686
+ transition: "var(--compass-transition-normal)"
8687
+ },
8688
+ children: isBusy ? /* @__PURE__ */ jsxs(Fragment, { children: [
8689
+ /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
8690
+ buttonLabel()
8691
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
8692
+ /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
8693
+ action === "deposit" ? "Transfer to Credit Account" : "Withdraw to Wallet"
8694
+ ] })
8117
8695
  }
8118
- )
8119
- ] }),
8120
- !isBusy && step !== "confirmed" && step !== "failed" && txState.status === "idle" && /* @__PURE__ */ jsx(
8121
- "p",
8122
- {
8123
- className: "text-xs",
8124
- style: {
8125
- color: "var(--compass-color-text-tertiary)",
8126
- fontFamily: "var(--compass-font-family)"
8127
- },
8128
- children: action === "deposit" ? "Approves the token transfer, then transfers tokens from your wallet into your credit account. Requires 2 signatures." : "Transfers tokens from your credit account back to your wallet. Requires 1 signature."
8129
- }
8130
- ),
8131
- /* @__PURE__ */ jsx(
8132
- "button",
8133
- {
8134
- onClick: handleTransfer,
8135
- disabled: !isValid || isBusy,
8136
- className: "w-full py-3 px-4 text-sm font-medium flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
8137
- style: {
8138
- backgroundColor: "var(--compass-color-primary)",
8139
- color: "white",
8140
- borderRadius: "var(--compass-border-radius-xl)",
8141
- fontFamily: "var(--compass-font-family)",
8142
- transition: "var(--compass-transition-normal)"
8143
- },
8144
- children: isBusy ? /* @__PURE__ */ jsxs(Fragment, { children: [
8145
- /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
8146
- buttonLabel()
8147
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
8148
- /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
8149
- action === "deposit" ? "Transfer to Credit Account" : "Withdraw to Wallet"
8150
- ] })
8151
- }
8152
- ),
8153
- /* @__PURE__ */ jsx(TxStatus, { state: txState })
8696
+ ),
8697
+ /* @__PURE__ */ jsx(TxStatus, { state: txState })
8698
+ ] })
8154
8699
  ] }) });
8155
8700
  }
8156
8701
  function StepIndicator({
@@ -8178,9 +8723,10 @@ function CreditSwapModal({ isOpen, onClose }) {
8178
8723
  const prices = useTokenPrices();
8179
8724
  const { txState, executeBundle, resetState } = useCreditBundle();
8180
8725
  const tokens = CREDIT_TOKENS;
8181
- const fromToken = "USDC";
8182
- const [toToken, setToToken] = useState(tokens.find((t) => t !== fromToken) || tokens[0] || "WETH");
8726
+ const [fromToken, setFromToken] = useState("USDC");
8727
+ const [toToken, setToToken] = useState(tokens.find((t) => t !== "USDC") || tokens[0] || "WETH");
8183
8728
  const [fromAmount, setFromAmount] = useState("");
8729
+ const [isFromOpen, setIsFromOpen] = useState(false);
8184
8730
  const [isToOpen, setIsToOpen] = useState(false);
8185
8731
  useEffect(() => {
8186
8732
  if (isOpen) {
@@ -8188,6 +8734,12 @@ function CreditSwapModal({ isOpen, onClose }) {
8188
8734
  resetState();
8189
8735
  }
8190
8736
  }, [isOpen]);
8737
+ useEffect(() => {
8738
+ if (fromToken === toToken) {
8739
+ const alt = tokens.find((t) => t !== fromToken);
8740
+ if (alt) setToToken(alt);
8741
+ }
8742
+ }, [fromToken, toToken, tokens]);
8191
8743
  const fromBalance = balances?.find((b) => b.tokenSymbol === fromToken);
8192
8744
  const fromBalanceAmount = fromBalance ? parseFloat(fromBalance.amount) : 0;
8193
8745
  const estimatedOutput = (() => {
@@ -8266,24 +8818,76 @@ function CreditSwapModal({ isOpen, onClose }) {
8266
8818
  }
8267
8819
  }
8268
8820
  ),
8269
- /* @__PURE__ */ jsx(
8270
- "div",
8271
- {
8272
- style: {
8273
- display: "flex",
8274
- alignItems: "center",
8275
- fontWeight: 500,
8276
- backgroundColor: "var(--compass-color-background)",
8277
- border: "1.5px solid var(--compass-color-border)",
8278
- color: "var(--compass-color-text)",
8279
- borderRadius: "var(--compass-border-radius-md)",
8280
- padding: "4px 8px",
8281
- fontSize: "0.8125rem",
8282
- flexShrink: 0
8283
- },
8284
- children: fromToken
8285
- }
8286
- )
8821
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", flexShrink: 0 }, children: [
8822
+ /* @__PURE__ */ jsxs(
8823
+ "button",
8824
+ {
8825
+ onClick: () => setIsFromOpen(!isFromOpen),
8826
+ disabled: isBusy,
8827
+ style: {
8828
+ display: "flex",
8829
+ alignItems: "center",
8830
+ fontWeight: 500,
8831
+ backgroundColor: "var(--compass-color-background)",
8832
+ border: "1.5px solid var(--compass-color-border)",
8833
+ color: "var(--compass-color-text)",
8834
+ borderRadius: "var(--compass-border-radius-md)",
8835
+ padding: "4px 8px",
8836
+ gap: "4px",
8837
+ fontSize: "0.8125rem",
8838
+ cursor: isBusy ? "not-allowed" : "pointer"
8839
+ },
8840
+ children: [
8841
+ fromToken,
8842
+ /* @__PURE__ */ jsx(ChevronDown, { size: 12, style: { color: "var(--compass-color-text-tertiary)" } })
8843
+ ]
8844
+ }
8845
+ ),
8846
+ isFromOpen && /* @__PURE__ */ jsx(
8847
+ "div",
8848
+ {
8849
+ style: {
8850
+ position: "absolute",
8851
+ right: 0,
8852
+ top: "100%",
8853
+ marginTop: "4px",
8854
+ zIndex: 10,
8855
+ backgroundColor: "var(--compass-color-surface)",
8856
+ border: "1.5px solid var(--compass-color-border)",
8857
+ borderRadius: "var(--compass-border-radius-lg)",
8858
+ boxShadow: "var(--compass-shadow-md)",
8859
+ minWidth: "120px",
8860
+ maxHeight: "200px",
8861
+ overflowY: "auto",
8862
+ scrollbarWidth: "none"
8863
+ },
8864
+ children: tokens.filter((t) => t !== toToken).map((token) => /* @__PURE__ */ jsx(
8865
+ "button",
8866
+ {
8867
+ onClick: () => {
8868
+ setFromToken(token);
8869
+ setFromAmount("");
8870
+ setIsFromOpen(false);
8871
+ },
8872
+ style: {
8873
+ display: "block",
8874
+ width: "100%",
8875
+ textAlign: "left",
8876
+ padding: "6px 10px",
8877
+ fontSize: "0.8125rem",
8878
+ fontWeight: 500,
8879
+ color: token === fromToken ? "var(--compass-color-primary)" : "var(--compass-color-text)",
8880
+ backgroundColor: token === fromToken ? "var(--compass-color-primary-muted)" : "transparent",
8881
+ border: "none",
8882
+ cursor: "pointer"
8883
+ },
8884
+ children: token
8885
+ },
8886
+ token
8887
+ ))
8888
+ }
8889
+ )
8890
+ ] })
8287
8891
  ] })
8288
8892
  ]
8289
8893
  }
@@ -8490,7 +9094,7 @@ function CreditAccount({
8490
9094
  onBorrow: _onBorrow,
8491
9095
  onRepay: _onRepay
8492
9096
  }) {
8493
- const { address, isConnected, login, logout } = useEmbeddableWallet();
9097
+ const { address, isConnected, login, logout, hasExternalWallet } = useEmbeddableWallet();
8494
9098
  const { isDeployed, creditAccountAddress } = useCreditAccount();
8495
9099
  const { chainId } = useChain();
8496
9100
  const { data: positions, isLoading: positionsLoading } = useCreditPositions();
@@ -8516,6 +9120,25 @@ function CreditAccount({
8516
9120
  return { ...b, usdValue };
8517
9121
  }).filter((b) => b.usdValue >= 0.01).sort((a, b) => a.tokenSymbol.localeCompare(b.tokenSymbol));
8518
9122
  const totalIdleUsd = balancesWithUsd.reduce((sum, b) => sum + b.usdValue, 0);
9123
+ const { data: walletBalance } = useQuery({
9124
+ queryKey: ["embeddedWalletBalance", chainId, address, "USDC"],
9125
+ queryFn: async () => {
9126
+ if (!address) return "0";
9127
+ try {
9128
+ const response = await fetch(
9129
+ `/api/compass/token/balance?chain=${chainId}&token=USDC&address=${address}`
9130
+ );
9131
+ if (response.ok) {
9132
+ const data = await response.json();
9133
+ return data.balance || "0";
9134
+ }
9135
+ } catch {
9136
+ }
9137
+ return "0";
9138
+ },
9139
+ enabled: !!address && hasExternalWallet,
9140
+ staleTime: 30 * 1e3
9141
+ });
8519
9142
  const totalInterestEarnedUsd = (() => {
8520
9143
  if (!positions) return 0;
8521
9144
  let total = 0;
@@ -9516,6 +10139,7 @@ function PositionDetailModal({ position, onClose }) {
9516
10139
  const isPendle = position.venue === "pendle";
9517
10140
  const duration = formatDuration(position.entryTimestamp);
9518
10141
  const expiryDate = formatExpiryUTC(position.expiry);
10142
+ const modalRef = useRef(null);
9519
10143
  useEffect(() => {
9520
10144
  const handleKeyDown = (e) => {
9521
10145
  if (e.key === "Escape") onClose();
@@ -9523,296 +10147,312 @@ function PositionDetailModal({ position, onClose }) {
9523
10147
  document.addEventListener("keydown", handleKeyDown);
9524
10148
  return () => document.removeEventListener("keydown", handleKeyDown);
9525
10149
  }, [onClose]);
10150
+ const handleOverlayWheel = useCallback((e) => {
10151
+ if (modalRef.current && !modalRef.current.contains(e.target)) {
10152
+ window.scrollBy(0, e.deltaY);
10153
+ }
10154
+ }, []);
9526
10155
  const hasStats = position.apy || isPendle && (duration || expiryDate) || position.pnl;
9527
- return /* @__PURE__ */ jsx(
10156
+ return /* @__PURE__ */ jsxs(
9528
10157
  "div",
9529
10158
  {
9530
10159
  className: "fixed inset-0 z-50 flex items-center justify-center p-4",
9531
- style: { backgroundColor: "rgba(0, 0, 0, 0.7)" },
10160
+ onWheel: handleOverlayWheel,
9532
10161
  onClick: onClose,
9533
- children: /* @__PURE__ */ jsxs(
9534
- "div",
9535
- {
9536
- className: "relative w-full max-w-md rounded-xl border overflow-hidden max-h-[90vh] overflow-y-auto",
9537
- style: {
9538
- backgroundColor: "var(--compass-color-background)",
9539
- borderColor: "var(--compass-color-border)",
9540
- scrollbarWidth: "none"
9541
- },
9542
- onClick: (e) => e.stopPropagation(),
9543
- children: [
9544
- /* @__PURE__ */ jsxs(
9545
- "div",
9546
- {
9547
- className: "sticky top-0 flex items-center justify-between p-4 border-b",
9548
- style: {
9549
- backgroundColor: "var(--compass-color-surface)",
9550
- borderColor: "var(--compass-color-border)"
9551
- },
9552
- children: [
9553
- /* @__PURE__ */ jsxs("div", { children: [
9554
- /* @__PURE__ */ jsx(
9555
- "h2",
9556
- {
9557
- className: "font-semibold text-lg",
9558
- style: { color: "var(--compass-color-text)" },
9559
- children: position.name
9560
- }
9561
- ),
9562
- /* @__PURE__ */ jsx(
9563
- "p",
9564
- {
9565
- className: "text-sm",
9566
- style: { color: "var(--compass-color-text-secondary)" },
9567
- children: position.venueName
9568
- }
9569
- )
9570
- ] }),
9571
- /* @__PURE__ */ jsx(
9572
- "button",
9573
- {
9574
- onClick: onClose,
9575
- className: "p-2 rounded-lg hover:opacity-80 transition-opacity",
9576
- style: { backgroundColor: "var(--compass-color-background)" },
9577
- children: /* @__PURE__ */ jsx(X, { size: 20, style: { color: "var(--compass-color-text-secondary)" } })
9578
- }
9579
- )
9580
- ]
9581
- }
9582
- ),
9583
- /* @__PURE__ */ jsx("div", { className: "p-4 border-b", style: { borderColor: "var(--compass-color-border)" }, children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
9584
- /* @__PURE__ */ jsx(
9585
- "p",
9586
- {
9587
- className: "text-sm mb-1",
9588
- style: { color: "var(--compass-color-text-secondary)" },
9589
- children: "Current Balance"
9590
- }
9591
- ),
9592
- /* @__PURE__ */ jsx(
9593
- "p",
9594
- {
9595
- className: "text-3xl font-bold",
9596
- style: { color: "var(--compass-color-text)" },
9597
- children: formatUSD(position.balanceUsd)
9598
- }
9599
- ),
10162
+ children: [
10163
+ /* @__PURE__ */ jsx(
10164
+ "div",
10165
+ {
10166
+ className: "absolute inset-0",
10167
+ style: { backgroundColor: "rgba(0, 0, 0, 0.7)" }
10168
+ }
10169
+ ),
10170
+ /* @__PURE__ */ jsxs(
10171
+ "div",
10172
+ {
10173
+ ref: modalRef,
10174
+ className: "relative w-full max-w-md rounded-xl border max-h-[85vh] overflow-y-auto",
10175
+ style: {
10176
+ backgroundColor: "var(--compass-color-background)",
10177
+ borderColor: "var(--compass-color-border)",
10178
+ scrollbarWidth: "none",
10179
+ overscrollBehavior: "contain"
10180
+ },
10181
+ onClick: (e) => e.stopPropagation(),
10182
+ children: [
9600
10183
  /* @__PURE__ */ jsxs(
9601
- "p",
9602
- {
9603
- className: "text-sm font-mono mt-1",
9604
- style: { color: "var(--compass-color-text-secondary)" },
9605
- children: [
9606
- formatAmount(position.balance),
9607
- " ",
9608
- position.assetSymbol
9609
- ]
9610
- }
9611
- )
9612
- ] }) }),
9613
- hasStats && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 p-4 border-b", style: { borderColor: "var(--compass-color-border)" }, children: [
9614
- position.apy && /* @__PURE__ */ jsxs(
9615
- "div",
9616
- {
9617
- className: "p-3 rounded-lg",
9618
- style: { backgroundColor: "var(--compass-color-surface)" },
9619
- children: [
9620
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
9621
- /* @__PURE__ */ jsx(Percent, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }),
9622
- /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "APY" })
9623
- ] }),
9624
- /* @__PURE__ */ jsxs(
9625
- "p",
9626
- {
9627
- className: "font-semibold",
9628
- style: { color: "var(--compass-color-success)" },
9629
- children: [
9630
- parseFloat(position.apy).toFixed(2),
9631
- "%"
9632
- ]
9633
- }
9634
- )
9635
- ]
9636
- }
9637
- ),
9638
- isPendle && duration && /* @__PURE__ */ jsxs(
9639
- "div",
9640
- {
9641
- className: "p-3 rounded-lg",
9642
- style: { backgroundColor: "var(--compass-color-surface)" },
9643
- children: [
9644
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
9645
- /* @__PURE__ */ jsx(Clock, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }),
9646
- /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "Duration" })
9647
- ] }),
9648
- /* @__PURE__ */ jsx(
9649
- "p",
9650
- {
9651
- className: "font-semibold",
9652
- style: { color: "var(--compass-color-text)" },
9653
- children: duration
9654
- }
9655
- )
9656
- ]
9657
- }
9658
- ),
9659
- isPendle && expiryDate && /* @__PURE__ */ jsxs(
9660
10184
  "div",
9661
10185
  {
9662
- className: "p-3 rounded-lg",
9663
- style: { backgroundColor: "var(--compass-color-surface)" },
10186
+ className: "sticky top-0 flex items-center justify-between p-4 border-b",
10187
+ style: {
10188
+ backgroundColor: "var(--compass-color-surface)",
10189
+ borderColor: "var(--compass-color-border)"
10190
+ },
9664
10191
  children: [
9665
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
9666
- /* @__PURE__ */ jsx(Calendar, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }),
9667
- /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "Expiry (UTC)" })
10192
+ /* @__PURE__ */ jsxs("div", { children: [
10193
+ /* @__PURE__ */ jsx(
10194
+ "h2",
10195
+ {
10196
+ className: "font-semibold text-lg",
10197
+ style: { color: "var(--compass-color-text)" },
10198
+ children: position.name
10199
+ }
10200
+ ),
10201
+ /* @__PURE__ */ jsx(
10202
+ "p",
10203
+ {
10204
+ className: "text-sm",
10205
+ style: { color: "var(--compass-color-text-secondary)" },
10206
+ children: position.venueName
10207
+ }
10208
+ )
9668
10209
  ] }),
9669
10210
  /* @__PURE__ */ jsx(
9670
- "p",
10211
+ "button",
9671
10212
  {
9672
- className: "font-semibold",
9673
- style: { color: "var(--compass-color-text)" },
9674
- children: expiryDate
10213
+ onClick: onClose,
10214
+ className: "p-2 rounded-lg hover:opacity-80 transition-opacity",
10215
+ style: { backgroundColor: "var(--compass-color-background)" },
10216
+ children: /* @__PURE__ */ jsx(X, { size: 20, style: { color: "var(--compass-color-text-secondary)" } })
9675
10217
  }
9676
10218
  )
9677
10219
  ]
9678
10220
  }
9679
10221
  ),
9680
- position.pnl && /* @__PURE__ */ jsxs(
9681
- "div",
9682
- {
9683
- className: "p-3 rounded-lg",
9684
- style: { backgroundColor: "var(--compass-color-surface)" },
9685
- children: [
9686
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
9687
- isPnlPositive ? /* @__PURE__ */ jsx(TrendingUp, { size: 14, style: { color: "var(--compass-color-success)" } }) : /* @__PURE__ */ jsx(TrendingDown, { size: 14, style: { color: "var(--compass-color-error)" } }),
9688
- /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "Total P&L" })
9689
- ] }),
9690
- /* @__PURE__ */ jsxs(
9691
- "p",
10222
+ /* @__PURE__ */ jsx("div", { className: "p-4 border-b", style: { borderColor: "var(--compass-color-border)" }, children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
10223
+ /* @__PURE__ */ jsx(
10224
+ "p",
10225
+ {
10226
+ className: "text-sm mb-1",
10227
+ style: { color: "var(--compass-color-text-secondary)" },
10228
+ children: "Current Balance"
10229
+ }
10230
+ ),
10231
+ /* @__PURE__ */ jsx(
10232
+ "p",
10233
+ {
10234
+ className: "text-3xl font-bold",
10235
+ style: { color: "var(--compass-color-text)" },
10236
+ children: formatUSD(position.balanceUsd)
10237
+ }
10238
+ ),
10239
+ /* @__PURE__ */ jsxs(
10240
+ "p",
10241
+ {
10242
+ className: "text-sm font-mono mt-1",
10243
+ style: { color: "var(--compass-color-text-secondary)" },
10244
+ children: [
10245
+ formatAmount(position.balance),
10246
+ " ",
10247
+ position.assetSymbol
10248
+ ]
10249
+ }
10250
+ )
10251
+ ] }) }),
10252
+ hasStats && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 p-4 border-b", style: { borderColor: "var(--compass-color-border)" }, children: [
10253
+ position.apy && /* @__PURE__ */ jsxs(
10254
+ "div",
10255
+ {
10256
+ className: "p-3 rounded-lg",
10257
+ style: { backgroundColor: "var(--compass-color-surface)" },
10258
+ children: [
10259
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
10260
+ /* @__PURE__ */ jsx(Percent, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }),
10261
+ /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "APY" })
10262
+ ] }),
10263
+ /* @__PURE__ */ jsxs(
10264
+ "p",
10265
+ {
10266
+ className: "font-semibold",
10267
+ style: { color: "var(--compass-color-success)" },
10268
+ children: [
10269
+ parseFloat(position.apy).toFixed(2),
10270
+ "%"
10271
+ ]
10272
+ }
10273
+ )
10274
+ ]
10275
+ }
10276
+ ),
10277
+ isPendle && duration && /* @__PURE__ */ jsxs(
10278
+ "div",
10279
+ {
10280
+ className: "p-3 rounded-lg",
10281
+ style: { backgroundColor: "var(--compass-color-surface)" },
10282
+ children: [
10283
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
10284
+ /* @__PURE__ */ jsx(Clock, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }),
10285
+ /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "Duration" })
10286
+ ] }),
10287
+ /* @__PURE__ */ jsx(
10288
+ "p",
10289
+ {
10290
+ className: "font-semibold",
10291
+ style: { color: "var(--compass-color-text)" },
10292
+ children: duration
10293
+ }
10294
+ )
10295
+ ]
10296
+ }
10297
+ ),
10298
+ isPendle && expiryDate && /* @__PURE__ */ jsxs(
10299
+ "div",
10300
+ {
10301
+ className: "p-3 rounded-lg",
10302
+ style: { backgroundColor: "var(--compass-color-surface)" },
10303
+ children: [
10304
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
10305
+ /* @__PURE__ */ jsx(Calendar, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }),
10306
+ /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "Expiry (UTC)" })
10307
+ ] }),
10308
+ /* @__PURE__ */ jsx(
10309
+ "p",
10310
+ {
10311
+ className: "font-semibold",
10312
+ style: { color: "var(--compass-color-text)" },
10313
+ children: expiryDate
10314
+ }
10315
+ )
10316
+ ]
10317
+ }
10318
+ ),
10319
+ position.pnl && /* @__PURE__ */ jsxs(
10320
+ "div",
10321
+ {
10322
+ className: "p-3 rounded-lg",
10323
+ style: { backgroundColor: "var(--compass-color-surface)" },
10324
+ children: [
10325
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
10326
+ isPnlPositive ? /* @__PURE__ */ jsx(TrendingUp, { size: 14, style: { color: "var(--compass-color-success)" } }) : /* @__PURE__ */ jsx(TrendingDown, { size: 14, style: { color: "var(--compass-color-error)" } }),
10327
+ /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "Total P&L" })
10328
+ ] }),
10329
+ /* @__PURE__ */ jsxs(
10330
+ "p",
10331
+ {
10332
+ className: "font-semibold",
10333
+ style: {
10334
+ color: isPnlPositive ? "var(--compass-color-success)" : "var(--compass-color-error)"
10335
+ },
10336
+ children: [
10337
+ isPnlPositive ? "+" : "",
10338
+ formatUSD(position.pnl.totalPnl)
10339
+ ]
10340
+ }
10341
+ )
10342
+ ]
10343
+ }
10344
+ )
10345
+ ] }),
10346
+ (position.deposits.length > 0 || position.withdrawals.length > 0) && /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
10347
+ /* @__PURE__ */ jsx(
10348
+ "h3",
10349
+ {
10350
+ className: "font-semibold mb-3",
10351
+ style: { color: "var(--compass-color-text)" },
10352
+ children: "Transaction History"
10353
+ }
10354
+ ),
10355
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
10356
+ position.deposits.map((tx, i) => {
10357
+ const date = formatDate(tx.timestamp);
10358
+ return /* @__PURE__ */ jsxs(
10359
+ "div",
9692
10360
  {
9693
- className: "font-semibold",
9694
- style: {
9695
- color: isPnlPositive ? "var(--compass-color-success)" : "var(--compass-color-error)"
9696
- },
10361
+ className: "flex items-center justify-between p-3 rounded-lg",
10362
+ style: { backgroundColor: "var(--compass-color-surface)" },
9697
10363
  children: [
9698
- isPnlPositive ? "+" : "",
9699
- formatUSD(position.pnl.totalPnl)
9700
- ]
9701
- }
9702
- )
9703
- ]
9704
- }
9705
- )
9706
- ] }),
9707
- (position.deposits.length > 0 || position.withdrawals.length > 0) && /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
9708
- /* @__PURE__ */ jsx(
9709
- "h3",
9710
- {
9711
- className: "font-semibold mb-3",
9712
- style: { color: "var(--compass-color-text)" },
9713
- children: "Transaction History"
9714
- }
9715
- ),
9716
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
9717
- position.deposits.map((tx, i) => {
9718
- const date = formatDate(tx.timestamp);
9719
- return /* @__PURE__ */ jsxs(
9720
- "div",
9721
- {
9722
- className: "flex items-center justify-between p-3 rounded-lg",
9723
- style: { backgroundColor: "var(--compass-color-surface)" },
9724
- children: [
9725
- /* @__PURE__ */ jsxs("div", { children: [
9726
- /* @__PURE__ */ jsxs(
9727
- "p",
9728
- {
9729
- className: "font-medium",
9730
- style: { color: "var(--compass-color-success)" },
9731
- children: [
9732
- "+",
9733
- formatAmount(tx.amount),
9734
- " ",
9735
- position.assetSymbol
9736
- ]
9737
- }
9738
- ),
9739
- /* @__PURE__ */ jsx(
9740
- "p",
10364
+ /* @__PURE__ */ jsxs("div", { children: [
10365
+ /* @__PURE__ */ jsxs(
10366
+ "p",
10367
+ {
10368
+ className: "font-medium",
10369
+ style: { color: "var(--compass-color-success)" },
10370
+ children: [
10371
+ "+",
10372
+ formatAmount(tx.amount),
10373
+ " ",
10374
+ position.assetSymbol
10375
+ ]
10376
+ }
10377
+ ),
10378
+ /* @__PURE__ */ jsx(
10379
+ "p",
10380
+ {
10381
+ className: "text-xs",
10382
+ style: { color: "var(--compass-color-text-tertiary)" },
10383
+ children: date ? `Deposit - ${date}` : "Deposit"
10384
+ }
10385
+ )
10386
+ ] }),
10387
+ tx.txHash && /* @__PURE__ */ jsx(
10388
+ "a",
9741
10389
  {
9742
- className: "text-xs",
9743
- style: { color: "var(--compass-color-text-tertiary)" },
9744
- children: date ? `Deposit - ${date}` : "Deposit"
10390
+ href: `https://etherscan.io/tx/${tx.txHash}`,
10391
+ target: "_blank",
10392
+ rel: "noopener noreferrer",
10393
+ className: "p-2 rounded-lg hover:opacity-80 transition-opacity",
10394
+ style: { backgroundColor: "var(--compass-color-background)" },
10395
+ children: /* @__PURE__ */ jsx(ExternalLink, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } })
9745
10396
  }
9746
10397
  )
9747
- ] }),
9748
- tx.txHash && /* @__PURE__ */ jsx(
9749
- "a",
9750
- {
9751
- href: `https://etherscan.io/tx/${tx.txHash}`,
9752
- target: "_blank",
9753
- rel: "noopener noreferrer",
9754
- className: "p-2 rounded-lg hover:opacity-80 transition-opacity",
9755
- style: { backgroundColor: "var(--compass-color-background)" },
9756
- children: /* @__PURE__ */ jsx(ExternalLink, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } })
9757
- }
9758
- )
9759
- ]
9760
- },
9761
- `deposit-${i}`
9762
- );
9763
- }),
9764
- position.withdrawals.map((tx, i) => {
9765
- const date = formatDate(tx.timestamp);
9766
- return /* @__PURE__ */ jsxs(
9767
- "div",
9768
- {
9769
- className: "flex items-center justify-between p-3 rounded-lg",
9770
- style: { backgroundColor: "var(--compass-color-surface)" },
9771
- children: [
9772
- /* @__PURE__ */ jsxs("div", { children: [
9773
- /* @__PURE__ */ jsxs(
9774
- "p",
9775
- {
9776
- className: "font-medium",
9777
- style: { color: "var(--compass-color-error)" },
9778
- children: [
9779
- "-",
9780
- formatAmount(tx.amount),
9781
- " ",
9782
- position.assetSymbol
9783
- ]
9784
- }
9785
- ),
9786
- /* @__PURE__ */ jsx(
9787
- "p",
10398
+ ]
10399
+ },
10400
+ `deposit-${i}`
10401
+ );
10402
+ }),
10403
+ position.withdrawals.map((tx, i) => {
10404
+ const date = formatDate(tx.timestamp);
10405
+ return /* @__PURE__ */ jsxs(
10406
+ "div",
10407
+ {
10408
+ className: "flex items-center justify-between p-3 rounded-lg",
10409
+ style: { backgroundColor: "var(--compass-color-surface)" },
10410
+ children: [
10411
+ /* @__PURE__ */ jsxs("div", { children: [
10412
+ /* @__PURE__ */ jsxs(
10413
+ "p",
10414
+ {
10415
+ className: "font-medium",
10416
+ style: { color: "var(--compass-color-error)" },
10417
+ children: [
10418
+ "-",
10419
+ formatAmount(tx.amount),
10420
+ " ",
10421
+ position.assetSymbol
10422
+ ]
10423
+ }
10424
+ ),
10425
+ /* @__PURE__ */ jsx(
10426
+ "p",
10427
+ {
10428
+ className: "text-xs",
10429
+ style: { color: "var(--compass-color-text-tertiary)" },
10430
+ children: date ? `Withdrawal - ${date}` : "Withdrawal"
10431
+ }
10432
+ )
10433
+ ] }),
10434
+ tx.txHash && /* @__PURE__ */ jsx(
10435
+ "a",
9788
10436
  {
9789
- className: "text-xs",
9790
- style: { color: "var(--compass-color-text-tertiary)" },
9791
- children: date ? `Withdrawal - ${date}` : "Withdrawal"
10437
+ href: `https://etherscan.io/tx/${tx.txHash}`,
10438
+ target: "_blank",
10439
+ rel: "noopener noreferrer",
10440
+ className: "p-2 rounded-lg hover:opacity-80 transition-opacity",
10441
+ style: { backgroundColor: "var(--compass-color-background)" },
10442
+ children: /* @__PURE__ */ jsx(ExternalLink, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } })
9792
10443
  }
9793
10444
  )
9794
- ] }),
9795
- tx.txHash && /* @__PURE__ */ jsx(
9796
- "a",
9797
- {
9798
- href: `https://etherscan.io/tx/${tx.txHash}`,
9799
- target: "_blank",
9800
- rel: "noopener noreferrer",
9801
- className: "p-2 rounded-lg hover:opacity-80 transition-opacity",
9802
- style: { backgroundColor: "var(--compass-color-background)" },
9803
- children: /* @__PURE__ */ jsx(ExternalLink, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } })
9804
- }
9805
- )
9806
- ]
9807
- },
9808
- `withdraw-${i}`
9809
- );
9810
- })
10445
+ ]
10446
+ },
10447
+ `withdraw-${i}`
10448
+ );
10449
+ })
10450
+ ] })
9811
10451
  ] })
9812
- ] })
9813
- ]
9814
- }
9815
- )
10452
+ ]
10453
+ }
10454
+ )
10455
+ ]
9816
10456
  }
9817
10457
  );
9818
10458
  }
@@ -10581,7 +11221,7 @@ function AllocationEditor({
10581
11221
  ) })
10582
11222
  ] });
10583
11223
  }
10584
- var EVM_CHAIN_IDS3 = {
11224
+ var EVM_CHAIN_IDS4 = {
10585
11225
  ethereum: 1,
10586
11226
  base: 8453,
10587
11227
  arbitrum: 42161
@@ -10615,7 +11255,7 @@ function RebalancingWidget({
10615
11255
  }) {
10616
11256
  const { chainId: contextChainId, setChainId } = useChain();
10617
11257
  const CHAIN_ID = chain || contextChainId;
10618
- const { address, signTypedData, isConnected, login, logout, switchChain, walletChainId } = useEmbeddableWallet();
11258
+ const { address, signTypedData, isConnected, login, logout, switchChain, walletChainId, fundWallet, hasExternalWallet } = useEmbeddableWallet();
10619
11259
  const { earnAccountAddress } = useEarnAccount();
10620
11260
  const queryClient = useQueryClient();
10621
11261
  const { portfolio, earnAccountMarkets, isMarketsLoading, isLoading, isError, error, refetch } = useRebalancingData(chain);
@@ -10664,10 +11304,11 @@ function RebalancingWidget({
10664
11304
  const [selectedToken, setSelectedToken] = useState("USDC");
10665
11305
  const [depositAmount, setDepositAmount] = useState("");
10666
11306
  const [isTokenDropdownOpen, setIsTokenDropdownOpen] = useState(false);
10667
- const [isProcessing, setIsProcessing] = useState(false);
10668
- const [depositError, setDepositError] = useState(null);
10669
- const [depositStatus, setDepositStatus] = useState("");
11307
+ const [depositTxState, setDepositTxState] = useState({ status: "idle" });
10670
11308
  const earnBalanceRef = useRef(null);
11309
+ const { startPolling: startDepositPolling, clearPolling: clearDepositPolling } = useTxPolling({
11310
+ queryKeysToInvalidate: [["earnAccountBalances"], ["rebalancing"]]
11311
+ });
10671
11312
  useEffect(() => {
10672
11313
  setTargets([]);
10673
11314
  setOriginalAllocations({});
@@ -10682,9 +11323,9 @@ function RebalancingWidget({
10682
11323
  setIsSwapModalOpen(false);
10683
11324
  setSelectedMarket(null);
10684
11325
  setDepositAmount("");
10685
- setDepositError(null);
10686
- setDepositStatus("");
10687
- }, [CHAIN_ID]);
11326
+ setDepositTxState({ status: "idle" });
11327
+ clearDepositPolling();
11328
+ }, [CHAIN_ID, clearDepositPolling]);
10688
11329
  useEffect(() => {
10689
11330
  if (portfolio && portfolio.positions.length > 0 && !hasInitializedTargets && !isLoading) {
10690
11331
  const origAllocs = {};
@@ -10765,7 +11406,7 @@ function RebalancingWidget({
10765
11406
  setTargets((prev) => prev.map((t, i) => i === index ? { ...t, targetPercent: rounded } : t));
10766
11407
  }, []);
10767
11408
  const ensureCorrectChain = useCallback(async () => {
10768
- const targetChainId = EVM_CHAIN_IDS3[CHAIN_ID];
11409
+ const targetChainId = EVM_CHAIN_IDS4[CHAIN_ID];
10769
11410
  if (!targetChainId) return;
10770
11411
  if (walletChainId !== void 0 && walletChainId !== targetChainId) {
10771
11412
  if (!switchChain) {
@@ -10897,13 +11538,11 @@ function RebalancingWidget({
10897
11538
  };
10898
11539
  const handleDeposit = useCallback(async () => {
10899
11540
  if (!selectedMarket || !depositAmount || parseFloat(depositAmount) <= 0 || !address || !signTypedData) return;
10900
- setIsProcessing(true);
10901
- setDepositError(null);
11541
+ setDepositTxState({ status: "preparing" });
10902
11542
  const underlyingToken = selectedMarket.underlyingToken;
10903
11543
  try {
10904
11544
  let resultTxHash;
10905
11545
  if (needsSwap) {
10906
- setDepositStatus("Getting swap quote...");
10907
11546
  const quoteResponse = await fetch(
10908
11547
  `/api/compass/swap/quote?owner=${address}&chain=${CHAIN_ID}&tokenIn=${selectedToken}&tokenOut=${underlyingToken}&amountIn=${depositAmount}`
10909
11548
  );
@@ -10917,7 +11556,6 @@ function RebalancingWidget({
10917
11556
  throw new Error("Invalid swap quote - no output amount");
10918
11557
  }
10919
11558
  const actualDepositAmount = (parseFloat(estimatedOutput) * 0.99999).toString();
10920
- setDepositStatus("Preparing swap and deposit...");
10921
11559
  const bundleActions = [
10922
11560
  {
10923
11561
  body: {
@@ -10951,7 +11589,7 @@ function RebalancingWidget({
10951
11589
  throw new Error(errorData.error || "Failed to prepare bundle");
10952
11590
  }
10953
11591
  const { eip712, normalizedTypes, domain, message } = await prepareResponse.json();
10954
- setDepositStatus("Please sign the transaction...");
11592
+ setDepositTxState({ status: "signing" });
10955
11593
  await ensureCorrectChain();
10956
11594
  const signature = await signTypedData({
10957
11595
  domain,
@@ -10959,7 +11597,7 @@ function RebalancingWidget({
10959
11597
  primaryType: "SafeTx",
10960
11598
  message
10961
11599
  });
10962
- setDepositStatus("Executing swap and deposit...");
11600
+ setDepositTxState({ status: "broadcasting" });
10963
11601
  const executeResponse = await fetch("/api/compass/bundle/execute", {
10964
11602
  method: "POST",
10965
11603
  headers: { "Content-Type": "application/json" },
@@ -10977,7 +11615,6 @@ function RebalancingWidget({
10977
11615
  const result = await executeResponse.json();
10978
11616
  resultTxHash = result.txHash;
10979
11617
  } else {
10980
- setDepositStatus("Preparing deposit...");
10981
11618
  const venueParams = buildVenueParamsFromMarket(selectedMarket);
10982
11619
  const prepareResponse = await fetch("/api/compass/deposit/prepare", {
10983
11620
  method: "POST",
@@ -10994,7 +11631,7 @@ function RebalancingWidget({
10994
11631
  throw new Error(errData.error || "Failed to prepare deposit");
10995
11632
  }
10996
11633
  const prepareData = await prepareResponse.json();
10997
- setDepositStatus("Please sign the transaction...");
11634
+ setDepositTxState({ status: "signing" });
10998
11635
  await ensureCorrectChain();
10999
11636
  const signature = await signTypedData({
11000
11637
  domain: prepareData.domain,
@@ -11002,7 +11639,7 @@ function RebalancingWidget({
11002
11639
  primaryType: "SafeTx",
11003
11640
  message: prepareData.message
11004
11641
  });
11005
- setDepositStatus("Executing deposit...");
11642
+ setDepositTxState({ status: "broadcasting" });
11006
11643
  const executeResponse = await fetch("/api/compass/deposit/execute", {
11007
11644
  method: "POST",
11008
11645
  headers: { "Content-Type": "application/json" },
@@ -11020,7 +11657,8 @@ function RebalancingWidget({
11020
11657
  const result = await executeResponse.json();
11021
11658
  resultTxHash = result.txHash;
11022
11659
  }
11023
- setDepositStatus("Deposit successful!");
11660
+ setDepositTxState({ status: "submitted", txHash: resultTxHash });
11661
+ startDepositPolling(resultTxHash, setDepositTxState);
11024
11662
  setDepositAmount("");
11025
11663
  queryClient.invalidateQueries({ queryKey: ["earnAccountBalances"] });
11026
11664
  queryClient.invalidateQueries({ queryKey: ["rebalancing"] });
@@ -11032,15 +11670,11 @@ function RebalancingWidget({
11032
11670
  refetch();
11033
11671
  }, delay);
11034
11672
  }
11035
- setTimeout(() => setDepositStatus(""), 3e3);
11036
11673
  } catch (err) {
11037
- setDepositError(err instanceof Error ? err.message : "Deposit failed");
11038
- setDepositStatus("");
11674
+ setDepositTxState({ status: "failed", error: err instanceof Error ? err.message : "Deposit failed" });
11039
11675
  onError?.(err instanceof Error ? err : new Error("Deposit failed"));
11040
- } finally {
11041
- setIsProcessing(false);
11042
11676
  }
11043
- }, [selectedMarket, depositAmount, address, signTypedData, selectedToken, needsSwap, CHAIN_ID, ensureCorrectChain, queryClient, refetch, onError]);
11677
+ }, [selectedMarket, depositAmount, address, signTypedData, selectedToken, needsSwap, CHAIN_ID, ensureCorrectChain, queryClient, refetch, onError, startDepositPolling]);
11044
11678
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full", style: { gap: "0", fontFamily: "var(--compass-font-family)", height, overflow: "hidden", borderRadius: "var(--compass-border-radius-xl)" }, children: [
11045
11679
  /* @__PURE__ */ jsxs("div", { style: { flex: 1, minHeight: 0, overflowY: "auto", scrollbarWidth: "none", display: "flex", flexDirection: "column" }, children: [
11046
11680
  showChainSwitcher && !chain && /* @__PURE__ */ jsx("div", { style: { padding: "0 4px", marginBottom: "8px" }, children: /* @__PURE__ */ jsx(ChainSwitcher, {}) }),
@@ -11069,8 +11703,19 @@ function RebalancingWidget({
11069
11703
  }
11070
11704
  )
11071
11705
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
11072
- /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold mb-2", style: { color: "var(--compass-color-text)" }, children: "No positions found" }),
11073
- /* @__PURE__ */ jsx("p", { className: "text-sm", style: { color: "var(--compass-color-text-secondary)" }, children: "Deposit into a vault, Aave market, or Pendle market to start rebalancing." })
11706
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold mb-2", style: { color: "var(--compass-color-text)" }, children: fundWallet && !hasExternalWallet ? "Fund your account to start" : "No positions found" }),
11707
+ /* @__PURE__ */ jsx("p", { className: "text-sm mb-4", style: { color: "var(--compass-color-text-secondary)" }, children: fundWallet && !hasExternalWallet ? "Buy crypto to get started with your DeFi portfolio." : "Deposit into a vault, Aave market, or Pendle market to start rebalancing." }),
11708
+ fundWallet && earnAccountAddress && /* @__PURE__ */ jsx("div", { style: { width: "100%", maxWidth: "320px" }, children: /* @__PURE__ */ jsx(
11709
+ BuyForm,
11710
+ {
11711
+ targetAddress: earnAccountAddress,
11712
+ onComplete: () => {
11713
+ queryClient.invalidateQueries({ queryKey: ["rebalancingPortfolio"] });
11714
+ queryClient.invalidateQueries({ queryKey: ["earnAccountBalances"] });
11715
+ refetch();
11716
+ }
11717
+ }
11718
+ ) })
11074
11719
  ] }) }),
11075
11720
  state !== "loading" && state !== "empty" && portfolio && /* @__PURE__ */ jsxs(Fragment, { children: [
11076
11721
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center text-center", style: { padding: "16px 16px 12px" }, children: [
@@ -11237,7 +11882,7 @@ function RebalancingWidget({
11237
11882
  selectedMarket,
11238
11883
  onMarketSelect: (market) => {
11239
11884
  setSelectedMarket(market);
11240
- setDepositError(null);
11885
+ if (depositTxState.status === "failed") setDepositTxState({ status: "idle" });
11241
11886
  },
11242
11887
  isLoading: isMarketsLoading
11243
11888
  }
@@ -11260,10 +11905,10 @@ function RebalancingWidget({
11260
11905
  value: depositAmount,
11261
11906
  onChange: (e) => {
11262
11907
  setDepositAmount(e.target.value);
11263
- setDepositError(null);
11908
+ if (depositTxState.status === "failed") setDepositTxState({ status: "idle" });
11264
11909
  },
11265
11910
  placeholder: "0.00",
11266
- disabled: isProcessing,
11911
+ disabled: depositTxState.status !== "idle" && depositTxState.status !== "confirmed" && depositTxState.status !== "failed",
11267
11912
  className: "flex-1 font-bold bg-transparent outline-none",
11268
11913
  style: {
11269
11914
  color: "var(--compass-color-text)",
@@ -11279,7 +11924,7 @@ function RebalancingWidget({
11279
11924
  "button",
11280
11925
  {
11281
11926
  onClick: () => setIsTokenDropdownOpen(!isTokenDropdownOpen),
11282
- disabled: isProcessing,
11927
+ disabled: depositTxState.status !== "idle" && depositTxState.status !== "confirmed" && depositTxState.status !== "failed",
11283
11928
  className: "flex items-center font-semibold",
11284
11929
  style: {
11285
11930
  backgroundColor: "var(--compass-color-surface-hover, var(--compass-color-surface))",
@@ -11316,7 +11961,7 @@ function RebalancingWidget({
11316
11961
  onClick: () => {
11317
11962
  setSelectedToken(token);
11318
11963
  setIsTokenDropdownOpen(false);
11319
- setDepositError(null);
11964
+ if (depositTxState.status === "failed") setDepositTxState({ status: "idle" });
11320
11965
  },
11321
11966
  className: "w-full text-left font-medium",
11322
11967
  style: {
@@ -11342,7 +11987,7 @@ function RebalancingWidget({
11342
11987
  "button",
11343
11988
  {
11344
11989
  onClick: () => setDepositAmount(earnBalancesQuery.toString()),
11345
- disabled: isProcessing,
11990
+ disabled: depositTxState.status !== "idle" && depositTxState.status !== "confirmed" && depositTxState.status !== "failed",
11346
11991
  className: "font-semibold",
11347
11992
  style: { color: "var(--compass-color-primary)", fontSize: "0.8125rem", background: "none", border: "none", padding: 0 },
11348
11993
  children: [
@@ -11379,26 +12024,12 @@ function RebalancingWidget({
11379
12024
  ]
11380
12025
  }
11381
12026
  ),
11382
- depositError && /* @__PURE__ */ jsx(
11383
- "div",
11384
- {
11385
- className: "flex items-center",
11386
- style: {
11387
- backgroundColor: "var(--compass-color-error-muted)",
11388
- color: "var(--compass-color-error)",
11389
- borderRadius: "var(--compass-border-radius-lg)",
11390
- padding: "12px 14px",
11391
- gap: "8px",
11392
- fontSize: "0.875rem"
11393
- },
11394
- children: depositError
11395
- }
11396
- ),
12027
+ /* @__PURE__ */ jsx(TxStatus, { state: depositTxState }),
11397
12028
  /* @__PURE__ */ jsx(
11398
12029
  "button",
11399
12030
  {
11400
12031
  onClick: !isConnected && login ? login : handleDeposit,
11401
- disabled: isConnected && (isProcessing || !selectedMarket || !depositAmount || parseFloat(depositAmount) <= 0 || parseFloat(depositAmount) > earnBalancesQuery),
12032
+ disabled: isConnected && (depositTxState.status !== "idle" && depositTxState.status !== "confirmed" && depositTxState.status !== "failed" || !selectedMarket || !depositAmount || parseFloat(depositAmount) <= 0 || parseFloat(depositAmount) > earnBalancesQuery),
11402
12033
  className: "w-full font-bold",
11403
12034
  style: {
11404
12035
  backgroundColor: "var(--compass-color-primary)",
@@ -11409,26 +12040,13 @@ function RebalancingWidget({
11409
12040
  fontSize: "1rem",
11410
12041
  transition: "all 200ms ease",
11411
12042
  border: "none",
11412
- opacity: isConnected && (isProcessing || !selectedMarket || !depositAmount || parseFloat(depositAmount) <= 0 || parseFloat(depositAmount) > earnBalancesQuery) ? 0.4 : 1
12043
+ opacity: isConnected && (depositTxState.status !== "idle" && depositTxState.status !== "confirmed" && depositTxState.status !== "failed" || !selectedMarket || !depositAmount || parseFloat(depositAmount) <= 0 || parseFloat(depositAmount) > earnBalancesQuery) ? 0.4 : 1
11413
12044
  },
11414
- children: !isConnected ? "Connect Wallet" : isProcessing ? /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-center", style: { gap: "8px" }, children: [
12045
+ children: !isConnected ? "Connect Wallet" : depositTxState.status !== "idle" && depositTxState.status !== "confirmed" && depositTxState.status !== "failed" ? /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-center", style: { gap: "8px" }, children: [
11415
12046
  /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }),
11416
12047
  "Processing..."
11417
12048
  ] }) : !selectedMarket ? "Select a market" : needsSwap ? "Swap & Deposit" : "Deposit"
11418
12049
  }
11419
- ),
11420
- depositStatus && !depositError && /* @__PURE__ */ jsx(
11421
- "div",
11422
- {
11423
- className: "text-sm text-center",
11424
- style: {
11425
- backgroundColor: depositStatus.includes("successful") ? "var(--compass-color-success-muted)" : "var(--compass-color-primary-muted, rgba(99, 102, 241, 0.1))",
11426
- color: depositStatus.includes("successful") ? "var(--compass-color-success)" : "var(--compass-color-primary)",
11427
- borderRadius: "var(--compass-border-radius-lg)",
11428
- padding: "10px 12px"
11429
- },
11430
- children: depositStatus
11431
- }
11432
12050
  )
11433
12051
  ] }),
11434
12052
  clientPreview && clientPreview.actions.length > 0 && state === "editing" && /* @__PURE__ */ jsxs(