@compass-labs/widgets 0.1.25 → 0.1.27

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,9 @@
1
- import { createContext, useMemo, useContext, useState, useEffect, useCallback, useRef } from 'react';
2
- import { QueryClient, QueryClientProvider, useQueryClient, useQuery } from '@tanstack/react-query';
1
+ import { createContext, forwardRef, useState, useCallback, useImperativeHandle, useContext, useEffect, useRef, useMemo } from 'react';
2
+ import { useQueryClient, useQuery, QueryClient, QueryClientProvider } from '@tanstack/react-query';
3
3
  import { CompassApiSDK } from '@compass-labs/api-sdk';
4
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
5
  import { arbitrum, base, mainnet } from 'viem/chains';
6
- import { Wallet, LogOut, X, ChevronDown, AlertCircle, ArrowRight, Loader2, ArrowDownLeft, ArrowUpRight, ExternalLink, Coins, Search, SlidersHorizontal, TrendingUp, Plus, ChevronRight, Minus, Shield, CheckCircle, Calendar, ArrowDownUp, ChevronUp, Inbox, Percent, Clock, TrendingDown } from 'lucide-react';
6
+ import { Wallet, Loader2, ArrowDownLeft, ArrowUpRight, Coins, X, ChevronDown, LogOut, AlertCircle, ArrowRight, ExternalLink, Plus, ChevronRight, Minus, Shield, CheckCircle, ArrowDownUp, AlertTriangle, Check, RotateCcw, Equal, ChevronUp, Inbox, Percent, Clock, Calendar, TrendingUp, TrendingDown } from 'lucide-react';
7
7
 
8
8
  // src/provider/CompassProvider.tsx
9
9
  var ApiContext = createContext(null);
@@ -1402,6 +1402,7 @@ function TokenSelector({
1402
1402
  borderColor: "var(--compass-color-border)",
1403
1403
  boxShadow: "var(--compass-shadow-lg)",
1404
1404
  maxHeight: "200px",
1405
+ scrollbarWidth: "none",
1405
1406
  borderRadius: "var(--compass-border-radius-lg)"
1406
1407
  },
1407
1408
  children: tokens.map((token) => {
@@ -1965,7 +1966,8 @@ function TransactionHistory({
1965
1966
  className: "mt-2 max-h-48 overflow-y-auto border",
1966
1967
  style: {
1967
1968
  borderColor: "var(--compass-color-border)",
1968
- borderRadius: "var(--compass-border-radius-lg)"
1969
+ borderRadius: "var(--compass-border-radius-lg)",
1970
+ scrollbarWidth: "none"
1969
1971
  },
1970
1972
  children: /* @__PURE__ */ jsx("div", { children: allEvents.map((item, index) => /* @__PURE__ */ jsxs(
1971
1973
  "div",
@@ -2449,10 +2451,11 @@ function AccountBalancesModal({
2449
2451
  ] });
2450
2452
  }
2451
2453
  var TRANSFER_TOKENS = ["USDC"];
2452
- function EarnAccountBalance({
2454
+ var EarnAccountBalance = forwardRef(function EarnAccountBalance2({
2453
2455
  compact = false,
2456
+ hideVisual = false,
2454
2457
  onTransferComplete
2455
- }) {
2458
+ }, ref) {
2456
2459
  const [isModalOpen, setIsModalOpen] = useState(false);
2457
2460
  const [activeAction, setActiveAction] = useState("deposit");
2458
2461
  const [selectedToken, setSelectedToken] = useState(TRANSFER_TOKENS[0]);
@@ -2526,6 +2529,9 @@ function EarnAccountBalance({
2526
2529
  resetForm();
2527
2530
  setIsModalOpen(true);
2528
2531
  };
2532
+ useImperativeHandle(ref, () => ({
2533
+ openTransferModal: handleOpenModal
2534
+ }));
2529
2535
  const handleCloseModal = () => {
2530
2536
  setIsModalOpen(false);
2531
2537
  resetForm();
@@ -2688,7 +2694,7 @@ function EarnAccountBalance({
2688
2694
  return null;
2689
2695
  }
2690
2696
  const isProcessing = transferState !== "idle" && transferState !== "success" && transferState !== "error";
2691
- if (!isDeployed) {
2697
+ if (!isDeployed && !hideVisual) {
2692
2698
  return /* @__PURE__ */ jsxs(
2693
2699
  "div",
2694
2700
  {
@@ -2714,44 +2720,46 @@ function EarnAccountBalance({
2714
2720
  );
2715
2721
  }
2716
2722
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2717
- /* @__PURE__ */ jsxs(
2718
- "button",
2719
- {
2720
- onClick: () => setIsBalancesModalOpen(true),
2721
- className: `flex items-center gap-2 border transition-colors hover:border-[var(--compass-color-primary)] ${compact ? "px-2 py-1.5" : "px-3 py-2"}`,
2722
- style: {
2723
- backgroundColor: "var(--compass-color-surface)",
2724
- borderColor: "var(--compass-color-border)",
2725
- cursor: "pointer",
2726
- borderRadius: "var(--compass-border-radius-lg)",
2727
- fontFamily: "var(--compass-font-family)"
2728
- },
2729
- children: [
2730
- /* @__PURE__ */ jsx(Wallet, { size: compact ? 14 : 16, style: { color: "var(--compass-color-primary)" } }),
2731
- balancesLoading ? /* @__PURE__ */ jsx(Loader2, { size: compact ? 12 : 14, className: "animate-spin", style: { color: "var(--compass-color-text-secondary)" } }) : /* @__PURE__ */ jsx(
2732
- "span",
2733
- {
2734
- className: `font-medium ${compact ? "text-xs" : "text-sm"}`,
2735
- style: { color: "var(--compass-color-text)" },
2736
- children: formatUSD(totalUsdValue)
2737
- }
2738
- )
2739
- ]
2740
- }
2741
- ),
2742
- /* @__PURE__ */ jsx(
2743
- "button",
2744
- {
2745
- onClick: handleOpenModal,
2746
- className: `font-medium transition-colors ${compact ? "px-2 py-1 text-xs" : "px-3 py-1.5 text-sm"}`,
2747
- style: {
2748
- backgroundColor: "var(--compass-color-primary)",
2749
- color: "var(--compass-color-primary-text)",
2750
- borderRadius: "var(--compass-border-radius-md)"
2751
- },
2752
- children: "Fund"
2753
- }
2754
- ),
2723
+ !hideVisual && /* @__PURE__ */ jsxs(Fragment, { children: [
2724
+ /* @__PURE__ */ jsxs(
2725
+ "button",
2726
+ {
2727
+ onClick: () => setIsBalancesModalOpen(true),
2728
+ className: `flex items-center gap-2 border transition-colors hover:border-[var(--compass-color-primary)] ${compact ? "px-2 py-1.5" : "px-3 py-2"}`,
2729
+ style: {
2730
+ backgroundColor: "var(--compass-color-surface)",
2731
+ borderColor: "var(--compass-color-border)",
2732
+ cursor: "pointer",
2733
+ borderRadius: "var(--compass-border-radius-lg)",
2734
+ fontFamily: "var(--compass-font-family)"
2735
+ },
2736
+ children: [
2737
+ /* @__PURE__ */ jsx(Wallet, { size: compact ? 14 : 16, style: { color: "var(--compass-color-primary)" } }),
2738
+ balancesLoading ? /* @__PURE__ */ jsx(Loader2, { size: compact ? 12 : 14, className: "animate-spin", style: { color: "var(--compass-color-text-secondary)" } }) : /* @__PURE__ */ jsx(
2739
+ "span",
2740
+ {
2741
+ className: `font-medium ${compact ? "text-xs" : "text-sm"}`,
2742
+ style: { color: "var(--compass-color-text)" },
2743
+ children: formatUSD(totalUsdValue)
2744
+ }
2745
+ )
2746
+ ]
2747
+ }
2748
+ ),
2749
+ /* @__PURE__ */ jsx(
2750
+ "button",
2751
+ {
2752
+ onClick: handleOpenModal,
2753
+ className: `font-medium transition-colors ${compact ? "px-2 py-1 text-xs" : "px-3 py-1.5 text-sm"}`,
2754
+ style: {
2755
+ backgroundColor: "var(--compass-color-primary)",
2756
+ color: "var(--compass-color-primary-text)",
2757
+ borderRadius: "var(--compass-border-radius-md)"
2758
+ },
2759
+ children: "Fund"
2760
+ }
2761
+ )
2762
+ ] }),
2755
2763
  /* @__PURE__ */ jsx(
2756
2764
  AccountBalancesModal,
2757
2765
  {
@@ -2942,1270 +2950,435 @@ function EarnAccountBalance({
2942
2950
  }
2943
2951
  )
2944
2952
  ] });
2945
- }
2946
- function formatTVL(tvl) {
2947
- if (!tvl) return "$0";
2948
- const num = parseFloat(tvl);
2949
- if (isNaN(num)) return "$0";
2950
- if (num >= 1e9) return `$${(num / 1e9).toFixed(2)}B`;
2951
- if (num >= 1e6) return `$${(num / 1e6).toFixed(2)}M`;
2952
- if (num >= 1e3) return `$${(num / 1e3).toFixed(2)}K`;
2953
- return `$${num.toFixed(2)}`;
2954
- }
2955
- function formatAPY(apy) {
2956
- if (!apy) return "0.00%";
2957
- const num = parseFloat(apy);
2958
- if (isNaN(num)) return "0.00%";
2959
- return `${num.toFixed(2)}%`;
2960
- }
2961
- function VaultCard({
2962
- vault,
2963
- showApy,
2964
- sortBy,
2965
- showTvl,
2966
- showUserPosition,
2967
- onClick
2953
+ });
2954
+ var MARKET_TABS = [
2955
+ { value: "variable", label: "VARIABLE" },
2956
+ { value: "fixed", label: "FIXED" }
2957
+ ];
2958
+ var TAB_TO_MARKET_TYPES = {
2959
+ variable: ["aave", "vaults"],
2960
+ fixed: ["pendle"]
2961
+ };
2962
+ function MarketSelector({
2963
+ markets,
2964
+ selectedMarket,
2965
+ onMarketSelect,
2966
+ marketTab,
2967
+ onMarketTabChange,
2968
+ isLoading,
2969
+ allowedMarketIds
2968
2970
  }) {
2969
- const getDisplayApy = () => {
2970
- switch (sortBy) {
2971
- case "apy_30d":
2972
- return { value: vault.apy30d, period: "30d" };
2973
- case "apy_90d":
2974
- return { value: vault.apy90d, period: "90d" };
2975
- case "apy_7d":
2976
- case "tvl":
2977
- default:
2978
- return { value: vault.apy7d, period: "7d" };
2971
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
2972
+ const dropdownRef = useRef(null);
2973
+ useEffect(() => {
2974
+ function handleClickOutside(e) {
2975
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
2976
+ setIsDropdownOpen(false);
2977
+ }
2979
2978
  }
2979
+ document.addEventListener("mousedown", handleClickOutside);
2980
+ return () => document.removeEventListener("mousedown", handleClickOutside);
2981
+ }, []);
2982
+ const allowedTypes = TAB_TO_MARKET_TYPES[marketTab];
2983
+ const showTvl = marketTab === "variable";
2984
+ const filteredMarkets = useMemo(() => {
2985
+ return markets.filter((m) => {
2986
+ if (!allowedTypes.includes(m.type)) return false;
2987
+ if (allowedMarketIds !== void 0) {
2988
+ return allowedMarketIds.includes(m.id);
2989
+ }
2990
+ return true;
2991
+ }).sort((a, b) => b.apy - a.apy);
2992
+ }, [markets, allowedTypes, allowedMarketIds]);
2993
+ const hasMarkets = filteredMarkets.length > 0;
2994
+ const formatTvl = (value) => {
2995
+ if (value >= 1e9) return `$${(value / 1e9).toFixed(1)}B`;
2996
+ if (value >= 1e6) return `$${(value / 1e6).toFixed(1)}M`;
2997
+ if (value >= 1e3) return `$${(value / 1e3).toFixed(0)}K`;
2998
+ return `$${value}`;
2980
2999
  };
2981
- const displayApy = getDisplayApy();
2982
- const hasPosition = vault.userPosition && parseFloat(vault.userPosition.balance) > 0;
2983
3000
  return /* @__PURE__ */ jsxs(
2984
- "button",
3001
+ "div",
2985
3002
  {
2986
- onClick,
2987
- className: "w-full border text-left hover:scale-[1.01]",
2988
3003
  style: {
2989
- backgroundColor: "var(--compass-color-surface)",
2990
- borderColor: hasPosition ? "var(--compass-color-primary)" : "var(--compass-color-border)",
2991
- borderRadius: "var(--compass-border-radius-xl)",
2992
- fontFamily: "var(--compass-font-family)",
2993
- padding: "var(--compass-spacing-card)",
2994
- transition: "var(--compass-transition-normal)"
3004
+ display: "flex",
3005
+ flexDirection: "column",
3006
+ gap: "calc(var(--compass-spacing-unit) * 1.5)"
2995
3007
  },
2996
3008
  children: [
2997
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2998
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
2999
- /* @__PURE__ */ jsx(
3000
- "h3",
3001
- {
3002
- className: "font-semibold",
3003
- style: {
3004
- fontSize: "var(--compass-font-size-body)",
3005
- color: "var(--compass-color-text)"
3006
- },
3007
- children: vault.name
3008
- }
3009
- ),
3010
- /* @__PURE__ */ jsx(
3011
- "span",
3012
- {
3013
- className: "text-sm",
3014
- style: { color: "var(--compass-color-text-secondary)" },
3015
- children: vault.assetSymbol
3016
- }
3017
- )
3018
- ] }),
3019
- showApy && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end", children: [
3020
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
3021
- /* @__PURE__ */ jsx(TrendingUp, { size: 16, style: { color: "var(--compass-color-success)" } }),
3022
- /* @__PURE__ */ jsx(
3023
- "span",
3009
+ /* @__PURE__ */ jsxs("div", { children: [
3010
+ /* @__PURE__ */ jsx(
3011
+ "label",
3012
+ {
3013
+ style: {
3014
+ display: "block",
3015
+ fontSize: "var(--compass-font-size-sm)",
3016
+ color: "var(--compass-color-text-secondary)",
3017
+ marginBottom: "var(--compass-spacing-unit)"
3018
+ },
3019
+ children: "Market Type"
3020
+ }
3021
+ ),
3022
+ /* @__PURE__ */ jsx(
3023
+ "div",
3024
+ {
3025
+ style: {
3026
+ display: "flex",
3027
+ borderRadius: "var(--compass-border-radius-md, 8px)",
3028
+ border: "1px solid var(--compass-color-border, rgba(255, 255, 255, 0.1))",
3029
+ overflow: "hidden"
3030
+ },
3031
+ children: MARKET_TABS.map((tab) => /* @__PURE__ */ jsx(
3032
+ "button",
3024
3033
  {
3025
- className: "font-mono text-lg font-bold",
3026
- style: { color: "var(--compass-color-success)" },
3027
- children: formatAPY(displayApy.value)
3028
- }
3029
- )
3030
- ] }),
3034
+ onClick: () => onMarketTabChange(tab.value),
3035
+ style: {
3036
+ flex: 1,
3037
+ padding: "6px 8px",
3038
+ backgroundColor: marketTab === tab.value ? "var(--compass-color-primary, #6366f1)" : "var(--compass-color-surface, #12121a)",
3039
+ color: marketTab === tab.value ? "var(--compass-color-primary-text, white)" : "var(--compass-color-text, #e4e4e7)",
3040
+ border: "none",
3041
+ cursor: "pointer",
3042
+ fontSize: "12px",
3043
+ fontWeight: 500,
3044
+ fontFamily: "var(--compass-font-family)",
3045
+ transition: "var(--compass-transition-normal, all 0.2s ease)"
3046
+ },
3047
+ children: tab.label
3048
+ },
3049
+ tab.value
3050
+ ))
3051
+ }
3052
+ )
3053
+ ] }),
3054
+ /* @__PURE__ */ jsxs("div", { children: [
3055
+ /* @__PURE__ */ jsx(
3056
+ "label",
3057
+ {
3058
+ style: {
3059
+ display: "block",
3060
+ fontSize: "var(--compass-font-size-sm)",
3061
+ color: "var(--compass-color-text-secondary)",
3062
+ marginBottom: "var(--compass-spacing-unit)"
3063
+ },
3064
+ children: "Select Market"
3065
+ }
3066
+ ),
3067
+ /* @__PURE__ */ jsxs("div", { ref: dropdownRef, style: { position: "relative" }, children: [
3031
3068
  /* @__PURE__ */ jsxs(
3032
- "span",
3069
+ "button",
3033
3070
  {
3034
- className: "text-xs",
3035
- style: { color: "var(--compass-color-text-tertiary)" },
3071
+ onClick: () => hasMarkets && setIsDropdownOpen(!isDropdownOpen),
3072
+ disabled: !hasMarkets || isLoading,
3073
+ style: {
3074
+ width: "100%",
3075
+ padding: "8px 12px",
3076
+ backgroundColor: "var(--compass-color-surface, #12121a)",
3077
+ border: "1px solid var(--compass-color-border, rgba(255, 255, 255, 0.1))",
3078
+ borderRadius: "var(--compass-border-radius-md, 8px)",
3079
+ fontSize: "13px",
3080
+ display: "flex",
3081
+ justifyContent: "space-between",
3082
+ alignItems: "center",
3083
+ cursor: hasMarkets ? "pointer" : "not-allowed",
3084
+ opacity: hasMarkets ? 1 : 0.5,
3085
+ fontFamily: "var(--compass-font-family)"
3086
+ },
3036
3087
  children: [
3037
- displayApy.period,
3038
- " APY"
3088
+ /* @__PURE__ */ jsx(
3089
+ "span",
3090
+ {
3091
+ style: {
3092
+ color: selectedMarket ? "var(--compass-color-text)" : "var(--compass-color-text-secondary)"
3093
+ },
3094
+ children: isLoading ? "Loading markets..." : !hasMarkets ? "No markets available" : selectedMarket ? selectedMarket.name : "Select a market"
3095
+ }
3096
+ ),
3097
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "var(--compass-spacing-unit)" }, children: [
3098
+ selectedMarket && /* @__PURE__ */ jsxs(
3099
+ "span",
3100
+ {
3101
+ style: {
3102
+ color: "var(--compass-color-success)",
3103
+ fontSize: "var(--compass-font-size-sm)"
3104
+ },
3105
+ children: [
3106
+ selectedMarket.apy.toFixed(2),
3107
+ "%"
3108
+ ]
3109
+ }
3110
+ ),
3111
+ /* @__PURE__ */ jsx(
3112
+ ChevronDown,
3113
+ {
3114
+ size: 16,
3115
+ style: {
3116
+ color: "var(--compass-color-text-secondary)",
3117
+ transform: isDropdownOpen ? "rotate(180deg)" : "rotate(0deg)",
3118
+ transition: "var(--compass-transition-normal)"
3119
+ }
3120
+ }
3121
+ )
3122
+ ] })
3039
3123
  ]
3040
3124
  }
3041
- )
3042
- ] })
3043
- ] }),
3044
- (showTvl || showUserPosition && hasPosition) && /* @__PURE__ */ jsxs(
3045
- "div",
3046
- {
3047
- className: "flex items-center justify-between border-t",
3048
- style: {
3049
- borderColor: "var(--compass-color-border)",
3050
- marginTop: "calc(var(--compass-spacing-unit) * 2)",
3051
- paddingTop: "calc(var(--compass-spacing-unit) * 2)"
3052
- },
3053
- children: [
3054
- showTvl && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
3055
- /* @__PURE__ */ jsx(
3056
- "span",
3057
- {
3058
- className: "text-xs",
3059
- style: { color: "var(--compass-color-text-tertiary)" },
3060
- children: "TVL"
3061
- }
3062
- ),
3063
- /* @__PURE__ */ jsx(
3064
- "span",
3065
- {
3066
- className: "font-mono text-sm",
3067
- style: { color: "var(--compass-color-text-secondary)" },
3068
- children: formatTVL(vault.tvlUsd)
3069
- }
3070
- )
3071
- ] }),
3072
- showUserPosition && hasPosition && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
3073
- /* @__PURE__ */ jsx(
3074
- "span",
3075
- {
3076
- className: "text-xs",
3077
- style: { color: "var(--compass-color-text-tertiary)" },
3078
- children: "Position"
3079
- }
3080
- ),
3081
- /* @__PURE__ */ jsxs(
3082
- "span",
3125
+ ),
3126
+ isDropdownOpen && /* @__PURE__ */ jsx(
3127
+ "div",
3128
+ {
3129
+ style: {
3130
+ position: "absolute",
3131
+ top: "100%",
3132
+ left: 0,
3133
+ right: 0,
3134
+ marginTop: "var(--compass-spacing-unit, 8px)",
3135
+ backgroundColor: "var(--compass-color-background, #0a0a0f)",
3136
+ border: "1px solid var(--compass-color-border, rgba(255, 255, 255, 0.1))",
3137
+ borderRadius: "var(--compass-border-radius-lg, 12px)",
3138
+ boxShadow: "var(--compass-shadow-lg, 0 25px 50px -12px rgba(0, 0, 0, 0.5))",
3139
+ zIndex: 50,
3140
+ maxHeight: "200px",
3141
+ overflowY: "auto",
3142
+ scrollbarWidth: "none"
3143
+ },
3144
+ children: filteredMarkets.map((market) => /* @__PURE__ */ jsxs(
3145
+ "button",
3083
3146
  {
3084
- className: "font-mono text-sm font-medium",
3085
- style: { color: "var(--compass-color-primary)" },
3147
+ onClick: () => {
3148
+ onMarketSelect(market);
3149
+ setIsDropdownOpen(false);
3150
+ },
3151
+ style: {
3152
+ width: "100%",
3153
+ padding: "calc(var(--compass-spacing-unit, 8px) * 2)",
3154
+ backgroundColor: selectedMarket?.id === market.id ? "var(--compass-color-primary-muted, rgba(99, 102, 241, 0.15))" : "var(--compass-color-surface, #12121a)",
3155
+ border: "none",
3156
+ borderBottom: "1px solid var(--compass-color-border, rgba(255, 255, 255, 0.1))",
3157
+ cursor: "pointer",
3158
+ textAlign: "left",
3159
+ fontFamily: "var(--compass-font-family)"
3160
+ },
3086
3161
  children: [
3087
- parseFloat(vault.userPosition.balance).toFixed(4),
3088
- " ",
3089
- vault.assetSymbol
3162
+ /* @__PURE__ */ jsxs(
3163
+ "div",
3164
+ {
3165
+ style: {
3166
+ display: "flex",
3167
+ justifyContent: "space-between",
3168
+ alignItems: "center"
3169
+ },
3170
+ children: [
3171
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text)" }, children: market.name }),
3172
+ /* @__PURE__ */ jsxs(
3173
+ "span",
3174
+ {
3175
+ style: {
3176
+ color: "var(--compass-color-success)",
3177
+ fontSize: "var(--compass-font-size-sm)"
3178
+ },
3179
+ children: [
3180
+ market.apy.toFixed(2),
3181
+ "% APY"
3182
+ ]
3183
+ }
3184
+ )
3185
+ ]
3186
+ }
3187
+ ),
3188
+ (showTvl && market.tvl > 0 || market.expiry) && /* @__PURE__ */ jsxs(
3189
+ "div",
3190
+ {
3191
+ style: {
3192
+ fontSize: "var(--compass-font-size-xs)",
3193
+ color: "var(--compass-color-text-tertiary)",
3194
+ marginTop: "calc(var(--compass-spacing-unit) * 0.5)"
3195
+ },
3196
+ children: [
3197
+ showTvl && market.tvl > 0 && `TVL: ${formatTvl(market.tvl)}`,
3198
+ showTvl && market.tvl > 0 && market.expiry && " \xB7 ",
3199
+ market.expiry && `Expires: ${market.expiry}`
3200
+ ]
3201
+ }
3202
+ )
3090
3203
  ]
3091
- }
3092
- )
3093
- ] })
3094
- ]
3095
- }
3096
- )
3204
+ },
3205
+ market.id
3206
+ ))
3207
+ }
3208
+ )
3209
+ ] })
3210
+ ] })
3097
3211
  ]
3098
3212
  }
3099
3213
  );
3100
3214
  }
