@compass-labs/widgets 0.1.1 → 0.1.2

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
@@ -3,7 +3,7 @@ import { QueryClient, QueryClientProvider, useQueryClient, useQuery } from '@tan
3
3
  import { CompassApiSDK } from '@compass-labs/api-sdk';
4
4
  import { jsx, jsxs, 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, Search, TrendingUp, Calendar, ArrowDownUp } from 'lucide-react';
6
+ import { Wallet, LogOut, X, ChevronDown, AlertCircle, ArrowRight, Loader2, ArrowDownLeft, ArrowUpRight, ExternalLink, Coins, Search, SlidersHorizontal, TrendingUp, Calendar, ArrowDownUp } from 'lucide-react';
7
7
 
8
8
  // src/provider/CompassProvider.tsx
9
9
  var ApiContext = createContext(null);
@@ -79,6 +79,7 @@ var WalletContext = createContext(null);
79
79
  var disconnectedWallet = {
80
80
  address: null,
81
81
  isConnected: false,
82
+ walletChainId: void 0,
82
83
  signTypedData: async () => {
83
84
  throw new Error("No wallet connected. Please connect a wallet first.");
84
85
  },
@@ -90,6 +91,7 @@ function WalletProvider({ children, wallet }) {
90
91
  const value = wallet ? {
91
92
  address: wallet.address,
92
93
  isConnected: wallet.address !== null,
94
+ walletChainId: wallet.chainId,
93
95
  signTypedData: wallet.signTypedData,
94
96
  switchChain: wallet.switchChain ?? null,
95
97
  login: wallet.login ?? null,
@@ -1261,6 +1263,16 @@ function formatAmount(value) {
1261
1263
  if (isNaN(num)) return "0";
1262
1264
  return parseFloat(num.toFixed(6)).toString();
1263
1265
  }
1266
+ function formatUSD(value) {
1267
+ const num = typeof value === "string" ? parseFloat(value) : value;
1268
+ if (isNaN(num)) return "$0.00";
1269
+ return new Intl.NumberFormat("en-US", {
1270
+ style: "currency",
1271
+ currency: "USD",
1272
+ minimumFractionDigits: 2,
1273
+ maximumFractionDigits: 2
1274
+ }).format(num);
1275
+ }
1264
1276
  function TokenSelector({
1265
1277
  tokens,
1266
1278
  selectedToken,
@@ -1382,8 +1394,8 @@ function DepositWithdrawForm({
1382
1394
  const [isSubmitting, setIsSubmitting] = useState(false);
1383
1395
  const [statusMessage, setStatusMessage] = useState("");
1384
1396
  const [error, setError] = useState(null);
1385
- const { address, isConnected, signTypedData } = useCompassWallet();
1386
- const { chainId } = useChain();
1397
+ const { address, isConnected, signTypedData, switchChain } = useCompassWallet();
1398
+ const { chainId, chain } = useChain();
1387
1399
  const { earnAccountAddress } = useEarnAccount();
1388
1400
  const queryClient = useQueryClient();
1389
1401
  const needsSwap = activeTab === "deposit" && selectedToken !== venueToken;
@@ -1397,7 +1409,7 @@ function DepositWithdrawForm({
1397
1409
  );
1398
1410
  if (!response.ok) return "0";
1399
1411
  const data = await response.json();
1400
- return data.balances?.[selectedToken] || "0";
1412
+ return data.balances?.[selectedToken]?.balance || "0";
1401
1413
  } catch {
1402
1414
  return "0";
1403
1415
  }
@@ -1421,6 +1433,15 @@ function DepositWithdrawForm({
1421
1433
  setStatusMessage("Preparing transaction...");
1422
1434
  setError(null);
1423
1435
  try {
1436
+ const targetChainId = chain.viemChain.id;
1437
+ if (switchChain) {
1438
+ setStatusMessage("Switching network...");
1439
+ try {
1440
+ await switchChain(targetChainId);
1441
+ } catch {
1442
+ throw new Error(`Please switch your wallet to ${chain.name} to continue`);
1443
+ }
1444
+ }
1424
1445
  const isDeposit = activeTab === "deposit";
1425
1446
  if (isDeposit && needsSwap) {
1426
1447
  setStatusMessage("Getting swap quote...");
@@ -1568,6 +1589,7 @@ function DepositWithdrawForm({
1568
1589
  address,
1569
1590
  amount,
1570
1591
  chainId,
1592
+ chain,
1571
1593
  activeTab,
1572
1594
  needsSwap,
1573
1595
  selectedToken,
@@ -1575,6 +1597,7 @@ function DepositWithdrawForm({
1575
1597
  venueType,
1576
1598
  venueAddress,
1577
1599
  signTypedData,
1600
+ switchChain,
1578
1601
  queryClient,
1579
1602
  onSuccess,
1580
1603
  onError
@@ -2074,51 +2097,221 @@ function EarnAccountGuard({
2074
2097
  }
2075
2098
  );
2076
2099
  }
2077
- var DEFAULT_TOKENS = ["SBC", "AUSD", "USDC", "USDT", "WETH", "DAI"];
2078
- var APPROVAL_CACHE_KEY = "compass_approved_tokens";
2079
- function getApprovalCacheKey(chain, owner) {
2080
- return `${APPROVAL_CACHE_KEY}_${chain}_${owner}`;
2081
- }
2082
- function getCachedApprovals(chain, owner) {
2083
- try {
2084
- const key = getApprovalCacheKey(chain, owner);
2085
- const cached = localStorage.getItem(key);
2086
- return cached ? new Set(JSON.parse(cached)) : /* @__PURE__ */ new Set();
2087
- } catch {
2088
- return /* @__PURE__ */ new Set();
2089
- }
2090
- }
2091
- function cacheApproval(chain, owner, token) {
2092
- try {
2093
- const key = getApprovalCacheKey(chain, owner);
2094
- const approvals = getCachedApprovals(chain, owner);
2095
- approvals.add(token);
2096
- localStorage.setItem(key, JSON.stringify([...approvals]));
2097
- } catch {
2098
- }
2100
+ function AccountBalancesModal({
2101
+ isOpen,
2102
+ onClose,
2103
+ balances,
2104
+ totalUsdValue,
2105
+ isLoading = false,
2106
+ earnAccountAddress
2107
+ }) {
2108
+ useEffect(() => {
2109
+ const handleEscape = (e) => {
2110
+ if (e.key === "Escape") onClose();
2111
+ };
2112
+ if (isOpen) {
2113
+ document.addEventListener("keydown", handleEscape);
2114
+ document.body.style.overflow = "hidden";
2115
+ }
2116
+ return () => {
2117
+ document.removeEventListener("keydown", handleEscape);
2118
+ document.body.style.overflow = "";
2119
+ };
2120
+ }, [isOpen, onClose]);
2121
+ if (!isOpen) return null;
2122
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
2123
+ /* @__PURE__ */ jsx(
2124
+ "div",
2125
+ {
2126
+ className: "absolute inset-0",
2127
+ style: { backgroundColor: "var(--compass-color-overlay)" },
2128
+ onClick: onClose
2129
+ }
2130
+ ),
2131
+ /* @__PURE__ */ jsxs(
2132
+ "div",
2133
+ {
2134
+ className: "relative w-full max-w-md mx-4 rounded-xl overflow-hidden",
2135
+ style: {
2136
+ backgroundColor: "var(--compass-color-surface)",
2137
+ boxShadow: "var(--compass-shadow-lg)"
2138
+ },
2139
+ children: [
2140
+ /* @__PURE__ */ jsxs(
2141
+ "div",
2142
+ {
2143
+ className: "flex items-center justify-between px-4 py-3 border-b",
2144
+ style: { borderColor: "var(--compass-color-border)" },
2145
+ children: [
2146
+ /* @__PURE__ */ jsxs(
2147
+ "h2",
2148
+ {
2149
+ className: "font-semibold flex items-center gap-2",
2150
+ style: {
2151
+ fontSize: "var(--compass-font-size-subheading)",
2152
+ color: "var(--compass-color-text)"
2153
+ },
2154
+ children: [
2155
+ /* @__PURE__ */ jsx(Coins, { size: 20 }),
2156
+ "Account Balances"
2157
+ ]
2158
+ }
2159
+ ),
2160
+ /* @__PURE__ */ jsx(
2161
+ "button",
2162
+ {
2163
+ onClick: onClose,
2164
+ className: "p-1 rounded-md transition-colors hover:opacity-70",
2165
+ style: { color: "var(--compass-color-text-secondary)" },
2166
+ children: /* @__PURE__ */ jsx(X, { size: 20 })
2167
+ }
2168
+ )
2169
+ ]
2170
+ }
2171
+ ),
2172
+ /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
2173
+ isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx(
2174
+ Loader2,
2175
+ {
2176
+ size: 24,
2177
+ className: "animate-spin",
2178
+ style: { color: "var(--compass-color-primary)" }
2179
+ }
2180
+ ) }) : balances.length === 0 ? /* @__PURE__ */ jsx(
2181
+ "div",
2182
+ {
2183
+ className: "text-center py-8",
2184
+ style: { color: "var(--compass-color-text-secondary)" },
2185
+ children: "No token balances found"
2186
+ }
2187
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
2188
+ balances.map((token) => /* @__PURE__ */ jsxs(
2189
+ "div",
2190
+ {
2191
+ className: "flex items-center justify-between p-3 rounded-lg",
2192
+ style: { backgroundColor: "var(--compass-color-background)" },
2193
+ children: [
2194
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2195
+ /* @__PURE__ */ jsx(
2196
+ "div",
2197
+ {
2198
+ className: "w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold",
2199
+ style: {
2200
+ backgroundColor: "var(--compass-color-primary-muted)",
2201
+ color: "var(--compass-color-primary)"
2202
+ },
2203
+ children: token.symbol.slice(0, 2)
2204
+ }
2205
+ ),
2206
+ /* @__PURE__ */ jsxs("div", { children: [
2207
+ /* @__PURE__ */ jsx(
2208
+ "div",
2209
+ {
2210
+ className: "font-medium",
2211
+ style: { color: "var(--compass-color-text)" },
2212
+ children: token.symbol
2213
+ }
2214
+ ),
2215
+ /* @__PURE__ */ jsx(
2216
+ "div",
2217
+ {
2218
+ className: "text-sm font-mono",
2219
+ style: { color: "var(--compass-color-text-secondary)" },
2220
+ children: formatAmount(token.balance)
2221
+ }
2222
+ )
2223
+ ] })
2224
+ ] }),
2225
+ /* @__PURE__ */ jsx(
2226
+ "div",
2227
+ {
2228
+ className: "font-medium",
2229
+ style: { color: "var(--compass-color-text)" },
2230
+ children: formatUSD(token.usdValue)
2231
+ }
2232
+ )
2233
+ ]
2234
+ },
2235
+ token.symbol
2236
+ )),
2237
+ /* @__PURE__ */ jsxs(
2238
+ "div",
2239
+ {
2240
+ className: "flex items-center justify-between p-3 mt-2 rounded-lg border",
2241
+ style: {
2242
+ borderColor: "var(--compass-color-border)",
2243
+ backgroundColor: "var(--compass-color-surface)"
2244
+ },
2245
+ children: [
2246
+ /* @__PURE__ */ jsx(
2247
+ "span",
2248
+ {
2249
+ className: "font-medium",
2250
+ style: { color: "var(--compass-color-text-secondary)" },
2251
+ children: "Total Balance"
2252
+ }
2253
+ ),
2254
+ /* @__PURE__ */ jsx(
2255
+ "span",
2256
+ {
2257
+ className: "font-bold text-lg",
2258
+ style: { color: "var(--compass-color-text)" },
2259
+ children: formatUSD(totalUsdValue)
2260
+ }
2261
+ )
2262
+ ]
2263
+ }
2264
+ )
2265
+ ] }),
2266
+ earnAccountAddress && /* @__PURE__ */ jsx(
2267
+ "div",
2268
+ {
2269
+ className: "mt-4 pt-4 border-t text-center",
2270
+ style: { borderColor: "var(--compass-color-border)" },
2271
+ children: /* @__PURE__ */ jsxs(
2272
+ "span",
2273
+ {
2274
+ className: "text-xs",
2275
+ style: { color: "var(--compass-color-text-tertiary)" },
2276
+ children: [
2277
+ "Earn Account: ",
2278
+ earnAccountAddress.slice(0, 6),
2279
+ "...",
2280
+ earnAccountAddress.slice(-4)
2281
+ ]
2282
+ }
2283
+ )
2284
+ }
2285
+ )
2286
+ ] })
2287
+ ]
2288
+ }
2289
+ )
2290
+ ] });
2099
2291
  }
2292
+ var TRANSFER_TOKENS = ["USDC"];
2100
2293
  function EarnAccountBalance({
2101
- tokens = DEFAULT_TOKENS,
2102
2294
  compact = false,
2103
2295
  onTransferComplete
2104
2296
  }) {
2105
2297
  const [isModalOpen, setIsModalOpen] = useState(false);
2106
2298
  const [activeAction, setActiveAction] = useState("deposit");
2107
- const [selectedToken, setSelectedToken] = useState(tokens[0]);
2299
+ const [selectedToken, setSelectedToken] = useState(TRANSFER_TOKENS[0]);
2108
2300
  const [amount, setAmount] = useState("");
2109
2301
  const [transferState, setTransferState] = useState("idle");
2110
2302
  const [statusMessage, setStatusMessage] = useState("");
2111
2303
  const [error, setError] = useState(null);
2112
- const { address, isConnected, signTypedData } = useEmbeddableWallet();
2113
- const { chainId } = useChain();
2304
+ const [isBalancesModalOpen, setIsBalancesModalOpen] = useState(false);
2305
+ const { address, isConnected, signTypedData, switchChain, walletChainId } = useEmbeddableWallet();
2306
+ const { chainId, chain } = useChain();
2114
2307
  const { earnAccountAddress, isDeployed } = useEarnAccount();
2115
2308
  const queryClient = useQueryClient();
2116
2309
  const { data: balanceData, isLoading: balancesLoading } = useQuery({
2117
- queryKey: ["earnAccountBalances", chainId, address, tokens.join(",")],
2310
+ queryKey: ["earnAccountBalances", chainId, address],
2118
2311
  queryFn: async () => {
2119
2312
  if (!address) return null;
2120
2313
  const response = await fetch(
2121
- `/api/compass/earn-account/balances?owner=${address}&chain=${chainId}&tokens=${tokens.join(",")}`
2314
+ `/api/compass/earn-account/balances?owner=${address}&chain=${chainId}`
2122
2315
  );
2123
2316
  if (!response.ok) {
2124
2317
  throw new Error("Failed to fetch balances");
@@ -2129,19 +2322,41 @@ function EarnAccountBalance({
2129
2322
  staleTime: 30 * 1e3
2130
2323
  });
2131
2324
  const { data: eoaBalances } = useQuery({
2132
- queryKey: ["eoaBalances", chainId, address, tokens.join(",")],
2325
+ queryKey: ["eoaBalances", chainId, address, TRANSFER_TOKENS.join(",")],
2133
2326
  queryFn: async () => {
2134
2327
  if (!address) return {};
2135
2328
  const balances = {};
2329
+ for (const token of TRANSFER_TOKENS) {
2330
+ try {
2331
+ const response = await fetch(
2332
+ `/api/compass/token/balance?chain=${chainId}&token=${token}&address=${address}`
2333
+ );
2334
+ if (response.ok) {
2335
+ const data = await response.json();
2336
+ balances[token] = data.balance || "0";
2337
+ }
2338
+ } catch {
2339
+ }
2340
+ }
2136
2341
  return balances;
2137
2342
  },
2138
2343
  enabled: !!address && activeAction === "deposit",
2139
2344
  staleTime: 30 * 1e3
2140
2345
  });
2141
2346
  const earnBalances = balanceData?.balances || {};
2142
- Object.values(earnBalances).reduce((sum, bal) => {
2143
- return sum + parseFloat(bal || "0");
2144
- }, 0);
2347
+ const totalUsdValue = parseFloat(balanceData?.totalUsdValue || "0");
2348
+ const tokenBalances = Object.entries(earnBalances).filter(([, data]) => {
2349
+ const usdVal = parseFloat(data.usdValue || "0");
2350
+ return usdVal > 0;
2351
+ }).map(([symbol, data]) => ({
2352
+ symbol,
2353
+ balance: data.balance || "0",
2354
+ usdValue: data.usdValue || "0"
2355
+ }));
2356
+ const earnBalanceAmounts = {};
2357
+ for (const [symbol, data] of Object.entries(earnBalances)) {
2358
+ earnBalanceAmounts[symbol] = data.balance;
2359
+ }
2145
2360
  const resetForm = useCallback(() => {
2146
2361
  setAmount("");
2147
2362
  setTransferState("idle");
@@ -2165,7 +2380,7 @@ function EarnAccountBalance({
2165
2380
  if (activeAction === "deposit") {
2166
2381
  return eoaBalances?.[selectedToken] || "0";
2167
2382
  }
2168
- return earnBalances[selectedToken] || "0";
2383
+ return earnBalanceAmounts[selectedToken] || "0";
2169
2384
  };
2170
2385
  const handleQuickAmount = (percentage) => {
2171
2386
  const max = parseFloat(getMaxBalance());
@@ -2176,55 +2391,70 @@ function EarnAccountBalance({
2176
2391
  if (!address || !amount || !signTypedData) return;
2177
2392
  setError(null);
2178
2393
  try {
2394
+ const targetChainId = chain.viemChain.id;
2395
+ if (walletChainId !== void 0 && walletChainId !== targetChainId) {
2396
+ if (!switchChain) {
2397
+ throw new Error(`Please switch your wallet to ${chain.name} (chain ID ${targetChainId}) to continue. Your wallet is currently on chain ID ${walletChainId}.`);
2398
+ }
2399
+ setStatusMessage(`Switching network to ${chain.name}...`);
2400
+ try {
2401
+ await switchChain(targetChainId);
2402
+ } catch (switchError) {
2403
+ throw new Error(`Please switch your wallet to ${chain.name} to continue. Your wallet is on chain ID ${walletChainId}, but ${chain.name} requires chain ID ${targetChainId}.`);
2404
+ }
2405
+ } else if (switchChain && walletChainId === void 0) {
2406
+ setStatusMessage("Switching network...");
2407
+ try {
2408
+ await switchChain(targetChainId);
2409
+ } catch (switchError) {
2410
+ throw new Error(`Please switch your wallet to ${chain.name} to continue`);
2411
+ }
2412
+ }
2179
2413
  if (activeAction === "deposit") {
2180
- const cachedApprovals = getCachedApprovals(chainId, address);
2181
- if (!cachedApprovals.has(selectedToken)) {
2182
- setTransferState("checking_approval");
2183
- setStatusMessage("Checking token approval...");
2184
- const approveResponse = await fetch("/api/compass/transfer/approve", {
2414
+ setTransferState("checking_approval");
2415
+ setStatusMessage("Checking token approval...");
2416
+ const approveResponse = await fetch("/api/compass/transfer/approve", {
2417
+ method: "POST",
2418
+ headers: { "Content-Type": "application/json" },
2419
+ body: JSON.stringify({
2420
+ owner: address,
2421
+ chain: chainId,
2422
+ token: selectedToken
2423
+ })
2424
+ });
2425
+ if (!approveResponse.ok) {
2426
+ const errData = await approveResponse.json();
2427
+ throw new Error(errData.error || "Failed to check approval");
2428
+ }
2429
+ const approvalData = await approveResponse.json();
2430
+ if (!approvalData.approved) {
2431
+ if (approvalData.requiresTransaction) {
2432
+ throw new Error("This token requires a transaction-based approval. Please approve manually.");
2433
+ }
2434
+ setTransferState("awaiting_approval_signature");
2435
+ setStatusMessage("Please sign the approval...");
2436
+ const approvalSignature = await signTypedData({
2437
+ domain: approvalData.domain,
2438
+ types: approvalData.normalizedTypes,
2439
+ primaryType: "Permit",
2440
+ message: approvalData.message
2441
+ });
2442
+ setTransferState("approving");
2443
+ setStatusMessage("Executing approval...");
2444
+ const executeApprovalResponse = await fetch("/api/compass/transfer/execute", {
2185
2445
  method: "POST",
2186
2446
  headers: { "Content-Type": "application/json" },
2187
2447
  body: JSON.stringify({
2188
2448
  owner: address,
2189
2449
  chain: chainId,
2190
- token: selectedToken
2450
+ eip712: approvalData.eip712,
2451
+ signature: approvalSignature
2191
2452
  })
2192
2453
  });
2193
- if (!approveResponse.ok) {
2194
- const errData = await approveResponse.json();
2195
- throw new Error(errData.error || "Failed to check approval");
2454
+ if (!executeApprovalResponse.ok) {
2455
+ const errData = await executeApprovalResponse.json();
2456
+ throw new Error(errData.error || "Approval failed");
2196
2457
  }
2197
- const approvalData = await approveResponse.json();
2198
- if (!approvalData.approved) {
2199
- if (approvalData.requiresTransaction) {
2200
- throw new Error("This token requires a transaction-based approval. Please approve manually.");
2201
- }
2202
- setTransferState("awaiting_approval_signature");
2203
- setStatusMessage("Please sign the approval...");
2204
- const approvalSignature = await signTypedData({
2205
- domain: approvalData.domain,
2206
- types: approvalData.normalizedTypes,
2207
- primaryType: "Permit",
2208
- message: approvalData.message
2209
- });
2210
- setTransferState("approving");
2211
- setStatusMessage("Executing approval...");
2212
- const executeApprovalResponse = await fetch("/api/compass/transfer/execute", {
2213
- method: "POST",
2214
- headers: { "Content-Type": "application/json" },
2215
- body: JSON.stringify({
2216
- owner: address,
2217
- chain: chainId,
2218
- eip712: approvalData.eip712,
2219
- signature: approvalSignature
2220
- })
2221
- });
2222
- if (!executeApprovalResponse.ok) {
2223
- const errData = await executeApprovalResponse.json();
2224
- throw new Error(errData.error || "Approval failed");
2225
- }
2226
- }
2227
- cacheApproval(chainId, address, selectedToken);
2228
2458
  }
2229
2459
  }
2230
2460
  setTransferState("awaiting_transfer_signature");
@@ -2288,7 +2518,10 @@ function EarnAccountBalance({
2288
2518
  activeAction,
2289
2519
  selectedToken,
2290
2520
  chainId,
2521
+ chain,
2522
+ walletChainId,
2291
2523
  signTypedData,
2524
+ switchChain,
2292
2525
  queryClient,
2293
2526
  onTransferComplete,
2294
2527
  resetForm
@@ -2321,6 +2554,29 @@ function EarnAccountBalance({
2321
2554
  );
2322
2555
  }
2323
2556
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2557
+ /* @__PURE__ */ jsxs(
2558
+ "button",
2559
+ {
2560
+ onClick: () => setIsBalancesModalOpen(true),
2561
+ className: `flex items-center gap-2 rounded-lg border transition-colors hover:border-[var(--compass-color-primary)] ${compact ? "px-2 py-1.5" : "px-3 py-2"}`,
2562
+ style: {
2563
+ backgroundColor: "var(--compass-color-surface)",
2564
+ borderColor: "var(--compass-color-border)",
2565
+ cursor: "pointer"
2566
+ },
2567
+ children: [
2568
+ /* @__PURE__ */ jsx(Wallet, { size: compact ? 14 : 16, style: { color: "var(--compass-color-primary)" } }),
2569
+ balancesLoading ? /* @__PURE__ */ jsx(Loader2, { size: compact ? 12 : 14, className: "animate-spin", style: { color: "var(--compass-color-text-secondary)" } }) : /* @__PURE__ */ jsx(
2570
+ "span",
2571
+ {
2572
+ className: `font-medium ${compact ? "text-xs" : "text-sm"}`,
2573
+ style: { color: "var(--compass-color-text)" },
2574
+ children: formatUSD(totalUsdValue)
2575
+ }
2576
+ )
2577
+ ]
2578
+ }
2579
+ ),
2324
2580
  /* @__PURE__ */ jsx(
2325
2581
  "button",
2326
2582
  {
@@ -2330,7 +2586,18 @@ function EarnAccountBalance({
2330
2586
  backgroundColor: "var(--compass-color-primary)",
2331
2587
  color: "var(--compass-color-primary-text)"
2332
2588
  },
2333
- children: "Transfer"
2589
+ children: "Fund"
2590
+ }
2591
+ ),
2592
+ /* @__PURE__ */ jsx(
2593
+ AccountBalancesModal,
2594
+ {
2595
+ isOpen: isBalancesModalOpen,
2596
+ onClose: () => setIsBalancesModalOpen(false),
2597
+ balances: tokenBalances,
2598
+ totalUsdValue: totalUsdValue.toString(),
2599
+ isLoading: balancesLoading,
2600
+ earnAccountAddress: earnAccountAddress ?? void 0
2334
2601
  }
2335
2602
  ),
2336
2603
  /* @__PURE__ */ jsx(
@@ -2376,10 +2643,10 @@ function EarnAccountBalance({
2376
2643
  /* @__PURE__ */ jsx(
2377
2644
  TokenSelector,
2378
2645
  {
2379
- tokens,
2646
+ tokens: TRANSFER_TOKENS,
2380
2647
  selectedToken,
2381
2648
  onSelect: setSelectedToken,
2382
- balances: activeAction === "deposit" ? eoaBalances : earnBalances,
2649
+ balances: activeAction === "deposit" ? eoaBalances : earnBalanceAmounts,
2383
2650
  disabled: isProcessing
2384
2651
  }
2385
2652
  )
@@ -2631,9 +2898,9 @@ function useVaultsData(options = {}) {
2631
2898
  const { client } = useEmbeddableApi();
2632
2899
  const { address } = useEmbeddableWallet();
2633
2900
  const { chainId } = useChain();
2634
- const { sortBy = "apy_7d", assetFilter, minApy } = options;
2901
+ const { sortBy = "apy_7d", assetFilter, minApy, minTvl } = options;
2635
2902
  const vaultsQuery = useQuery({
2636
- queryKey: ["vaults", chainId, sortBy, assetFilter, minApy],
2903
+ queryKey: ["vaults", chainId, sortBy, assetFilter, minApy, minTvl],
2637
2904
  queryFn: async () => {
2638
2905
  const assetSymbol = assetFilter && assetFilter.length === 1 ? assetFilter[0] : void 0;
2639
2906
  const response = await client.earn.earnVaults({
@@ -2663,6 +2930,12 @@ function useVaultsData(options = {}) {
2663
2930
  return apy >= minApy;
2664
2931
  });
2665
2932
  }
2933
+ if (minTvl !== void 0 && minTvl > 0) {
2934
+ vaults = vaults.filter((v) => {
2935
+ const tvl = parseFloat(v.tvlUsd || "0");
2936
+ return tvl >= minTvl;
2937
+ });
2938
+ }
2666
2939
  return vaults;
2667
2940
  },
2668
2941
  staleTime: 30 * 1e3
@@ -2728,6 +3001,8 @@ function VaultsList({
2728
3001
  defaultSort = "apy_7d",
2729
3002
  assetFilter,
2730
3003
  minApy,
3004
+ minTvl: initialMinTvl,
3005
+ showTvlFilter = true,
2731
3006
  onVaultSelect,
2732
3007
  onDeposit,
2733
3008
  onWithdraw
@@ -2735,11 +3010,18 @@ function VaultsList({
2735
3010
  const [searchQuery, setSearchQuery] = useState("");
2736
3011
  const [sortBy, setSortBy] = useState(defaultSort);
2737
3012
  const [selectedVault, setSelectedVault] = useState(null);
3013
+ const [minTvlFilter, setMinTvlFilter] = useState(initialMinTvl);
3014
+ const [showFilterPanel, setShowFilterPanel] = useState(false);
2738
3015
  const { vaults, isLoading, isError, refetch } = useVaultsData({
2739
3016
  sortBy,
2740
3017
  assetFilter,
2741
- minApy
3018
+ minApy,
3019
+ minTvl: minTvlFilter
2742
3020
  });
3021
+ const handleMinTvlChange = useCallback((value) => {
3022
+ const num = parseFloat(value);
3023
+ setMinTvlFilter(isNaN(num) || num <= 0 ? void 0 : num);
3024
+ }, []);
2743
3025
  const filteredVaults = useMemo(() => {
2744
3026
  if (!searchQuery) return vaults;
2745
3027
  const query = searchQuery.toLowerCase();
@@ -2767,49 +3049,116 @@ function VaultsList({
2767
3049
  /* @__PURE__ */ jsx(WalletStatus, { compact: true })
2768
3050
  ] })
2769
3051
  ] }),
2770
- (showSearch || showSort) && /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
2771
- showSearch && /* @__PURE__ */ jsxs(
3052
+ (showSearch || showSort || showTvlFilter) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
3053
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
3054
+ showSearch && /* @__PURE__ */ jsxs(
3055
+ "div",
3056
+ {
3057
+ className: "flex-1 flex items-center gap-2 px-3 py-2 rounded-lg border",
3058
+ style: {
3059
+ backgroundColor: "var(--compass-color-background)",
3060
+ borderColor: "var(--compass-color-border)"
3061
+ },
3062
+ children: [
3063
+ /* @__PURE__ */ jsx(Search, { size: 16, style: { color: "var(--compass-color-text-tertiary)" } }),
3064
+ /* @__PURE__ */ jsx(
3065
+ "input",
3066
+ {
3067
+ type: "text",
3068
+ placeholder: "Search vaults...",
3069
+ value: searchQuery,
3070
+ onChange: (e) => setSearchQuery(e.target.value),
3071
+ className: "flex-1 bg-transparent outline-none text-sm",
3072
+ style: { color: "var(--compass-color-text)" }
3073
+ }
3074
+ )
3075
+ ]
3076
+ }
3077
+ ),
3078
+ showSort && /* @__PURE__ */ jsxs(
3079
+ "select",
3080
+ {
3081
+ value: sortBy,
3082
+ onChange: (e) => setSortBy(e.target.value),
3083
+ className: "px-3 py-2 rounded-lg border text-sm cursor-pointer",
3084
+ style: {
3085
+ backgroundColor: "var(--compass-color-background)",
3086
+ borderColor: "var(--compass-color-border)",
3087
+ color: "var(--compass-color-text)"
3088
+ },
3089
+ children: [
3090
+ /* @__PURE__ */ jsx("option", { value: "apy_7d", children: "APY (7D)" }),
3091
+ /* @__PURE__ */ jsx("option", { value: "apy_30d", children: "APY (30D)" }),
3092
+ /* @__PURE__ */ jsx("option", { value: "apy_90d", children: "APY (90D)" }),
3093
+ /* @__PURE__ */ jsx("option", { value: "tvl", children: "TVL" })
3094
+ ]
3095
+ }
3096
+ ),
3097
+ showTvlFilter && /* @__PURE__ */ jsxs(
3098
+ "button",
3099
+ {
3100
+ onClick: () => setShowFilterPanel(!showFilterPanel),
3101
+ className: "px-3 py-2 rounded-lg border text-sm flex items-center gap-2",
3102
+ style: {
3103
+ backgroundColor: showFilterPanel || minTvlFilter ? "var(--compass-color-primary-muted)" : "var(--compass-color-background)",
3104
+ borderColor: showFilterPanel || minTvlFilter ? "var(--compass-color-primary)" : "var(--compass-color-border)",
3105
+ color: showFilterPanel || minTvlFilter ? "var(--compass-color-primary)" : "var(--compass-color-text)"
3106
+ },
3107
+ children: [
3108
+ /* @__PURE__ */ jsx(SlidersHorizontal, { size: 14 }),
3109
+ "Filter"
3110
+ ]
3111
+ }
3112
+ )
3113
+ ] }),
3114
+ showTvlFilter && showFilterPanel && /* @__PURE__ */ jsxs(
2772
3115
  "div",
2773
3116
  {
2774
- className: "flex-1 flex items-center gap-2 px-3 py-2 rounded-lg border",
3117
+ className: "flex items-center gap-3 p-3 rounded-lg border",
2775
3118
  style: {
2776
- backgroundColor: "var(--compass-color-background)",
3119
+ backgroundColor: "var(--compass-color-surface)",
2777
3120
  borderColor: "var(--compass-color-border)"
2778
3121
  },
2779
3122
  children: [
2780
- /* @__PURE__ */ jsx(Search, { size: 16, style: { color: "var(--compass-color-text-tertiary)" } }),
2781
3123
  /* @__PURE__ */ jsx(
2782
- "input",
3124
+ "label",
2783
3125
  {
2784
- type: "text",
2785
- placeholder: "Search vaults...",
2786
- value: searchQuery,
2787
- onChange: (e) => setSearchQuery(e.target.value),
2788
- className: "flex-1 bg-transparent outline-none text-sm",
2789
- style: { color: "var(--compass-color-text)" }
3126
+ className: "text-sm font-medium whitespace-nowrap",
3127
+ style: { color: "var(--compass-color-text-secondary)" },
3128
+ children: "Min TVL:"
3129
+ }
3130
+ ),
3131
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3132
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text-tertiary)" }, children: "$" }),
3133
+ /* @__PURE__ */ jsx(
3134
+ "input",
3135
+ {
3136
+ type: "number",
3137
+ placeholder: "0",
3138
+ value: minTvlFilter || "",
3139
+ onChange: (e) => handleMinTvlChange(e.target.value),
3140
+ className: "w-24 px-2 py-1 rounded border text-sm bg-transparent",
3141
+ style: {
3142
+ borderColor: "var(--compass-color-border)",
3143
+ color: "var(--compass-color-text)"
3144
+ }
3145
+ }
3146
+ )
3147
+ ] }),
3148
+ minTvlFilter && /* @__PURE__ */ jsx(
3149
+ "button",
3150
+ {
3151
+ onClick: () => setMinTvlFilter(void 0),
3152
+ className: "text-xs px-2 py-1 rounded",
3153
+ style: {
3154
+ backgroundColor: "var(--compass-color-error-muted)",
3155
+ color: "var(--compass-color-error)"
3156
+ },
3157
+ children: "Clear"
2790
3158
  }
2791
3159
  )
2792
3160
  ]
2793
3161
  }
2794
- ),
2795
- showSort && /* @__PURE__ */ jsxs(
2796
- "select",
2797
- {
2798
- value: sortBy,
2799
- onChange: (e) => setSortBy(e.target.value),
2800
- className: "px-3 py-2 rounded-lg border text-sm cursor-pointer",
2801
- style: {
2802
- backgroundColor: "var(--compass-color-background)",
2803
- borderColor: "var(--compass-color-border)",
2804
- color: "var(--compass-color-text)"
2805
- },
2806
- children: [
2807
- /* @__PURE__ */ jsx("option", { value: "apy_7d", children: "APY (7D)" }),
2808
- /* @__PURE__ */ jsx("option", { value: "apy_30d", children: "APY (30D)" }),
2809
- /* @__PURE__ */ jsx("option", { value: "apy_90d", children: "APY (90D)" }),
2810
- /* @__PURE__ */ jsx("option", { value: "tvl", children: "TVL" })
2811
- ]
2812
- }
2813
3162
  )
2814
3163
  ] }),
2815
3164
  isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx(Loader2, { size: 24, className: "animate-spin", style: { color: "var(--compass-color-primary)" } }) }) : isError ? /* @__PURE__ */ jsx(
@@ -3221,9 +3570,9 @@ function usePendleData(options = {}) {
3221
3570
  const { client } = useEmbeddableApi();
3222
3571
  const { address } = useEmbeddableWallet();
3223
3572
  const { chainId } = useChain();
3224
- const { sortBy = "fixed_apy", assetFilter } = options;
3573
+ const { sortBy = "fixed_apy", assetFilter, minTvl } = options;
3225
3574
  const marketsQuery = useQuery({
3226
- queryKey: ["pendleMarkets", chainId, sortBy, assetFilter],
3575
+ queryKey: ["pendleMarkets", chainId, sortBy, assetFilter, minTvl],
3227
3576
  queryFn: async () => {
3228
3577
  const underlyingSymbol = assetFilter && assetFilter.length === 1 ? assetFilter[0] : void 0;
3229
3578
  const orderBy = sortBy === "tvl" ? "tvl_usd" : "implied_apy";
@@ -3252,6 +3601,12 @@ function usePendleData(options = {}) {
3252
3601
  (m) => assetFilter.includes(m.underlyingSymbol.toUpperCase())
3253
3602
  );
3254
3603
  }
3604
+ if (minTvl !== void 0 && minTvl > 0) {
3605
+ markets = markets.filter((m) => {
3606
+ const tvl = parseFloat(m.tvlUsd || "0");
3607
+ return tvl >= minTvl;
3608
+ });
3609
+ }
3255
3610
  if (sortBy === "expiry") {
3256
3611
  markets.sort((a, b) => {
3257
3612
  return new Date(a.expiry).getTime() - new Date(b.expiry).getTime();
@@ -3337,6 +3692,8 @@ function PendleMarketsList({
3337
3692
  showSort = true,
3338
3693
  defaultSort = "fixed_apy",
3339
3694
  assetFilter,
3695
+ minTvl: initialMinTvl,
3696
+ showTvlFilter = true,
3340
3697
  onMarketSelect,
3341
3698
  onDeposit,
3342
3699
  onWithdraw
@@ -3344,7 +3701,13 @@ function PendleMarketsList({
3344
3701
  const [searchQuery, setSearchQuery] = useState("");
3345
3702
  const [sortBy, setSortBy] = useState(defaultSort);
3346
3703
  const [selectedMarket, setSelectedMarket] = useState(null);
3347
- const { markets, isLoading, isError, refetch } = usePendleData({ sortBy, assetFilter });
3704
+ const [minTvlFilter, setMinTvlFilter] = useState(initialMinTvl);
3705
+ const [showFilterPanel, setShowFilterPanel] = useState(false);
3706
+ const { markets, isLoading, isError, refetch } = usePendleData({ sortBy, assetFilter, minTvl: minTvlFilter });
3707
+ const handleMinTvlChange = useCallback((value) => {
3708
+ const num = parseFloat(value);
3709
+ setMinTvlFilter(isNaN(num) || num <= 0 ? void 0 : num);
3710
+ }, []);
3348
3711
  const filteredMarkets = useMemo(() => {
3349
3712
  if (!searchQuery) return markets;
3350
3713
  const query = searchQuery.toLowerCase();
@@ -3372,48 +3735,115 @@ function PendleMarketsList({
3372
3735
  /* @__PURE__ */ jsx(WalletStatus, { compact: true })
3373
3736
  ] })
3374
3737
  ] }),
3375
- (showSearch || showSort) && /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
3376
- showSearch && /* @__PURE__ */ jsxs(
3738
+ (showSearch || showSort || showTvlFilter) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
3739
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
3740
+ showSearch && /* @__PURE__ */ jsxs(
3741
+ "div",
3742
+ {
3743
+ className: "flex-1 flex items-center gap-2 px-3 py-2 rounded-lg border",
3744
+ style: {
3745
+ backgroundColor: "var(--compass-color-background)",
3746
+ borderColor: "var(--compass-color-border)"
3747
+ },
3748
+ children: [
3749
+ /* @__PURE__ */ jsx(Search, { size: 16, style: { color: "var(--compass-color-text-tertiary)" } }),
3750
+ /* @__PURE__ */ jsx(
3751
+ "input",
3752
+ {
3753
+ type: "text",
3754
+ placeholder: "Search markets...",
3755
+ value: searchQuery,
3756
+ onChange: (e) => setSearchQuery(e.target.value),
3757
+ className: "flex-1 bg-transparent outline-none text-sm",
3758
+ style: { color: "var(--compass-color-text)" }
3759
+ }
3760
+ )
3761
+ ]
3762
+ }
3763
+ ),
3764
+ showSort && /* @__PURE__ */ jsxs(
3765
+ "select",
3766
+ {
3767
+ value: sortBy,
3768
+ onChange: (e) => setSortBy(e.target.value),
3769
+ className: "px-3 py-2 rounded-lg border text-sm cursor-pointer",
3770
+ style: {
3771
+ backgroundColor: "var(--compass-color-background)",
3772
+ borderColor: "var(--compass-color-border)",
3773
+ color: "var(--compass-color-text)"
3774
+ },
3775
+ children: [
3776
+ /* @__PURE__ */ jsx("option", { value: "fixed_apy", children: "Fixed APY" }),
3777
+ /* @__PURE__ */ jsx("option", { value: "tvl", children: "TVL" }),
3778
+ /* @__PURE__ */ jsx("option", { value: "expiry", children: "Expiry" })
3779
+ ]
3780
+ }
3781
+ ),
3782
+ showTvlFilter && /* @__PURE__ */ jsxs(
3783
+ "button",
3784
+ {
3785
+ onClick: () => setShowFilterPanel(!showFilterPanel),
3786
+ className: "px-3 py-2 rounded-lg border text-sm flex items-center gap-2",
3787
+ style: {
3788
+ backgroundColor: showFilterPanel || minTvlFilter ? "var(--compass-color-primary-muted)" : "var(--compass-color-background)",
3789
+ borderColor: showFilterPanel || minTvlFilter ? "var(--compass-color-primary)" : "var(--compass-color-border)",
3790
+ color: showFilterPanel || minTvlFilter ? "var(--compass-color-primary)" : "var(--compass-color-text)"
3791
+ },
3792
+ children: [
3793
+ /* @__PURE__ */ jsx(SlidersHorizontal, { size: 14 }),
3794
+ "Filter"
3795
+ ]
3796
+ }
3797
+ )
3798
+ ] }),
3799
+ showTvlFilter && showFilterPanel && /* @__PURE__ */ jsxs(
3377
3800
  "div",
3378
3801
  {
3379
- className: "flex-1 flex items-center gap-2 px-3 py-2 rounded-lg border",
3802
+ className: "flex items-center gap-3 p-3 rounded-lg border",
3380
3803
  style: {
3381
- backgroundColor: "var(--compass-color-background)",
3804
+ backgroundColor: "var(--compass-color-surface)",
3382
3805
  borderColor: "var(--compass-color-border)"
3383
3806
  },
3384
3807
  children: [
3385
- /* @__PURE__ */ jsx(Search, { size: 16, style: { color: "var(--compass-color-text-tertiary)" } }),
3386
3808
  /* @__PURE__ */ jsx(
3387
- "input",
3809
+ "label",
3388
3810
  {
3389
- type: "text",
3390
- placeholder: "Search markets...",
3391
- value: searchQuery,
3392
- onChange: (e) => setSearchQuery(e.target.value),
3393
- className: "flex-1 bg-transparent outline-none text-sm",
3394
- style: { color: "var(--compass-color-text)" }
3811
+ className: "text-sm font-medium whitespace-nowrap",
3812
+ style: { color: "var(--compass-color-text-secondary)" },
3813
+ children: "Min TVL:"
3814
+ }
3815
+ ),
3816
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3817
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--compass-color-text-tertiary)" }, children: "$" }),
3818
+ /* @__PURE__ */ jsx(
3819
+ "input",
3820
+ {
3821
+ type: "number",
3822
+ placeholder: "0",
3823
+ value: minTvlFilter || "",
3824
+ onChange: (e) => handleMinTvlChange(e.target.value),
3825
+ className: "w-24 px-2 py-1 rounded border text-sm bg-transparent",
3826
+ style: {
3827
+ borderColor: "var(--compass-color-border)",
3828
+ color: "var(--compass-color-text)"
3829
+ }
3830
+ }
3831
+ )
3832
+ ] }),
3833
+ minTvlFilter && /* @__PURE__ */ jsx(
3834
+ "button",
3835
+ {
3836
+ onClick: () => setMinTvlFilter(void 0),
3837
+ className: "text-xs px-2 py-1 rounded",
3838
+ style: {
3839
+ backgroundColor: "var(--compass-color-error-muted)",
3840
+ color: "var(--compass-color-error)"
3841
+ },
3842
+ children: "Clear"
3395
3843
  }
3396
3844
  )
3397
3845
  ]
3398
3846
  }
3399
- ),
3400
- showSort && /* @__PURE__ */ jsxs(
3401
- "select",
3402
- {
3403
- value: sortBy,
3404
- onChange: (e) => setSortBy(e.target.value),
3405
- className: "px-3 py-2 rounded-lg border text-sm cursor-pointer",
3406
- style: {
3407
- backgroundColor: "var(--compass-color-background)",
3408
- borderColor: "var(--compass-color-border)",
3409
- color: "var(--compass-color-text)"
3410
- },
3411
- children: [
3412
- /* @__PURE__ */ jsx("option", { value: "fixed_apy", children: "Fixed APY" }),
3413
- /* @__PURE__ */ jsx("option", { value: "tvl", children: "TVL" }),
3414
- /* @__PURE__ */ jsx("option", { value: "expiry", children: "Expiry" })
3415
- ]
3416
- }
3417
3847
  )
3418
3848
  ] }),
3419
3849
  isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx(Loader2, { size: 24, className: "animate-spin", style: { color: "var(--compass-color-primary)" } }) }) : isError ? /* @__PURE__ */ jsx(
@@ -3587,51 +4017,62 @@ function PendleMarketsList({
3587
4017
  ] });
3588
4018
  }
3589
4019
  function useSwapQuote({ fromToken, toToken, amount, enabled = true }) {
3590
- const { client } = useEmbeddableApi();
3591
4020
  const { chainId } = useChain();
4021
+ const { address } = useCompassWallet();
3592
4022
  const query = useQuery({
3593
- queryKey: ["swapQuote", chainId, fromToken, toToken, amount],
4023
+ queryKey: ["swapQuote", chainId, fromToken, toToken, amount, address],
3594
4024
  queryFn: async () => {
3595
- if (!fromToken || !toToken || !amount || parseFloat(amount) <= 0) {
4025
+ if (!fromToken || !toToken || !amount || parseFloat(amount) <= 0 || !address) {
3596
4026
  return null;
3597
4027
  }
3598
4028
  try {
3599
- const estimatedRate = 1;
3600
- const outputAmount = (parseFloat(amount) * estimatedRate).toString();
4029
+ const params = new URLSearchParams({
4030
+ owner: address,
4031
+ chain: chainId,
4032
+ tokenIn: fromToken,
4033
+ tokenOut: toToken,
4034
+ amountIn: amount
4035
+ });
4036
+ const response = await fetch(`/api/compass/swap/quote?${params}`);
4037
+ if (!response.ok) {
4038
+ const error = await response.json();
4039
+ throw new Error(error.error || "Failed to get swap quote");
4040
+ }
4041
+ const data = await response.json();
4042
+ const outputAmount = data.estimatedAmountOut || "0";
4043
+ const inputAmountNum = parseFloat(amount);
4044
+ const outputAmountNum = parseFloat(outputAmount);
3601
4045
  return {
3602
4046
  inputAmount: amount,
3603
4047
  outputAmount,
3604
- rate: estimatedRate.toString(),
3605
- priceImpact: "0",
3606
- fee: "0"
4048
+ rate: inputAmountNum > 0 ? (outputAmountNum / inputAmountNum).toString() : "0"
3607
4049
  };
3608
4050
  } catch (error) {
3609
4051
  console.error("Failed to get swap quote:", error);
3610
- return null;
4052
+ throw error;
3611
4053
  }
3612
4054
  },
3613
- enabled: enabled && !!fromToken && !!toToken && !!amount && parseFloat(amount) > 0,
4055
+ enabled: enabled && !!address && !!fromToken && !!toToken && !!amount && parseFloat(amount) > 0,
3614
4056
  staleTime: 10 * 1e3,
3615
- // 10 seconds - quotes change frequently
3616
- refetchInterval: 15 * 1e3
3617
- // Refresh every 15 seconds
4057
+ refetchInterval: 15 * 1e3,
4058
+ retry: 1
3618
4059
  });
3619
4060
  return {
3620
4061
  quote: query.data,
3621
4062
  isLoading: query.isLoading,
3622
4063
  isError: query.isError,
4064
+ error: query.error,
3623
4065
  refetch: query.refetch
3624
4066
  };
3625
4067
  }
3626
- var DEFAULT_TOKENS2 = ["USDC", "ETH", "WETH", "WBTC", "DAI", "USDT"];
4068
+ var DEFAULT_TOKENS = ["USDC", "ETH", "WETH", "WBTC", "DAI", "USDT", "AUSD", "SBC"];
3627
4069
  function SwapWidget({
3628
4070
  layout = "full",
3629
4071
  defaultFromToken = "ETH",
3630
4072
  defaultToToken = "USDC",
3631
- allowedTokens = DEFAULT_TOKENS2,
4073
+ allowedTokens = DEFAULT_TOKENS,
3632
4074
  showReverseButton = true,
3633
4075
  showSettings = false,
3634
- showPriceImpact = true,
3635
4076
  onSwapSuccess,
3636
4077
  onSwapError
3637
4078
  }) {
@@ -3639,8 +4080,8 @@ function SwapWidget({
3639
4080
  const [toToken, setToToken] = useState(defaultToToken);
3640
4081
  const [fromAmount, setFromAmount] = useState("");
3641
4082
  const [isSwapping, setIsSwapping] = useState(false);
3642
- const { address, isConnected } = useCompassWallet();
3643
- const { client } = useEmbeddableApi();
4083
+ const [swapStatus, setSwapStatus] = useState("");
4084
+ const { address, isConnected, signTypedData } = useCompassWallet();
3644
4085
  const { chainId } = useChain();
3645
4086
  const { quote, isLoading: isQuoteLoading } = useSwapQuote({
3646
4087
  fromToken,
@@ -3656,32 +4097,75 @@ function SwapWidget({
3656
4097
  const handleSwap = useCallback(async () => {
3657
4098
  if (!address || !fromAmount || !quote) return;
3658
4099
  setIsSwapping(true);
4100
+ setSwapStatus("Preparing swap...");
3659
4101
  try {
3660
- const response = await client.earn.earnSwap({
3661
- chain: chainId,
3662
- sender: address,
3663
- tokenIn: fromToken,
3664
- tokenOut: toToken,
3665
- humanReadableAmountIn: fromAmount
4102
+ const prepareResponse = await fetch("/api/compass/swap/prepare", {
4103
+ method: "POST",
4104
+ headers: { "Content-Type": "application/json" },
4105
+ body: JSON.stringify({
4106
+ owner: address,
4107
+ chain: chainId,
4108
+ tokenIn: fromToken,
4109
+ tokenOut: toToken,
4110
+ amountIn: fromAmount
4111
+ })
4112
+ });
4113
+ if (!prepareResponse.ok) {
4114
+ const error = await prepareResponse.json();
4115
+ throw new Error(error.error || "Failed to prepare swap");
4116
+ }
4117
+ const prepareData = await prepareResponse.json();
4118
+ const { eip712, normalizedTypes } = prepareData;
4119
+ if (!eip712) {
4120
+ throw new Error("No EIP-712 data returned from prepare");
4121
+ }
4122
+ setSwapStatus("Please sign the transaction...");
4123
+ const signature = await signTypedData({
4124
+ domain: eip712.domain,
4125
+ types: normalizedTypes || eip712.types,
4126
+ primaryType: "SafeTx",
4127
+ message: eip712.message
4128
+ });
4129
+ setSwapStatus("Executing swap...");
4130
+ const executeResponse = await fetch("/api/compass/swap/execute", {
4131
+ method: "POST",
4132
+ headers: { "Content-Type": "application/json" },
4133
+ body: JSON.stringify({
4134
+ owner: address,
4135
+ chain: chainId,
4136
+ eip712,
4137
+ signature
4138
+ })
3666
4139
  });
3667
- console.log("Swap prepared:", response);
3668
- onSwapSuccess?.(fromToken, toToken, fromAmount, quote.outputAmount, "pending");
4140
+ if (!executeResponse.ok) {
4141
+ const error = await executeResponse.json();
4142
+ throw new Error(error.error || "Failed to execute swap");
4143
+ }
4144
+ const executeData = await executeResponse.json();
4145
+ const txHash = executeData.txHash;
4146
+ setSwapStatus("Swap successful!");
4147
+ onSwapSuccess?.(fromToken, toToken, fromAmount, quote.outputAmount, txHash);
3669
4148
  setFromAmount("");
4149
+ setTimeout(() => setSwapStatus(""), 3e3);
3670
4150
  } catch (error) {
3671
4151
  console.error("Swap failed:", error);
4152
+ setSwapStatus("");
3672
4153
  onSwapError?.(error);
3673
4154
  } finally {
3674
4155
  setIsSwapping(false);
3675
4156
  }
3676
- }, [address, fromAmount, quote, client, chainId, fromToken, toToken, onSwapSuccess, onSwapError]);
4157
+ }, [address, fromAmount, quote, chainId, fromToken, toToken, signTypedData, onSwapSuccess, onSwapError]);
3677
4158
  const isCompact = layout === "compact";
3678
4159
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
3679
4160
  !isCompact && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
3680
4161
  /* @__PURE__ */ jsx(ChainSwitcher, {}),
3681
- /* @__PURE__ */ jsx(WalletStatus, { compact: true })
4162
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4163
+ /* @__PURE__ */ jsx(EarnAccountBalance, { compact: true }),
4164
+ /* @__PURE__ */ jsx(WalletStatus, { compact: true })
4165
+ ] })
3682
4166
  ] }),
3683
4167
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
3684
- /* @__PURE__ */ jsxs("div", { className: "p-4 rounded-xl border", style: { backgroundColor: "var(--compass-color-surface)", borderColor: "var(--compass-color-border)" }, children: [
4168
+ /* @__PURE__ */ jsxs("div", { className: "p-4 rounded-xl border relative", style: { backgroundColor: "var(--compass-color-surface)", borderColor: "var(--compass-color-border)" }, children: [
3685
4169
  /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between mb-2", children: /* @__PURE__ */ jsx("span", { className: "text-xs", style: { color: "var(--compass-color-text-tertiary)" }, children: "From" }) }),
3686
4170
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3687
4171
  /* @__PURE__ */ jsx(
@@ -3691,7 +4175,7 @@ function SwapWidget({
3691
4175
  value: fromAmount,
3692
4176
  onChange: (e) => setFromAmount(e.target.value),
3693
4177
  placeholder: "0.00",
3694
- className: "flex-1 bg-transparent outline-none text-2xl font-mono",
4178
+ className: "flex-1 bg-transparent outline-none text-2xl font-mono min-w-0",
3695
4179
  style: { color: "var(--compass-color-text)" }
3696
4180
  }
3697
4181
  ),
@@ -3700,9 +4184,9 @@ function SwapWidget({
3700
4184
  {
3701
4185
  value: fromToken,
3702
4186
  onChange: (e) => setFromToken(e.target.value),
3703
- className: "px-3 py-2 rounded-lg border text-sm font-medium cursor-pointer",
4187
+ className: "px-3 py-2 rounded-lg border text-sm font-medium cursor-pointer flex-shrink-0",
3704
4188
  style: { backgroundColor: "var(--compass-color-background)", borderColor: "var(--compass-color-border)", color: "var(--compass-color-text)" },
3705
- children: allowedTokens.map((token) => /* @__PURE__ */ jsx("option", { value: token, children: token }, token))
4189
+ children: allowedTokens.filter((t) => t !== toToken).map((token) => /* @__PURE__ */ jsx("option", { value: token, children: token }, token))
3706
4190
  }
3707
4191
  )
3708
4192
  ] })
@@ -3722,7 +4206,7 @@ function SwapWidget({
3722
4206
  /* @__PURE__ */ jsx("div", { className: "flex-1 text-2xl font-mono", style: { color: isQuoteLoading ? "var(--compass-color-text-tertiary)" : "var(--compass-color-text)" }, children: isQuoteLoading ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
3723
4207
  /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }),
3724
4208
  "Loading..."
3725
- ] }) : quote?.outputAmount ? parseFloat(quote.outputAmount).toFixed(6) : "0.00" }),
4209
+ ] }) : quote?.outputAmount ? parseFloat(quote.outputAmount).toFixed(8) : "0.00000000" }),
3726
4210
  /* @__PURE__ */ jsx(
3727
4211
  "select",
3728
4212
  {
@@ -3736,14 +4220,6 @@ function SwapWidget({
3736
4220
  ] })
3737
4221
  ] })
3738
4222
  ] }),
3739
- showPriceImpact && quote && parseFloat(quote.priceImpact) > 0.01 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-3 rounded-lg", style: { backgroundColor: "var(--compass-color-warning-muted)" }, children: [
3740
- /* @__PURE__ */ jsx(AlertCircle, { size: 16, style: { color: "var(--compass-color-warning)" } }),
3741
- /* @__PURE__ */ jsxs("span", { className: "text-sm", style: { color: "var(--compass-color-warning)" }, children: [
3742
- "Price impact: ",
3743
- (parseFloat(quote.priceImpact) * 100).toFixed(2),
3744
- "%"
3745
- ] })
3746
- ] }),
3747
4223
  quote && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm", style: { color: "var(--compass-color-text-secondary)" }, children: [
3748
4224
  /* @__PURE__ */ jsx("span", { children: "Rate" }),
3749
4225
  /* @__PURE__ */ jsxs("span", { className: "font-mono", children: [
@@ -3755,6 +4231,20 @@ function SwapWidget({
3755
4231
  toToken
3756
4232
  ] })
3757
4233
  ] }),
4234
+ swapStatus && /* @__PURE__ */ jsxs(
4235
+ "div",
4236
+ {
4237
+ className: "flex items-center gap-2 p-3 rounded-lg text-sm",
4238
+ style: {
4239
+ backgroundColor: swapStatus.includes("successful") ? "var(--compass-color-success-muted)" : "var(--compass-color-surface)",
4240
+ color: swapStatus.includes("successful") ? "var(--compass-color-success)" : "var(--compass-color-text-secondary)"
4241
+ },
4242
+ children: [
4243
+ !swapStatus.includes("successful") && /* @__PURE__ */ jsx(Loader2, { size: 14, className: "animate-spin" }),
4244
+ swapStatus
4245
+ ]
4246
+ }
4247
+ ),
3758
4248
  !isConnected ? /* @__PURE__ */ jsxs(
3759
4249
  "div",
3760
4250
  {
@@ -3790,78 +4280,29 @@ function SwapWidget({
3790
4280
  )
3791
4281
  ] });
3792
4282
  }
3793
-
3794
- // src/components/CompassEarnWidget/presets.ts
3795
- var allTabs = [
4283
+ var tabs = [
3796
4284
  { id: "vaults", label: "Vaults", enabled: true },
3797
4285
  { id: "aave", label: "Aave", enabled: true },
3798
4286
  { id: "pendle", label: "Pendle", enabled: true },
3799
4287
  { id: "swap", label: "Swap", enabled: true }
3800
4288
  ];
3801
- function getTabsForPreset(preset) {
3802
- switch (preset) {
3803
- case "full":
3804
- return allTabs;
3805
- case "earn-only":
3806
- return allTabs.map((tab) => ({
3807
- ...tab,
3808
- enabled: tab.id !== "swap"
3809
- }));
3810
- case "swap-only":
3811
- return allTabs.map((tab) => ({
3812
- ...tab,
3813
- enabled: tab.id === "swap"
3814
- }));
3815
- case "vaults-only":
3816
- return allTabs.map((tab) => ({
3817
- ...tab,
3818
- enabled: tab.id === "vaults"
3819
- }));
3820
- default:
3821
- return allTabs;
3822
- }
3823
- }
3824
- function getDefaultTab(tabs) {
3825
- const enabledTab = tabs.find((t) => t.enabled);
3826
- return enabledTab?.id || "vaults";
3827
- }
3828
4289
  function CompassEarnWidget({
3829
- preset = "full",
3830
- enableVaults,
3831
- enableAave,
3832
- enablePendle,
3833
- enableSwap,
3834
- defaultTab,
3835
- showHeader = true,
4290
+ defaultTab = "vaults",
3836
4291
  onTabChange
3837
4292
  }) {
3838
- const tabs = useMemo(() => {
3839
- const baseTabs = getTabsForPreset(preset);
3840
- return baseTabs.map((tab) => {
3841
- let enabled = tab.enabled;
3842
- if (tab.id === "vaults" && enableVaults !== void 0) enabled = enableVaults;
3843
- if (tab.id === "aave" && enableAave !== void 0) enabled = enableAave;
3844
- if (tab.id === "pendle" && enablePendle !== void 0) enabled = enablePendle;
3845
- if (tab.id === "swap" && enableSwap !== void 0) enabled = enableSwap;
3846
- return { ...tab, enabled };
3847
- });
3848
- }, [preset, enableVaults, enableAave, enablePendle, enableSwap]);
3849
- const enabledTabs = tabs.filter((t) => t.enabled);
3850
- const initialTab = defaultTab && tabs.find((t) => t.id === defaultTab)?.enabled ? defaultTab : getDefaultTab(tabs);
3851
- const [activeTab, setActiveTab] = useState(initialTab);
4293
+ const [activeTab, setActiveTab] = useState(defaultTab);
3852
4294
  const handleTabChange = (tab) => {
3853
4295
  setActiveTab(tab);
3854
4296
  onTabChange?.(tab);
3855
4297
  };
3856
- const showTabs = enabledTabs.length > 1;
3857
4298
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
3858
- showHeader && /* @__PURE__ */ jsx("h1", { className: "font-bold text-xl", style: { color: "var(--compass-color-text)" }, children: "Compass Earn" }),
3859
- showTabs && /* @__PURE__ */ jsx(
4299
+ /* @__PURE__ */ jsx("h1", { className: "font-bold text-xl", style: { color: "var(--compass-color-text)" }, children: "Compass Earn" }),
4300
+ /* @__PURE__ */ jsx(
3860
4301
  "div",
3861
4302
  {
3862
4303
  className: "flex gap-1 p-1 rounded-lg",
3863
4304
  style: { backgroundColor: "var(--compass-color-surface)" },
3864
- children: enabledTabs.map((tab) => /* @__PURE__ */ jsx(
4305
+ children: tabs.map((tab) => /* @__PURE__ */ jsx(
3865
4306
  "button",
3866
4307
  {
3867
4308
  onClick: () => handleTabChange(tab.id),
@@ -3877,10 +4318,10 @@ function CompassEarnWidget({
3877
4318
  }
3878
4319
  ),
3879
4320
  /* @__PURE__ */ jsxs("div", { children: [
3880
- activeTab === "vaults" && tabs.find((t) => t.id === "vaults")?.enabled && /* @__PURE__ */ jsx(VaultsList, { showSearch: true, showSort: true }),
3881
- activeTab === "aave" && tabs.find((t) => t.id === "aave")?.enabled && /* @__PURE__ */ jsx(AaveMarketsList, { showSearch: true, showSort: true }),
3882
- activeTab === "pendle" && tabs.find((t) => t.id === "pendle")?.enabled && /* @__PURE__ */ jsx(PendleMarketsList, { showSearch: true, showSort: true }),
3883
- activeTab === "swap" && tabs.find((t) => t.id === "swap")?.enabled && /* @__PURE__ */ jsx(SwapWidget, { layout: "full" })
4321
+ activeTab === "vaults" && /* @__PURE__ */ jsx(VaultsList, {}),
4322
+ activeTab === "aave" && /* @__PURE__ */ jsx(AaveMarketsList, {}),
4323
+ activeTab === "pendle" && /* @__PURE__ */ jsx(PendleMarketsList, {}),
4324
+ activeTab === "swap" && /* @__PURE__ */ jsx(SwapWidget, {})
3884
4325
  ] })
3885
4326
  ] });
3886
4327
  }
@@ -3905,6 +4346,6 @@ var CHAINS = {
3905
4346
  }
3906
4347
  };
3907
4348
 
3908
- export { AaveMarketsList, ActionModal, ApiProvider, CHAINS, ChainSwitcher, CompassEarnWidget, CompassProvider, DepositWithdrawForm, EarnAccountBalance, EarnAccountGuard, PendleMarketsList, PnLSummary, SwapWidget, ThemeProvider, TransactionHistory, VaultsList, WalletStatus, themePresets, useAaveData, useChain, useCompassApi, useCompassChain, useCompassWallet, useEarnAccount, useEmbeddableApi, useEmbeddableWallet, usePendleData, useSwapQuote, useTheme, useVaultsData };
4349
+ export { AaveMarketsList, AccountBalancesModal, ActionModal, ApiProvider, CHAINS, ChainSwitcher, CompassEarnWidget, CompassProvider, DepositWithdrawForm, EarnAccountBalance, EarnAccountGuard, PendleMarketsList, PnLSummary, SwapWidget, ThemeProvider, TransactionHistory, VaultsList, WalletStatus, themePresets, useAaveData, useChain, useCompassApi, useCompassChain, useCompassWallet, useEarnAccount, useEmbeddableApi, useEmbeddableWallet, usePendleData, useSwapQuote, useTheme, useVaultsData };
3909
4350
  //# sourceMappingURL=index.mjs.map
3910
4351
  //# sourceMappingURL=index.mjs.map