@agether/sdk 2.4.0 → 2.6.0

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/cli.js CHANGED
@@ -274,7 +274,7 @@ var MorphoClient_exports = {};
274
274
  __export(MorphoClient_exports, {
275
275
  MorphoClient: () => MorphoClient
276
276
  });
277
- var import_ethers, import_axios, BASE_COLLATERALS, MORPHO_API_URL, MODE_SINGLE, MODE_BATCH, morphoIface, erc20Iface, MorphoClient;
277
+ var import_ethers, import_axios, MORPHO_API_URL, MODE_SINGLE, MODE_BATCH, morphoIface, erc20Iface, MorphoClient;
278
278
  var init_MorphoClient = __esm({
279
279
  "src/clients/MorphoClient.ts"() {
280
280
  "use strict";
@@ -283,11 +283,6 @@ var init_MorphoClient = __esm({
283
283
  init_types();
284
284
  init_abis();
285
285
  init_config();
286
- BASE_COLLATERALS = {
287
- WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
288
- wstETH: { address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", symbol: "wstETH", decimals: 18 },
289
- cbETH: { address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", symbol: "cbETH", decimals: 18 }
290
- };
291
286
  MORPHO_API_URL = "https://api.morpho.org/graphql";
292
287
  MODE_SINGLE = "0x0000000000000000000000000000000000000000000000000000000000000000";
293
288
  MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
@@ -296,6 +291,8 @@ var init_MorphoClient = __esm({
296
291
  MorphoClient = class {
297
292
  constructor(config) {
298
293
  this._marketCache = /* @__PURE__ */ new Map();
294
+ /** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
295
+ this._tokenCache = /* @__PURE__ */ new Map();
299
296
  this._discoveredAt = 0;
300
297
  const chainId = config.chainId ?? 8453 /* Base */;
301
298
  const defaultCfg = getDefaultConfig(chainId);
@@ -482,15 +479,16 @@ var init_MorphoClient = __esm({
482
479
  const usdc = new import_ethers.Contract(this.config.contracts.usdc, ERC20_ABI, this.provider);
483
480
  const ethBal = await this.provider.getBalance(eoaAddr);
484
481
  const usdcBal = await usdc.balanceOf(eoaAddr);
482
+ const discoveredTokens = await this._getDiscoveredTokens();
485
483
  const eoaCollateral = {};
486
- for (const [symbol, info] of Object.entries(BASE_COLLATERALS)) {
484
+ for (const info of discoveredTokens) {
487
485
  try {
488
486
  const token = new import_ethers.Contract(info.address, ERC20_ABI, this.provider);
489
487
  const bal = await token.balanceOf(eoaAddr);
490
- eoaCollateral[symbol] = import_ethers.ethers.formatUnits(bal, info.decimals);
488
+ eoaCollateral[info.symbol] = import_ethers.ethers.formatUnits(bal, info.decimals);
491
489
  } catch (e) {
492
- console.warn(`[agether] EOA collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
493
- eoaCollateral[symbol] = "0";
490
+ console.warn(`[agether] EOA collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
491
+ eoaCollateral[info.symbol] = "0";
494
492
  }
495
493
  }
496
494
  const result = {
@@ -505,14 +503,14 @@ var init_MorphoClient = __esm({
505
503
  const acctEth = await this.provider.getBalance(acctAddr);
506
504
  const acctUsdc = await usdc.balanceOf(acctAddr);
507
505
  const acctCollateral = {};
508
- for (const [symbol, info] of Object.entries(BASE_COLLATERALS)) {
506
+ for (const info of discoveredTokens) {
509
507
  try {
510
508
  const token = new import_ethers.Contract(info.address, ERC20_ABI, this.provider);
511
509
  const bal = await token.balanceOf(acctAddr);
512
- acctCollateral[symbol] = import_ethers.ethers.formatUnits(bal, info.decimals);
510
+ acctCollateral[info.symbol] = import_ethers.ethers.formatUnits(bal, info.decimals);
513
511
  } catch (e) {
514
- console.warn(`[agether] AgentAccount collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
515
- acctCollateral[symbol] = "0";
512
+ console.warn(`[agether] AgentAccount collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
513
+ acctCollateral[info.symbol] = "0";
516
514
  }
517
515
  }
518
516
  result.agentAccount = {
@@ -539,6 +537,50 @@ var init_MorphoClient = __esm({
539
537
  this._refreshSigner();
540
538
  return { tx: receipt.hash, amount: usdcAmount, agentAccount: acctAddr };
541
539
  }
540
+ /**
541
+ * Withdraw (transfer) a token from AgentAccount to EOA.
542
+ * Executes an ERC-20 transfer via Safe UserOp.
543
+ *
544
+ * @param tokenSymbol - Token to withdraw (e.g. 'USDC', 'WETH', 'wstETH')
545
+ * @param amount - Amount to withdraw (human-readable, e.g. '100' for 100 USDC, or 'all')
546
+ */
547
+ async withdrawToken(tokenSymbol, amount) {
548
+ const acctAddr = await this.getAccountAddress();
549
+ const eoaAddr = await this.getSignerAddress();
550
+ const tokenInfo = await this._resolveToken(tokenSymbol);
551
+ const tokenContract = new import_ethers.Contract(tokenInfo.address, ERC20_ABI, this.provider);
552
+ let weiAmount;
553
+ if (amount === "all") {
554
+ weiAmount = await tokenContract.balanceOf(acctAddr);
555
+ if (weiAmount === 0n) throw new AgetherError(`No ${tokenSymbol} in AgentAccount`, "INSUFFICIENT_BALANCE");
556
+ } else {
557
+ weiAmount = import_ethers.ethers.parseUnits(amount, tokenInfo.decimals);
558
+ }
559
+ const data = erc20Iface.encodeFunctionData("transfer", [eoaAddr, weiAmount]);
560
+ const receipt = await this.exec(tokenInfo.address, data);
561
+ const actualAmount = amount === "all" ? import_ethers.ethers.formatUnits(weiAmount, tokenInfo.decimals) : amount;
562
+ return { tx: receipt.hash, token: tokenSymbol, amount: actualAmount, destination: eoaAddr };
563
+ }
564
+ /**
565
+ * Withdraw ETH from AgentAccount to EOA.
566
+ * Executes a native ETH transfer via Safe UserOp.
567
+ *
568
+ * @param amount - ETH amount (e.g. '0.01' or 'all')
569
+ */
570
+ async withdrawEth(amount) {
571
+ const acctAddr = await this.getAccountAddress();
572
+ const eoaAddr = await this.getSignerAddress();
573
+ let weiAmount;
574
+ if (amount === "all") {
575
+ weiAmount = await this.provider.getBalance(acctAddr);
576
+ if (weiAmount === 0n) throw new AgetherError("No ETH in AgentAccount", "INSUFFICIENT_BALANCE");
577
+ } else {
578
+ weiAmount = import_ethers.ethers.parseEther(amount);
579
+ }
580
+ const receipt = await this.exec(eoaAddr, "0x", weiAmount);
581
+ const actualAmount = amount === "all" ? import_ethers.ethers.formatEther(weiAmount) : amount;
582
+ return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: eoaAddr };
583
+ }
542
584
  // ════════════════════════════════════════════════════════
543
585
  // Market Discovery (Morpho GraphQL API)
544
586
  // ════════════════════════════════════════════════════════
@@ -598,6 +640,28 @@ var init_MorphoClient = __esm({
598
640
  irm: mi.irm,
599
641
  lltv: mi.lltv
600
642
  });
643
+ this._tokenCache.set(mi.collateralAsset.symbol.toUpperCase(), {
644
+ address: mi.collateralAsset.address,
645
+ symbol: mi.collateralAsset.symbol,
646
+ decimals: mi.collateralAsset.decimals
647
+ });
648
+ this._tokenCache.set(mi.collateralAsset.address.toLowerCase(), {
649
+ address: mi.collateralAsset.address,
650
+ symbol: mi.collateralAsset.symbol,
651
+ decimals: mi.collateralAsset.decimals
652
+ });
653
+ }
654
+ if (mi.loanAsset.address !== import_ethers.ethers.ZeroAddress) {
655
+ this._tokenCache.set(mi.loanAsset.symbol.toUpperCase(), {
656
+ address: mi.loanAsset.address,
657
+ symbol: mi.loanAsset.symbol,
658
+ decimals: mi.loanAsset.decimals
659
+ });
660
+ this._tokenCache.set(mi.loanAsset.address.toLowerCase(), {
661
+ address: mi.loanAsset.address,
662
+ symbol: mi.loanAsset.symbol,
663
+ decimals: mi.loanAsset.decimals
664
+ });
601
665
  }
602
666
  }
603
667
  return this._discoveredMarkets;
@@ -611,8 +675,17 @@ var init_MorphoClient = __esm({
611
675
  * Tries cache → API → onchain idToMarketParams.
612
676
  */
613
677
  async findMarketForCollateral(collateralSymbolOrAddress) {
614
- const colInfo = BASE_COLLATERALS[collateralSymbolOrAddress];
615
- const colAddr = (colInfo?.address ?? collateralSymbolOrAddress).toLowerCase();
678
+ let colAddr;
679
+ if (collateralSymbolOrAddress.startsWith("0x")) {
680
+ colAddr = collateralSymbolOrAddress.toLowerCase();
681
+ } else {
682
+ try {
683
+ const resolved = await this._resolveToken(collateralSymbolOrAddress);
684
+ colAddr = resolved.address.toLowerCase();
685
+ } catch {
686
+ colAddr = collateralSymbolOrAddress.toLowerCase();
687
+ }
688
+ }
616
689
  const cached = this._marketCache.get(colAddr);
617
690
  if (cached) return cached;
618
691
  await this.getMarkets();
@@ -771,8 +844,17 @@ var init_MorphoClient = __esm({
771
844
  const usdcAddr = this.config.contracts.usdc.toLowerCase();
772
845
  let collateralFilter = "";
773
846
  if (collateralSymbolOrAddress) {
774
- const colInfo = BASE_COLLATERALS[collateralSymbolOrAddress];
775
- const colAddr = (colInfo?.address ?? collateralSymbolOrAddress).toLowerCase();
847
+ let colAddr;
848
+ if (collateralSymbolOrAddress.startsWith("0x")) {
849
+ colAddr = collateralSymbolOrAddress.toLowerCase();
850
+ } else {
851
+ try {
852
+ const resolved = await this._resolveToken(collateralSymbolOrAddress);
853
+ colAddr = resolved.address.toLowerCase();
854
+ } catch {
855
+ colAddr = collateralSymbolOrAddress.toLowerCase();
856
+ }
857
+ }
776
858
  collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
777
859
  }
778
860
  const query = `{
@@ -831,8 +913,7 @@ var init_MorphoClient = __esm({
831
913
  * @returns Estimated yield in USD for the period
832
914
  */
833
915
  async getYieldEstimate(collateralSymbol, amount, periodDays = 1, ethPriceUsd) {
834
- const colInfo = BASE_COLLATERALS[collateralSymbol];
835
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${collateralSymbol}`, "UNKNOWN_COLLATERAL");
916
+ const colInfo = await this._resolveToken(collateralSymbol);
836
917
  const rates = await this.getMarketRates(collateralSymbol);
837
918
  if (rates.length === 0) {
838
919
  throw new AgetherError(`No market found for ${collateralSymbol}`, "MARKET_NOT_FOUND");
@@ -1134,8 +1215,7 @@ var init_MorphoClient = __esm({
1134
1215
  */
1135
1216
  async supplyCollateral(tokenSymbol, amount, marketParams) {
1136
1217
  const acctAddr = await this.getAccountAddress();
1137
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1138
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1218
+ const colInfo = await this._resolveToken(tokenSymbol);
1139
1219
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1140
1220
  const weiAmount = import_ethers.ethers.parseUnits(amount, colInfo.decimals);
1141
1221
  const morphoAddr = this.config.contracts.morphoBlue;
@@ -1256,8 +1336,7 @@ var init_MorphoClient = __esm({
1256
1336
  */
1257
1337
  async depositAndBorrow(tokenSymbol, collateralAmount, borrowUsdcAmount, marketParams) {
1258
1338
  const acctAddr = await this.getAccountAddress();
1259
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1260
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1339
+ const colInfo = await this._resolveToken(tokenSymbol);
1261
1340
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1262
1341
  const colWei = import_ethers.ethers.parseUnits(collateralAmount, colInfo.decimals);
1263
1342
  const borrowWei = import_ethers.ethers.parseUnits(borrowUsdcAmount, 6);
@@ -1425,8 +1504,7 @@ var init_MorphoClient = __esm({
1425
1504
  */
1426
1505
  async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
1427
1506
  const acctAddr = await this.getAccountAddress();
1428
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1429
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1507
+ const colInfo = await this._resolveToken(tokenSymbol);
1430
1508
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1431
1509
  const morphoAddr = this.config.contracts.morphoBlue;
1432
1510
  const usdcAddr = this.config.contracts.usdc;
@@ -1523,8 +1601,7 @@ var init_MorphoClient = __esm({
1523
1601
  * (The agent must then supplyCollateral themselves via their own account.)
1524
1602
  */
1525
1603
  async sponsor(target, tokenSymbol, amount) {
1526
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1527
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1604
+ const colInfo = await this._resolveToken(tokenSymbol);
1528
1605
  let targetAddr;
1529
1606
  if (target.address) {
1530
1607
  targetAddr = target.address;
@@ -1780,6 +1857,43 @@ var init_MorphoClient = __esm({
1780
1857
  }
1781
1858
  throw new AgetherError("No active supply position found", "NO_SUPPLY");
1782
1859
  }
1860
+ /**
1861
+ * Resolve a token symbol or address to { address, symbol, decimals }.
1862
+ *
1863
+ * Uses the dynamic `_tokenCache` populated by `getMarkets()` from the
1864
+ * Morpho GraphQL API — no hardcoded token list needed.
1865
+ *
1866
+ * @param symbolOrAddress - e.g. 'WETH', 'wstETH', or '0x4200...'
1867
+ */
1868
+ async _resolveToken(symbolOrAddress) {
1869
+ const key = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
1870
+ const cached = this._tokenCache.get(key);
1871
+ if (cached) return cached;
1872
+ await this.getMarkets();
1873
+ const fromApi = this._tokenCache.get(key);
1874
+ if (fromApi) return fromApi;
1875
+ throw new AgetherError(
1876
+ `Unknown token: ${symbolOrAddress}. No Morpho market found with this collateral.`,
1877
+ "UNKNOWN_COLLATERAL"
1878
+ );
1879
+ }
1880
+ /**
1881
+ * Get all discovered collateral tokens (for balance iteration, etc.).
1882
+ * Returns unique tokens from the Morpho API market discovery.
1883
+ */
1884
+ async _getDiscoveredTokens() {
1885
+ await this.getMarkets();
1886
+ const seen = /* @__PURE__ */ new Set();
1887
+ const tokens = [];
1888
+ for (const [key, info] of this._tokenCache) {
1889
+ if (key.startsWith("0x")) continue;
1890
+ if (seen.has(info.address.toLowerCase())) continue;
1891
+ seen.add(info.address.toLowerCase());
1892
+ if (info.symbol === "USDC" || info.symbol === "USDbC") continue;
1893
+ tokens.push(info);
1894
+ }
1895
+ return tokens;
1896
+ }
1783
1897
  /**
1784
1898
  * Compute net deposited amounts per market using Morpho GraphQL API.
1785
1899
  *
@@ -1928,7 +2042,9 @@ var init_X402Client = __esm({
1928
2042
  this.paidFetch = (0, import_fetch.wrapFetchWithPayment)(fetch, client);
1929
2043
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1930
2044
  const dailyLimit = config.dailySpendLimitUsdc ? BigInt(Math.round(parseFloat(config.dailySpendLimitUsdc) * 1e6)) : 0n;
1931
- this._spendingTracker = { date: today, totalBorrowed: 0n, dailyLimit };
2045
+ const restored = config.initialSpendingState;
2046
+ const restoredBorrowed = restored && restored.date === today ? BigInt(restored.totalBorrowed) : 0n;
2047
+ this._spendingTracker = { date: today, totalBorrowed: restoredBorrowed, dailyLimit };
1932
2048
  }
1933
2049
  async get(url, opts) {
1934
2050
  return this.request(url, { ...opts, method: "GET" });
@@ -1957,66 +2073,122 @@ var init_X402Client = __esm({
1957
2073
  return (Number(remaining > 0n ? remaining : 0n) / 1e6).toFixed(2);
1958
2074
  }
1959
2075
  /**
1960
- * Pay with auto-draw: Make an x402 request with automatic Morpho borrowing.
2076
+ * Pay with auto-funding: Make an x402 request with automatic USDC sourcing.
1961
2077
  *
1962
- * Flow:
1963
- * 1. Check USDC balance on AgentAccount
1964
- * 2. Probe the URL to discover payment amount (if 402)
1965
- * 3. If insufficient USDC, calculate deficit
1966
- * 4. Check spending limit
1967
- * 5. Borrow from Morpho via MorphoClient
1968
- * 6. Proceed with x402 payment
2078
+ * Uses a **plan-then-execute** approach: all reads happen first, and if the
2079
+ * full deficit can't be covered the method fails with NO side-effects
2080
+ * (no yield withdrawn, no USDC borrowed).
2081
+ *
2082
+ * Waterfall (when both autoYield + autoDraw enabled):
2083
+ * 1. Check USDC balance on AgentAccount
2084
+ * 2. Probe the URL to discover payment amount (if 402)
2085
+ * 3. If insufficient USDC — PLANNING PHASE (read-only):
2086
+ * a. Calculate total available yield across supply positions
2087
+ * b. Calculate max borrowable from Morpho markets
2088
+ * c. Verify yield + borrow can cover full deficit — if not, fail immediately
2089
+ * 4. EXECUTION PHASE (only if plan is feasible):
2090
+ * a. Withdraw needed yield from supply positions
2091
+ * b. Borrow remaining deficit from Morpho
2092
+ * 5. Proceed with x402 payment
1969
2093
  */
1970
2094
  async payWithAutoDraw(url, opts) {
1971
2095
  const { morphoClient, ...fetchOpts } = opts || {};
1972
- if (!this.config.autoDraw || !morphoClient) {
2096
+ if (!this.config.autoDraw && !this.config.autoYield || !morphoClient) {
1973
2097
  return this.request(url, fetchOpts);
1974
2098
  }
1975
2099
  try {
1976
2100
  const usdcBalance = await morphoClient.getUsdcBalance();
1977
- console.log(` [auto-draw] AgentAccount USDC balance: ${(Number(usdcBalance) / 1e6).toFixed(2)}`);
2101
+ console.log(` [auto-fund] AgentAccount USDC balance: ${(Number(usdcBalance) / 1e6).toFixed(2)}`);
1978
2102
  const paymentAmount = await this._probePaymentAmount(url, fetchOpts);
1979
2103
  if (paymentAmount !== null) {
1980
- console.log(` [auto-draw] Payment required: ${(Number(paymentAmount) / 1e6).toFixed(6)} USDC`);
2104
+ console.log(` [auto-fund] Payment required: ${(Number(paymentAmount) / 1e6).toFixed(6)} USDC`);
1981
2105
  const bufferStr = this.config.autoDrawBuffer || "0.5";
1982
2106
  const buffer = BigInt(Math.round(parseFloat(bufferStr) * 1e6));
1983
2107
  const needed = paymentAmount + buffer;
1984
2108
  if (usdcBalance < needed) {
1985
- const deficit = needed - usdcBalance;
1986
- console.log(` [auto-draw] Insufficient balance. Need to borrow ${(Number(deficit) / 1e6).toFixed(2)} USDC`);
1987
- const limitCheck = await this._checkSpendingLimit(deficit, morphoClient);
1988
- if (!limitCheck.allowed) {
1989
- return {
1990
- success: false,
1991
- error: `Auto-draw blocked: ${limitCheck.reason}`
1992
- };
2109
+ const totalDeficit = needed - usdcBalance;
2110
+ console.log(` [auto-fund] Insufficient balance. Deficit: ${(Number(totalDeficit) / 1e6).toFixed(2)} USDC`);
2111
+ const yieldPlan = [];
2112
+ let totalYieldAvailable = 0n;
2113
+ if (this.config.autoYield) {
2114
+ try {
2115
+ const positions = await morphoClient.getSupplyPositions();
2116
+ const withYield = positions.filter((p) => parseFloat(p.earnedYield) > 1e-6).sort((a, b) => parseFloat(b.earnedYield) - parseFloat(a.earnedYield));
2117
+ for (const pos of withYield) {
2118
+ const yieldRaw = BigInt(Math.floor(parseFloat(pos.earnedYield) * 1e6));
2119
+ if (yieldRaw > 0n) {
2120
+ yieldPlan.push({ collateralToken: pos.collateralToken, amount: yieldRaw });
2121
+ totalYieldAvailable += yieldRaw;
2122
+ }
2123
+ }
2124
+ console.log(` [auto-fund] Plan: ${(Number(totalYieldAvailable) / 1e6).toFixed(6)} USDC available from yield`);
2125
+ } catch (e) {
2126
+ console.warn(` [auto-fund] Plan: yield check failed: ${e instanceof Error ? e.message : String(e)}`);
2127
+ }
1993
2128
  }
1994
- const maxBorrowable = await morphoClient.getMaxBorrowable();
1995
- if (maxBorrowable.total < deficit) {
2129
+ const yieldToUse = totalYieldAvailable < totalDeficit ? totalYieldAvailable : totalDeficit;
2130
+ const deficitAfterYield = totalDeficit - yieldToUse;
2131
+ let canBorrow = 0n;
2132
+ if (this.config.autoDraw && deficitAfterYield > 0n) {
2133
+ const limitCheck = await this._checkSpendingLimit(deficitAfterYield, morphoClient);
2134
+ if (!limitCheck.allowed) {
2135
+ return { success: false, error: `Auto-fund blocked: ${limitCheck.reason}` };
2136
+ }
2137
+ const maxBorrowable = await morphoClient.getMaxBorrowable();
2138
+ canBorrow = maxBorrowable.total;
2139
+ console.log(` [auto-fund] Plan: ${(Number(canBorrow) / 1e6).toFixed(2)} USDC borrowable from Morpho`);
2140
+ }
2141
+ const totalAvailable = yieldToUse + canBorrow;
2142
+ if (totalAvailable < totalDeficit) {
2143
+ const parts = [];
2144
+ if (totalYieldAvailable > 0n) parts.push(`yield: $${(Number(totalYieldAvailable) / 1e6).toFixed(2)}`);
2145
+ if (canBorrow > 0n) parts.push(`borrowable: $${(Number(canBorrow) / 1e6).toFixed(2)}`);
2146
+ if (!this.config.autoYield) parts.push("autoYield: off");
2147
+ if (!this.config.autoDraw) parts.push("autoDraw: off");
1996
2148
  return {
1997
2149
  success: false,
1998
- error: `Auto-draw failed: insufficient collateral. Need ${(Number(deficit) / 1e6).toFixed(2)} USDC but can only borrow ${(Number(maxBorrowable.total) / 1e6).toFixed(2)} USDC more.`
2150
+ error: `Cannot cover deficit of $${(Number(totalDeficit) / 1e6).toFixed(2)} USDC. Available: ${parts.join(", ")}. Balance: $${(Number(usdcBalance) / 1e6).toFixed(2)}, needed: $${(Number(needed) / 1e6).toFixed(2)}.`
1999
2151
  };
2000
2152
  }
2001
- const borrowAmount = (Number(deficit) / 1e6).toFixed(6);
2002
- console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
2003
- const borrowResult = await morphoClient.borrow(borrowAmount);
2004
- console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
2005
- this._trackSpending(deficit);
2006
- const result = await this.request(url, fetchOpts);
2007
- return {
2008
- ...result,
2009
- autoDrawInfo: {
2010
- borrowed: borrowAmount,
2011
- borrowTx: borrowResult.tx,
2012
- reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2013
- }
2153
+ const drawInfo = {
2154
+ reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2014
2155
  };
2156
+ if (yieldToUse > 0n) {
2157
+ let remaining = yieldToUse;
2158
+ let totalWithdrawn = 0n;
2159
+ let lastTx = "";
2160
+ for (const plan of yieldPlan) {
2161
+ if (remaining <= 0n) break;
2162
+ const toWithdraw = plan.amount < remaining ? plan.amount : remaining;
2163
+ const withdrawStr = (Number(toWithdraw) / 1e6).toFixed(6);
2164
+ console.log(` [auto-yield] Withdrawing $${withdrawStr} yield from ${plan.collateralToken} market`);
2165
+ const wr = await morphoClient.withdrawSupply(withdrawStr, plan.collateralToken);
2166
+ lastTx = wr.tx;
2167
+ totalWithdrawn += toWithdraw;
2168
+ remaining -= toWithdraw;
2169
+ }
2170
+ if (totalWithdrawn > 0n) {
2171
+ drawInfo.yieldWithdrawn = (Number(totalWithdrawn) / 1e6).toFixed(6);
2172
+ drawInfo.yieldTx = lastTx;
2173
+ console.log(` [auto-yield] Withdrawn: $${drawInfo.yieldWithdrawn}`);
2174
+ }
2175
+ }
2176
+ if (deficitAfterYield > 0n && this.config.autoDraw) {
2177
+ const borrowAmount = (Number(deficitAfterYield) / 1e6).toFixed(6);
2178
+ console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
2179
+ const borrowResult = await morphoClient.borrow(borrowAmount);
2180
+ console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
2181
+ drawInfo.borrowed = borrowAmount;
2182
+ drawInfo.borrowTx = borrowResult.tx;
2183
+ this._trackSpending(deficitAfterYield);
2184
+ }
2185
+ const result = await this.request(url, fetchOpts);
2186
+ return { ...result, autoDrawInfo: drawInfo };
2015
2187
  }
2016
2188
  }
2017
2189
  return this.request(url, fetchOpts);
2018
2190
  } catch (error) {
2019
- console.log(` [auto-draw] Auto-draw check failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
2191
+ console.log(` [auto-fund] Failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
2020
2192
  return this.request(url, fetchOpts);
2021
2193
  }
2022
2194
  }
@@ -2117,48 +2289,26 @@ var init_X402Client = __esm({
2117
2289
  };
2118
2290
  }
2119
2291
  }
2120
- /** Track a new spending amount */
2292
+ /** Track a new spending amount and notify the caller for persistence */
2121
2293
  _trackSpending(amount) {
2122
2294
  this._resetTrackerIfNewDay();
2123
2295
  this._spendingTracker.totalBorrowed += amount;
2124
- }
2125
- /**
2126
- * Check if a borrow amount is within spending limits.
2127
- * Considers both fixed daily limits and yield-limited spending.
2128
- */
2129
- async _checkSpendingLimit(amount, morphoClient) {
2130
- this._resetTrackerIfNewDay();
2131
- if (this.config.yieldLimitedSpending) {
2296
+ if (this.config.onSpendingUpdate) {
2132
2297
  try {
2133
- const status = await morphoClient.getStatus();
2134
- let totalDailyYieldUsdc = 0;
2135
- for (const pos of status.positions) {
2136
- if (parseFloat(pos.collateral) > 0) {
2137
- try {
2138
- const estimate = await morphoClient.getYieldEstimate(
2139
- pos.collateralToken,
2140
- pos.collateral,
2141
- 1
2142
- // 1 day
2143
- );
2144
- totalDailyYieldUsdc += estimate.estimatedYieldUsd;
2145
- } catch (e) {
2146
- console.warn(`[agether] yield calc failed for ${pos.collateralToken}:`, e instanceof Error ? e.message : e);
2147
- }
2148
- }
2149
- }
2150
- const yieldLimit = BigInt(Math.round(totalDailyYieldUsdc * 1e6));
2151
- const newTotal = this._spendingTracker.totalBorrowed + amount;
2152
- if (yieldLimit > 0n && newTotal > yieldLimit) {
2153
- return {
2154
- allowed: false,
2155
- reason: `Yield-limited spending exceeded. Daily yield cap: $${(Number(yieldLimit) / 1e6).toFixed(2)}, already spent: $${(Number(this._spendingTracker.totalBorrowed) / 1e6).toFixed(2)}, requested: $${(Number(amount) / 1e6).toFixed(2)}`
2156
- };
2157
- }
2298
+ this.config.onSpendingUpdate({
2299
+ date: this._spendingTracker.date,
2300
+ totalBorrowed: this._spendingTracker.totalBorrowed.toString()
2301
+ });
2158
2302
  } catch (e) {
2159
- console.warn("[agether] yield-limited spending check failed, falling through to fixed limit:", e instanceof Error ? e.message : e);
2303
+ console.warn("[agether] onSpendingUpdate callback failed:", e instanceof Error ? e.message : e);
2160
2304
  }
2161
2305
  }
2306
+ }
2307
+ /**
2308
+ * Check if a borrow amount is within the fixed daily spending limit.
2309
+ */
2310
+ async _checkSpendingLimit(amount, _morphoClient) {
2311
+ this._resetTrackerIfNewDay();
2162
2312
  if (this._spendingTracker.dailyLimit > 0n) {
2163
2313
  const newTotal = this._spendingTracker.totalBorrowed + amount;
2164
2314
  if (newTotal > this._spendingTracker.dailyLimit) {