3101
- function useVaultsData(options = {}) {
3102
- const { address } = useEmbeddableWallet();
3103
- const { chainId } = useChain();
3104
- const { sortBy = "apy_7d", assetFilter, minApy, minTvl } = options;
3105
- const vaultsQuery = useQuery({
3106
- queryKey: ["vaults", chainId, sortBy, assetFilter, minApy, minTvl],
3107
- queryFn: async () => {
3108
- const assetSymbol = assetFilter && assetFilter.length === 1 ? assetFilter[0] : void 0;
3109
- const params = new URLSearchParams({
3110
- chain: chainId,
3111
- orderBy: sortBy === "tvl" ? "tvl_usd" : sortBy,
3112
- direction: "desc",
3113
- limit: "100",
3114
- ...assetSymbol && { assetSymbol }
3115
- });
3116
- const response = await fetch(`/api/compass/vaults?${params}`);
3117
- if (!response.ok) {
3118
- throw new Error("Failed to fetch vaults");
3119
- }
3120
- const data = await response.json();
3121
- let vaults = (data.vaults || []).map((v) => ({
3122
- vaultAddress: v.vaultAddress,
3123
- name: v.name || "Unknown Vault",
3124
- assetSymbol: v.assetSymbol || "UNKNOWN",
3125
- apy7d: v.apy7d ?? null,
3126
- apy30d: v.apy30d ?? null,
3127
- apy90d: v.apy90d ?? null,
3128
- tvlUsd: v.tvlUsd ?? null
3129
- }));
3130
- if (assetFilter && assetFilter.length > 1) {
3131
- vaults = vaults.filter(
3132
- (v) => assetFilter.includes(v.assetSymbol.toUpperCase())
3133
- );
3134
- }
3135
- if (minApy !== void 0 && minApy > 0) {
3136
- vaults = vaults.filter((v) => {
3137
- const apy = parseFloat(v.apy7d || "0");
3138
- return apy >= minApy;
3139
- });
3140
- }
3141
- if (minTvl !== void 0 && minTvl > 0) {
3142
- vaults = vaults.filter((v) => {
3143
- const tvl = parseFloat(v.tvlUsd || "0");
3144
- return tvl >= minTvl;
3145
- });
3146
- }
3147
- return vaults;
3148
- },
3149
- staleTime: 30 * 1e3
3150
- });
3151
- const positionsQuery = useQuery({
3152
- queryKey: ["vaultPositions", chainId, address],
3153
- queryFn: async () => {
3154
- if (!address) return [];
3155
- const params = new URLSearchParams({
3156
- chain: chainId,
3157
- owner: address
3158
- });
3159
- const response = await fetch(`/api/compass/positions?${params}`);
3160
- if (!response.ok) {
3161
- throw new Error("Failed to fetch positions");
3162
- }
3163
- const data = await response.json();
3164
- return (data.vaults || []).map((p) => ({
3165
- vaultAddress: p.vaultAddress,
3166
- balance: p.balance || "0",
3167
- pnl: p.pnl ? {
3168
- unrealizedPnl: p.pnl.unrealizedPnl || "0",
3169
- realizedPnl: p.pnl.realizedPnl || "0",
3170
- totalPnl: p.pnl.totalPnl || "0",
3171
- totalDeposited: p.pnl.totalDeposited || "0"
3172
- } : void 0,
3173
- deposits: (p.deposits || []).map((d) => ({
3174
- amount: d.inputAmount || d.amount || "0",
3175
- blockNumber: d.blockNumber || 0,
3176
- txHash: d.transactionHash || d.txHash || ""
3177
- })),
3178
- withdrawals: (p.withdrawals || []).map((w) => ({
3179
- amount: w.outputAmount || w.amount || "0",
3180
- blockNumber: w.blockNumber || 0,
3181
- txHash: w.transactionHash || w.txHash || ""
3182
- }))
3183
- }));
3184
- },
3185
- enabled: !!address,
3186
- staleTime: 30 * 1e3
3187
- });
3188
- const vaultsWithPositions = (vaultsQuery.data || []).map((vault) => {
3189
- const position = positionsQuery.data?.find(
3190
- (p) => p.vaultAddress.toLowerCase() === vault.vaultAddress.toLowerCase()
3191
- );
3192
- return { ...vault, userPosition: position };
3193
- });
3194
- return {
3195
- vaults: vaultsWithPositions,
3196
- isLoading: vaultsQuery.isLoading,
3197
- isError: vaultsQuery.isError,
3198
- error: vaultsQuery.error,
3199
- refetch: () => {
3200
- vaultsQuery.refetch();
3201
- positionsQuery.refetch();
3202
- }
3203
- };
3204
- }
3205
- function VaultsList({
3206
- showApy = true,
3207
- apyPeriods = ["7d", "30d", "90d"],
3208
- showTvl = true,
3209
- showUserPosition = true,
3210
- showPnL = true,
3211
- showHistory = true,
3212
- showSearch = true,
3213
- showSort = true,
3214
- defaultSort = "apy_7d",
3215
- assetFilter,
3216
- minApy,
3217
- minTvl: initialMinTvl,
3218
- showTvlFilter = true,
3219
- onVaultSelect,
3220
- onDeposit,
3221
- onWithdraw
3222
- }) {
3223
- const [searchQuery, setSearchQuery] = useState("");
3224
- const [sortBy, setSortBy] = useState(defaultSort);
3225
- const [selectedVault, setSelectedVault] = useState(null);
3226
- const [minTvlFilter, setMinTvlFilter] = useState(initialMinTvl);
3227
- const [showFilterPanel, setShowFilterPanel] = useState(false);
3228
- const { vaults, isLoading, isError, refetch } = useVaultsData({
3229
- sortBy,
3230
- assetFilter,
3231
- minApy,
3232
- minTvl: minTvlFilter
3233
- });
3234
- const handleMinTvlChange = useCallback((value) => {
3235
- const num = parseFloat(value);
3236
- setMinTvlFilter(isNaN(num) || num <= 0 ? void 0 : num);
3237
- }, []);
3238
- const filteredVaults = useMemo(() => {
3239
- if (!searchQuery) return vaults;
3240
- const query = searchQuery.toLowerCase();
3241
- return vaults.filter(
3242
- (v) => v.name.toLowerCase().includes(query) || v.assetSymbol.toLowerCase().includes(query)
3243
- );
3244
- }, [vaults, searchQuery]);
3245
- const handleVaultClick = (vault) => {
3246
- setSelectedVault(vault);
3247
- onVaultSelect?.(vault);
3248
- };
3249
- const handleActionSuccess = (action, amount, txHash) => {
3250
- if (action === "deposit") {
3251
- onDeposit?.(selectedVault, amount, txHash);
3252
- } else {
3253
- onWithdraw?.(selectedVault, amount, txHash);
3254
- }
3255
- refetch();
3215
+ function PositionCard({ position }) {
3216
+ const [isHistoryExpanded, setIsHistoryExpanded] = useState(false);
3217
+ const formatShortDate = (dateStr) => {
3218
+ if (!dateStr) return null;
3219
+ const date = new Date(dateStr);
3220
+ if (isNaN(date.getTime())) return null;
3221
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
3256
3222
  };
3257
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "var(--compass-spacing-card)" }, children: [
3258
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between flex-wrap", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
3259
- /* @__PURE__ */ jsx(ChainSwitcher, {}),
3260
- /* @__PURE__ */ jsxs("div", { className: "flex items-center", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
3261
- /* @__PURE__ */ jsx(EarnAccountBalance, { compact: true }),
3262
- /* @__PURE__ */ jsx(WalletStatus, { compact: true })
3263
- ] })
3264
- ] }),
3265
- (showSearch || showSort || showTvlFilter) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
3266
- /* @__PURE__ */ jsxs("div", { className: "flex", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
3267
- showSearch && /* @__PURE__ */ jsxs(
3223
+ return /* @__PURE__ */ jsxs(
3224
+ "div",
3225
+ {
3226
+ style: {
3227
+ backgroundColor: "var(--compass-color-surface, #12121a)",
3228
+ border: "1px solid var(--compass-color-border, rgba(255, 255, 255, 0.1))",
3229
+ borderRadius: "var(--compass-border-radius-lg, 12px)",
3230
+ padding: "var(--compass-spacing-card, 16px)"
3231
+ },
3232
+ children: [
3233
+ /* @__PURE__ */ jsxs(
3268
3234
  "div",
3269
3235
  {
3270
- className: "flex-1 flex items-center border",
3271
3236
  style: {
3272
- backgroundColor: "var(--compass-color-background)",
3273
- borderColor: "var(--compass-color-border)",
3274
- borderRadius: "var(--compass-border-radius-lg)",
3275
- fontFamily: "var(--compass-font-family)",
3276
- padding: "var(--compass-spacing-input)",
3277
- gap: "calc(var(--compass-spacing-unit) * 0.5)"
3237
+ display: "flex",
3238
+ justifyContent: "space-between",
3239
+ alignItems: "flex-start",
3240
+ marginBottom: "calc(var(--compass-spacing-unit) * 2)"
3278
3241
  },
3279
3242
  children: [
3280
- /* @__PURE__ */ jsx(Search, { size: 16, style: { color: "var(--compass-color-text-tertiary)" } }),
3281
3243
  /* @__PURE__ */ jsx(
3282
- "input",
3244
+ "span",
3245
+ {
3246
+ style: {
3247
+ fontWeight: 600,
3248
+ color: "var(--compass-color-text)",
3249
+ fontSize: "var(--compass-font-size-base)"
3250
+ },
3251
+ children: position.marketName
3252
+ }
3253
+ ),
3254
+ /* @__PURE__ */ jsxs(
3255
+ "span",
3283
3256
  {
3284
- type: "text",
3285
- placeholder: "Search vaults...",
3286
- value: searchQuery,
3287
- onChange: (e) => setSearchQuery(e.target.value),
3288
- className: "flex-1 bg-transparent outline-none text-sm",
3289
- style: { color: "var(--compass-color-text)" }
3257
+ style: {
3258
+ color: "var(--compass-color-success)",
3259
+ fontSize: "var(--compass-font-size-sm)",
3260
+ backgroundColor: "var(--compass-color-success-muted)",
3261
+ padding: "calc(var(--compass-spacing-unit) * 0.5) calc(var(--compass-spacing-unit) * 1.5)",
3262
+ borderRadius: "var(--compass-border-radius-full)"
3263
+ },
3264
+ children: [
3265
+ "APY: ",
3266
+ position.apy.toFixed(2),
3267
+ "%"
3268
+ ]
3290
3269
  }
3291
3270
  )
3292
3271
  ]
3293
3272
  }
3294
3273
  ),
3295
- showSort && /* @__PURE__ */ jsxs(
3296
- "select",
3297
- {
3298
- value: sortBy,
3299
- onChange: (e) => setSortBy(e.target.value),
3300
- className: "border text-sm cursor-pointer",
3301
- style: {
3302
- backgroundColor: "var(--compass-color-background)",
3303
- borderColor: "var(--compass-color-border)",
3304
- color: "var(--compass-color-text)",
3305
- borderRadius: "var(--compass-border-radius-lg)",
3306
- fontFamily: "var(--compass-font-family)",
3307
- padding: "var(--compass-spacing-input)"
3308
- },
3309
- children: [
3310
- /* @__PURE__ */ jsx("option", { value: "apy_7d", children: "APY (7D)" }),
3311
- /* @__PURE__ */ jsx("option", { value: "apy_30d", children: "APY (30D)" }),
3312
- /* @__PURE__ */ jsx("option", { value: "apy_90d", children: "APY (90D)" }),
3313
- /* @__PURE__ */ jsx("option", { value: "tvl", children: "TVL" })
3314
- ]
3315
- }
3316
- ),
3317
- showTvlFilter && /* @__PURE__ */ jsxs(
3318
- "button",
3274
+ /* @__PURE__ */ jsxs(
3275
+ "div",
3319
3276
  {
3320
- onClick: () => setShowFilterPanel(!showFilterPanel),
3321
- className: "border text-sm flex items-center",
3322
3277
  style: {
3323
- backgroundColor: showFilterPanel || minTvlFilter ? "var(--compass-color-primary-muted)" : "var(--compass-color-background)",
3324
- borderColor: showFilterPanel || minTvlFilter ? "var(--compass-color-primary)" : "var(--compass-color-border)",
3325
- color: showFilterPanel || minTvlFilter ? "var(--compass-color-primary)" : "var(--compass-color-text)",
3326
- borderRadius: "var(--compass-border-radius-lg)",
3327
- fontFamily: "var(--compass-font-family)",
3328
- padding: "var(--compass-spacing-input)",
3329
- gap: "calc(var(--compass-spacing-unit) * 0.5)",
3330
- transition: "var(--compass-transition-normal)"
3278
+ display: "grid",
3279
+ gridTemplateColumns: "1fr 1fr",
3280
+ gap: "calc(var(--compass-spacing-unit) * 2)",
3281
+ marginBottom: "calc(var(--compass-spacing-unit) * 2)"
3331
3282
  },
3332
3283
  children: [
3333
- /* @__PURE__ */ jsx(SlidersHorizontal, { size: 14 }),
3334
- "Filter"
3335
- ]
3336
- }
3337
- )
3338
- ] }),
3339
- showTvlFilter && showFilterPanel && /* @__PURE__ */ jsxs(
3340
- "div",
3341
- {
3342
- className: "flex items-center border",
3343
- style: {
3344
- backgroundColor: "var(--compass-color-surface)",
3345
- borderColor: "var(--compass-color-border)",
3346
- borderRadius: "var(--compass-border-radius-lg)",
3347
- fontFamily: "var(--compass-font-family)",
3348
- padding: "calc(var(--compass-spacing-unit) * 0.75)",
3349
- gap: "calc(var(--compass-spacing-unit) * 0.75)"
3350
- },
3351
- children: [
3352
- /* @__PURE__ */ jsx(
3353
- "label",
3354
- {
3355
- className: "text-sm font-medium whitespace-nowrap",
3356
- style: { color: "var(--compass-color-text-secondary)" },
3357
- children: "Min TVL:"
3358
- }
3359
- ),
3360
- /* @__PURE__ */ jsxs("div", { className: "flex items-center", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
3361
- /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text-tertiary)" }, children: "$" }),
3362
- /* @__PURE__ */ jsx(
3363
- "input",
3364
- {
3365
- type: "number",
3366
- placeholder: "0",
3367
- value: minTvlFilter || "",
3368
- onChange: (e) => handleMinTvlChange(e.target.value),
3369
- className: "w-24 border text-sm bg-transparent",
3370
- style: {
3371
- borderColor: "var(--compass-color-border)",
3372
- color: "var(--compass-color-text)",
3373
- borderRadius: "var(--compass-border-radius-sm)",
3374
- fontFamily: "var(--compass-font-family)",
3375
- padding: "calc(var(--compass-spacing-unit) * 0.25) calc(var(--compass-spacing-unit) * 0.5)"
3376
- }
3377
- }
3378
- )
3379
- ] }),
3380
- minTvlFilter && /* @__PURE__ */ jsx(
3381
- "button",
3382
- {
3383
- onClick: () => setMinTvlFilter(void 0),
3384
- className: "text-xs",
3385
- style: {
3386
- backgroundColor: "var(--compass-color-error-muted)",
3387
- color: "var(--compass-color-error)",
3388
- borderRadius: "var(--compass-border-radius-sm)",
3389
- padding: "calc(var(--compass-spacing-unit) * 0.25) calc(var(--compass-spacing-unit) * 0.5)",
3390
- transition: "var(--compass-transition-fast)"
3391
- },
3392
- children: "Clear"
3393
- }
3394
- )
3395
- ]
3396
- }
3397
- )
3398
- ] }),
3399
- isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", style: { padding: "calc(var(--compass-spacing-unit) * 3) 0" }, children: /* @__PURE__ */ jsx(Loader2, { size: 24, className: "animate-spin", style: { color: "var(--compass-color-primary)" } }) }) : isError ? /* @__PURE__ */ jsx(
3400
- "div",
3401
- {
3402
- className: "text-center",
3403
- style: {
3404
- backgroundColor: "var(--compass-color-error-muted)",
3405
- color: "var(--compass-color-error)",
3406
- borderRadius: "var(--compass-border-radius-lg)",
3407
- padding: "var(--compass-spacing-card)"
3408
- },
3409
- children: "Failed to load vaults. Please try again."
3410
- }
3411
- ) : filteredVaults.length === 0 ? /* @__PURE__ */ jsx(
3412
- "div",
3413
- {
3414
- className: "text-center",
3415
- style: { color: "var(--compass-color-text-secondary)", padding: "calc(var(--compass-spacing-unit) * 2)" },
3416
- children: "No vaults found"
3417
- }
3418
- ) : /* @__PURE__ */ jsx("div", { className: "flex flex-col", style: { gap: "calc(var(--compass-spacing-unit) * 0.75)" }, children: filteredVaults.map((vault) => /* @__PURE__ */ jsx(
3419
- VaultCard,
3420
- {
3421
- vault,
3422
- showApy,
3423
- sortBy,
3424
- showTvl,
3425
- showUserPosition,
3426
- onClick: () => handleVaultClick(vault)
3427
- },
3428
- vault.vaultAddress
3429
- )) }),
3430
- selectedVault && /* @__PURE__ */ jsx(
3431
- ActionModal,
3432
- {
3433
- isOpen: !!selectedVault,
3434
- onClose: () => setSelectedVault(null),
3435
- title: selectedVault.name,
3436
- children: /* @__PURE__ */ jsx(EarnAccountGuard, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "var(--compass-spacing-card)" }, children: [
3437
- showPnL && selectedVault.userPosition?.pnl && /* @__PURE__ */ jsx(
3438
- PnLSummary,
3439
- {
3440
- pnl: selectedVault.userPosition.pnl,
3441
- tokenSymbol: selectedVault.assetSymbol,
3442
- tokenPrice: 1
3443
- }
3444
- ),
3445
- showHistory && (selectedVault.userPosition?.deposits?.length || selectedVault.userPosition?.withdrawals?.length) && /* @__PURE__ */ jsx(
3446
- TransactionHistory,
3447
- {
3448
- deposits: selectedVault.userPosition?.deposits,
3449
- withdrawals: selectedVault.userPosition?.withdrawals,
3450
- tokenSymbol: selectedVault.assetSymbol
3451
- }
3452
- ),
3453
- /* @__PURE__ */ jsx(
3454
- DepositWithdrawForm,
3455
- {
3456
- venueType: "VAULT",
3457
- venueAddress: selectedVault.vaultAddress,
3458
- venueToken: selectedVault.assetSymbol,
3459
- positionBalance: selectedVault.userPosition?.balance,
3460
- onSuccess: handleActionSuccess
3461
- }
3462
- )
3463
- ] }) })
3464
- }
3465
- )
3466
- ] });
3467
- }
3468
- function useAaveData(options = {}) {
3469
- const { address } = useEmbeddableWallet();
3470
- const { chainId } = useChain();
3471
- const { assetFilter } = options;
3472
- const marketsQuery = useQuery({
3473
- queryKey: ["aaveMarkets", chainId, assetFilter],
3474
- queryFn: async () => {
3475
- const params = new URLSearchParams({ chain: chainId });
3476
- const response = await fetch(`/api/compass/aave/markets?${params}`);
3477
- if (!response.ok) {
3478
- throw new Error("Failed to fetch Aave markets");
3479
- }
3480
- const data = await response.json();
3481
- const marketsDict = data.markets || {};
3482
- let markets = [];
3483
- for (const [symbol, tokenData] of Object.entries(marketsDict)) {
3484
- if (assetFilter && assetFilter.length > 0) {
3485
- if (!assetFilter.includes(symbol.toUpperCase())) {
3486
- continue;
3487
- }
3488
- }
3489
- const token = tokenData;
3490
- const chainData = token.chains?.[chainId];
3491
- if (chainData) {
3492
- markets.push({
3493
- marketAddress: chainData.address || "",
3494
- reserveSymbol: symbol,
3495
- underlyingSymbol: symbol,
3496
- supplyApy: chainData.supplyApy?.toString() ?? null,
3497
- borrowApy: chainData.borrowApy?.toString() ?? null
3498
- });
3499
- }
3500
- }
3501
- markets.sort((a, b) => {
3502
- return parseFloat(b.supplyApy || "0") - parseFloat(a.supplyApy || "0");
3503
- });
3504
- return markets;
3505
- },
3506
- staleTime: 30 * 1e3
3507
- });
3508
- const positionsQuery = useQuery({
3509
- queryKey: ["aavePositions", chainId, address],
3510
- queryFn: async () => {
3511
- if (!address) return [];
3512
- const params = new URLSearchParams({
3513
- chain: chainId,
3514
- owner: address
3515
- });
3516
- const response = await fetch(`/api/compass/positions?${params}`);
3517
- if (!response.ok) {
3518
- throw new Error("Failed to fetch positions");
3519
- }
3520
- const data = await response.json();
3521
- return (data.aave || []).map((p) => ({
3522
- marketAddress: p.marketAddress || "",
3523
- reserveSymbol: p.reserveSymbol || "",
3524
- balance: p.balance || "0",
3525
- pnl: p.pnl ? {
3526
- unrealizedPnl: p.pnl.unrealizedPnl || "0",
3527
- realizedPnl: p.pnl.realizedPnl || "0",
3528
- totalPnl: p.pnl.totalPnl || "0",
3529
- totalDeposited: p.pnl.totalDeposited || "0"
3530
- } : void 0,
3531
- deposits: (p.deposits || []).map((d) => ({
3532
- amount: d.inputAmount || d.amount || "0",
3533
- blockNumber: d.blockNumber || 0,
3534
- txHash: d.transactionHash || d.txHash || ""
3535
- })),
3536
- withdrawals: (p.withdrawals || []).map((w) => ({
3537
- amount: w.outputAmount || w.amount || "0",
3538
- blockNumber: w.blockNumber || 0,
3539
- txHash: w.transactionHash || w.txHash || ""
3540
- }))
3541
- }));
3542
- },
3543
- enabled: !!address,
3544
- staleTime: 30 * 1e3
3545
- });
3546
- const marketsWithPositions = (marketsQuery.data || []).map((market) => {
3547
- const position = positionsQuery.data?.find(
3548
- (p) => p.reserveSymbol.toLowerCase() === market.reserveSymbol.toLowerCase()
3549
- );
3550
- return { ...market, userPosition: position };
3551
- });
3552
- return {
3553
- markets: marketsWithPositions,
3554
- isLoading: marketsQuery.isLoading,
3555
- isError: marketsQuery.isError,
3556
- error: marketsQuery.error,
3557
- refetch: () => {
3558
- marketsQuery.refetch();
3559
- positionsQuery.refetch();
3560
- }
3561
- };
3562
- }
3563
- function formatAPY2(apy) {
3564
- if (!apy) return "0.00%";
3565
- const num = parseFloat(apy);
3566
- return `${num.toFixed(2)}%`;
3567
- }
3568
- function AaveMarketsList({
3569
- showApy = true,
3570
- showUserPosition = true,
3571
- showPnL = true,
3572
- showHistory = true,
3573
- showSearch = true,
3574
- assetFilter,
3575
- onMarketSelect,
3576
- onSupply,
3577
- onWithdraw
3578
- }) {
3579
- const [searchQuery, setSearchQuery] = useState("");
3580
- const [selectedMarket, setSelectedMarket] = useState(null);
3581
- const { markets, isLoading, isError, refetch } = useAaveData({ assetFilter });
3582
- const filteredMarkets = useMemo(() => {
3583
- if (!searchQuery) return markets;
3584
- const query = searchQuery.toLowerCase();
3585
- return markets.filter(
3586
- (m) => m.reserveSymbol.toLowerCase().includes(query) || m.underlyingSymbol.toLowerCase().includes(query)
3587
- );
3588
- }, [markets, searchQuery]);
3589
- const handleMarketClick = (market) => {
3590
- setSelectedMarket(market);
3591
- onMarketSelect?.(market);
3592
- };
3593
- const handleActionSuccess = (action, amount, txHash) => {
3594
- if (action === "deposit") {
3595
- onSupply?.(selectedMarket, amount, txHash);
3596
- } else {
3597
- onWithdraw?.(selectedMarket, amount, txHash);
3598
- }
3599
- refetch();
3600
- };
3601
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "var(--compass-spacing-card)" }, children: [
3602
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between flex-wrap", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
3603
- /* @__PURE__ */ jsx(ChainSwitcher, {}),
3604
- /* @__PURE__ */ jsxs("div", { className: "flex items-center", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
3605
- /* @__PURE__ */ jsx(EarnAccountBalance, { compact: true }),
3606
- /* @__PURE__ */ jsx(WalletStatus, { compact: true })
3607
- ] })
3608
- ] }),
3609
- showSearch && /* @__PURE__ */ jsxs(
3610
- "div",
3611
- {
3612
- className: "flex items-center border",
3613
- style: {
3614
- backgroundColor: "var(--compass-color-background)",
3615
- borderColor: "var(--compass-color-border)",
3616
- borderRadius: "var(--compass-border-radius-lg)",
3617
- fontFamily: "var(--compass-font-family)",
3618
- padding: "var(--compass-spacing-input)",
3619
- gap: "calc(var(--compass-spacing-unit) * 0.5)"
3620
- },
3621
- children: [
3622
- /* @__PURE__ */ jsx(Search, { size: 16, style: { color: "var(--compass-color-text-tertiary)" } }),
3623
- /* @__PURE__ */ jsx(
3624
- "input",
3625
- {
3626
- type: "text",
3627
- placeholder: "Search markets...",
3628
- value: searchQuery,
3629
- onChange: (e) => setSearchQuery(e.target.value),
3630
- className: "flex-1 bg-transparent outline-none text-sm",
3631
- style: { color: "var(--compass-color-text)" }
3632
- }
3633
- )
3634
- ]
3635
- }
3636
- ),
3637
- isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", style: { padding: "calc(var(--compass-spacing-unit) * 3) 0" }, children: /* @__PURE__ */ jsx(Loader2, { size: 24, className: "animate-spin", style: { color: "var(--compass-color-primary)" } }) }) : isError ? /* @__PURE__ */ jsx(
3638
- "div",
3639
- {
3640
- className: "text-center",
3641
- style: {
3642
- backgroundColor: "var(--compass-color-error-muted)",
3643
- color: "var(--compass-color-error)",
3644
- borderRadius: "var(--compass-border-radius-lg)",
3645
- padding: "var(--compass-spacing-card)"
3646
- },
3647
- children: "Failed to load Aave markets. Please try again."
3648
- }
3649
- ) : filteredMarkets.length === 0 ? /* @__PURE__ */ jsx(
3650
- "div",
3651
- {
3652
- className: "text-center",
3653
- style: { color: "var(--compass-color-text-secondary)", padding: "calc(var(--compass-spacing-unit) * 2)" },
3654
- children: "No markets found"
3655
- }
3656
- ) : /* @__PURE__ */ jsx("div", { className: "flex flex-col", style: { gap: "calc(var(--compass-spacing-unit) * 0.75)" }, children: filteredMarkets.map((market) => {
3657
- const hasPosition = market.userPosition && parseFloat(market.userPosition.balance) > 0;
3658
- return /* @__PURE__ */ jsxs(
3659
- "button",
3660
- {
3661
- onClick: () => handleMarketClick(market),
3662
- className: "w-full border text-left hover:scale-[1.01]",
3663
- style: {
3664
- backgroundColor: "var(--compass-color-surface)",
3665
- borderColor: hasPosition ? "var(--compass-color-primary)" : "var(--compass-color-border)",
3666
- borderRadius: "var(--compass-border-radius-xl)",
3667
- fontFamily: "var(--compass-font-family)",
3668
- padding: "var(--compass-spacing-card)",
3669
- transition: "var(--compass-transition-normal)"
3670
- },
3671
- children: [
3672
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
3673
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "calc(var(--compass-spacing-unit) * 0.25)" }, children: [
3284
+ /* @__PURE__ */ jsxs("div", { children: [
3674
3285
  /* @__PURE__ */ jsx(
3675
- "h3",
3286
+ "div",
3676
3287
  {
3677
- className: "font-semibold",
3678
3288
  style: {
3679
- fontSize: "var(--compass-font-size-body)",
3680
- color: "var(--compass-color-text)"
3289
+ fontSize: "var(--compass-font-size-xs)",
3290
+ color: "var(--compass-color-text-tertiary)",
3291
+ marginBottom: "calc(var(--compass-spacing-unit) * 0.5)"
3681
3292
  },
3682
- children: market.underlyingSymbol
3293
+ children: "Amount"
3683
3294
  }
3684
3295
  ),
3685
- /* @__PURE__ */ jsx(
3686
- "span",
3687
- {
3688
- className: "text-sm",
3689
- style: { color: "var(--compass-color-text-secondary)" },
3690
- children: "Aave V3"
3691
- }
3692
- )
3296
+ /* @__PURE__ */ jsxs("div", { style: { color: "var(--compass-color-text)", fontWeight: 500 }, children: [
3297
+ position.amount.toLocaleString(),
3298
+ " ",
3299
+ position.token
3300
+ ] })
3693
3301
  ] }),
3694
- showApy && /* @__PURE__ */ jsxs("div", { className: "flex items-center", style: { gap: "calc(var(--compass-spacing-unit) * 0.25)" }, children: [
3695
- /* @__PURE__ */ jsx(TrendingUp, { size: 14, style: { color: "var(--compass-color-success)" } }),
3696
- /* @__PURE__ */ jsx(
3697
- "span",
3698
- {
3699
- className: "font-mono font-semibold",
3700
- style: { color: "var(--compass-color-success)" },
3701
- children: formatAPY2(market.supplyApy)
3702
- }
3703
- )
3704
- ] })
3705
- ] }),
3706
- showUserPosition && hasPosition && /* @__PURE__ */ jsx(
3707
- "div",
3708
- {
3709
- className: "flex items-center justify-end border-t",
3710
- style: {
3711
- borderColor: "var(--compass-color-border)",
3712
- marginTop: "calc(var(--compass-spacing-unit) * 0.75)",
3713
- paddingTop: "calc(var(--compass-spacing-unit) * 0.75)"
3714
- },
3715
- children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end", children: [
3302
+ (() => {
3303
+ const deposits = position.transactions.filter((t) => t.type === "deposit" && t.timestamp);
3304
+ const earliest = deposits.length > 0 ? deposits.reduce((min, t) => !min || t.timestamp < min ? t.timestamp : min, "") : void 0;
3305
+ const formatted = formatShortDate(earliest);
3306
+ if (!formatted) return null;
3307
+ return /* @__PURE__ */ jsxs("div", { children: [
3716
3308
  /* @__PURE__ */ jsx(
3717
- "span",
3309
+ "div",
3718
3310
  {
3719
- className: "text-xs",
3720
- style: { color: "var(--compass-color-text-tertiary)" },
3721
- children: "Your Position"
3311
+ style: {
3312
+ fontSize: "var(--compass-font-size-xs)",
3313
+ color: "var(--compass-color-text-tertiary)",
3314
+ marginBottom: "calc(var(--compass-spacing-unit) * 0.5)"
3315
+ },
3316
+ children: "Entered"
3722
3317
  }
3723
3318
  ),
3724
- /* @__PURE__ */ jsxs(
3725
- "span",
3726
- {
3727
- className: "font-mono text-sm font-medium",
3728
- style: { color: "var(--compass-color-primary)" },
3729
- children: [
3730
- parseFloat(market.userPosition.balance).toFixed(4),
3731
- " ",
3732
- market.underlyingSymbol
3733
- ]
3734
- }
3735
- )
3736
- ] })
3737
- }
3738
- )
3739
- ]
3740
- },
3741
- market.marketAddress
3742
- );
3743
- }) }),
3744
- selectedMarket && /* @__PURE__ */ jsx(
3745
- ActionModal,
3746
- {
3747
- isOpen: !!selectedMarket,
3748
- onClose: () => setSelectedMarket(null),
3749
- title: `${selectedMarket.underlyingSymbol} - Aave`,
3750
- children: /* @__PURE__ */ jsx(EarnAccountGuard, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "var(--compass-spacing-card)" }, children: [
3751
- showPnL && selectedMarket.userPosition?.pnl && /* @__PURE__ */ jsx(
3752
- PnLSummary,
3753
- {
3754
- pnl: selectedMarket.userPosition.pnl,
3755
- tokenSymbol: selectedMarket.underlyingSymbol,
3756
- tokenPrice: 1
3757
- }
3758
- ),
3759
- showHistory && (selectedMarket.userPosition?.deposits?.length || selectedMarket.userPosition?.withdrawals?.length) && /* @__PURE__ */ jsx(
3760
- TransactionHistory,
3761
- {
3762
- deposits: selectedMarket.userPosition?.deposits,
3763
- withdrawals: selectedMarket.userPosition?.withdrawals,
3764
- tokenSymbol: selectedMarket.underlyingSymbol
3765
- }
3766
- ),
3767
- /* @__PURE__ */ jsx(
3768
- DepositWithdrawForm,
3769
- {
3770
- venueType: "AAVE",
3771
- venueAddress: selectedMarket.marketAddress,
3772
- venueToken: selectedMarket.underlyingSymbol,
3773
- positionBalance: selectedMarket.userPosition?.balance,
3774
- onSuccess: handleActionSuccess
3775
- }
3776
- )
3777
- ] }) })
3778
- }
3779
- )
3780
- ] });
3781
- }
3782
- var MARKET_TABS = [
3783
- { value: "variable", label: "VARIABLE" },
3784
- { value: "fixed", label: "FIXED" }
3785
- ];
3786
- var TAB_TO_MARKET_TYPES = {
3787
- variable: ["aave", "vaults"],
3788
- fixed: ["pendle"]
3789
- };
3790
- function MarketSelector({
3791
- markets,
3792
- selectedMarket,
3793
- onMarketSelect,
3794
- marketTab,
3795
- onMarketTabChange,
3796
- isLoading,
3797
- allowedMarketIds
3798
- }) {
3799
- const [isDropdownOpen, setIsDropdownOpen] = useState(false);
3800
- const dropdownRef = useRef(null);
3801
- useEffect(() => {
3802
- function handleClickOutside(e) {
3803
- if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
3804
- setIsDropdownOpen(false);
3805
- }
3806
- }
3807
- document.addEventListener("mousedown", handleClickOutside);
3808
- return () => document.removeEventListener("mousedown", handleClickOutside);
3809
- }, []);
3810
- const allowedTypes = TAB_TO_MARKET_TYPES[marketTab];
3811
- const showTvl = marketTab === "variable";
3812
- const filteredMarkets = useMemo(() => {
3813
- return markets.filter((m) => {
3814
- if (!allowedTypes.includes(m.type)) return false;
3815
- if (allowedMarketIds !== void 0) {
3816
- return allowedMarketIds.includes(m.id);
3817
- }
3818
- return true;
3819
- }).sort((a, b) => b.apy - a.apy);
3820
- }, [markets, allowedTypes, allowedMarketIds]);
3821
- const hasMarkets = filteredMarkets.length > 0;
3822
- const formatTvl = (value) => {
3823
- if (value >= 1e9) return `$${(value / 1e9).toFixed(1)}B`;
3824
- if (value >= 1e6) return `$${(value / 1e6).toFixed(1)}M`;
3825
- if (value >= 1e3) return `$${(value / 1e3).toFixed(0)}K`;
3826
- return `$${value}`;
3827
- };
3828
- return /* @__PURE__ */ jsxs(
3829
- "div",
3830
- {
3831
- style: {
3832
- display: "flex",
3833
- flexDirection: "column",
3834
- gap: "calc(var(--compass-spacing-unit) * 1.5)"
3835
- },
3836
- children: [
3837
- /* @__PURE__ */ jsxs("div", { children: [
3838
- /* @__PURE__ */ jsx(
3839
- "label",
3840
- {
3841
- style: {
3842
- display: "block",
3843
- fontSize: "var(--compass-font-size-sm)",
3844
- color: "var(--compass-color-text-secondary)",
3845
- marginBottom: "var(--compass-spacing-unit)"
3846
- },
3847
- children: "Market Type"
3848
- }
3849
- ),
3850
- /* @__PURE__ */ jsx(
3851
- "div",
3852
- {
3853
- style: {
3854
- display: "flex",
3855
- borderRadius: "var(--compass-border-radius-md, 8px)",
3856
- border: "1px solid var(--compass-color-border, rgba(255, 255, 255, 0.1))",
3857
- overflow: "hidden"
3858
- },
3859
- children: MARKET_TABS.map((tab) => /* @__PURE__ */ jsx(
3860
- "button",
3319
+ /* @__PURE__ */ jsx("div", { style: { color: "var(--compass-color-text)" }, children: formatted })
3320
+ ] });
3321
+ })()
3322
+ ]
3323
+ }
3324
+ ),
3325
+ /* @__PURE__ */ jsxs(
3326
+ "div",
3327
+ {
3328
+ style: {
3329
+ paddingTop: "calc(var(--compass-spacing-unit) * 2)",
3330
+ borderTop: "1px solid var(--compass-color-border)",
3331
+ display: "flex",
3332
+ flexDirection: "column",
3333
+ gap: "calc(var(--compass-spacing-unit) * 1.5)"
3334
+ },
3335
+ children: [
3336
+ position.pnl ? /* @__PURE__ */ jsxs(
3337
+ "div",
3861
3338
  {
3862
- onClick: () => onMarketTabChange(tab.value),
3863
3339
  style: {
3864
- flex: 1,
3865
- padding: "6px 8px",
3866
- backgroundColor: marketTab === tab.value ? "var(--compass-color-primary, #6366f1)" : "var(--compass-color-surface, #12121a)",
3867
- color: marketTab === tab.value ? "var(--compass-color-primary-text, white)" : "var(--compass-color-text, #e4e4e7)",
3868
- border: "none",
3869
- cursor: "pointer",
3870
- fontSize: "12px",
3871
- fontWeight: 500,
3872
- fontFamily: "var(--compass-font-family)",
3873
- transition: "var(--compass-transition-normal, all 0.2s ease)"
3340
+ display: "grid",
3341
+ gridTemplateColumns: "1fr 1fr",
3342
+ gap: "calc(var(--compass-spacing-unit) * 2)"
3874
3343
  },
3875
- children: tab.label
3876
- },
3877
- tab.value
3878
- ))
3879
- }
3880
- )
3881
- ] }),
3882
- /* @__PURE__ */ jsxs("div", { children: [
3883
- /* @__PURE__ */ jsx(
3884
- "label",
3885
- {
3886
- style: {
3887
- display: "block",
3888
- fontSize: "var(--compass-font-size-sm)",
3889
- color: "var(--compass-color-text-secondary)",
3890
- marginBottom: "var(--compass-spacing-unit)"
3891
- },
3892
- children: "Select Market"
3893
- }
3894
- ),
3895
- /* @__PURE__ */ jsxs("div", { ref: dropdownRef, style: { position: "relative" }, children: [
3896
- /* @__PURE__ */ jsxs(
3897
- "button",
3898
- {
3899
- onClick: () => hasMarkets && setIsDropdownOpen(!isDropdownOpen),
3900
- disabled: !hasMarkets || isLoading,
3901
- style: {
3902
- width: "100%",
3903
- padding: "8px 12px",
3904
- backgroundColor: "var(--compass-color-surface, #12121a)",
3905
- border: "1px solid var(--compass-color-border, rgba(255, 255, 255, 0.1))",
3906
- borderRadius: "var(--compass-border-radius-md, 8px)",
3907
- fontSize: "13px",
3908
- display: "flex",
3909
- justifyContent: "space-between",
3910
- alignItems: "center",
3911
- cursor: hasMarkets ? "pointer" : "not-allowed",
3912
- opacity: hasMarkets ? 1 : 0.5,
3913
- fontFamily: "var(--compass-font-family)"
3914
- },
3915
- children: [
3916
- /* @__PURE__ */ jsx(
3917
- "span",
3918
- {
3919
- style: {
3920
- color: selectedMarket ? "var(--compass-color-text)" : "var(--compass-color-text-secondary)"
3921
- },
3922
- children: isLoading ? "Loading markets..." : !hasMarkets ? "No markets available" : selectedMarket ? selectedMarket.name : "Select a market"
3923
- }
3924
- ),
3925
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "var(--compass-spacing-unit)" }, children: [
3926
- selectedMarket && /* @__PURE__ */ jsxs(
3927
- "span",
3928
- {
3929
- style: {
3930
- color: "var(--compass-color-success)",
3931
- fontSize: "var(--compass-font-size-sm)"
3932
- },
3933
- children: [
3934
- selectedMarket.apy.toFixed(2),
3935
- "%"
3936
- ]
3937
- }
3938
- ),
3939
- /* @__PURE__ */ jsx(
3940
- ChevronDown,
3941
- {
3942
- size: 16,
3943
- style: {
3944
- color: "var(--compass-color-text-secondary)",
3945
- transform: isDropdownOpen ? "rotate(180deg)" : "rotate(0deg)",
3946
- transition: "var(--compass-transition-normal)"
3344
+ children: [
3345
+ /* @__PURE__ */ jsxs("div", { children: [
3346
+ /* @__PURE__ */ jsx(
3347
+ "div",
3348
+ {
3349
+ style: {
3350
+ fontSize: "var(--compass-font-size-xs)",
3351
+ color: "var(--compass-color-text-tertiary)",
3352
+ marginBottom: "calc(var(--compass-spacing-unit) * 0.5)"
3353
+ },
3354
+ children: "Unrealised P&L"
3947
3355
  }
3948
- }
3949
- )
3950
- ] })
3951
- ]
3952
- }
3953
- ),
3954
- isDropdownOpen && /* @__PURE__ */ jsx(
3955
- "div",
3956
- {
3957
- style: {
3958
- position: "absolute",
3959
- top: "100%",
3960
- left: 0,
3961
- right: 0,
3962
- marginTop: "var(--compass-spacing-unit, 8px)",
3963
- backgroundColor: "var(--compass-color-background, #0a0a0f)",
3964
- border: "1px solid var(--compass-color-border, rgba(255, 255, 255, 0.1))",
3965
- borderRadius: "var(--compass-border-radius-lg, 12px)",
3966
- boxShadow: "var(--compass-shadow-lg, 0 25px 50px -12px rgba(0, 0, 0, 0.5))",
3967
- zIndex: 50,
3968
- maxHeight: "200px",
3969
- overflowY: "auto"
3970
- },
3971
- children: filteredMarkets.map((market) => /* @__PURE__ */ jsxs(
3972
- "button",
3973
- {
3974
- onClick: () => {
3975
- onMarketSelect(market);
3976
- setIsDropdownOpen(false);
3977
- },
3978
- style: {
3979
- width: "100%",
3980
- padding: "calc(var(--compass-spacing-unit, 8px) * 2)",
3981
- backgroundColor: selectedMarket?.id === market.id ? "var(--compass-color-primary-muted, rgba(99, 102, 241, 0.15))" : "var(--compass-color-surface, #12121a)",
3982
- border: "none",
3983
- borderBottom: "1px solid var(--compass-color-border, rgba(255, 255, 255, 0.1))",
3984
- cursor: "pointer",
3985
- textAlign: "left",
3986
- fontFamily: "var(--compass-font-family)"
3987
- },
3988
- children: [
3356
+ ),
3989
3357
  /* @__PURE__ */ jsxs(
3990
3358
  "div",
3991
3359
  {
3992
3360
  style: {
3993
- display: "flex",
3994
- justifyContent: "space-between",
3995
- alignItems: "center"
3361
+ fontWeight: 600,
3362
+ color: position.pnl.unrealizedPnl >= 0 ? "var(--compass-color-success)" : "var(--compass-color-error)"
3996
3363
  },
3997
3364
  children: [
3998
- /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text)" }, children: market.name }),
3999
- /* @__PURE__ */ jsxs(
4000
- "span",
4001
- {
4002
- style: {
4003
- color: "var(--compass-color-success)",
4004
- fontSize: "var(--compass-font-size-sm)"
4005
- },
4006
- children: [
4007
- market.apy.toFixed(2),
4008
- "% APY"
4009
- ]
4010
- }
4011
- )
3365
+ position.pnl.unrealizedPnl >= 0 ? "+" : "",
3366
+ "$",
3367
+ position.pnl.unrealizedPnl.toFixed(2)
4012
3368
  ]
4013
3369
  }
4014
- ),
4015
- (showTvl && market.tvl > 0 || market.expiry) && /* @__PURE__ */ jsxs(
3370
+ )
3371
+ ] }),
3372
+ /* @__PURE__ */ jsxs("div", { children: [
3373
+ /* @__PURE__ */ jsx(
4016
3374
  "div",
4017
3375
  {
4018
3376
  style: {
4019
3377
  fontSize: "var(--compass-font-size-xs)",
4020
3378
  color: "var(--compass-color-text-tertiary)",
4021
- marginTop: "calc(var(--compass-spacing-unit) * 0.5)"
3379
+ marginBottom: "calc(var(--compass-spacing-unit) * 0.5)"
4022
3380
  },
4023
- children: [
4024
- showTvl && market.tvl > 0 && `TVL: ${formatTvl(market.tvl)}`,
4025
- showTvl && market.tvl > 0 && market.expiry && " \xB7 ",
4026
- market.expiry && `Expires: ${market.expiry}`
4027
- ]
4028
- }
4029
- )
4030
- ]
4031
- },
4032
- market.id
4033
- ))
4034
- }
4035
- )
4036
- ] })
4037
- ] })
4038
- ]
4039
- }
4040
- );
4041
- }
4042
- function PositionCard({ position }) {
4043
- const [isHistoryExpanded, setIsHistoryExpanded] = useState(false);
4044
- const formatShortDate = (dateStr) => {
4045
- if (!dateStr) return null;
4046
- const date = new Date(dateStr);
4047
- if (isNaN(date.getTime())) return null;
4048
- return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
4049
- };
4050
- return /* @__PURE__ */ jsxs(
4051
- "div",
4052
- {
4053
- style: {
4054
- backgroundColor: "var(--compass-color-surface, #12121a)",
4055
- border: "1px solid var(--compass-color-border, rgba(255, 255, 255, 0.1))",
4056
- borderRadius: "var(--compass-border-radius-lg, 12px)",
4057
- padding: "var(--compass-spacing-card, 16px)"
4058
- },
4059
- children: [
4060
- /* @__PURE__ */ jsxs(
4061
- "div",
4062
- {
4063
- style: {
4064
- display: "flex",
4065
- justifyContent: "space-between",
4066
- alignItems: "flex-start",
4067
- marginBottom: "calc(var(--compass-spacing-unit) * 2)"
4068
- },
4069
- children: [
4070
- /* @__PURE__ */ jsx(
4071
- "span",
4072
- {
4073
- style: {
4074
- fontWeight: 600,
4075
- color: "var(--compass-color-text)",
4076
- fontSize: "var(--compass-font-size-base)"
4077
- },
4078
- children: position.marketName
4079
- }
4080
- ),
4081
- /* @__PURE__ */ jsxs(
4082
- "span",
4083
- {
4084
- style: {
4085
- color: "var(--compass-color-success)",
4086
- fontSize: "var(--compass-font-size-sm)",
4087
- backgroundColor: "var(--compass-color-success-muted)",
4088
- padding: "calc(var(--compass-spacing-unit) * 0.5) calc(var(--compass-spacing-unit) * 1.5)",
4089
- borderRadius: "var(--compass-border-radius-full)"
4090
- },
4091
- children: [
4092
- "APY: ",
4093
- position.apy.toFixed(2),
4094
- "%"
4095
- ]
4096
- }
4097
- )
4098
- ]
4099
- }
4100
- ),
4101
- /* @__PURE__ */ jsxs(
4102
- "div",
4103
- {
4104
- style: {
4105
- display: "grid",
4106
- gridTemplateColumns: "1fr 1fr",
4107
- gap: "calc(var(--compass-spacing-unit) * 2)",
4108
- marginBottom: "calc(var(--compass-spacing-unit) * 2)"
4109
- },
4110
- children: [
4111
- /* @__PURE__ */ jsxs("div", { children: [
4112
- /* @__PURE__ */ jsx(
4113
- "div",
4114
- {
4115
- style: {
4116
- fontSize: "var(--compass-font-size-xs)",
4117
- color: "var(--compass-color-text-tertiary)",
4118
- marginBottom: "calc(var(--compass-spacing-unit) * 0.5)"
4119
- },
4120
- children: "Amount"
4121
- }
4122
- ),
4123
- /* @__PURE__ */ jsxs("div", { style: { color: "var(--compass-color-text)", fontWeight: 500 }, children: [
4124
- position.amount.toLocaleString(),
4125
- " ",
4126
- position.token
4127
- ] })
4128
- ] }),
4129
- (() => {
4130
- const deposits = position.transactions.filter((t) => t.type === "deposit" && t.timestamp);
4131
- const earliest = deposits.length > 0 ? deposits.reduce((min, t) => !min || t.timestamp < min ? t.timestamp : min, "") : void 0;
4132
- const formatted = formatShortDate(earliest);
4133
- if (!formatted) return null;
4134
- return /* @__PURE__ */ jsxs("div", { children: [
4135
- /* @__PURE__ */ jsx(
4136
- "div",
4137
- {
4138
- style: {
4139
- fontSize: "var(--compass-font-size-xs)",
4140
- color: "var(--compass-color-text-tertiary)",
4141
- marginBottom: "calc(var(--compass-spacing-unit) * 0.5)"
4142
- },
4143
- children: "Entered"
4144
- }
4145
- ),
4146
- /* @__PURE__ */ jsx("div", { style: { color: "var(--compass-color-text)" }, children: formatted })
4147
- ] });
4148
- })()
4149
- ]
4150
- }
4151
- ),
4152
- /* @__PURE__ */ jsxs(
4153
- "div",
4154
- {
4155
- style: {
4156
- paddingTop: "calc(var(--compass-spacing-unit) * 2)",
4157
- borderTop: "1px solid var(--compass-color-border)",
4158
- display: "flex",
4159
- flexDirection: "column",
4160
- gap: "calc(var(--compass-spacing-unit) * 1.5)"
4161
- },
4162
- children: [
4163
- position.pnl ? /* @__PURE__ */ jsxs(
4164
- "div",
4165
- {
4166
- style: {
4167
- display: "grid",
4168
- gridTemplateColumns: "1fr 1fr",
4169
- gap: "calc(var(--compass-spacing-unit) * 2)"
4170
- },
4171
- children: [
4172
- /* @__PURE__ */ jsxs("div", { children: [
4173
- /* @__PURE__ */ jsx(
4174
- "div",
4175
- {
4176
- style: {
4177
- fontSize: "var(--compass-font-size-xs)",
4178
- color: "var(--compass-color-text-tertiary)",
4179
- marginBottom: "calc(var(--compass-spacing-unit) * 0.5)"
4180
- },
4181
- children: "Unrealised P&L"
4182
- }
4183
- ),
4184
- /* @__PURE__ */ jsxs(
4185
- "div",
4186
- {
4187
- style: {
4188
- fontWeight: 600,
4189
- color: position.pnl.unrealizedPnl >= 0 ? "var(--compass-color-success)" : "var(--compass-color-error)"
4190
- },
4191
- children: [
4192
- position.pnl.unrealizedPnl >= 0 ? "+" : "",
4193
- "$",
4194
- position.pnl.unrealizedPnl.toFixed(2)
4195
- ]
4196
- }
4197
- )
4198
- ] }),
4199
- /* @__PURE__ */ jsxs("div", { children: [
4200
- /* @__PURE__ */ jsx(
4201
- "div",
4202
- {
4203
- style: {
4204
- fontSize: "var(--compass-font-size-xs)",
4205
- color: "var(--compass-color-text-tertiary)",
4206
- marginBottom: "calc(var(--compass-spacing-unit) * 0.5)"
4207
- },
4208
- children: "Realised P&L"
3381
+ children: "Realised P&L"
4209
3382
  }
4210
3383
  ),
4211
3384
  /* @__PURE__ */ jsxs(
@@ -4566,6 +3739,7 @@ function EarningsModal({ isOpen, onClose, positions, totalEarned, isLoading }) {
4566
3739
  style: {
4567
3740
  flex: 1,
4568
3741
  overflowY: "auto",
3742
+ scrollbarWidth: "none",
4569
3743
  padding: "var(--compass-spacing-card, 16px)",
4570
3744
  display: "flex",
4571
3745
  flexDirection: "column",
@@ -4759,7 +3933,7 @@ function EarnAccount({
4759
3933
  if (!response.ok) return [];
4760
3934
  const data = await response.json();
4761
3935
  const marketsList = data.markets || [];
4762
- const parseExpiry = (expiry) => {
3936
+ const parseExpiry2 = (expiry) => {
4763
3937
  if (!expiry || expiry === 0) return void 0;
4764
3938
  const timestamp = typeof expiry === "number" ? expiry : parseInt(expiry, 10);
4765
3939
  if (isNaN(timestamp) || timestamp <= 0) return void 0;
@@ -4769,7 +3943,7 @@ function EarnAccount({
4769
3943
  return date.toLocaleDateString("en-US", { month: "short", year: "numeric" });
4770
3944
  };
4771
3945
  return marketsList.map((m) => {
4772
- const expiryStr = parseExpiry(m.expiry);
3946
+ const expiryStr = parseExpiry2(m.expiry);
4773
3947
  return {
4774
3948
  id: `pendle-${m.marketAddress || m.address}`,
4775
3949
  name: m.name || `${m.underlyingSymbol || "PT"}${expiryStr ? ` ${expiryStr}` : ""}`,
@@ -6016,551 +5190,53 @@ function EarnAccount({
6016
5190
  )
6017
5191
  ] });
6018
5192
  }
6019
- function usePendleData(options = {}) {
6020
- const { address } = useEmbeddableWallet();
5193
+ function useSwapQuote({ fromToken, toToken, amount, enabled = true }) {
6021
5194
  const { chainId } = useChain();
6022
- const { sortBy = "fixed_apy", assetFilter, minTvl } = options;
6023
- const marketsQuery = useQuery({
6024
- queryKey: ["pendleMarkets", chainId, sortBy, assetFilter, minTvl],
5195
+ const { address } = useCompassWallet();
5196
+ const query = useQuery({
5197
+ queryKey: ["swapQuote", chainId, fromToken, toToken, amount, address],
6025
5198
  queryFn: async () => {
6026
- const underlyingSymbol = assetFilter && assetFilter.length === 1 ? assetFilter[0] : void 0;
6027
- const orderBy = sortBy === "tvl" ? "tvl_usd" : "implied_apy";
6028
- const params = new URLSearchParams({
6029
- chain: chainId,
6030
- orderBy,
6031
- direction: "desc",
6032
- limit: "100",
6033
- ...underlyingSymbol && { underlyingSymbol }
6034
- });
6035
- const response = await fetch(`/api/compass/pendle/markets?${params}`);
6036
- if (!response.ok) {
6037
- throw new Error("Failed to fetch Pendle markets");
6038
- }
6039
- const data = await response.json();
6040
- const now = Date.now() / 1e3;
6041
- let markets = (data.markets || []).filter((m) => m.expiry && m.expiry > now).map((m) => ({
6042
- marketAddress: m.marketAddress || "",
6043
- ptAddress: m.ptAddress || "",
6044
- name: m.ptName || `PT-${m.underlyingSymbol || "UNKNOWN"}`,
6045
- underlyingSymbol: m.underlyingSymbol || "UNKNOWN",
6046
- // Use impliedApy as the main APY (this is what wallet-earn displays)
6047
- fixedApy: m.impliedApy?.toString() ?? null,
6048
- impliedApy: m.impliedApy?.toString() ?? null,
6049
- tvlUsd: m.tvlUsd?.toString() ?? null,
6050
- // Convert Unix timestamp to ISO string for display
6051
- expiry: new Date(m.expiry * 1e3).toISOString()
6052
- }));
6053
- if (assetFilter && assetFilter.length > 1) {
6054
- markets = markets.filter(
6055
- (m) => assetFilter.includes(m.underlyingSymbol.toUpperCase())
6056
- );
6057
- }
6058
- if (minTvl !== void 0 && minTvl > 0) {
6059
- markets = markets.filter((m) => {
6060
- const tvl = parseFloat(m.tvlUsd || "0");
6061
- return tvl >= minTvl;
6062
- });
5199
+ if (!fromToken || !toToken || !amount || parseFloat(amount) <= 0 || !address) {
5200
+ return null;
6063
5201
  }
6064
- if (sortBy === "expiry") {
6065
- markets.sort((a, b) => {
6066
- return new Date(a.expiry).getTime() - new Date(b.expiry).getTime();
5202
+ try {
5203
+ const params = new URLSearchParams({
5204
+ owner: address,
5205
+ chain: chainId,
5206
+ tokenIn: fromToken,
5207
+ tokenOut: toToken,
5208
+ amountIn: amount
6067
5209
  });
5210
+ const response = await fetch(`/api/compass/swap/quote?${params}`);
5211
+ if (!response.ok) {
5212
+ const errorData = await response.json();
5213
+ const errorMessage = errorData.message || errorData.error || "Failed to get swap quote";
5214
+ throw new Error(errorMessage);
5215
+ }
5216
+ const data = await response.json();
5217
+ const outputAmount = data.estimatedAmountOut || "0";
5218
+ const inputAmountNum = parseFloat(amount);
5219
+ const outputAmountNum = parseFloat(outputAmount);
5220
+ return {
5221
+ inputAmount: amount,
5222
+ outputAmount,
5223
+ rate: inputAmountNum > 0 ? (outputAmountNum / inputAmountNum).toString() : "0"
5224
+ };
5225
+ } catch (error) {
5226
+ throw error;
6068
5227
  }
6069
- return markets;
6070
- },
6071
- staleTime: 30 * 1e3
6072
- });
6073
- const positionsQuery = useQuery({
6074
- queryKey: ["pendlePositions", chainId, address],
6075
- queryFn: async () => {
6076
- if (!address) return [];
6077
- const params = new URLSearchParams({
6078
- chain: chainId,
6079
- owner: address
6080
- });
6081
- const response = await fetch(`/api/compass/positions?${params}`);
6082
- if (!response.ok) {
6083
- throw new Error("Failed to fetch positions");
6084
- }
6085
- const data = await response.json();
6086
- return (data.pendlePt || []).map((p) => ({
6087
- marketAddress: p.marketAddress || "",
6088
- balance: p.balance || "0",
6089
- pnl: p.pnl ? {
6090
- unrealizedPnl: p.pnl.unrealizedPnl || "0",
6091
- realizedPnl: p.pnl.realizedPnl || "0",
6092
- totalPnl: p.pnl.totalPnl || "0",
6093
- totalDeposited: p.pnl.totalDeposited || "0"
6094
- } : void 0,
6095
- deposits: (p.deposits || []).map((d) => ({
6096
- amount: d.inputAmount || d.amount || "0",
6097
- blockNumber: d.blockNumber || 0,
6098
- txHash: d.transactionHash || d.txHash || ""
6099
- })),
6100
- withdrawals: (p.withdrawals || []).map((w) => ({
6101
- amount: w.outputAmount || w.amount || "0",
6102
- blockNumber: w.blockNumber || 0,
6103
- txHash: w.transactionHash || w.txHash || ""
6104
- }))
6105
- }));
6106
5228
  },
6107
- enabled: !!address,
6108
- staleTime: 30 * 1e3
6109
- });
6110
- const marketsWithPositions = (marketsQuery.data || []).map((market) => {
6111
- const position = positionsQuery.data?.find(
6112
- (p) => p.marketAddress.toLowerCase() === market.marketAddress.toLowerCase()
6113
- );
6114
- return { ...market, userPosition: position };
5229
+ enabled: enabled && !!address && !!fromToken && !!toToken && !!amount && parseFloat(amount) > 0,
5230
+ staleTime: 10 * 1e3,
5231
+ refetchInterval: 15 * 1e3,
5232
+ retry: 1
6115
5233
  });
6116
5234
  return {
6117
- markets: marketsWithPositions,
6118
- isLoading: marketsQuery.isLoading,
6119
- isError: marketsQuery.isError,
6120
- error: marketsQuery.error,
6121
- refetch: () => {
6122
- marketsQuery.refetch();
6123
- positionsQuery.refetch();
6124
- }
6125
- };
6126
- }
6127
- function formatAPY3(apy) {
6128
- if (!apy) return "0.00%";
6129
- return `${parseFloat(apy).toFixed(2)}%`;
6130
- }
6131
- function formatExpiry(expiry) {
6132
- const date = new Date(expiry);
6133
- return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
6134
- }
6135
- function formatTVL2(tvl) {
6136
- if (!tvl) return "$0";
6137
- const num = parseFloat(tvl);
6138
- if (num >= 1e9) return `$${(num / 1e9).toFixed(2)}B`;
6139
- if (num >= 1e6) return `$${(num / 1e6).toFixed(2)}M`;
6140
- if (num >= 1e3) return `$${(num / 1e3).toFixed(2)}K`;
6141
- return `$${num.toFixed(2)}`;
6142
- }
6143
- function PendleMarketsList({
6144
- showApy = true,
6145
- showTvl = true,
6146
- showExpiry = true,
6147
- showUserPosition = true,
6148
- showPnL = true,
6149
- showHistory = true,
6150
- showSearch = true,
6151
- showSort = true,
6152
- defaultSort = "fixed_apy",
6153
- assetFilter,
6154
- minTvl: initialMinTvl,
6155
- showTvlFilter = true,
6156
- onMarketSelect,
6157
- onDeposit,
6158
- onWithdraw
6159
- }) {
6160
- const [searchQuery, setSearchQuery] = useState("");
6161
- const [sortBy, setSortBy] = useState(defaultSort);
6162
- const [selectedMarket, setSelectedMarket] = useState(null);
6163
- const [minTvlFilter, setMinTvlFilter] = useState(initialMinTvl);
6164
- const [showFilterPanel, setShowFilterPanel] = useState(false);
6165
- const { markets, isLoading, isError, refetch } = usePendleData({ sortBy, assetFilter, minTvl: minTvlFilter });
6166
- const handleMinTvlChange = useCallback((value) => {
6167
- const num = parseFloat(value);
6168
- setMinTvlFilter(isNaN(num) || num <= 0 ? void 0 : num);
6169
- }, []);
6170
- const filteredMarkets = useMemo(() => {
6171
- if (!searchQuery) return markets;
6172
- const query = searchQuery.toLowerCase();
6173
- return markets.filter(
6174
- (m) => m.name.toLowerCase().includes(query) || m.underlyingSymbol.toLowerCase().includes(query)
6175
- );
6176
- }, [markets, searchQuery]);
6177
- const handleMarketClick = (market) => {
6178
- setSelectedMarket(market);
6179
- onMarketSelect?.(market);
6180
- };
6181
- const handleActionSuccess = (action, amount, txHash) => {
6182
- if (action === "deposit") {
6183
- onDeposit?.(selectedMarket, amount, txHash);
6184
- } else {
6185
- onWithdraw?.(selectedMarket, amount, txHash);
6186
- }
6187
- refetch();
6188
- };
6189
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "var(--compass-spacing-card)" }, children: [
6190
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between flex-wrap", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
6191
- /* @__PURE__ */ jsx(ChainSwitcher, {}),
6192
- /* @__PURE__ */ jsxs("div", { className: "flex items-center", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
6193
- /* @__PURE__ */ jsx(EarnAccountBalance, { compact: true }),
6194
- /* @__PURE__ */ jsx(WalletStatus, { compact: true })
6195
- ] })
6196
- ] }),
6197
- (showSearch || showSort || showTvlFilter) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
6198
- /* @__PURE__ */ jsxs("div", { className: "flex", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
6199
- showSearch && /* @__PURE__ */ jsxs(
6200
- "div",
6201
- {
6202
- className: "flex-1 flex items-center border",
6203
- style: {
6204
- backgroundColor: "var(--compass-color-background)",
6205
- borderColor: "var(--compass-color-border)",
6206
- borderRadius: "var(--compass-border-radius-lg)",
6207
- fontFamily: "var(--compass-font-family)",
6208
- padding: "var(--compass-spacing-input)",
6209
- gap: "calc(var(--compass-spacing-unit) * 0.5)"
6210
- },
6211
- children: [
6212
- /* @__PURE__ */ jsx(Search, { size: 16, style: { color: "var(--compass-color-text-tertiary)" } }),
6213
- /* @__PURE__ */ jsx(
6214
- "input",
6215
- {
6216
- type: "text",
6217
- placeholder: "Search markets...",
6218
- value: searchQuery,
6219
- onChange: (e) => setSearchQuery(e.target.value),
6220
- className: "flex-1 bg-transparent outline-none text-sm",
6221
- style: { color: "var(--compass-color-text)" }
6222
- }
6223
- )
6224
- ]
6225
- }
6226
- ),
6227
- showSort && /* @__PURE__ */ jsxs(
6228
- "select",
6229
- {
6230
- value: sortBy,
6231
- onChange: (e) => setSortBy(e.target.value),
6232
- className: "border text-sm cursor-pointer",
6233
- style: {
6234
- backgroundColor: "var(--compass-color-background)",
6235
- borderColor: "var(--compass-color-border)",
6236
- color: "var(--compass-color-text)",
6237
- borderRadius: "var(--compass-border-radius-lg)",
6238
- fontFamily: "var(--compass-font-family)",
6239
- padding: "var(--compass-spacing-input)"
6240
- },
6241
- children: [
6242
- /* @__PURE__ */ jsx("option", { value: "fixed_apy", children: "Fixed APY" }),
6243
- /* @__PURE__ */ jsx("option", { value: "tvl", children: "TVL" }),
6244
- /* @__PURE__ */ jsx("option", { value: "expiry", children: "Expiry" })
6245
- ]
6246
- }
6247
- ),
6248
- showTvlFilter && /* @__PURE__ */ jsxs(
6249
- "button",
6250
- {
6251
- onClick: () => setShowFilterPanel(!showFilterPanel),
6252
- className: "border text-sm flex items-center",
6253
- style: {
6254
- backgroundColor: showFilterPanel || minTvlFilter ? "var(--compass-color-primary-muted)" : "var(--compass-color-background)",
6255
- borderColor: showFilterPanel || minTvlFilter ? "var(--compass-color-primary)" : "var(--compass-color-border)",
6256
- color: showFilterPanel || minTvlFilter ? "var(--compass-color-primary)" : "var(--compass-color-text)",
6257
- borderRadius: "var(--compass-border-radius-lg)",
6258
- fontFamily: "var(--compass-font-family)",
6259
- padding: "var(--compass-spacing-input)",
6260
- gap: "calc(var(--compass-spacing-unit) * 0.5)",
6261
- transition: "var(--compass-transition-normal)"
6262
- },
6263
- children: [
6264
- /* @__PURE__ */ jsx(SlidersHorizontal, { size: 14 }),
6265
- "Filter"
6266
- ]
6267
- }
6268
- )
6269
- ] }),
6270
- showTvlFilter && showFilterPanel && /* @__PURE__ */ jsxs(
6271
- "div",
6272
- {
6273
- className: "flex items-center border",
6274
- style: {
6275
- backgroundColor: "var(--compass-color-surface)",
6276
- borderColor: "var(--compass-color-border)",
6277
- borderRadius: "var(--compass-border-radius-lg)",
6278
- fontFamily: "var(--compass-font-family)",
6279
- padding: "calc(var(--compass-spacing-unit) * 0.75)",
6280
- gap: "calc(var(--compass-spacing-unit) * 0.75)"
6281
- },
6282
- children: [
6283
- /* @__PURE__ */ jsx(
6284
- "label",
6285
- {
6286
- className: "text-sm font-medium whitespace-nowrap",
6287
- style: { color: "var(--compass-color-text-secondary)" },
6288
- children: "Min TVL:"
6289
- }
6290
- ),
6291
- /* @__PURE__ */ jsxs("div", { className: "flex items-center", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
6292
- /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text-tertiary)" }, children: "$" }),
6293
- /* @__PURE__ */ jsx(
6294
- "input",
6295
- {
6296
- type: "number",
6297
- placeholder: "0",
6298
- value: minTvlFilter || "",
6299
- onChange: (e) => handleMinTvlChange(e.target.value),
6300
- className: "w-24 border text-sm bg-transparent",
6301
- style: {
6302
- borderColor: "var(--compass-color-border)",
6303
- color: "var(--compass-color-text)",
6304
- borderRadius: "var(--compass-border-radius-sm)",
6305
- fontFamily: "var(--compass-font-family)",
6306
- padding: "calc(var(--compass-spacing-unit) * 0.25) calc(var(--compass-spacing-unit) * 0.5)"
6307
- }
6308
- }
6309
- )
6310
- ] }),
6311
- minTvlFilter && /* @__PURE__ */ jsx(
6312
- "button",
6313
- {
6314
- onClick: () => setMinTvlFilter(void 0),
6315
- className: "text-xs",
6316
- style: {
6317
- backgroundColor: "var(--compass-color-error-muted)",
6318
- color: "var(--compass-color-error)",
6319
- borderRadius: "var(--compass-border-radius-sm)",
6320
- padding: "calc(var(--compass-spacing-unit) * 0.25) calc(var(--compass-spacing-unit) * 0.5)",
6321
- transition: "var(--compass-transition-fast)"
6322
- },
6323
- children: "Clear"
6324
- }
6325
- )
6326
- ]
6327
- }
6328
- )
6329
- ] }),
6330
- isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", style: { padding: "calc(var(--compass-spacing-unit) * 3) 0" }, children: /* @__PURE__ */ jsx(Loader2, { size: 24, className: "animate-spin", style: { color: "var(--compass-color-primary)" } }) }) : isError ? /* @__PURE__ */ jsx(
6331
- "div",
6332
- {
6333
- className: "text-center",
6334
- style: {
6335
- backgroundColor: "var(--compass-color-error-muted)",
6336
- color: "var(--compass-color-error)",
6337
- borderRadius: "var(--compass-border-radius-lg)",
6338
- padding: "var(--compass-spacing-card)"
6339
- },
6340
- children: "Failed to load Pendle markets. Please try again."
6341
- }
6342
- ) : filteredMarkets.length === 0 ? /* @__PURE__ */ jsx(
6343
- "div",
6344
- {
6345
- className: "text-center",
6346
- style: { color: "var(--compass-color-text-secondary)", padding: "calc(var(--compass-spacing-unit) * 2)" },
6347
- children: "No active markets found"
6348
- }
6349
- ) : /* @__PURE__ */ jsx("div", { className: "flex flex-col", style: { gap: "calc(var(--compass-spacing-unit) * 0.75)" }, children: filteredMarkets.map((market) => {
6350
- const hasPosition = market.userPosition && parseFloat(market.userPosition.balance) > 0;
6351
- return /* @__PURE__ */ jsxs(
6352
- "button",
6353
- {
6354
- onClick: () => handleMarketClick(market),
6355
- className: "w-full border text-left hover:scale-[1.01]",
6356
- style: {
6357
- backgroundColor: "var(--compass-color-surface)",
6358
- borderColor: hasPosition ? "var(--compass-color-primary)" : "var(--compass-color-border)",
6359
- borderRadius: "var(--compass-border-radius-xl)",
6360
- fontFamily: "var(--compass-font-family)",
6361
- padding: "var(--compass-spacing-card)",
6362
- transition: "var(--compass-transition-normal)"
6363
- },
6364
- children: [
6365
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
6366
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "calc(var(--compass-spacing-unit) * 0.25)" }, children: [
6367
- /* @__PURE__ */ jsx(
6368
- "h3",
6369
- {
6370
- className: "font-semibold",
6371
- style: {
6372
- fontSize: "var(--compass-font-size-body)",
6373
- color: "var(--compass-color-text)"
6374
- },
6375
- children: market.name
6376
- }
6377
- ),
6378
- /* @__PURE__ */ jsx(
6379
- "span",
6380
- {
6381
- className: "text-sm",
6382
- style: { color: "var(--compass-color-text-secondary)" },
6383
- children: market.underlyingSymbol
6384
- }
6385
- )
6386
- ] }),
6387
- showApy && /* @__PURE__ */ jsxs("div", { className: "flex items-center", style: { gap: "calc(var(--compass-spacing-unit) * 0.25)" }, children: [
6388
- /* @__PURE__ */ jsx(TrendingUp, { size: 14, style: { color: "var(--compass-color-success)" } }),
6389
- /* @__PURE__ */ jsx(
6390
- "span",
6391
- {
6392
- className: "font-mono font-semibold",
6393
- style: { color: "var(--compass-color-success)" },
6394
- children: formatAPY3(market.fixedApy)
6395
- }
6396
- )
6397
- ] })
6398
- ] }),
6399
- /* @__PURE__ */ jsxs(
6400
- "div",
6401
- {
6402
- className: "flex items-center justify-between border-t",
6403
- style: {
6404
- borderColor: "var(--compass-color-border)",
6405
- marginTop: "calc(var(--compass-spacing-unit) * 0.75)",
6406
- paddingTop: "calc(var(--compass-spacing-unit) * 0.75)"
6407
- },
6408
- children: [
6409
- /* @__PURE__ */ jsxs("div", { className: "flex", style: { gap: "var(--compass-spacing-card)" }, children: [
6410
- showTvl && /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
6411
- /* @__PURE__ */ jsx(
6412
- "span",
6413
- {
6414
- className: "text-xs",
6415
- style: { color: "var(--compass-color-text-tertiary)" },
6416
- children: "TVL"
6417
- }
6418
- ),
6419
- /* @__PURE__ */ jsx(
6420
- "span",
6421
- {
6422
- className: "font-mono text-sm",
6423
- style: { color: "var(--compass-color-text-secondary)" },
6424
- children: formatTVL2(market.tvlUsd)
6425
- }
6426
- )
6427
- ] }),
6428
- showExpiry && /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
6429
- /* @__PURE__ */ jsx(
6430
- "span",
6431
- {
6432
- className: "text-xs",
6433
- style: { color: "var(--compass-color-text-tertiary)" },
6434
- children: "Expiry"
6435
- }
6436
- ),
6437
- /* @__PURE__ */ jsxs("div", { className: "flex items-center", style: { gap: "calc(var(--compass-spacing-unit) * 0.25)" }, children: [
6438
- /* @__PURE__ */ jsx(Calendar, { size: 12, style: { color: "var(--compass-color-text-secondary)" } }),
6439
- /* @__PURE__ */ jsx(
6440
- "span",
6441
- {
6442
- className: "font-mono text-sm",
6443
- style: { color: "var(--compass-color-text-secondary)" },
6444
- children: formatExpiry(market.expiry)
6445
- }
6446
- )
6447
- ] })
6448
- ] })
6449
- ] }),
6450
- showUserPosition && hasPosition && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end", children: [
6451
- /* @__PURE__ */ jsx(
6452
- "span",
6453
- {
6454
- className: "text-xs",
6455
- style: { color: "var(--compass-color-text-tertiary)" },
6456
- children: "Your Position"
6457
- }
6458
- ),
6459
- /* @__PURE__ */ jsxs(
6460
- "span",
6461
- {
6462
- className: "font-mono text-sm font-medium",
6463
- style: { color: "var(--compass-color-primary)" },
6464
- children: [
6465
- parseFloat(market.userPosition.balance).toFixed(4),
6466
- " PT"
6467
- ]
6468
- }
6469
- )
6470
- ] })
6471
- ]
6472
- }
6473
- )
6474
- ]
6475
- },
6476
- market.marketAddress
6477
- );
6478
- }) }),
6479
- selectedMarket && /* @__PURE__ */ jsx(
6480
- ActionModal,
6481
- {
6482
- isOpen: !!selectedMarket,
6483
- onClose: () => setSelectedMarket(null),
6484
- title: selectedMarket.name,
6485
- children: /* @__PURE__ */ jsx(EarnAccountGuard, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "var(--compass-spacing-card)" }, children: [
6486
- showPnL && selectedMarket.userPosition?.pnl && /* @__PURE__ */ jsx(
6487
- PnLSummary,
6488
- {
6489
- pnl: selectedMarket.userPosition.pnl,
6490
- tokenSymbol: selectedMarket.underlyingSymbol,
6491
- tokenPrice: 1
6492
- }
6493
- ),
6494
- showHistory && (selectedMarket.userPosition?.deposits?.length || selectedMarket.userPosition?.withdrawals?.length) && /* @__PURE__ */ jsx(
6495
- TransactionHistory,
6496
- {
6497
- deposits: selectedMarket.userPosition?.deposits,
6498
- withdrawals: selectedMarket.userPosition?.withdrawals,
6499
- tokenSymbol: selectedMarket.underlyingSymbol
6500
- }
6501
- ),
6502
- /* @__PURE__ */ jsx(
6503
- DepositWithdrawForm,
6504
- {
6505
- venueType: "PENDLE_PT",
6506
- venueAddress: selectedMarket.marketAddress,
6507
- venueToken: selectedMarket.underlyingSymbol,
6508
- positionBalance: selectedMarket.userPosition?.balance,
6509
- onSuccess: handleActionSuccess
6510
- }
6511
- )
6512
- ] }) })
6513
- }
6514
- )
6515
- ] });
6516
- }
6517
- function useSwapQuote({ fromToken, toToken, amount, enabled = true }) {
6518
- const { chainId } = useChain();
6519
- const { address } = useCompassWallet();
6520
- const query = useQuery({
6521
- queryKey: ["swapQuote", chainId, fromToken, toToken, amount, address],
6522
- queryFn: async () => {
6523
- if (!fromToken || !toToken || !amount || parseFloat(amount) <= 0 || !address) {
6524
- return null;
6525
- }
6526
- try {
6527
- const params = new URLSearchParams({
6528
- owner: address,
6529
- chain: chainId,
6530
- tokenIn: fromToken,
6531
- tokenOut: toToken,
6532
- amountIn: amount
6533
- });
6534
- const response = await fetch(`/api/compass/swap/quote?${params}`);
6535
- if (!response.ok) {
6536
- const errorData = await response.json();
6537
- const errorMessage = errorData.message || errorData.error || "Failed to get swap quote";
6538
- throw new Error(errorMessage);
6539
- }
6540
- const data = await response.json();
6541
- const outputAmount = data.estimatedAmountOut || "0";
6542
- const inputAmountNum = parseFloat(amount);
6543
- const outputAmountNum = parseFloat(outputAmount);
6544
- return {
6545
- inputAmount: amount,
6546
- outputAmount,
6547
- rate: inputAmountNum > 0 ? (outputAmountNum / inputAmountNum).toString() : "0"
6548
- };
6549
- } catch (error) {
6550
- throw error;
6551
- }
6552
- },
6553
- enabled: enabled && !!address && !!fromToken && !!toToken && !!amount && parseFloat(amount) > 0,
6554
- staleTime: 10 * 1e3,
6555
- refetchInterval: 15 * 1e3,
6556
- retry: 1
6557
- });
6558
- return {
6559
- quote: query.data,
6560
- isLoading: query.isLoading,
6561
- isError: query.isError,
6562
- error: query.error,
6563
- refetch: query.refetch
5235
+ quote: query.data,
5236
+ isLoading: query.isLoading,
5237
+ isError: query.isError,
5238
+ error: query.error,
5239
+ refetch: query.refetch
6564
5240
  };
6565
5241
  }
