@compass-labs/widgets 0.1.1 → 0.1.3

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