6566
5242
 
@@ -7397,343 +6073,2039 @@ function PositionDetailModal({ position, onClose }) {
7397
6073
  isPendle && duration && /* @__PURE__ */ jsxs(
7398
6074
  "div",
7399
6075
  {
7400
- className: "p-3 rounded-lg",
7401
- style: { backgroundColor: "var(--compass-color-surface)" },
6076
+ className: "p-3 rounded-lg",
6077
+ style: { backgroundColor: "var(--compass-color-surface)" },
6078
+ children: [
6079
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
6080
+ /* @__PURE__ */ jsx(Clock, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }),
6081
+ /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "Duration" })
6082
+ ] }),
6083
+ /* @__PURE__ */ jsx(
6084
+ "p",
6085
+ {
6086
+ className: "font-semibold",
6087
+ style: { color: "var(--compass-color-text)" },
6088
+ children: duration
6089
+ }
6090
+ )
6091
+ ]
6092
+ }
6093
+ ),
6094
+ isPendle && expiryDate && /* @__PURE__ */ jsxs(
6095
+ "div",
6096
+ {
6097
+ className: "p-3 rounded-lg",
6098
+ style: { backgroundColor: "var(--compass-color-surface)" },
6099
+ children: [
6100
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
6101
+ /* @__PURE__ */ jsx(Calendar, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }),
6102
+ /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "Expiry (UTC)" })
6103
+ ] }),
6104
+ /* @__PURE__ */ jsx(
6105
+ "p",
6106
+ {
6107
+ className: "font-semibold",
6108
+ style: { color: "var(--compass-color-text)" },
6109
+ children: expiryDate
6110
+ }
6111
+ )
6112
+ ]
6113
+ }
6114
+ ),
6115
+ position.pnl && /* @__PURE__ */ jsxs(
6116
+ "div",
6117
+ {
6118
+ className: "p-3 rounded-lg",
6119
+ style: { backgroundColor: "var(--compass-color-surface)" },
6120
+ children: [
6121
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
6122
+ isPnlPositive ? /* @__PURE__ */ jsx(TrendingUp, { size: 14, style: { color: "var(--compass-color-success)" } }) : /* @__PURE__ */ jsx(TrendingDown, { size: 14, style: { color: "var(--compass-color-error)" } }),
6123
+ /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "Total P&L" })
6124
+ ] }),
6125
+ /* @__PURE__ */ jsxs(
6126
+ "p",
6127
+ {
6128
+ className: "font-semibold",
6129
+ style: {
6130
+ color: isPnlPositive ? "var(--compass-color-success)" : "var(--compass-color-error)"
6131
+ },
6132
+ children: [
6133
+ isPnlPositive ? "+" : "",
6134
+ formatUSD(position.pnl.totalPnl)
6135
+ ]
6136
+ }
6137
+ )
6138
+ ]
6139
+ }
6140
+ )
6141
+ ] }),
6142
+ (position.deposits.length > 0 || position.withdrawals.length > 0) && /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
6143
+ /* @__PURE__ */ jsx(
6144
+ "h3",
6145
+ {
6146
+ className: "font-semibold mb-3",
6147
+ style: { color: "var(--compass-color-text)" },
6148
+ children: "Transaction History"
6149
+ }
6150
+ ),
6151
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
6152
+ position.deposits.map((tx, i) => {
6153
+ const date = formatDate(tx.timestamp);
6154
+ return /* @__PURE__ */ jsxs(
6155
+ "div",
6156
+ {
6157
+ className: "flex items-center justify-between p-3 rounded-lg",
6158
+ style: { backgroundColor: "var(--compass-color-surface)" },
6159
+ children: [
6160
+ /* @__PURE__ */ jsxs("div", { children: [
6161
+ /* @__PURE__ */ jsxs(
6162
+ "p",
6163
+ {
6164
+ className: "font-medium",
6165
+ style: { color: "var(--compass-color-success)" },
6166
+ children: [
6167
+ "+",
6168
+ formatAmount(tx.amount),
6169
+ " ",
6170
+ position.assetSymbol
6171
+ ]
6172
+ }
6173
+ ),
6174
+ /* @__PURE__ */ jsx(
6175
+ "p",
6176
+ {
6177
+ className: "text-xs",
6178
+ style: { color: "var(--compass-color-text-tertiary)" },
6179
+ children: date ? `Deposit - ${date}` : "Deposit"
6180
+ }
6181
+ )
6182
+ ] }),
6183
+ tx.txHash && /* @__PURE__ */ jsx(
6184
+ "a",
6185
+ {
6186
+ href: `https://etherscan.io/tx/${tx.txHash}`,
6187
+ target: "_blank",
6188
+ rel: "noopener noreferrer",
6189
+ className: "p-2 rounded-lg hover:opacity-80 transition-opacity",
6190
+ style: { backgroundColor: "var(--compass-color-background)" },
6191
+ children: /* @__PURE__ */ jsx(ExternalLink, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } })
6192
+ }
6193
+ )
6194
+ ]
6195
+ },
6196
+ `deposit-${i}`
6197
+ );
6198
+ }),
6199
+ position.withdrawals.map((tx, i) => {
6200
+ const date = formatDate(tx.timestamp);
6201
+ return /* @__PURE__ */ jsxs(
6202
+ "div",
6203
+ {
6204
+ className: "flex items-center justify-between p-3 rounded-lg",
6205
+ style: { backgroundColor: "var(--compass-color-surface)" },
6206
+ children: [
6207
+ /* @__PURE__ */ jsxs("div", { children: [
6208
+ /* @__PURE__ */ jsxs(
6209
+ "p",
6210
+ {
6211
+ className: "font-medium",
6212
+ style: { color: "var(--compass-color-error)" },
6213
+ children: [
6214
+ "-",
6215
+ formatAmount(tx.amount),
6216
+ " ",
6217
+ position.assetSymbol
6218
+ ]
6219
+ }
6220
+ ),
6221
+ /* @__PURE__ */ jsx(
6222
+ "p",
6223
+ {
6224
+ className: "text-xs",
6225
+ style: { color: "var(--compass-color-text-tertiary)" },
6226
+ children: date ? `Withdrawal - ${date}` : "Withdrawal"
6227
+ }
6228
+ )
6229
+ ] }),
6230
+ tx.txHash && /* @__PURE__ */ jsx(
6231
+ "a",
6232
+ {
6233
+ href: `https://etherscan.io/tx/${tx.txHash}`,
6234
+ target: "_blank",
6235
+ rel: "noopener noreferrer",
6236
+ className: "p-2 rounded-lg hover:opacity-80 transition-opacity",
6237
+ style: { backgroundColor: "var(--compass-color-background)" },
6238
+ children: /* @__PURE__ */ jsx(ExternalLink, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } })
6239
+ }
6240
+ )
6241
+ ]
6242
+ },
6243
+ `withdraw-${i}`
6244
+ );
6245
+ })
6246
+ ] })
6247
+ ] })
6248
+ ]
6249
+ }
6250
+ )
6251
+ }
6252
+ );
6253
+ }
6254
+ function EarnPositionsList({
6255
+ showApy = true,
6256
+ showPnL = true,
6257
+ defaultCollapsed = [],
6258
+ onPositionSelect
6259
+ }) {
6260
+ const { isConnected } = useEmbeddableWallet();
6261
+ const { venuePositions, totalPositions, totalBalanceUsd, isLoading, isError } = usePositionsData();
6262
+ const [selectedPosition, setSelectedPosition] = useState(null);
6263
+ const handlePositionClick = (position) => {
6264
+ setSelectedPosition(position);
6265
+ onPositionSelect?.(position);
6266
+ };
6267
+ const renderContent = () => {
6268
+ if (!isConnected) {
6269
+ return /* @__PURE__ */ jsxs(
6270
+ "div",
6271
+ {
6272
+ className: "flex flex-col items-center justify-center py-12 text-center",
6273
+ style: { color: "var(--compass-color-text-secondary)" },
6274
+ children: [
6275
+ /* @__PURE__ */ jsx(Inbox, { size: 48, className: "mb-4 opacity-50" }),
6276
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: "Connect your wallet" }),
6277
+ /* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: "to view your earn positions" })
6278
+ ]
6279
+ }
6280
+ );
6281
+ }
6282
+ if (isLoading) {
6283
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx(
6284
+ Loader2,
6285
+ {
6286
+ size: 32,
6287
+ className: "animate-spin",
6288
+ style: { color: "var(--compass-color-primary)" }
6289
+ }
6290
+ ) });
6291
+ }
6292
+ if (isError) {
6293
+ return /* @__PURE__ */ jsxs(
6294
+ "div",
6295
+ {
6296
+ className: "flex flex-col items-center justify-center py-12 text-center",
6297
+ style: { color: "var(--compass-color-error)" },
6298
+ children: [
6299
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: "Failed to load positions" }),
6300
+ /* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: "Please try again later" })
6301
+ ]
6302
+ }
6303
+ );
6304
+ }
6305
+ if (totalPositions === 0) {
6306
+ return /* @__PURE__ */ jsxs(
6307
+ "div",
6308
+ {
6309
+ className: "flex flex-col items-center justify-center py-12 text-center",
6310
+ style: { color: "var(--compass-color-text-secondary)" },
6311
+ children: [
6312
+ /* @__PURE__ */ jsx(Inbox, { size: 48, className: "mb-4 opacity-50" }),
6313
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: "No positions yet" }),
6314
+ /* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: "Deposit to a vault or market to get started" })
6315
+ ]
6316
+ }
6317
+ );
6318
+ }
6319
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
6320
+ /* @__PURE__ */ jsxs(
6321
+ "div",
6322
+ {
6323
+ className: "flex items-center justify-between p-4 rounded-xl",
6324
+ style: { backgroundColor: "var(--compass-color-surface)" },
6325
+ children: [
6326
+ /* @__PURE__ */ jsxs("div", { children: [
6327
+ /* @__PURE__ */ jsx(
6328
+ "h2",
6329
+ {
6330
+ className: "font-bold text-lg",
6331
+ style: { color: "var(--compass-color-text)" },
6332
+ children: "Your Positions"
6333
+ }
6334
+ ),
6335
+ /* @__PURE__ */ jsxs(
6336
+ "p",
6337
+ {
6338
+ className: "text-sm",
6339
+ style: { color: "var(--compass-color-text-secondary)" },
6340
+ children: [
6341
+ totalPositions,
6342
+ " position",
6343
+ totalPositions !== 1 ? "s" : "",
6344
+ " across ",
6345
+ venuePositions.length,
6346
+ " venue",
6347
+ venuePositions.length !== 1 ? "s" : ""
6348
+ ]
6349
+ }
6350
+ )
6351
+ ] }),
6352
+ /* @__PURE__ */ jsxs("div", { className: "text-right", children: [
6353
+ /* @__PURE__ */ jsx(
6354
+ "p",
6355
+ {
6356
+ className: "text-sm",
6357
+ style: { color: "var(--compass-color-text-secondary)" },
6358
+ children: "Total Value"
6359
+ }
6360
+ ),
6361
+ /* @__PURE__ */ jsx(
6362
+ "p",
6363
+ {
6364
+ className: "font-bold text-xl",
6365
+ style: { color: "var(--compass-color-text)" },
6366
+ children: formatUSD(totalBalanceUsd)
6367
+ }
6368
+ )
6369
+ ] })
6370
+ ]
6371
+ }
6372
+ ),
6373
+ /* @__PURE__ */ jsx("div", { className: "space-y-4", children: venuePositions.map((vp) => /* @__PURE__ */ jsx(
6374
+ VenueSection,
6375
+ {
6376
+ venuePositions: vp,
6377
+ defaultCollapsed: defaultCollapsed.includes(vp.venue),
6378
+ showApy,
6379
+ showPnL,
6380
+ onPositionSelect: handlePositionClick
6381
+ },
6382
+ vp.venue
6383
+ )) })
6384
+ ] });
6385
+ };
6386
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
6387
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
6388
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
6389
+ /* @__PURE__ */ jsx(ChainSwitcher, {}),
6390
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
6391
+ /* @__PURE__ */ jsx(EarnAccountBalance, { compact: true }),
6392
+ /* @__PURE__ */ jsx(WalletStatus, { compact: true })
6393
+ ] })
6394
+ ] }),
6395
+ renderContent()
6396
+ ] }),
6397
+ selectedPosition && /* @__PURE__ */ jsx(
6398
+ PositionDetailModal,
6399
+ {
6400
+ position: selectedPosition,
6401
+ onClose: () => setSelectedPosition(null)
6402
+ }
6403
+ )
6404
+ ] });
6405
+ }
6406
+ function parseExpiry(expiry) {
6407
+ if (!expiry || expiry === 0) return void 0;
6408
+ const timestamp = typeof expiry === "number" ? expiry : parseInt(expiry, 10);
6409
+ if (isNaN(timestamp) || timestamp <= 0) return void 0;
6410
+ const ms = timestamp < 3250368e4 ? timestamp * 1e3 : timestamp;
6411
+ const date = new Date(ms);
6412
+ if (date.getFullYear() < 2e3) return void 0;
6413
+ return date.toLocaleDateString("en-US", { month: "short", year: "numeric" });
6414
+ }
6415
+ function useRebalancingData(chainOverride) {
6416
+ const { address } = useEmbeddableWallet();
6417
+ const { chainId: contextChainId } = useChain();
6418
+ const chainId = chainOverride || contextChainId;
6419
+ const positionsQuery = useQuery({
6420
+ queryKey: ["rebalancing", "portfolio", chainId, address],
6421
+ queryFn: async () => {
6422
+ if (!address) return [];
6423
+ const params = new URLSearchParams({ chain: chainId, owner: address });
6424
+ const response = await fetch(`/api/compass/positions?${params}`);
6425
+ if (!response.ok) throw new Error("Failed to fetch positions");
6426
+ const data = await response.json();
6427
+ const positions2 = [];
6428
+ for (const p of data.positions || []) {
6429
+ const usdValue = parseFloat(p.balanceUsd || p.balance || "0");
6430
+ if (usdValue <= 0) continue;
6431
+ let venueType;
6432
+ let venueAddress = "";
6433
+ if (p.protocol === "vaults") {
6434
+ venueType = "vault";
6435
+ venueAddress = p.vaultAddress || "";
6436
+ } else if (p.protocol === "aave") {
6437
+ venueType = "aave";
6438
+ venueAddress = p.symbol || "";
6439
+ } else if (p.protocol === "pendle") {
6440
+ venueType = "pendle_pt";
6441
+ venueAddress = p.marketAddress || "";
6442
+ } else {
6443
+ continue;
6444
+ }
6445
+ positions2.push({
6446
+ id: `${p.protocol}-${venueAddress}`,
6447
+ venueType,
6448
+ venueName: p.name || `${p.symbol} on ${p.protocol}`,
6449
+ venueAddress,
6450
+ token: p.symbol || "UNKNOWN",
6451
+ balance: parseFloat(p.balance || "0"),
6452
+ usdValue,
6453
+ apy: p.apy || 0,
6454
+ allocationPercent: 0
6455
+ // computed below
6456
+ });
6457
+ }
6458
+ return positions2;
6459
+ },
6460
+ enabled: !!address,
6461
+ staleTime: 3e4
6462
+ });
6463
+ const balancesQuery = useQuery({
6464
+ queryKey: ["rebalancing", "balances", chainId, address],
6465
+ queryFn: async () => {
6466
+ if (!address) return [];
6467
+ const params = new URLSearchParams({ chain: chainId, owner: address });
6468
+ const response = await fetch(`/api/compass/earn-account/balances?${params}`);
6469
+ if (!response.ok) throw new Error("Failed to fetch balances");
6470
+ const data = await response.json();
6471
+ const balances = [];
6472
+ for (const [symbol, tokenData] of Object.entries(data.balances || {})) {
6473
+ const td = tokenData;
6474
+ const usdValue = parseFloat(td.usdValue || "0");
6475
+ if (usdValue <= 0) continue;
6476
+ balances.push({
6477
+ token: symbol,
6478
+ balance: parseFloat(td.balance || "0"),
6479
+ usdValue
6480
+ });
6481
+ }
6482
+ return balances;
6483
+ },
6484
+ enabled: !!address,
6485
+ staleTime: 3e4
6486
+ });
6487
+ const venuesQuery = useQuery({
6488
+ queryKey: ["rebalancing", "venues", chainId],
6489
+ queryFn: async () => {
6490
+ const [vaultsRes, aaveRes, pendleRes] = await Promise.all([
6491
+ fetch(`/api/compass/vaults?chain=${chainId}&orderBy=apy_7d&direction=desc&limit=200`),
6492
+ fetch(`/api/compass/aave/markets?chain=${chainId}`),
6493
+ fetch(`/api/compass/pendle/markets?chain=${chainId}&orderBy=implied_apy&direction=desc&limit=200`)
6494
+ ]);
6495
+ const venues = [];
6496
+ const earnMarkets = [];
6497
+ if (vaultsRes.ok) {
6498
+ const data = await vaultsRes.json();
6499
+ const vaultsList = data.vaults || [];
6500
+ for (const v of vaultsList) {
6501
+ const vaultAddress = v.vaultAddress || v.address || "";
6502
+ const name = v.name || `${v.curatorName || "Morpho"} ${v.assetSymbol || "Vault"}`;
6503
+ const token = (v.assetSymbol || "UNKNOWN").toUpperCase();
6504
+ const apy = parseFloat(v.apy7d || v.apy || "0");
6505
+ venues.push({
6506
+ venueType: "vault",
6507
+ venueAddress: vaultAddress,
6508
+ venueName: name,
6509
+ token,
6510
+ apy
6511
+ });
6512
+ earnMarkets.push({
6513
+ id: `vault-${vaultAddress}`,
6514
+ name,
6515
+ apy,
6516
+ tvl: parseFloat(v.tvlUsd || v.tvl || "0"),
6517
+ type: "vaults",
6518
+ underlyingToken: token,
6519
+ curator: v.curatorName || v.curator,
6520
+ vaultAddress
6521
+ });
6522
+ }
6523
+ }
6524
+ if (aaveRes.ok) {
6525
+ const data = await aaveRes.json();
6526
+ const marketsDict = data.markets || {};
6527
+ for (const [symbol, marketData] of Object.entries(marketsDict)) {
6528
+ const chainData = marketData.chains?.[chainId];
6529
+ if (chainData) {
6530
+ const upperSymbol = symbol.toUpperCase();
6531
+ const apy = parseFloat(chainData.supplyApy || "0");
6532
+ venues.push({
6533
+ venueType: "aave",
6534
+ venueAddress: upperSymbol,
6535
+ venueName: `${upperSymbol} on Aave`,
6536
+ token: upperSymbol,
6537
+ apy
6538
+ });
6539
+ earnMarkets.push({
6540
+ id: `aave-${symbol.toLowerCase()}`,
6541
+ name: upperSymbol,
6542
+ apy,
6543
+ tvl: parseFloat(chainData.totalSupplyUsd || "0"),
6544
+ type: "aave",
6545
+ underlyingToken: upperSymbol
6546
+ });
6547
+ }
6548
+ }
6549
+ }
6550
+ if (pendleRes.ok) {
6551
+ const data = await pendleRes.json();
6552
+ const marketsList = data.markets || [];
6553
+ for (const m of marketsList) {
6554
+ const symbol = (m.underlyingSymbol || "PT").toUpperCase();
6555
+ const marketAddress = m.marketAddress || m.address || "";
6556
+ const expiryStr = parseExpiry(m.expiry);
6557
+ const name = m.name || `PT-${symbol}${expiryStr ? ` ${expiryStr}` : ""}`;
6558
+ const apy = parseFloat(m.impliedApy || m.apy || "0");
6559
+ venues.push({
6560
+ venueType: "pendle_pt",
6561
+ venueAddress: marketAddress,
6562
+ venueName: name,
6563
+ token: symbol,
6564
+ apy
6565
+ });
6566
+ earnMarkets.push({
6567
+ id: `pendle-${marketAddress}`,
6568
+ name,
6569
+ apy,
6570
+ tvl: parseFloat(m.tvlUsd || m.tvl || "0"),
6571
+ type: "pendle",
6572
+ underlyingToken: symbol,
6573
+ expiry: expiryStr,
6574
+ marketAddress
6575
+ });
6576
+ }
6577
+ }
6578
+ return { venues, earnMarkets };
6579
+ },
6580
+ staleTime: 6e4
6581
+ });
6582
+ const positions = positionsQuery.data || [];
6583
+ const idleBalances = balancesQuery.data || [];
6584
+ const totalPositionUsd = positions.reduce((sum, p) => sum + p.usdValue, 0);
6585
+ const totalIdleUsd = idleBalances.reduce((sum, b) => sum + b.usdValue, 0);
6586
+ const totalUsd = totalPositionUsd + totalIdleUsd;
6587
+ const positionsWithAllocation = positions.map((p) => ({
6588
+ ...p,
6589
+ allocationPercent: totalUsd > 0 ? p.usdValue / totalUsd * 100 : 0
6590
+ }));
6591
+ const portfolio = address ? {
6592
+ positions: positionsWithAllocation,
6593
+ idleBalances,
6594
+ totalUsd,
6595
+ totalIdleUsd
6596
+ } : null;
6597
+ return {
6598
+ portfolio,
6599
+ availableVenues: venuesQuery.data?.venues || [],
6600
+ earnAccountMarkets: venuesQuery.data?.earnMarkets || [],
6601
+ isMarketsLoading: venuesQuery.isLoading,
6602
+ isLoading: positionsQuery.isLoading || balancesQuery.isLoading || venuesQuery.isLoading,
6603
+ isError: positionsQuery.isError || balancesQuery.isError || venuesQuery.isError,
6604
+ error: positionsQuery.error || balancesQuery.error || venuesQuery.error,
6605
+ refetch: () => {
6606
+ positionsQuery.refetch();
6607
+ balancesQuery.refetch();
6608
+ venuesQuery.refetch();
6609
+ }
6610
+ };
6611
+ }
6612
+
6613
+ // src/components/RebalancingWidget/rebalancingEngine.ts
6614
+ var DEFAULT_OPTIONS = {
6615
+ slippagePercent: 0.5,
6616
+ minThresholdUsd: 0.01
6617
+ };
6618
+ function computeRebalancePlan(portfolio, targets, options = {}) {
6619
+ const opts = { ...DEFAULT_OPTIONS, ...options };
6620
+ const warnings = [];
6621
+ const actions = [];
6622
+ const totalUsd = portfolio.totalUsd;
6623
+ if (totalUsd <= 0) {
6624
+ return { actions: [], totalActions: 0, estimatedGasSavings: "$0", warnings: ["No portfolio value to rebalance"] };
6625
+ }
6626
+ const deltas = [];
6627
+ for (const target of targets) {
6628
+ const targetUsd = totalUsd * (target.targetPercent / 100);
6629
+ const position = portfolio.positions.find(
6630
+ (p) => p.venueType === target.venueType && p.venueAddress.toLowerCase() === target.venueAddress.toLowerCase()
6631
+ );
6632
+ const currentUsd = position?.usdValue || 0;
6633
+ const deltaUsd = targetUsd - currentUsd;
6634
+ if (Math.abs(deltaUsd) < opts.minThresholdUsd) continue;
6635
+ deltas.push({
6636
+ venueType: target.venueType,
6637
+ venueAddress: target.venueAddress,
6638
+ venueName: target.venueName,
6639
+ token: target.token,
6640
+ currentUsd,
6641
+ targetUsd,
6642
+ deltaUsd
6643
+ });
6644
+ }
6645
+ for (const position of portfolio.positions) {
6646
+ const hasTarget = targets.some(
6647
+ (t) => t.venueType === position.venueType && t.venueAddress.toLowerCase() === position.venueAddress.toLowerCase()
6648
+ );
6649
+ if (!hasTarget && position.usdValue >= opts.minThresholdUsd) {
6650
+ deltas.push({
6651
+ venueType: position.venueType,
6652
+ venueAddress: position.venueAddress,
6653
+ venueName: position.venueName,
6654
+ token: position.token,
6655
+ currentUsd: position.usdValue,
6656
+ targetUsd: 0,
6657
+ deltaUsd: -position.usdValue
6658
+ });
6659
+ }
6660
+ }
6661
+ const overAllocated = deltas.filter((d) => d.deltaUsd < 0);
6662
+ const underAllocated = deltas.filter((d) => d.deltaUsd > 0);
6663
+ for (const delta of overAllocated) {
6664
+ const withdrawUsd = Math.abs(delta.deltaUsd);
6665
+ const withdrawAmount = delta.currentUsd > 0 ? withdrawUsd / delta.currentUsd * (portfolio.positions.find(
6666
+ (p) => p.venueType === delta.venueType && p.venueAddress.toLowerCase() === delta.venueAddress.toLowerCase()
6667
+ )?.balance || 0) : 0;
6668
+ actions.push({
6669
+ type: "withdraw",
6670
+ venueType: delta.venueType,
6671
+ venueAddress: delta.venueAddress,
6672
+ token: delta.token,
6673
+ amount: withdrawAmount,
6674
+ usdValue: withdrawUsd
6675
+ });
6676
+ if (delta.venueType === "pendle_pt") {
6677
+ warnings.push(`Withdrawing from Pendle PT position (${delta.venueName}) - check maturity date before proceeding`);
6678
+ }
6679
+ }
6680
+ const withdrawnTokenAmounts = {};
6681
+ for (const action of actions) {
6682
+ if (action.type === "withdraw") {
6683
+ const key = action.token.toUpperCase();
6684
+ withdrawnTokenAmounts[key] = (withdrawnTokenAmounts[key] || 0) + action.usdValue;
6685
+ }
6686
+ }
6687
+ for (const idle of portfolio.idleBalances) {
6688
+ const key = idle.token.toUpperCase();
6689
+ withdrawnTokenAmounts[key] = (withdrawnTokenAmounts[key] || 0) + idle.usdValue;
6690
+ }
6691
+ const depositTokenNeeds = {};
6692
+ for (const delta of underAllocated) {
6693
+ const key = delta.token.toUpperCase();
6694
+ depositTokenNeeds[key] = (depositTokenNeeds[key] || 0) + delta.deltaUsd;
6695
+ }
6696
+ for (const [depositToken, neededUsd] of Object.entries(depositTokenNeeds)) {
6697
+ const availableUsd = withdrawnTokenAmounts[depositToken] || 0;
6698
+ const shortfallUsd = neededUsd - availableUsd;
6699
+ if (shortfallUsd > opts.minThresholdUsd) {
6700
+ for (const [withdrawToken, excessUsd] of Object.entries(withdrawnTokenAmounts)) {
6701
+ if (withdrawToken === depositToken) continue;
6702
+ if (excessUsd <= opts.minThresholdUsd) continue;
6703
+ const swapUsd = Math.min(shortfallUsd, excessUsd);
6704
+ if (swapUsd < opts.minThresholdUsd) continue;
6705
+ const slippageFactor = 1 - opts.slippagePercent / 100;
6706
+ actions.push({
6707
+ type: "swap",
6708
+ token: withdrawToken,
6709
+ amount: swapUsd,
6710
+ // USD-denominated for now
6711
+ usdValue: swapUsd,
6712
+ tokenOut: depositToken,
6713
+ estimatedAmountOut: swapUsd * slippageFactor
6714
+ // Approximate
6715
+ });
6716
+ withdrawnTokenAmounts[withdrawToken] -= swapUsd;
6717
+ withdrawnTokenAmounts[depositToken] = (withdrawnTokenAmounts[depositToken] || 0) + swapUsd * slippageFactor;
6718
+ warnings.push(`Swap ${withdrawToken} to ${depositToken} involves slippage risk`);
6719
+ break;
6720
+ }
6721
+ }
6722
+ }
6723
+ for (const delta of underAllocated) {
6724
+ const depositUsd = delta.deltaUsd;
6725
+ const depositAmount = depositUsd;
6726
+ actions.push({
6727
+ type: "deposit",
6728
+ venueType: delta.venueType,
6729
+ venueAddress: delta.venueAddress,
6730
+ token: delta.token,
6731
+ amount: depositAmount,
6732
+ usdValue: depositUsd
6733
+ });
6734
+ }
6735
+ const totalRebalanceUsd = actions.filter((a) => a.type === "withdraw" || a.type === "deposit").reduce((sum, a) => sum + a.usdValue, 0);
6736
+ if (totalRebalanceUsd > totalUsd * 0.5) {
6737
+ warnings.push("This rebalance involves more than 50% of your portfolio");
6738
+ }
6739
+ if (actions.some((a) => a.type === "swap")) {
6740
+ warnings.push("Swap amounts are estimates - actual amounts may vary due to slippage");
6741
+ }
6742
+ const sortOrder = { withdraw: 0, swap: 1, deposit: 2 };
6743
+ actions.sort((a, b) => sortOrder[a.type] - sortOrder[b.type]);
6744
+ const individualTxCount = actions.length;
6745
+ const bundleTxCount = 1;
6746
+ const savedTxs = Math.max(0, individualTxCount - bundleTxCount);
6747
+ const estimatedGasSavings = `~$${(savedTxs * 2).toFixed(0)}`;
6748
+ return {
6749
+ actions,
6750
+ totalActions: actions.length,
6751
+ estimatedGasSavings,
6752
+ warnings
6753
+ };
6754
+ }
6755
+ function PortfolioBalanceCard({
6756
+ totalUsd,
6757
+ totalIdleUsd,
6758
+ idleBalances,
6759
+ earnAccountAddress,
6760
+ isPositionsExpanded,
6761
+ onTogglePositions,
6762
+ positionCount,
6763
+ showTopUp = true,
6764
+ onTopUp
6765
+ }) {
6766
+ const [showBalancesModal, setShowBalancesModal] = useState(false);
6767
+ const tokenBalances = idleBalances.map((b) => ({
6768
+ symbol: b.token,
6769
+ balance: b.balance.toString(),
6770
+ usdValue: b.usdValue.toString()
6771
+ }));
6772
+ const earningInterestUsd = totalUsd - totalIdleUsd;
6773
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
6774
+ /* @__PURE__ */ jsxs(
6775
+ "div",
6776
+ {
6777
+ style: {
6778
+ backgroundColor: "var(--compass-color-surface)",
6779
+ borderRadius: "var(--compass-border-radius-xl)",
6780
+ border: "1px solid var(--compass-color-border)",
6781
+ fontFamily: "var(--compass-font-family)",
6782
+ padding: "var(--compass-spacing-card)"
6783
+ },
6784
+ children: [
6785
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
6786
+ /* @__PURE__ */ jsxs(
6787
+ "button",
6788
+ {
6789
+ onClick: () => setShowBalancesModal(true),
6790
+ className: "flex items-center text-left transition-opacity hover:opacity-80",
6791
+ style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" },
6792
+ children: [
6793
+ /* @__PURE__ */ jsx(
6794
+ "span",
6795
+ {
6796
+ className: "text-xs font-medium uppercase tracking-wide",
6797
+ style: { color: "var(--compass-color-text-tertiary)" },
6798
+ children: "Total Balance"
6799
+ }
6800
+ ),
6801
+ /* @__PURE__ */ jsx(ChevronDown, { size: 12, style: { color: "var(--compass-color-text-tertiary)" } })
6802
+ ]
6803
+ }
6804
+ ),
6805
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
6806
+ /* @__PURE__ */ jsx(
6807
+ "button",
6808
+ {
6809
+ onClick: () => setShowBalancesModal(true),
6810
+ className: "transition-opacity hover:opacity-80",
6811
+ children: /* @__PURE__ */ jsx(
6812
+ "span",
6813
+ {
6814
+ className: "font-bold",
6815
+ style: {
6816
+ color: "var(--compass-color-text)",
6817
+ fontSize: "2rem",
6818
+ lineHeight: "1"
6819
+ },
6820
+ children: formatUSD(totalUsd)
6821
+ }
6822
+ )
6823
+ }
6824
+ ),
6825
+ showTopUp && onTopUp && /* @__PURE__ */ jsxs(
6826
+ "button",
6827
+ {
6828
+ onClick: onTopUp,
6829
+ className: "flex items-center font-medium transition-all hover:opacity-80",
6830
+ style: {
6831
+ backgroundColor: "var(--compass-color-surface-elevated, var(--compass-color-surface))",
6832
+ border: "1px solid var(--compass-color-border)",
6833
+ color: "var(--compass-color-text-secondary)",
6834
+ borderRadius: "var(--compass-border-radius-md)",
6835
+ padding: "6px 10px",
6836
+ gap: "4px",
6837
+ fontSize: "12px"
6838
+ },
6839
+ children: [
6840
+ /* @__PURE__ */ jsx(Plus, { size: 14 }),
6841
+ "Top Up"
6842
+ ]
6843
+ }
6844
+ )
6845
+ ] })
6846
+ ] }),
6847
+ /* @__PURE__ */ jsx(
6848
+ "div",
6849
+ {
6850
+ style: {
6851
+ height: "1px",
6852
+ backgroundColor: "var(--compass-color-border)",
6853
+ margin: "var(--compass-spacing-card) 0"
6854
+ }
6855
+ }
6856
+ ),
6857
+ /* @__PURE__ */ jsx(
6858
+ "button",
6859
+ {
6860
+ onClick: onTogglePositions,
6861
+ className: "flex items-center justify-between w-full text-left transition-opacity hover:opacity-80",
6862
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "calc(var(--compass-spacing-unit) * 0.25)" }, children: [
6863
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
6864
+ /* @__PURE__ */ jsxs("span", { className: "text-sm", style: { color: "var(--compass-color-text-tertiary)" }, children: [
6865
+ "Earning interest",
6866
+ positionCount > 0 ? ` \xB7 ${positionCount} ${positionCount === 1 ? "position" : "positions"}` : ""
6867
+ ] }),
6868
+ positionCount > 0 && (isPositionsExpanded ? /* @__PURE__ */ jsx(ChevronDown, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }) : /* @__PURE__ */ jsx(ChevronRight, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }))
6869
+ ] }),
6870
+ /* @__PURE__ */ jsx(
6871
+ "span",
6872
+ {
6873
+ className: "font-semibold",
6874
+ style: {
6875
+ color: "var(--compass-color-text)",
6876
+ fontSize: "1rem"
6877
+ },
6878
+ children: formatUSD(earningInterestUsd)
6879
+ }
6880
+ )
6881
+ ] })
6882
+ }
6883
+ )
6884
+ ]
6885
+ }
6886
+ ),
6887
+ /* @__PURE__ */ jsx(
6888
+ AccountBalancesModal,
6889
+ {
6890
+ isOpen: showBalancesModal,
6891
+ onClose: () => setShowBalancesModal(false),
6892
+ balances: tokenBalances,
6893
+ totalUsdValue: totalIdleUsd.toString(),
6894
+ earnAccountAddress
6895
+ }
6896
+ )
6897
+ ] });
6898
+ }
6899
+
6900
+ // src/components/RebalancingWidget/constants.ts
6901
+ var VENUE_COLORS = {
6902
+ vault: "var(--compass-color-primary)",
6903
+ aave: "#9333ea",
6904
+ pendle_pt: "#22c55e"
6905
+ };
6906
+ var VENUE_LABELS = {
6907
+ vault: "Vault",
6908
+ aave: "Aave",
6909
+ pendle_pt: "Pendle PT"
6910
+ };
6911
+ function AllocationEditor({
6912
+ portfolio,
6913
+ targets,
6914
+ targetSum,
6915
+ hasChanges,
6916
+ highlightedVenueAddress,
6917
+ onUpdatePercent,
6918
+ onRemoveVenue,
6919
+ onResetToCurrent,
6920
+ onEqualSplit
6921
+ }) {
6922
+ return /* @__PURE__ */ jsxs("div", { children: [
6923
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end mb-2", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
6924
+ /* @__PURE__ */ jsxs(
6925
+ "button",
6926
+ {
6927
+ onClick: onResetToCurrent,
6928
+ className: "text-xs px-2 py-1 rounded-md transition-colors",
6929
+ style: {
6930
+ backgroundColor: "var(--compass-color-background)",
6931
+ color: "var(--compass-color-text-secondary)"
6932
+ },
6933
+ title: "Reset to current",
6934
+ children: [
6935
+ /* @__PURE__ */ jsx(RotateCcw, { size: 12, className: "inline mr-1" }),
6936
+ "Reset"
6937
+ ]
6938
+ }
6939
+ ),
6940
+ targets.length > 1 && /* @__PURE__ */ jsxs(
6941
+ "button",
6942
+ {
6943
+ onClick: onEqualSplit,
6944
+ className: "text-xs px-2 py-1 rounded-md transition-colors",
6945
+ style: {
6946
+ backgroundColor: "var(--compass-color-background)",
6947
+ color: "var(--compass-color-text-secondary)"
6948
+ },
6949
+ title: "Split equally",
6950
+ children: [
6951
+ /* @__PURE__ */ jsx(Equal, { size: 12, className: "inline mr-1" }),
6952
+ "Equal"
6953
+ ]
6954
+ }
6955
+ )
6956
+ ] }),
6957
+ /* @__PURE__ */ jsxs(
6958
+ "div",
6959
+ {
6960
+ className: "flex flex-col",
6961
+ style: { gap: "6px" },
6962
+ children: [
6963
+ targets.map((target, index) => {
6964
+ const currentPos = portfolio.positions.find(
6965
+ (p) => p.venueType === target.venueType && p.venueAddress === target.venueAddress
6966
+ );
6967
+ const currentPercent = currentPos?.allocationPercent ?? 0;
6968
+ const diff = target.targetPercent - currentPercent;
6969
+ const hasDiff = Math.abs(diff) > 0.1;
6970
+ const isHighlighted = highlightedVenueAddress === target.venueAddress;
6971
+ return /* @__PURE__ */ jsxs(
6972
+ "div",
6973
+ {
6974
+ className: "p-2.5 rounded-lg",
6975
+ style: {
6976
+ backgroundColor: "var(--compass-color-background)",
6977
+ borderLeft: isHighlighted ? "3px solid var(--compass-color-primary)" : "3px solid transparent"
6978
+ },
6979
+ children: [
6980
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1.5", children: [
6981
+ /* @__PURE__ */ jsx(
6982
+ "div",
6983
+ {
6984
+ className: "w-1.5 rounded-full flex-shrink-0",
6985
+ style: {
6986
+ backgroundColor: VENUE_COLORS[target.venueType],
6987
+ alignSelf: "stretch"
6988
+ }
6989
+ }
6990
+ ),
6991
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
6992
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium truncate block", style: { color: "var(--compass-color-text)" }, children: target.venueName }),
6993
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
6994
+ /* @__PURE__ */ jsx(
6995
+ "span",
6996
+ {
6997
+ className: "text-xs px-1 py-0.5 rounded",
6998
+ style: {
6999
+ backgroundColor: VENUE_COLORS[target.venueType] + "20",
7000
+ color: VENUE_COLORS[target.venueType],
7001
+ fontSize: "10px"
7002
+ },
7003
+ children: VENUE_LABELS[target.venueType]
7004
+ }
7005
+ ),
7006
+ currentPos && /* @__PURE__ */ jsxs(Fragment, { children: [
7007
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-mono", style: { color: "var(--compass-color-text-secondary)", fontSize: "10px" }, children: formatUSD(currentPos.usdValue) }),
7008
+ /* @__PURE__ */ jsxs("span", { style: { color: "var(--compass-color-success)", fontSize: "10px" }, children: [
7009
+ currentPos.apy.toFixed(1),
7010
+ "%"
7011
+ ] })
7012
+ ] }),
7013
+ !currentPos && /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text-tertiary)", fontSize: "10px" }, children: "New" })
7014
+ ] })
7015
+ ] }),
7016
+ /* @__PURE__ */ jsx(
7017
+ "button",
7018
+ {
7019
+ onClick: () => onRemoveVenue(index),
7020
+ className: "w-5 h-5 flex items-center justify-center rounded flex-shrink-0 opacity-40 hover:opacity-100 transition-opacity",
7021
+ style: { color: "var(--compass-color-text-secondary)" },
7022
+ children: /* @__PURE__ */ jsx(X, { size: 11 })
7023
+ }
7024
+ )
7025
+ ] }),
7026
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7027
+ /* @__PURE__ */ jsx(
7028
+ "div",
7029
+ {
7030
+ className: "flex-1 h-1.5 rounded-full overflow-hidden",
7031
+ style: { backgroundColor: "var(--compass-color-border)" },
7032
+ children: /* @__PURE__ */ jsx(
7033
+ "div",
7034
+ {
7035
+ className: "h-full rounded-full transition-all",
7036
+ style: {
7037
+ width: `${Math.min(target.targetPercent, 100)}%`,
7038
+ backgroundColor: VENUE_COLORS[target.venueType],
7039
+ opacity: hasDiff ? 0.85 : 1
7040
+ }
7041
+ }
7042
+ )
7043
+ }
7044
+ ),
7045
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 flex-shrink-0", children: [
7046
+ /* @__PURE__ */ jsx(
7047
+ "input",
7048
+ {
7049
+ type: "number",
7050
+ value: target.targetPercent.toFixed(3),
7051
+ onChange: (e) => onUpdatePercent(index, parseFloat(e.target.value) || 0),
7052
+ className: "w-16 text-center text-xs font-mono rounded bg-transparent outline-none",
7053
+ style: {
7054
+ color: "var(--compass-color-text)",
7055
+ border: "1px solid var(--compass-color-border)",
7056
+ padding: "2px 0"
7057
+ },
7058
+ min: 0,
7059
+ max: 100,
7060
+ step: 1e-3
7061
+ }
7062
+ ),
7063
+ /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)", width: "12px" }, children: "%" })
7064
+ ] })
7065
+ ] }),
7066
+ hasDiff && /* @__PURE__ */ jsx("div", { className: "flex justify-end mt-0.5", children: /* @__PURE__ */ jsxs(
7067
+ "span",
7068
+ {
7069
+ className: "text-xs font-mono",
7070
+ style: { color: diff > 0 ? "var(--compass-color-success)" : "var(--compass-color-error)", fontSize: "10px" },
7071
+ children: [
7072
+ currentPercent.toFixed(3),
7073
+ "% \u2192 ",
7074
+ target.targetPercent.toFixed(3),
7075
+ "%"
7076
+ ]
7077
+ }
7078
+ ) })
7079
+ ]
7080
+ },
7081
+ `${target.venueType}-${target.venueAddress}`
7082
+ );
7083
+ }),
7084
+ portfolio.totalIdleUsd > 0 && /* @__PURE__ */ jsx(
7085
+ "div",
7086
+ {
7087
+ className: "p-2.5 rounded-lg",
7088
+ style: { backgroundColor: "var(--compass-color-background)" },
7089
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7090
+ /* @__PURE__ */ jsx(
7091
+ "div",
7092
+ {
7093
+ className: "w-1.5 rounded-full flex-shrink-0",
7094
+ style: {
7095
+ backgroundColor: "var(--compass-color-text-tertiary)",
7096
+ alignSelf: "stretch",
7097
+ minHeight: "20px"
7098
+ }
7099
+ }
7100
+ ),
7101
+ /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-secondary)" }, children: "Idle (undeployed)" }),
7102
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-mono ml-auto", style: { color: "var(--compass-color-text-secondary)" }, children: formatUSD(portfolio.totalIdleUsd) })
7103
+ ] })
7104
+ }
7105
+ )
7106
+ ]
7107
+ }
7108
+ ),
7109
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end mt-2", children: /* @__PURE__ */ jsxs(
7110
+ "span",
7111
+ {
7112
+ className: "text-xs font-mono font-medium",
7113
+ style: {
7114
+ color: "var(--compass-color-text-secondary)"
7115
+ },
7116
+ children: [
7117
+ targetSum.toFixed(3),
7118
+ "% allocated"
7119
+ ]
7120
+ }
7121
+ ) })
7122
+ ] });
7123
+ }
7124
+ var SUPPORTED_TOKENS2 = ["USDC", "USDT", "DAI", "WETH", "SBC", "AUSD"];
7125
+ var EVM_CHAIN_IDS2 = {
7126
+ ethereum: 1,
7127
+ base: 8453,
7128
+ arbitrum: 42161
7129
+ };
7130
+ function formatAmount3(value) {
7131
+ const num = typeof value === "string" ? parseFloat(value) : value;
7132
+ if (isNaN(num)) return "0";
7133
+ return num.toFixed(6).replace(/\.?0+$/, "");
7134
+ }
7135
+ function RebalancingWidget({
7136
+ showChainSwitcher = true,
7137
+ showWalletStatus = true,
7138
+ minRebalanceThresholdUsd = 0.01,
7139
+ defaultSlippage = 0.5,
7140
+ chain,
7141
+ onRebalance,
7142
+ onError,
7143
+ title = "Portfolio Manager",
7144
+ venues,
7145
+ showTopUp = true,
7146
+ height = "600px"
7147
+ }) {
7148
+ const { chainId: contextChainId, setChainId } = useChain();
7149
+ const CHAIN_ID = chain || contextChainId;
7150
+ const { address, signTypedData, isConnected, login, switchChain, walletChainId } = useEmbeddableWallet();
7151
+ const queryClient = useQueryClient();
7152
+ const { portfolio, earnAccountMarkets, isMarketsLoading, isLoading, isError, error, refetch } = useRebalancingData(chain);
7153
+ const allowedVariableMarketIds = useMemo(() => {
7154
+ if (!venues) return void 0;
7155
+ if (!venues.aave && !venues.vaults) return void 0;
7156
+ const ids = [];
7157
+ if (venues.aave) {
7158
+ for (const symbol of venues.aave) ids.push(`aave-${symbol.toLowerCase()}`);
7159
+ }
7160
+ if (venues.vaults) {
7161
+ for (const addr of venues.vaults) ids.push(`vault-${addr}`);
7162
+ }
7163
+ return ids;
7164
+ }, [venues]);
7165
+ const allowedFixedMarketIds = useMemo(() => {
7166
+ if (!venues) return void 0;
7167
+ if (!venues.pendle) return void 0;
7168
+ const ids = [];
7169
+ for (const addr of venues.pendle) ids.push(`pendle-${addr}`);
7170
+ return ids;
7171
+ }, [venues]);
7172
+ useEffect(() => {
7173
+ if (chain && chain !== contextChainId) {
7174
+ setChainId(chain);
7175
+ }
7176
+ }, [chain, contextChainId, setChainId]);
7177
+ const [targets, setTargets] = useState([]);
7178
+ const [previewPlan, setPreviewPlan] = useState(null);
7179
+ const [serverPreview, setServerPreview] = useState(null);
7180
+ const [widgetState, setWidgetState] = useState("editing");
7181
+ const [errorMessage, setErrorMessage] = useState(null);
7182
+ const [txHash, setTxHash] = useState(null);
7183
+ const [hasInitializedTargets, setHasInitializedTargets] = useState(false);
7184
+ const [isPositionsExpanded, setIsPositionsExpanded] = useState(false);
7185
+ const [marketTab, setMarketTab] = useState("variable");
7186
+ const [selectedMarket, setSelectedMarket] = useState(null);
7187
+ const [selectedToken, setSelectedToken] = useState("USDC");
7188
+ const [depositAmount, setDepositAmount] = useState("");
7189
+ const [isTokenDropdownOpen, setIsTokenDropdownOpen] = useState(false);
7190
+ const [isProcessing, setIsProcessing] = useState(false);
7191
+ const [depositError, setDepositError] = useState(null);
7192
+ const [depositStatus, setDepositStatus] = useState("");
7193
+ const earnBalanceRef = useRef(null);
7194
+ useEffect(() => {
7195
+ setTargets([]);
7196
+ setPreviewPlan(null);
7197
+ setServerPreview(null);
7198
+ setWidgetState("editing");
7199
+ setErrorMessage(null);
7200
+ setTxHash(null);
7201
+ setHasInitializedTargets(false);
7202
+ setIsPositionsExpanded(false);
7203
+ setSelectedMarket(null);
7204
+ setDepositAmount("");
7205
+ setDepositError(null);
7206
+ setDepositStatus("");
7207
+ }, [CHAIN_ID]);
7208
+ useEffect(() => {
7209
+ if (portfolio && portfolio.positions.length > 0 && !hasInitializedTargets) {
7210
+ setTargets(
7211
+ portfolio.positions.map((p) => ({
7212
+ venueType: p.venueType,
7213
+ venueAddress: p.venueAddress,
7214
+ venueName: p.venueName,
7215
+ token: p.token,
7216
+ targetPercent: p.allocationPercent
7217
+ }))
7218
+ );
7219
+ setHasInitializedTargets(true);
7220
+ }
7221
+ }, [portfolio, hasInitializedTargets]);
7222
+ const state = useMemo(() => {
7223
+ if (isLoading) return "loading";
7224
+ if (!portfolio || portfolio.positions.length === 0) return "empty";
7225
+ return widgetState;
7226
+ }, [isLoading, portfolio, widgetState]);
7227
+ const targetSum = useMemo(() => targets.reduce((s, t) => s + t.targetPercent, 0), [targets]);
7228
+ const hasChanges = useMemo(() => {
7229
+ if (!portfolio || targets.length === 0) return false;
7230
+ const hasDiff = targets.some((t) => {
7231
+ const pos = portfolio.positions.find(
7232
+ (p) => p.venueType === t.venueType && p.venueAddress === t.venueAddress
7233
+ );
7234
+ return Math.abs(t.targetPercent - (pos?.allocationPercent ?? 0)) > 0.1;
7235
+ });
7236
+ const hasRemovedPositions = portfolio.positions.some(
7237
+ (p) => p.usdValue > 1 && !targets.some((t) => t.venueType === p.venueType && t.venueAddress === p.venueAddress)
7238
+ );
7239
+ return hasDiff || hasRemovedPositions;
7240
+ }, [portfolio, targets]);
7241
+ const clientPreview = useMemo(() => {
7242
+ if (!portfolio || !hasChanges) return null;
7243
+ return computeRebalancePlan(portfolio, targets, {
7244
+ slippagePercent: defaultSlippage,
7245
+ minThresholdUsd: minRebalanceThresholdUsd
7246
+ });
7247
+ }, [portfolio, targets, hasChanges, defaultSlippage, minRebalanceThresholdUsd]);
7248
+ const handleResetToCurrent = useCallback(() => {
7249
+ if (!portfolio) return;
7250
+ setTargets(
7251
+ portfolio.positions.map((p) => ({
7252
+ venueType: p.venueType,
7253
+ venueAddress: p.venueAddress,
7254
+ venueName: p.venueName,
7255
+ token: p.token,
7256
+ targetPercent: p.allocationPercent
7257
+ }))
7258
+ );
7259
+ setPreviewPlan(null);
7260
+ setServerPreview(null);
7261
+ setWidgetState("editing");
7262
+ }, [portfolio]);
7263
+ const handleEqualSplit = useCallback(() => {
7264
+ if (targets.length === 0) return;
7265
+ const equalPercent = 100 / targets.length;
7266
+ setTargets((prev) => prev.map((t) => ({ ...t, targetPercent: parseFloat(equalPercent.toFixed(2)) })));
7267
+ }, [targets.length]);
7268
+ const handleRemoveVenue = useCallback((index) => {
7269
+ setTargets((prev) => prev.filter((_, i) => i !== index));
7270
+ }, []);
7271
+ const handleUpdatePercent = useCallback((index, value) => {
7272
+ setTargets((prev) => prev.map((t, i) => i === index ? { ...t, targetPercent: Math.max(0, Math.min(100, value)) } : t));
7273
+ }, []);
7274
+ const handlePreview = useCallback(async () => {
7275
+ if (!portfolio || !hasChanges || !address) return;
7276
+ setWidgetState("previewing");
7277
+ setErrorMessage(null);
7278
+ try {
7279
+ const response = await fetch("/api/compass/rebalance/preview", {
7280
+ method: "POST",
7281
+ headers: { "Content-Type": "application/json" },
7282
+ body: JSON.stringify({
7283
+ owner: address,
7284
+ chain: CHAIN_ID,
7285
+ targets: targets.map((t) => ({
7286
+ venueType: t.venueType === "vault" ? "VAULT" : t.venueType === "aave" ? "AAVE" : "PENDLE_PT",
7287
+ venueAddress: t.venueAddress,
7288
+ targetPercent: t.targetPercent,
7289
+ token: t.token
7290
+ })),
7291
+ slippage: defaultSlippage
7292
+ })
7293
+ });
7294
+ if (!response.ok) {
7295
+ const err = await response.json();
7296
+ throw new Error(err.error || "Failed to compute rebalance preview");
7297
+ }
7298
+ const data = await response.json();
7299
+ setServerPreview(data);
7300
+ setPreviewPlan(clientPreview);
7301
+ setWidgetState("ready");
7302
+ } catch (err) {
7303
+ setErrorMessage(err instanceof Error ? err.message : "Preview failed");
7304
+ setWidgetState("error");
7305
+ onError?.(err instanceof Error ? err : new Error("Preview failed"));
7306
+ }
7307
+ }, [portfolio, hasChanges, address, CHAIN_ID, targets, defaultSlippage, clientPreview, onError]);
7308
+ const handleExecute = useCallback(async () => {
7309
+ if (!serverPreview?.eip712 || !address) return;
7310
+ setWidgetState("signing");
7311
+ setErrorMessage(null);
7312
+ try {
7313
+ const signature = await signTypedData({
7314
+ domain: serverPreview.domain,
7315
+ types: serverPreview.normalizedTypes,
7316
+ primaryType: "SafeTx",
7317
+ message: serverPreview.message
7318
+ });
7319
+ setWidgetState("executing");
7320
+ const response = await fetch("/api/compass/bundle/execute", {
7321
+ method: "POST",
7322
+ headers: { "Content-Type": "application/json" },
7323
+ body: JSON.stringify({
7324
+ owner: address,
7325
+ chain: CHAIN_ID,
7326
+ eip712: serverPreview.eip712,
7327
+ signature
7328
+ })
7329
+ });
7330
+ if (!response.ok) {
7331
+ const err = await response.json();
7332
+ throw new Error(err.error || "Transaction execution failed");
7333
+ }
7334
+ const data = await response.json();
7335
+ setTxHash(data.txHash);
7336
+ setWidgetState("success");
7337
+ setTimeout(() => refetch(), 5e3);
7338
+ setTimeout(() => refetch(), 15e3);
7339
+ setTimeout(() => refetch(), 3e4);
7340
+ if (previewPlan) {
7341
+ onRebalance?.(previewPlan, data.txHash);
7342
+ }
7343
+ } catch (err) {
7344
+ setErrorMessage(err instanceof Error ? err.message : "Execution failed");
7345
+ setWidgetState("error");
7346
+ onError?.(err instanceof Error ? err : new Error("Execution failed"));
7347
+ }
7348
+ }, [serverPreview, address, signTypedData, CHAIN_ID, refetch, previewPlan, onRebalance, onError]);
7349
+ const earnBalancesQuery = useMemo(() => {
7350
+ const idleBalance = portfolio?.idleBalances.find((b) => b.token === selectedToken);
7351
+ return idleBalance?.balance ?? 0;
7352
+ }, [portfolio, selectedToken]);
7353
+ const needsSwap = selectedMarket ? selectedToken !== selectedMarket.underlyingToken : false;
7354
+ const ensureCorrectChain = useCallback(async () => {
7355
+ const targetChainId = EVM_CHAIN_IDS2[CHAIN_ID];
7356
+ if (!targetChainId) return;
7357
+ if (walletChainId !== void 0 && walletChainId !== targetChainId) {
7358
+ if (!switchChain) {
7359
+ throw new Error(`Please switch your wallet to ${CHAIN_ID} (chain ${targetChainId})`);
7360
+ }
7361
+ await switchChain(targetChainId);
7362
+ }
7363
+ }, [walletChainId, switchChain, CHAIN_ID]);
7364
+ const buildVenueParamsFromMarket = (market) => {
7365
+ switch (market.type) {
7366
+ case "aave":
7367
+ return { venueType: "AAVE", token: market.underlyingToken };
7368
+ case "vaults":
7369
+ return { venueType: "VAULT", token: market.underlyingToken, vaultAddress: market.vaultAddress };
7370
+ case "pendle":
7371
+ return { venueType: "PENDLE_PT", token: market.underlyingToken, marketAddress: market.marketAddress, maxSlippagePercent: 1 };
7372
+ }
7373
+ };
7374
+ const buildVenueFromMarket = (market) => {
7375
+ switch (market.type) {
7376
+ case "aave":
7377
+ return { type: "AAVE", token: market.underlyingToken };
7378
+ case "vaults":
7379
+ return { type: "VAULT", vaultAddress: market.vaultAddress };
7380
+ case "pendle":
7381
+ return { type: "PENDLE_PT", marketAddress: market.marketAddress, token: market.underlyingToken, maxSlippagePercent: 1 };
7382
+ }
7383
+ };
7384
+ const handleDeposit = useCallback(async () => {
7385
+ if (!selectedMarket || !depositAmount || parseFloat(depositAmount) <= 0 || !address || !signTypedData) return;
7386
+ setIsProcessing(true);
7387
+ setDepositError(null);
7388
+ const underlyingToken = selectedMarket.underlyingToken;
7389
+ try {
7390
+ let resultTxHash;
7391
+ if (needsSwap) {
7392
+ setDepositStatus("Getting swap quote...");
7393
+ const quoteResponse = await fetch(
7394
+ `/api/compass/swap/quote?owner=${address}&chain=${CHAIN_ID}&tokenIn=${selectedToken}&tokenOut=${underlyingToken}&amountIn=${depositAmount}`
7395
+ );
7396
+ if (!quoteResponse.ok) {
7397
+ const errorData = await quoteResponse.json();
7398
+ throw new Error(errorData.error || "Failed to get swap quote");
7399
+ }
7400
+ const quoteData = await quoteResponse.json();
7401
+ const estimatedOutput = quoteData.estimatedAmountOut;
7402
+ if (!estimatedOutput || parseFloat(estimatedOutput) <= 0) {
7403
+ throw new Error("Invalid swap quote - no output amount");
7404
+ }
7405
+ const actualDepositAmount = (parseFloat(estimatedOutput) * 0.99999).toString();
7406
+ setDepositStatus("Preparing swap and deposit...");
7407
+ const bundleActions = [
7408
+ {
7409
+ body: {
7410
+ actionType: "V2_SWAP",
7411
+ tokenIn: selectedToken,
7412
+ tokenOut: underlyingToken,
7413
+ amountIn: depositAmount,
7414
+ maxSlippagePercent: 1
7415
+ }
7416
+ },
7417
+ {
7418
+ body: {
7419
+ actionType: "V2_MANAGE",
7420
+ action: "DEPOSIT",
7421
+ venue: buildVenueFromMarket(selectedMarket),
7422
+ amount: actualDepositAmount
7423
+ }
7424
+ }
7425
+ ];
7426
+ const prepareResponse = await fetch("/api/compass/bundle/prepare", {
7427
+ method: "POST",
7428
+ headers: { "Content-Type": "application/json" },
7429
+ body: JSON.stringify({
7430
+ owner: address,
7431
+ chain: CHAIN_ID,
7432
+ actions: bundleActions
7433
+ })
7434
+ });
7435
+ if (!prepareResponse.ok) {
7436
+ const errorData = await prepareResponse.json();
7437
+ throw new Error(errorData.error || "Failed to prepare bundle");
7438
+ }
7439
+ const { eip712, normalizedTypes, domain, message } = await prepareResponse.json();
7440
+ setDepositStatus("Please sign the transaction...");
7441
+ await ensureCorrectChain();
7442
+ const signature = await signTypedData({
7443
+ domain,
7444
+ types: normalizedTypes,
7445
+ primaryType: "SafeTx",
7446
+ message
7447
+ });
7448
+ setDepositStatus("Executing swap and deposit...");
7449
+ const executeResponse = await fetch("/api/compass/bundle/execute", {
7450
+ method: "POST",
7451
+ headers: { "Content-Type": "application/json" },
7452
+ body: JSON.stringify({
7453
+ owner: address,
7454
+ eip712,
7455
+ signature,
7456
+ chain: CHAIN_ID
7457
+ })
7458
+ });
7459
+ if (!executeResponse.ok) {
7460
+ const errorData = await executeResponse.json();
7461
+ throw new Error(errorData.error || "Failed to execute bundle");
7462
+ }
7463
+ const result = await executeResponse.json();
7464
+ resultTxHash = result.txHash;
7465
+ } else {
7466
+ setDepositStatus("Preparing deposit...");
7467
+ const venueParams = buildVenueParamsFromMarket(selectedMarket);
7468
+ const prepareResponse = await fetch("/api/compass/deposit/prepare", {
7469
+ method: "POST",
7470
+ headers: { "Content-Type": "application/json" },
7471
+ body: JSON.stringify({
7472
+ owner: address,
7473
+ chain: CHAIN_ID,
7474
+ ...venueParams,
7475
+ amount: depositAmount
7476
+ })
7477
+ });
7478
+ if (!prepareResponse.ok) {
7479
+ const errData = await prepareResponse.json();
7480
+ throw new Error(errData.error || "Failed to prepare deposit");
7481
+ }
7482
+ const prepareData = await prepareResponse.json();
7483
+ setDepositStatus("Please sign the transaction...");
7484
+ await ensureCorrectChain();
7485
+ const signature = await signTypedData({
7486
+ domain: prepareData.domain,
7487
+ types: prepareData.normalizedTypes,
7488
+ primaryType: "SafeTx",
7489
+ message: prepareData.message
7490
+ });
7491
+ setDepositStatus("Executing deposit...");
7492
+ const executeResponse = await fetch("/api/compass/deposit/execute", {
7493
+ method: "POST",
7494
+ headers: { "Content-Type": "application/json" },
7495
+ body: JSON.stringify({
7496
+ owner: address,
7497
+ chain: CHAIN_ID,
7498
+ eip712: prepareData.eip712,
7499
+ signature
7500
+ })
7501
+ });
7502
+ if (!executeResponse.ok) {
7503
+ const errData = await executeResponse.json();
7504
+ throw new Error(errData.error || "Transaction failed");
7505
+ }
7506
+ const result = await executeResponse.json();
7507
+ resultTxHash = result.txHash;
7508
+ }
7509
+ setDepositStatus("Deposit successful!");
7510
+ setDepositAmount("");
7511
+ queryClient.invalidateQueries({ queryKey: ["earnAccountBalances"] });
7512
+ queryClient.invalidateQueries({ queryKey: ["rebalancing"] });
7513
+ refetch();
7514
+ const delays = [5e3, 15e3, 3e4];
7515
+ for (const delay of delays) {
7516
+ setTimeout(() => {
7517
+ queryClient.invalidateQueries({ queryKey: ["rebalancing"] });
7518
+ refetch();
7519
+ }, delay);
7520
+ }
7521
+ setTimeout(() => setDepositStatus(""), 3e3);
7522
+ } catch (err) {
7523
+ setDepositError(err instanceof Error ? err.message : "Deposit failed");
7524
+ setDepositStatus("");
7525
+ onError?.(err instanceof Error ? err : new Error("Deposit failed"));
7526
+ } finally {
7527
+ setIsProcessing(false);
7528
+ }
7529
+ }, [selectedMarket, depositAmount, address, signTypedData, selectedToken, needsSwap, CHAIN_ID, ensureCorrectChain, queryClient, refetch, onError]);
7530
+ return /* @__PURE__ */ jsxs(
7531
+ "div",
7532
+ {
7533
+ className: "flex flex-col w-full",
7534
+ style: {
7535
+ gap: "calc(var(--compass-spacing-unit) * 0.75)",
7536
+ fontFamily: "var(--compass-font-family)",
7537
+ height,
7538
+ overflow: "hidden"
7539
+ },
7540
+ children: [
7541
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
7542
+ /* @__PURE__ */ jsx(
7543
+ "h2",
7544
+ {
7545
+ className: "font-semibold",
7546
+ style: {
7547
+ color: "var(--compass-color-text)",
7548
+ fontSize: "var(--compass-font-size-subheading, 1.25rem)"
7549
+ },
7550
+ children: title
7551
+ }
7552
+ ),
7553
+ /* @__PURE__ */ jsx(WalletStatus, { compact: true })
7554
+ ] }),
7555
+ showChainSwitcher && !chain && /* @__PURE__ */ jsx(ChainSwitcher, {}),
7556
+ /* @__PURE__ */ jsx(EarnAccountGuard, { children: /* @__PURE__ */ jsxs(
7557
+ "div",
7558
+ {
7559
+ style: {
7560
+ flex: 1,
7561
+ minHeight: 0,
7562
+ overflowY: "auto",
7563
+ scrollbarWidth: "none",
7564
+ display: "flex",
7565
+ flexDirection: "column",
7566
+ gap: "calc(var(--compass-spacing-unit) * 0.75)"
7567
+ },
7568
+ children: [
7569
+ state === "loading" && /* @__PURE__ */ jsxs(
7570
+ "div",
7571
+ {
7572
+ className: "p-8 text-center",
7573
+ style: {
7574
+ backgroundColor: "var(--compass-color-surface)",
7575
+ borderRadius: "var(--compass-border-radius-xl)"
7576
+ },
7402
7577
  children: [
7403
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
7404
- /* @__PURE__ */ jsx(Clock, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }),
7405
- /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "Duration" })
7406
- ] }),
7407
7578
  /* @__PURE__ */ jsx(
7408
- "p",
7579
+ Loader2,
7409
7580
  {
7410
- className: "font-semibold",
7411
- style: { color: "var(--compass-color-text)" },
7412
- children: duration
7581
+ size: 24,
7582
+ className: "animate-spin mx-auto mb-3",
7583
+ style: { color: "var(--compass-color-primary)" }
7413
7584
  }
7414
- )
7585
+ ),
7586
+ /* @__PURE__ */ jsx("p", { className: "text-sm", style: { color: "var(--compass-color-text-secondary)" }, children: "Loading portfolio..." })
7415
7587
  ]
7416
7588
  }
7417
7589
  ),
7418
- isPendle && expiryDate && /* @__PURE__ */ jsxs(
7590
+ state === "empty" && /* @__PURE__ */ jsxs(
7419
7591
  "div",
7420
7592
  {
7421
- className: "p-3 rounded-lg",
7422
- style: { backgroundColor: "var(--compass-color-surface)" },
7593
+ className: "p-8 text-center",
7594
+ style: {
7595
+ backgroundColor: "var(--compass-color-surface)",
7596
+ borderRadius: "var(--compass-border-radius-xl)"
7597
+ },
7423
7598
  children: [
7424
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
7425
- /* @__PURE__ */ jsx(Calendar, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }),
7426
- /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "Expiry (UTC)" })
7427
- ] }),
7428
7599
  /* @__PURE__ */ jsx(
7429
7600
  "p",
7430
7601
  {
7431
- className: "font-semibold",
7602
+ className: "text-lg font-semibold mb-2",
7432
7603
  style: { color: "var(--compass-color-text)" },
7433
- children: expiryDate
7604
+ children: "No positions found"
7434
7605
  }
7435
- )
7606
+ ),
7607
+ /* @__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." })
7436
7608
  ]
7437
7609
  }
7438
7610
  ),
7439
- position.pnl && /* @__PURE__ */ jsxs(
7440
- "div",
7441
- {
7442
- className: "p-3 rounded-lg",
7443
- style: { backgroundColor: "var(--compass-color-surface)" },
7444
- children: [
7445
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
7446
- isPnlPositive ? /* @__PURE__ */ jsx(TrendingUp, { size: 14, style: { color: "var(--compass-color-success)" } }) : /* @__PURE__ */ jsx(TrendingDown, { size: 14, style: { color: "var(--compass-color-error)" } }),
7447
- /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "Total P&L" })
7448
- ] }),
7611
+ state !== "loading" && state !== "empty" && portfolio && /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "calc(var(--compass-spacing-unit) * 0.75)" }, children: [
7612
+ /* @__PURE__ */ jsx(
7613
+ PortfolioBalanceCard,
7614
+ {
7615
+ totalUsd: portfolio.totalUsd,
7616
+ totalIdleUsd: portfolio.totalIdleUsd,
7617
+ idleBalances: portfolio.idleBalances,
7618
+ isPositionsExpanded,
7619
+ onTogglePositions: () => setIsPositionsExpanded((prev) => !prev),
7620
+ positionCount: portfolio.positions.length,
7621
+ showTopUp,
7622
+ onTopUp: () => earnBalanceRef.current?.openTransferModal()
7623
+ }
7624
+ ),
7625
+ isPositionsExpanded && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
7626
+ AllocationEditor,
7627
+ {
7628
+ portfolio,
7629
+ targets,
7630
+ targetSum,
7631
+ hasChanges,
7632
+ highlightedVenueAddress: void 0,
7633
+ onUpdatePercent: handleUpdatePercent,
7634
+ onRemoveVenue: handleRemoveVenue,
7635
+ onResetToCurrent: handleResetToCurrent,
7636
+ onEqualSplit: handleEqualSplit
7637
+ }
7638
+ ) }),
7639
+ !isPositionsExpanded && /* @__PURE__ */ jsxs(Fragment, { children: [
7640
+ /* @__PURE__ */ jsx(
7641
+ MarketSelector,
7642
+ {
7643
+ markets: earnAccountMarkets,
7644
+ selectedMarket,
7645
+ onMarketSelect: (market) => {
7646
+ setSelectedMarket(market);
7647
+ setDepositError(null);
7648
+ },
7649
+ marketTab,
7650
+ onMarketTabChange: (tab) => {
7651
+ setMarketTab(tab);
7652
+ setSelectedMarket(null);
7653
+ setDepositError(null);
7654
+ },
7655
+ isLoading: isMarketsLoading,
7656
+ allowedMarketIds: marketTab === "variable" ? allowedVariableMarketIds : allowedFixedMarketIds
7657
+ }
7658
+ ),
7659
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", style: { gap: "calc(var(--compass-spacing-unit) * 0.75)" }, children: [
7449
7660
  /* @__PURE__ */ jsxs(
7450
- "p",
7661
+ "div",
7451
7662
  {
7452
- className: "font-semibold",
7663
+ className: "flex items-center flex-wrap",
7453
7664
  style: {
7454
- color: isPnlPositive ? "var(--compass-color-success)" : "var(--compass-color-error)"
7665
+ backgroundColor: "var(--compass-color-surface)",
7666
+ border: "1px solid var(--compass-color-border)",
7667
+ borderRadius: "var(--compass-border-radius-lg)",
7668
+ padding: "calc(var(--compass-spacing-unit) * 0.75)",
7669
+ gap: "calc(var(--compass-spacing-unit) * 0.5)"
7455
7670
  },
7456
7671
  children: [
7457
- isPnlPositive ? "+" : "",
7458
- formatUSD(position.pnl.totalPnl)
7459
- ]
7460
- }
7461
- )
7462
- ]
7463
- }
7464
- )
7465
- ] }),
7466
- (position.deposits.length > 0 || position.withdrawals.length > 0) && /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
7467
- /* @__PURE__ */ jsx(
7468
- "h3",
7469
- {
7470
- className: "font-semibold mb-3",
7471
- style: { color: "var(--compass-color-text)" },
7472
- children: "Transaction History"
7473
- }
7474
- ),
7475
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
7476
- position.deposits.map((tx, i) => {
7477
- const date = formatDate(tx.timestamp);
7478
- return /* @__PURE__ */ jsxs(
7479
- "div",
7480
- {
7481
- className: "flex items-center justify-between p-3 rounded-lg",
7482
- style: { backgroundColor: "var(--compass-color-surface)" },
7483
- children: [
7484
- /* @__PURE__ */ jsxs("div", { children: [
7485
- /* @__PURE__ */ jsxs(
7486
- "p",
7672
+ /* @__PURE__ */ jsx(
7673
+ "input",
7487
7674
  {
7488
- className: "font-medium",
7489
- style: { color: "var(--compass-color-success)" },
7490
- children: [
7491
- "+",
7492
- formatAmount(tx.amount),
7493
- " ",
7494
- position.assetSymbol
7495
- ]
7675
+ type: "number",
7676
+ value: depositAmount,
7677
+ onChange: (e) => {
7678
+ setDepositAmount(e.target.value);
7679
+ setDepositError(null);
7680
+ },
7681
+ placeholder: "0.00",
7682
+ disabled: isProcessing,
7683
+ className: "flex-1 font-medium bg-transparent outline-none",
7684
+ style: { color: "var(--compass-color-text)", fontSize: "1.25rem", minWidth: 0 }
7496
7685
  }
7497
7686
  ),
7687
+ /* @__PURE__ */ jsxs("div", { className: "relative", style: { flexShrink: 0 }, children: [
7688
+ /* @__PURE__ */ jsxs(
7689
+ "button",
7690
+ {
7691
+ onClick: () => setIsTokenDropdownOpen(!isTokenDropdownOpen),
7692
+ disabled: isProcessing,
7693
+ className: "flex items-center font-medium",
7694
+ style: {
7695
+ backgroundColor: "var(--compass-color-surface-elevated, var(--compass-color-background))",
7696
+ border: "1px solid var(--compass-color-border)",
7697
+ color: "var(--compass-color-text)",
7698
+ borderRadius: "var(--compass-border-radius-md)",
7699
+ padding: "calc(var(--compass-spacing-unit) * 0.5) calc(var(--compass-spacing-unit) * 0.75)",
7700
+ gap: "calc(var(--compass-spacing-unit) * 0.25)",
7701
+ fontSize: "0.875rem"
7702
+ },
7703
+ children: [
7704
+ selectedToken,
7705
+ /* @__PURE__ */ jsx(ChevronDown, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } })
7706
+ ]
7707
+ }
7708
+ ),
7709
+ isTokenDropdownOpen && /* @__PURE__ */ jsx(
7710
+ "div",
7711
+ {
7712
+ className: "absolute right-0 mt-1 z-10",
7713
+ style: {
7714
+ backgroundColor: "var(--compass-color-surface)",
7715
+ border: "1px solid var(--compass-color-border)",
7716
+ borderRadius: "var(--compass-border-radius-lg)",
7717
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
7718
+ minWidth: "100px"
7719
+ },
7720
+ children: SUPPORTED_TOKENS2.map((token) => /* @__PURE__ */ jsx(
7721
+ "button",
7722
+ {
7723
+ onClick: () => {
7724
+ setSelectedToken(token);
7725
+ setIsTokenDropdownOpen(false);
7726
+ setDepositError(null);
7727
+ },
7728
+ className: "w-full text-left font-medium transition-colors",
7729
+ style: {
7730
+ padding: "calc(var(--compass-spacing-unit) * 0.5) calc(var(--compass-spacing-unit) * 0.75)",
7731
+ color: token === selectedToken ? "var(--compass-color-primary)" : "var(--compass-color-text)",
7732
+ backgroundColor: token === selectedToken ? "var(--compass-color-primary-muted, rgba(99, 102, 241, 0.1))" : "transparent",
7733
+ fontSize: "0.875rem"
7734
+ },
7735
+ children: token
7736
+ },
7737
+ token
7738
+ ))
7739
+ }
7740
+ )
7741
+ ] }),
7498
7742
  /* @__PURE__ */ jsx(
7499
- "p",
7743
+ "button",
7500
7744
  {
7501
- className: "text-xs",
7502
- style: { color: "var(--compass-color-text-tertiary)" },
7503
- children: date ? `Deposit - ${date}` : "Deposit"
7745
+ onClick: () => setDepositAmount(earnBalancesQuery.toString()),
7746
+ disabled: isProcessing,
7747
+ className: "text-xs font-medium uppercase tracking-wide",
7748
+ style: {
7749
+ backgroundColor: "var(--compass-color-surface-elevated, var(--compass-color-background))",
7750
+ border: "1px solid var(--compass-color-border)",
7751
+ color: "var(--compass-color-text-secondary)",
7752
+ borderRadius: "var(--compass-border-radius-md)",
7753
+ padding: "calc(var(--compass-spacing-unit) * 0.5) calc(var(--compass-spacing-unit) * 0.75)",
7754
+ flexShrink: 0
7755
+ },
7756
+ children: "MAX"
7504
7757
  }
7505
7758
  )
7506
- ] }),
7507
- tx.txHash && /* @__PURE__ */ jsx(
7508
- "a",
7509
- {
7510
- href: `https://etherscan.io/tx/${tx.txHash}`,
7511
- target: "_blank",
7512
- rel: "noopener noreferrer",
7513
- className: "p-2 rounded-lg hover:opacity-80 transition-opacity",
7514
- style: { backgroundColor: "var(--compass-color-background)" },
7515
- children: /* @__PURE__ */ jsx(ExternalLink, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } })
7516
- }
7517
- )
7518
- ]
7519
- },
7520
- `deposit-${i}`
7521
- );
7522
- }),
7523
- position.withdrawals.map((tx, i) => {
7524
- const date = formatDate(tx.timestamp);
7525
- return /* @__PURE__ */ jsxs(
7759
+ ]
7760
+ }
7761
+ ),
7762
+ needsSwap && /* @__PURE__ */ jsxs(
7763
+ "div",
7764
+ {
7765
+ className: "flex items-center text-sm",
7766
+ style: {
7767
+ backgroundColor: "var(--compass-color-primary-muted, rgba(99, 102, 241, 0.1))",
7768
+ color: "var(--compass-color-primary)",
7769
+ borderRadius: "var(--compass-border-radius-lg)",
7770
+ padding: "calc(var(--compass-spacing-unit) * 0.5) calc(var(--compass-spacing-unit) * 0.75)",
7771
+ gap: "calc(var(--compass-spacing-unit) * 0.5)"
7772
+ },
7773
+ children: [
7774
+ /* @__PURE__ */ jsx(ArrowRight, { size: 14 }),
7775
+ /* @__PURE__ */ jsxs("span", { children: [
7776
+ "Swaps ",
7777
+ selectedToken,
7778
+ " to ",
7779
+ selectedMarket?.underlyingToken,
7780
+ ", then deposits"
7781
+ ] })
7782
+ ]
7783
+ }
7784
+ ),
7785
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between", style: { padding: "0 calc(var(--compass-spacing-unit) * 0.25)" }, children: [
7786
+ /* @__PURE__ */ jsxs("span", { className: "text-sm", style: { color: "var(--compass-color-text-tertiary)" }, children: [
7787
+ "Available ",
7788
+ selectedToken
7789
+ ] }),
7790
+ /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", style: { color: "var(--compass-color-text-secondary)" }, children: [
7791
+ formatAmount3(earnBalancesQuery),
7792
+ " ",
7793
+ selectedToken
7794
+ ] })
7795
+ ] })
7796
+ ] }),
7797
+ depositError && /* @__PURE__ */ jsx(
7526
7798
  "div",
7527
7799
  {
7528
- className: "flex items-center justify-between p-3 rounded-lg",
7529
- style: { backgroundColor: "var(--compass-color-surface)" },
7800
+ className: "text-sm",
7801
+ style: {
7802
+ backgroundColor: "var(--compass-color-error-muted)",
7803
+ color: "var(--compass-color-error)",
7804
+ borderRadius: "var(--compass-border-radius-lg)",
7805
+ padding: "calc(var(--compass-spacing-unit) * 0.75)"
7806
+ },
7807
+ children: depositError
7808
+ }
7809
+ ),
7810
+ /* @__PURE__ */ jsx(
7811
+ "button",
7812
+ {
7813
+ onClick: !isConnected && login ? login : handleDeposit,
7814
+ disabled: isConnected && (isProcessing || !selectedMarket || !depositAmount || parseFloat(depositAmount) <= 0 || parseFloat(depositAmount) > earnBalancesQuery),
7815
+ className: "w-full font-semibold transition-all disabled:opacity-50 disabled:cursor-not-allowed",
7816
+ style: {
7817
+ backgroundColor: "var(--compass-color-primary)",
7818
+ color: "var(--compass-color-primary-text, white)",
7819
+ borderRadius: "var(--compass-border-radius-lg)",
7820
+ padding: "calc(var(--compass-spacing-unit) * 0.75)",
7821
+ fontSize: "0.875rem",
7822
+ transition: "var(--compass-transition-normal)"
7823
+ },
7824
+ children: !isConnected ? "Connect Wallet" : isProcessing ? /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-center", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
7825
+ /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }),
7826
+ "Processing..."
7827
+ ] }) : !selectedMarket ? "Select a market" : needsSwap ? "Swap & Deposit" : "Deposit"
7828
+ }
7829
+ ),
7830
+ depositStatus && !depositError && /* @__PURE__ */ jsx(
7831
+ "div",
7832
+ {
7833
+ className: "text-sm text-center",
7834
+ style: {
7835
+ backgroundColor: depositStatus.includes("successful") ? "var(--compass-color-success-muted)" : "var(--compass-color-primary-muted, rgba(99, 102, 241, 0.1))",
7836
+ color: depositStatus.includes("successful") ? "var(--compass-color-success)" : "var(--compass-color-primary)",
7837
+ borderRadius: "var(--compass-border-radius-lg)",
7838
+ padding: "calc(var(--compass-spacing-unit) * 0.75)"
7839
+ },
7840
+ children: depositStatus
7841
+ }
7842
+ )
7843
+ ] }),
7844
+ clientPreview && clientPreview.actions.length > 0 && state === "editing" && /* @__PURE__ */ jsxs(
7845
+ "div",
7846
+ {
7847
+ className: "p-4",
7848
+ style: {
7849
+ backgroundColor: "var(--compass-color-surface)",
7850
+ borderRadius: "var(--compass-border-radius-xl)",
7851
+ border: "1px solid var(--compass-color-border)"
7852
+ },
7853
+ children: [
7854
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold mb-3", style: { color: "var(--compass-color-text)" }, children: "Preview" }),
7855
+ /* @__PURE__ */ jsx(ActionList, { actions: clientPreview.actions }),
7856
+ clientPreview.warnings.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-3 flex flex-col gap-1", children: clientPreview.warnings.map((w, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-xs", style: { color: "var(--compass-color-warning, #eab308)" }, children: [
7857
+ /* @__PURE__ */ jsx(AlertTriangle, { size: 12, className: "flex-shrink-0 mt-0.5" }),
7858
+ /* @__PURE__ */ jsx("span", { children: w })
7859
+ ] }, i)) }),
7860
+ /* @__PURE__ */ jsxs("p", { className: "text-xs mt-2", style: { color: "var(--compass-color-text-tertiary)" }, children: [
7861
+ "Estimated gas savings: ",
7862
+ clientPreview.estimatedGasSavings
7863
+ ] })
7864
+ ]
7865
+ }
7866
+ ),
7867
+ state === "ready" && serverPreview && /* @__PURE__ */ jsxs(
7868
+ "div",
7869
+ {
7870
+ className: "p-4",
7871
+ style: {
7872
+ backgroundColor: "var(--compass-color-surface)",
7873
+ borderRadius: "var(--compass-border-radius-xl)",
7874
+ border: "1px solid var(--compass-color-primary)"
7875
+ },
7876
+ children: [
7877
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold mb-3", style: { color: "var(--compass-color-text)" }, children: "Ready to Execute" }),
7878
+ /* @__PURE__ */ jsx(ActionList, { actions: serverPreview.actions || [] }),
7879
+ (serverPreview.warnings || []).length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-3 flex flex-col gap-1", children: serverPreview.warnings.map((w, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-xs", style: { color: "var(--compass-color-warning, #eab308)" }, children: [
7880
+ /* @__PURE__ */ jsx(AlertTriangle, { size: 12, className: "flex-shrink-0 mt-0.5" }),
7881
+ /* @__PURE__ */ jsx("span", { children: w })
7882
+ ] }, i)) }),
7883
+ /* @__PURE__ */ jsxs("p", { className: "text-xs mt-2", style: { color: "var(--compass-color-text-tertiary)" }, children: [
7884
+ serverPreview.actionsCount,
7885
+ " actions in 1 atomic transaction"
7886
+ ] })
7887
+ ]
7888
+ }
7889
+ ),
7890
+ state === "success" && txHash && /* @__PURE__ */ jsxs(
7891
+ "div",
7892
+ {
7893
+ className: "p-4 text-center",
7894
+ style: {
7895
+ backgroundColor: "var(--compass-color-success-muted, rgba(34, 197, 94, 0.1))",
7896
+ borderRadius: "var(--compass-border-radius-xl)"
7897
+ },
7898
+ children: [
7899
+ /* @__PURE__ */ jsx(Check, { size: 24, className: "mx-auto mb-2", style: { color: "var(--compass-color-success)" } }),
7900
+ /* @__PURE__ */ jsx("p", { className: "font-semibold mb-1", style: { color: "var(--compass-color-success)" }, children: "Rebalance Complete" }),
7901
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-mono break-all", style: { color: "var(--compass-color-text-secondary)" }, children: txHash })
7902
+ ]
7903
+ }
7904
+ ),
7905
+ state === "error" && errorMessage && /* @__PURE__ */ jsxs(
7906
+ "div",
7907
+ {
7908
+ className: "p-4",
7909
+ style: {
7910
+ backgroundColor: "var(--compass-color-error-muted, rgba(239, 68, 68, 0.1))",
7911
+ borderRadius: "var(--compass-border-radius-xl)"
7912
+ },
7913
+ children: [
7914
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1", style: { color: "var(--compass-color-error)" }, children: errorMessage }),
7915
+ /* @__PURE__ */ jsx(
7916
+ "button",
7917
+ {
7918
+ onClick: () => {
7919
+ setWidgetState("editing");
7920
+ setErrorMessage(null);
7921
+ },
7922
+ className: "text-xs underline",
7923
+ style: { color: "var(--compass-color-error)" },
7924
+ children: "Try again"
7925
+ }
7926
+ )
7927
+ ]
7928
+ }
7929
+ ),
7930
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
7931
+ state === "editing" && hasChanges && /* @__PURE__ */ jsx(
7932
+ "button",
7933
+ {
7934
+ onClick: handlePreview,
7935
+ disabled: !address,
7936
+ className: "w-full py-3 rounded-xl font-semibold text-base transition-all disabled:opacity-50 disabled:cursor-not-allowed",
7937
+ style: {
7938
+ backgroundColor: "var(--compass-color-primary)",
7939
+ color: "white"
7940
+ },
7941
+ children: "Preview Rebalance"
7942
+ }
7943
+ ),
7944
+ state === "previewing" && /* @__PURE__ */ jsxs(
7945
+ "button",
7946
+ {
7947
+ disabled: true,
7948
+ className: "w-full py-3 rounded-xl font-semibold text-base flex items-center justify-center gap-2",
7949
+ style: {
7950
+ backgroundColor: "var(--compass-color-surface-hover, var(--compass-color-surface))",
7951
+ color: "var(--compass-color-text-secondary)"
7952
+ },
7953
+ children: [
7954
+ /* @__PURE__ */ jsx(Loader2, { size: 18, className: "animate-spin" }),
7955
+ "Computing exact amounts..."
7956
+ ]
7957
+ }
7958
+ ),
7959
+ state === "ready" && /* @__PURE__ */ jsx(
7960
+ "button",
7961
+ {
7962
+ onClick: handleExecute,
7963
+ className: "w-full py-3 rounded-xl font-semibold text-base transition-all",
7964
+ style: {
7965
+ backgroundColor: "var(--compass-color-primary)",
7966
+ color: "white"
7967
+ },
7968
+ children: "Confirm & Sign"
7969
+ }
7970
+ ),
7971
+ state === "signing" && /* @__PURE__ */ jsxs(
7972
+ "button",
7973
+ {
7974
+ disabled: true,
7975
+ className: "w-full py-3 rounded-xl font-semibold text-base flex items-center justify-center gap-2",
7976
+ style: {
7977
+ backgroundColor: "var(--compass-color-surface-hover, var(--compass-color-surface))",
7978
+ color: "var(--compass-color-text-secondary)"
7979
+ },
7980
+ children: [
7981
+ /* @__PURE__ */ jsx(Loader2, { size: 18, className: "animate-spin" }),
7982
+ "Waiting for signature..."
7983
+ ]
7984
+ }
7985
+ ),
7986
+ state === "executing" && /* @__PURE__ */ jsxs(
7987
+ "button",
7988
+ {
7989
+ disabled: true,
7990
+ className: "w-full py-3 rounded-xl font-semibold text-base flex items-center justify-center gap-2",
7991
+ style: {
7992
+ backgroundColor: "var(--compass-color-surface-hover, var(--compass-color-surface))",
7993
+ color: "var(--compass-color-text-secondary)"
7994
+ },
7530
7995
  children: [
7531
- /* @__PURE__ */ jsxs("div", { children: [
7532
- /* @__PURE__ */ jsxs(
7533
- "p",
7534
- {
7535
- className: "font-medium",
7536
- style: { color: "var(--compass-color-error)" },
7537
- children: [
7538
- "-",
7539
- formatAmount(tx.amount),
7540
- " ",
7541
- position.assetSymbol
7542
- ]
7543
- }
7544
- ),
7545
- /* @__PURE__ */ jsx(
7546
- "p",
7547
- {
7548
- className: "text-xs",
7549
- style: { color: "var(--compass-color-text-tertiary)" },
7550
- children: date ? `Withdrawal - ${date}` : "Withdrawal"
7551
- }
7552
- )
7553
- ] }),
7554
- tx.txHash && /* @__PURE__ */ jsx(
7555
- "a",
7556
- {
7557
- href: `https://etherscan.io/tx/${tx.txHash}`,
7558
- target: "_blank",
7559
- rel: "noopener noreferrer",
7560
- className: "p-2 rounded-lg hover:opacity-80 transition-opacity",
7561
- style: { backgroundColor: "var(--compass-color-background)" },
7562
- children: /* @__PURE__ */ jsx(ExternalLink, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } })
7563
- }
7564
- )
7996
+ /* @__PURE__ */ jsx(Loader2, { size: 18, className: "animate-spin" }),
7997
+ "Executing on-chain..."
7565
7998
  ]
7999
+ }
8000
+ ),
8001
+ state === "success" && /* @__PURE__ */ jsx(
8002
+ "button",
8003
+ {
8004
+ onClick: () => {
8005
+ setWidgetState("editing");
8006
+ setTxHash(null);
8007
+ setServerPreview(null);
8008
+ setPreviewPlan(null);
8009
+ },
8010
+ className: "w-full py-3 rounded-xl font-semibold text-base transition-all",
8011
+ style: {
8012
+ backgroundColor: "var(--compass-color-primary)",
8013
+ color: "white"
8014
+ },
8015
+ children: "Rebalance Again"
8016
+ }
8017
+ ),
8018
+ (state === "ready" || state === "previewing") && /* @__PURE__ */ jsx(
8019
+ "button",
8020
+ {
8021
+ onClick: () => {
8022
+ setWidgetState("editing");
8023
+ setServerPreview(null);
8024
+ },
8025
+ className: "w-full py-2 rounded-xl text-sm transition-all",
8026
+ style: {
8027
+ backgroundColor: "transparent",
8028
+ color: "var(--compass-color-text-secondary)"
8029
+ },
8030
+ children: "Back to editing"
8031
+ }
8032
+ )
8033
+ ] }),
8034
+ /* @__PURE__ */ jsxs(
8035
+ "div",
8036
+ {
8037
+ className: "flex items-center justify-center",
8038
+ style: {
8039
+ gap: "calc(var(--compass-spacing-unit) * 1)",
8040
+ paddingTop: "calc(var(--compass-spacing-unit) * 0.5)"
7566
8041
  },
7567
- `withdraw-${i}`
7568
- );
7569
- })
8042
+ children: [
8043
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
8044
+ /* @__PURE__ */ jsx(Shield, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }),
8045
+ /* @__PURE__ */ jsx("span", { className: "text-sm", style: { color: "var(--compass-color-text-tertiary)" }, children: "Non-custodial" })
8046
+ ] }),
8047
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-border)" }, children: "|" }),
8048
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center", style: { gap: "calc(var(--compass-spacing-unit) * 0.5)" }, children: [
8049
+ /* @__PURE__ */ jsx(CheckCircle, { size: 14, style: { color: "var(--compass-color-text-tertiary)" } }),
8050
+ /* @__PURE__ */ jsx("span", { className: "text-sm", style: { color: "var(--compass-color-text-tertiary)" }, children: "Audited protocols" })
8051
+ ] })
8052
+ ]
8053
+ }
8054
+ )
7570
8055
  ] })
7571
- ] })
7572
- ]
7573
- }
7574
- )
8056
+ ]
8057
+ }
8058
+ ) }),
8059
+ /* @__PURE__ */ jsx(EarnAccountBalance, { ref: earnBalanceRef, compact: true, hideVisual: true, onTransferComplete: () => refetch() })
8060
+ ]
7575
8061
  }
7576
8062
  );
7577
8063
  }
7578
- function EarnPositionsList({
7579
- showApy = true,
7580
- showPnL = true,
7581
- defaultCollapsed = [],
7582
- onPositionSelect
7583
- }) {
7584
- const { isConnected } = useEmbeddableWallet();
7585
- const { venuePositions, totalPositions, totalBalanceUsd, isLoading, isError } = usePositionsData();
7586
- const [selectedPosition, setSelectedPosition] = useState(null);
7587
- const handlePositionClick = (position) => {
7588
- setSelectedPosition(position);
7589
- onPositionSelect?.(position);
8064
+ function ActionList({ actions }) {
8065
+ const actionIcons = {
8066
+ withdraw: "Withdraw",
8067
+ swap: "Swap",
8068
+ deposit: "Deposit"
7590
8069
  };
7591
- const renderContent = () => {
7592
- if (!isConnected) {
7593
- return /* @__PURE__ */ jsxs(
7594
- "div",
7595
- {
7596
- className: "flex flex-col items-center justify-center py-12 text-center",
7597
- style: { color: "var(--compass-color-text-secondary)" },
7598
- children: [
7599
- /* @__PURE__ */ jsx(Inbox, { size: 48, className: "mb-4 opacity-50" }),
7600
- /* @__PURE__ */ jsx("p", { className: "font-medium", children: "Connect your wallet" }),
7601
- /* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: "to view your earn positions" })
7602
- ]
7603
- }
7604
- );
7605
- }
7606
- if (isLoading) {
7607
- return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx(
7608
- Loader2,
7609
- {
7610
- size: 32,
7611
- className: "animate-spin",
7612
- style: { color: "var(--compass-color-primary)" }
7613
- }
7614
- ) });
7615
- }
7616
- if (isError) {
7617
- return /* @__PURE__ */ jsxs(
7618
- "div",
7619
- {
7620
- className: "flex flex-col items-center justify-center py-12 text-center",
7621
- style: { color: "var(--compass-color-error)" },
7622
- children: [
7623
- /* @__PURE__ */ jsx("p", { className: "font-medium", children: "Failed to load positions" }),
7624
- /* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: "Please try again later" })
7625
- ]
7626
- }
7627
- );
7628
- }
7629
- if (totalPositions === 0) {
7630
- return /* @__PURE__ */ jsxs(
7631
- "div",
7632
- {
7633
- className: "flex flex-col items-center justify-center py-12 text-center",
7634
- style: { color: "var(--compass-color-text-secondary)" },
7635
- children: [
7636
- /* @__PURE__ */ jsx(Inbox, { size: 48, className: "mb-4 opacity-50" }),
7637
- /* @__PURE__ */ jsx("p", { className: "font-medium", children: "No positions yet" }),
7638
- /* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: "Deposit to a vault or market to get started" })
7639
- ]
7640
- }
7641
- );
7642
- }
7643
- return /* @__PURE__ */ jsxs(Fragment, { children: [
7644
- /* @__PURE__ */ jsxs(
7645
- "div",
7646
- {
7647
- className: "flex items-center justify-between p-4 rounded-xl",
7648
- style: { backgroundColor: "var(--compass-color-surface)" },
7649
- children: [
7650
- /* @__PURE__ */ jsxs("div", { children: [
7651
- /* @__PURE__ */ jsx(
7652
- "h2",
7653
- {
7654
- className: "font-bold text-lg",
7655
- style: { color: "var(--compass-color-text)" },
7656
- children: "Your Positions"
7657
- }
7658
- ),
7659
- /* @__PURE__ */ jsxs(
7660
- "p",
7661
- {
7662
- className: "text-sm",
7663
- style: { color: "var(--compass-color-text-secondary)" },
7664
- children: [
7665
- totalPositions,
7666
- " position",
7667
- totalPositions !== 1 ? "s" : "",
7668
- " across ",
7669
- venuePositions.length,
7670
- " venue",
7671
- venuePositions.length !== 1 ? "s" : ""
7672
- ]
7673
- }
7674
- )
7675
- ] }),
7676
- /* @__PURE__ */ jsxs("div", { className: "text-right", children: [
7677
- /* @__PURE__ */ jsx(
7678
- "p",
7679
- {
7680
- className: "text-sm",
7681
- style: { color: "var(--compass-color-text-secondary)" },
7682
- children: "Total Value"
7683
- }
7684
- ),
7685
- /* @__PURE__ */ jsx(
7686
- "p",
7687
- {
7688
- className: "font-bold text-xl",
7689
- style: { color: "var(--compass-color-text)" },
7690
- children: formatUSD(totalBalanceUsd)
7691
- }
7692
- )
7693
- ] })
7694
- ]
7695
- }
7696
- ),
7697
- /* @__PURE__ */ jsx("div", { className: "space-y-4", children: venuePositions.map((vp) => /* @__PURE__ */ jsx(
7698
- VenueSection,
7699
- {
7700
- venuePositions: vp,
7701
- defaultCollapsed: defaultCollapsed.includes(vp.venue),
7702
- showApy,
7703
- showPnL,
7704
- onPositionSelect: handlePositionClick
7705
- },
7706
- vp.venue
7707
- )) })
7708
- ] });
8070
+ const actionColors = {
8071
+ withdraw: "var(--compass-color-error)",
8072
+ swap: "var(--compass-color-warning, #eab308)",
8073
+ deposit: "var(--compass-color-success)"
7709
8074
  };
7710
- return /* @__PURE__ */ jsxs(Fragment, { children: [
7711
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
7712
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
7713
- /* @__PURE__ */ jsx(ChainSwitcher, {}),
8075
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", children: actions.map((action, i) => /* @__PURE__ */ jsxs(
8076
+ "div",
8077
+ {
8078
+ className: "flex items-center justify-between p-2 rounded-lg text-sm",
8079
+ style: { backgroundColor: "var(--compass-color-background)" },
8080
+ children: [
7714
8081
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7715
- /* @__PURE__ */ jsx(EarnAccountBalance, { compact: true }),
7716
- /* @__PURE__ */ jsx(WalletStatus, { compact: true })
7717
- ] })
7718
- ] }),
7719
- renderContent()
7720
- ] }),
7721
- selectedPosition && /* @__PURE__ */ jsx(
7722
- PositionDetailModal,
7723
- {
7724
- position: selectedPosition,
7725
- onClose: () => setSelectedPosition(null)
7726
- }
7727
- )
7728
- ] });
8082
+ /* @__PURE__ */ jsx(
8083
+ "span",
8084
+ {
8085
+ className: "text-xs font-medium px-1.5 py-0.5 rounded",
8086
+ style: {
8087
+ backgroundColor: actionColors[action.type] + "20",
8088
+ color: actionColors[action.type]
8089
+ },
8090
+ children: actionIcons[action.type] || action.type
8091
+ }
8092
+ ),
8093
+ /* @__PURE__ */ jsxs("span", { style: { color: "var(--compass-color-text)" }, children: [
8094
+ action.token,
8095
+ action.tokenOut && ` \u2192 ${action.tokenOut}`
8096
+ ] })
8097
+ ] }),
8098
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", style: { color: "var(--compass-color-text-secondary)" }, children: formatUSD(action.usdValue || 0) })
8099
+ ]
8100
+ },
8101
+ i
8102
+ )) });
7729
8103
  }
7730
8104
 
7731
8105
  // src/components/CompassEarnWidget/presets.ts
7732
8106
  var allTabs = [
7733
- { id: "vaults", label: "Vaults", enabled: true },
7734
- { id: "aave", label: "Aave", enabled: true },
7735
- { id: "pendle", label: "Pendle", enabled: true },
7736
8107
  { id: "swap", label: "Swap", enabled: true },
8108
+ { id: "rebalance", label: "Rebalance", enabled: true },
7737
8109
  // TODO: Positions tab temporarily disabled - needs more work on API response handling
7738
8110
  { id: "positions", label: "Positions", enabled: false }
7739
8111
  ];
@@ -7741,59 +8113,39 @@ function getTabsForPreset(preset) {
7741
8113
  switch (preset) {
7742
8114
  case "full":
7743
8115
  return allTabs;
7744
- case "earn-only":
7745
- return allTabs.map((tab) => ({
7746
- ...tab,
7747
- enabled: tab.id !== "swap"
7748
- }));
7749
8116
  case "swap-only":
7750
8117
  return allTabs.map((tab) => ({
7751
8118
  ...tab,
7752
8119
  enabled: tab.id === "swap"
7753
8120
  }));
7754
- case "vaults-only":
7755
- return allTabs.map((tab) => ({
7756
- ...tab,
7757
- enabled: tab.id === "vaults"
7758
- }));
7759
8121
  default:
7760
8122
  return allTabs;
7761
8123
  }
7762
8124
  }
7763
8125
  function getDefaultTab(tabs) {
7764
8126
  const enabledTab = tabs.find((t) => t.enabled);
7765
- return enabledTab?.id || "vaults";
8127
+ return enabledTab?.id || "swap";
7766
8128
  }
7767
8129
  function CompassEarnWidget({
7768
8130
  preset = "full",
7769
- enableVaults,
7770
- enableAave,
7771
- enablePendle,
7772
8131
  enableSwap,
7773
8132
  enablePositions,
8133
+ enableRebalance,
7774
8134
  defaultTab,
7775
8135
  showHeader = true,
7776
- showApy = true,
7777
- showTvl = true,
7778
- showExpiry = true,
7779
- showUserPosition = true,
7780
8136
  showPnL = true,
7781
- showSearch = true,
7782
- showSort = true,
7783
8137
  onTabChange
7784
8138
  }) {
7785
8139
  const tabs = useMemo(() => {
7786
8140
  const baseTabs = getTabsForPreset(preset);
7787
8141
  return baseTabs.map((tab) => {
7788
8142
  let enabled = tab.enabled;
7789
- if (tab.id === "vaults" && enableVaults !== void 0) enabled = enableVaults;
7790
- if (tab.id === "aave" && enableAave !== void 0) enabled = enableAave;
7791
- if (tab.id === "pendle" && enablePendle !== void 0) enabled = enablePendle;
7792
8143
  if (tab.id === "swap" && enableSwap !== void 0) enabled = enableSwap;
7793
8144
  if (tab.id === "positions" && enablePositions !== void 0) enabled = enablePositions;
8145
+ if (tab.id === "rebalance" && enableRebalance !== void 0) enabled = enableRebalance;
7794
8146
  return { ...tab, enabled };
7795
8147
  });
7796
- }, [preset, enableVaults, enableAave, enablePendle, enableSwap, enablePositions]);
8148
+ }, [preset, enableSwap, enablePositions, enableRebalance]);
7797
8149
  const enabledTabs = tabs.filter((t) => t.enabled);
7798
8150
  const initialTab = defaultTab && tabs.find((t) => t.id === defaultTab)?.enabled ? defaultTab : getDefaultTab(tabs);
7799
8151
  const [activeTab, setActiveTab] = useState(initialTab);
@@ -7834,35 +8186,8 @@ function CompassEarnWidget({
7834
8186
  }
7835
8187
  ),
7836
8188
  /* @__PURE__ */ jsxs("div", { children: [
7837
- activeTab === "vaults" && /* @__PURE__ */ jsx(
7838
- VaultsList,
7839
- {
7840
- showApy,
7841
- showTvl,
7842
- showUserPosition,
7843
- showSearch,
7844
- showSort
7845
- }
7846
- ),
7847
- activeTab === "aave" && /* @__PURE__ */ jsx(
7848
- AaveMarketsList,
7849
- {
7850
- showApy,
7851
- showUserPosition,
7852
- showSearch
7853
- }
7854
- ),
7855
- activeTab === "pendle" && /* @__PURE__ */ jsx(
7856
- PendleMarketsList,
7857
- {
7858
- showApy,
7859
- showExpiry,
7860
- showUserPosition,
7861
- showSearch,
7862
- showSort
7863
- }
7864
- ),
7865
8189
  activeTab === "swap" && /* @__PURE__ */ jsx(SwapWidget, {}),
8190
+ activeTab === "rebalance" && /* @__PURE__ */ jsx(RebalancingWidget, {}),
7866
8191
  activeTab === "positions" && /* @__PURE__ */ jsx(
7867
8192
  EarnPositionsList,
7868
8193
  {
@@ -7893,6 +8218,6 @@ var CHAINS = {
7893
8218
  }
7894
8219
  };
7895
8220
 
7896
- export { AaveMarketsList, AccountBalancesModal, ActionModal, ApiProvider, CHAINS, ChainSwitcher, CompassEarnWidget, CompassProvider, DEFAULT_SWAP_TOKENS, DepositWithdrawForm, EarnAccount, EarnAccountBalance, EarnAccountGuard, PendleMarketsList, PnLSummary, SwapWidget, ThemeProvider, TransactionHistory, VaultsList, WalletStatus, themePresets, useAaveData, useChain, useCompassApi, useCompassChain, useCompassWallet, useEarnAccount, useEmbeddableApi, useEmbeddableWallet, usePendleData, useSwapQuote, useTheme, useVaultsData };
8221
+ export { AccountBalancesModal, ActionModal, ApiProvider, CHAINS, ChainSwitcher, CompassEarnWidget, CompassProvider, DEFAULT_SWAP_TOKENS, DepositWithdrawForm, EarnAccount, EarnAccountBalance, EarnAccountGuard, PnLSummary, RebalancingWidget, SwapWidget, ThemeProvider, TransactionHistory, WalletStatus, themePresets, useChain, useCompassApi, useCompassChain, useCompassWallet, useEarnAccount, useEmbeddableApi, useEmbeddableWallet, useRebalancingData, useSwapQuote, useTheme };
7897
8222
  //# sourceMappingURL=index.mjs.map
7898
8223
  //# sourceMappingURL=index.mjs.map