@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/index.js CHANGED
@@ -532,11 +532,6 @@ var AgetherClient = class _AgetherClient {
532
532
  // src/clients/MorphoClient.ts
533
533
  var import_ethers2 = require("ethers");
534
534
  var import_axios = __toESM(require("axios"));
535
- var BASE_COLLATERALS = {
536
- WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
537
- wstETH: { address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", symbol: "wstETH", decimals: 18 },
538
- cbETH: { address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", symbol: "cbETH", decimals: 18 }
539
- };
540
535
  var MORPHO_API_URL = "https://api.morpho.org/graphql";
541
536
  var MODE_SINGLE = "0x0000000000000000000000000000000000000000000000000000000000000000";
542
537
  var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
@@ -545,6 +540,8 @@ var erc20Iface = new import_ethers2.ethers.Interface(ERC20_ABI);
545
540
  var MorphoClient = class {
546
541
  constructor(config) {
547
542
  this._marketCache = /* @__PURE__ */ new Map();
543
+ /** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
544
+ this._tokenCache = /* @__PURE__ */ new Map();
548
545
  this._discoveredAt = 0;
549
546
  const chainId = config.chainId ?? 8453 /* Base */;
550
547
  const defaultCfg = getDefaultConfig(chainId);
@@ -731,15 +728,16 @@ var MorphoClient = class {
731
728
  const usdc = new import_ethers2.Contract(this.config.contracts.usdc, ERC20_ABI, this.provider);
732
729
  const ethBal = await this.provider.getBalance(eoaAddr);
733
730
  const usdcBal = await usdc.balanceOf(eoaAddr);
731
+ const discoveredTokens = await this._getDiscoveredTokens();
734
732
  const eoaCollateral = {};
735
- for (const [symbol, info] of Object.entries(BASE_COLLATERALS)) {
733
+ for (const info of discoveredTokens) {
736
734
  try {
737
735
  const token = new import_ethers2.Contract(info.address, ERC20_ABI, this.provider);
738
736
  const bal = await token.balanceOf(eoaAddr);
739
- eoaCollateral[symbol] = import_ethers2.ethers.formatUnits(bal, info.decimals);
737
+ eoaCollateral[info.symbol] = import_ethers2.ethers.formatUnits(bal, info.decimals);
740
738
  } catch (e) {
741
- console.warn(`[agether] EOA collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
742
- eoaCollateral[symbol] = "0";
739
+ console.warn(`[agether] EOA collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
740
+ eoaCollateral[info.symbol] = "0";
743
741
  }
744
742
  }
745
743
  const result = {
@@ -754,14 +752,14 @@ var MorphoClient = class {
754
752
  const acctEth = await this.provider.getBalance(acctAddr);
755
753
  const acctUsdc = await usdc.balanceOf(acctAddr);
756
754
  const acctCollateral = {};
757
- for (const [symbol, info] of Object.entries(BASE_COLLATERALS)) {
755
+ for (const info of discoveredTokens) {
758
756
  try {
759
757
  const token = new import_ethers2.Contract(info.address, ERC20_ABI, this.provider);
760
758
  const bal = await token.balanceOf(acctAddr);
761
- acctCollateral[symbol] = import_ethers2.ethers.formatUnits(bal, info.decimals);
759
+ acctCollateral[info.symbol] = import_ethers2.ethers.formatUnits(bal, info.decimals);
762
760
  } catch (e) {
763
- console.warn(`[agether] AgentAccount collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
764
- acctCollateral[symbol] = "0";
761
+ console.warn(`[agether] AgentAccount collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
762
+ acctCollateral[info.symbol] = "0";
765
763
  }
766
764
  }
767
765
  result.agentAccount = {
@@ -788,6 +786,50 @@ var MorphoClient = class {
788
786
  this._refreshSigner();
789
787
  return { tx: receipt.hash, amount: usdcAmount, agentAccount: acctAddr };
790
788
  }
789
+ /**
790
+ * Withdraw (transfer) a token from AgentAccount to EOA.
791
+ * Executes an ERC-20 transfer via Safe UserOp.
792
+ *
793
+ * @param tokenSymbol - Token to withdraw (e.g. 'USDC', 'WETH', 'wstETH')
794
+ * @param amount - Amount to withdraw (human-readable, e.g. '100' for 100 USDC, or 'all')
795
+ */
796
+ async withdrawToken(tokenSymbol, amount) {
797
+ const acctAddr = await this.getAccountAddress();
798
+ const eoaAddr = await this.getSignerAddress();
799
+ const tokenInfo = await this._resolveToken(tokenSymbol);
800
+ const tokenContract = new import_ethers2.Contract(tokenInfo.address, ERC20_ABI, this.provider);
801
+ let weiAmount;
802
+ if (amount === "all") {
803
+ weiAmount = await tokenContract.balanceOf(acctAddr);
804
+ if (weiAmount === 0n) throw new AgetherError(`No ${tokenSymbol} in AgentAccount`, "INSUFFICIENT_BALANCE");
805
+ } else {
806
+ weiAmount = import_ethers2.ethers.parseUnits(amount, tokenInfo.decimals);
807
+ }
808
+ const data = erc20Iface.encodeFunctionData("transfer", [eoaAddr, weiAmount]);
809
+ const receipt = await this.exec(tokenInfo.address, data);
810
+ const actualAmount = amount === "all" ? import_ethers2.ethers.formatUnits(weiAmount, tokenInfo.decimals) : amount;
811
+ return { tx: receipt.hash, token: tokenSymbol, amount: actualAmount, destination: eoaAddr };
812
+ }
813
+ /**
814
+ * Withdraw ETH from AgentAccount to EOA.
815
+ * Executes a native ETH transfer via Safe UserOp.
816
+ *
817
+ * @param amount - ETH amount (e.g. '0.01' or 'all')
818
+ */
819
+ async withdrawEth(amount) {
820
+ const acctAddr = await this.getAccountAddress();
821
+ const eoaAddr = await this.getSignerAddress();
822
+ let weiAmount;
823
+ if (amount === "all") {
824
+ weiAmount = await this.provider.getBalance(acctAddr);
825
+ if (weiAmount === 0n) throw new AgetherError("No ETH in AgentAccount", "INSUFFICIENT_BALANCE");
826
+ } else {
827
+ weiAmount = import_ethers2.ethers.parseEther(amount);
828
+ }
829
+ const receipt = await this.exec(eoaAddr, "0x", weiAmount);
830
+ const actualAmount = amount === "all" ? import_ethers2.ethers.formatEther(weiAmount) : amount;
831
+ return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: eoaAddr };
832
+ }
791
833
  // ════════════════════════════════════════════════════════
792
834
  // Market Discovery (Morpho GraphQL API)
793
835
  // ════════════════════════════════════════════════════════
@@ -847,6 +889,28 @@ var MorphoClient = class {
847
889
  irm: mi.irm,
848
890
  lltv: mi.lltv
849
891
  });
892
+ this._tokenCache.set(mi.collateralAsset.symbol.toUpperCase(), {
893
+ address: mi.collateralAsset.address,
894
+ symbol: mi.collateralAsset.symbol,
895
+ decimals: mi.collateralAsset.decimals
896
+ });
897
+ this._tokenCache.set(mi.collateralAsset.address.toLowerCase(), {
898
+ address: mi.collateralAsset.address,
899
+ symbol: mi.collateralAsset.symbol,
900
+ decimals: mi.collateralAsset.decimals
901
+ });
902
+ }
903
+ if (mi.loanAsset.address !== import_ethers2.ethers.ZeroAddress) {
904
+ this._tokenCache.set(mi.loanAsset.symbol.toUpperCase(), {
905
+ address: mi.loanAsset.address,
906
+ symbol: mi.loanAsset.symbol,
907
+ decimals: mi.loanAsset.decimals
908
+ });
909
+ this._tokenCache.set(mi.loanAsset.address.toLowerCase(), {
910
+ address: mi.loanAsset.address,
911
+ symbol: mi.loanAsset.symbol,
912
+ decimals: mi.loanAsset.decimals
913
+ });
850
914
  }
851
915
  }
852
916
  return this._discoveredMarkets;
@@ -860,8 +924,17 @@ var MorphoClient = class {
860
924
  * Tries cache → API → onchain idToMarketParams.
861
925
  */
862
926
  async findMarketForCollateral(collateralSymbolOrAddress) {
863
- const colInfo = BASE_COLLATERALS[collateralSymbolOrAddress];
864
- const colAddr = (colInfo?.address ?? collateralSymbolOrAddress).toLowerCase();
927
+ let colAddr;
928
+ if (collateralSymbolOrAddress.startsWith("0x")) {
929
+ colAddr = collateralSymbolOrAddress.toLowerCase();
930
+ } else {
931
+ try {
932
+ const resolved = await this._resolveToken(collateralSymbolOrAddress);
933
+ colAddr = resolved.address.toLowerCase();
934
+ } catch {
935
+ colAddr = collateralSymbolOrAddress.toLowerCase();
936
+ }
937
+ }
865
938
  const cached = this._marketCache.get(colAddr);
866
939
  if (cached) return cached;
867
940
  await this.getMarkets();
@@ -1020,8 +1093,17 @@ var MorphoClient = class {
1020
1093
  const usdcAddr = this.config.contracts.usdc.toLowerCase();
1021
1094
  let collateralFilter = "";
1022
1095
  if (collateralSymbolOrAddress) {
1023
- const colInfo = BASE_COLLATERALS[collateralSymbolOrAddress];
1024
- const colAddr = (colInfo?.address ?? collateralSymbolOrAddress).toLowerCase();
1096
+ let colAddr;
1097
+ if (collateralSymbolOrAddress.startsWith("0x")) {
1098
+ colAddr = collateralSymbolOrAddress.toLowerCase();
1099
+ } else {
1100
+ try {
1101
+ const resolved = await this._resolveToken(collateralSymbolOrAddress);
1102
+ colAddr = resolved.address.toLowerCase();
1103
+ } catch {
1104
+ colAddr = collateralSymbolOrAddress.toLowerCase();
1105
+ }
1106
+ }
1025
1107
  collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
1026
1108
  }
1027
1109
  const query = `{
@@ -1080,8 +1162,7 @@ var MorphoClient = class {
1080
1162
  * @returns Estimated yield in USD for the period
1081
1163
  */
1082
1164
  async getYieldEstimate(collateralSymbol, amount, periodDays = 1, ethPriceUsd) {
1083
- const colInfo = BASE_COLLATERALS[collateralSymbol];
1084
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${collateralSymbol}`, "UNKNOWN_COLLATERAL");
1165
+ const colInfo = await this._resolveToken(collateralSymbol);
1085
1166
  const rates = await this.getMarketRates(collateralSymbol);
1086
1167
  if (rates.length === 0) {
1087
1168
  throw new AgetherError(`No market found for ${collateralSymbol}`, "MARKET_NOT_FOUND");
@@ -1383,8 +1464,7 @@ var MorphoClient = class {
1383
1464
  */
1384
1465
  async supplyCollateral(tokenSymbol, amount, marketParams) {
1385
1466
  const acctAddr = await this.getAccountAddress();
1386
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1387
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1467
+ const colInfo = await this._resolveToken(tokenSymbol);
1388
1468
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1389
1469
  const weiAmount = import_ethers2.ethers.parseUnits(amount, colInfo.decimals);
1390
1470
  const morphoAddr = this.config.contracts.morphoBlue;
@@ -1505,8 +1585,7 @@ var MorphoClient = class {
1505
1585
  */
1506
1586
  async depositAndBorrow(tokenSymbol, collateralAmount, borrowUsdcAmount, marketParams) {
1507
1587
  const acctAddr = await this.getAccountAddress();
1508
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1509
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1588
+ const colInfo = await this._resolveToken(tokenSymbol);
1510
1589
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1511
1590
  const colWei = import_ethers2.ethers.parseUnits(collateralAmount, colInfo.decimals);
1512
1591
  const borrowWei = import_ethers2.ethers.parseUnits(borrowUsdcAmount, 6);
@@ -1674,8 +1753,7 @@ var MorphoClient = class {
1674
1753
  */
1675
1754
  async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
1676
1755
  const acctAddr = await this.getAccountAddress();
1677
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1678
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1756
+ const colInfo = await this._resolveToken(tokenSymbol);
1679
1757
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1680
1758
  const morphoAddr = this.config.contracts.morphoBlue;
1681
1759
  const usdcAddr = this.config.contracts.usdc;
@@ -1772,8 +1850,7 @@ var MorphoClient = class {
1772
1850
  * (The agent must then supplyCollateral themselves via their own account.)
1773
1851
  */
1774
1852
  async sponsor(target, tokenSymbol, amount) {
1775
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1776
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1853
+ const colInfo = await this._resolveToken(tokenSymbol);
1777
1854
  let targetAddr;
1778
1855
  if (target.address) {
1779
1856
  targetAddr = target.address;
@@ -2029,6 +2106,43 @@ var MorphoClient = class {
2029
2106
  }
2030
2107
  throw new AgetherError("No active supply position found", "NO_SUPPLY");
2031
2108
  }
2109
+ /**
2110
+ * Resolve a token symbol or address to { address, symbol, decimals }.
2111
+ *
2112
+ * Uses the dynamic `_tokenCache` populated by `getMarkets()` from the
2113
+ * Morpho GraphQL API — no hardcoded token list needed.
2114
+ *
2115
+ * @param symbolOrAddress - e.g. 'WETH', 'wstETH', or '0x4200...'
2116
+ */
2117
+ async _resolveToken(symbolOrAddress) {
2118
+ const key = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
2119
+ const cached = this._tokenCache.get(key);
2120
+ if (cached) return cached;
2121
+ await this.getMarkets();
2122
+ const fromApi = this._tokenCache.get(key);
2123
+ if (fromApi) return fromApi;
2124
+ throw new AgetherError(
2125
+ `Unknown token: ${symbolOrAddress}. No Morpho market found with this collateral.`,
2126
+ "UNKNOWN_COLLATERAL"
2127
+ );
2128
+ }
2129
+ /**
2130
+ * Get all discovered collateral tokens (for balance iteration, etc.).
2131
+ * Returns unique tokens from the Morpho API market discovery.
2132
+ */
2133
+ async _getDiscoveredTokens() {
2134
+ await this.getMarkets();
2135
+ const seen = /* @__PURE__ */ new Set();
2136
+ const tokens = [];
2137
+ for (const [key, info] of this._tokenCache) {
2138
+ if (key.startsWith("0x")) continue;
2139
+ if (seen.has(info.address.toLowerCase())) continue;
2140
+ seen.add(info.address.toLowerCase());
2141
+ if (info.symbol === "USDC" || info.symbol === "USDbC") continue;
2142
+ tokens.push(info);
2143
+ }
2144
+ return tokens;
2145
+ }
2032
2146
  /**
2033
2147
  * Compute net deposited amounts per market using Morpho GraphQL API.
2034
2148
  *
@@ -2170,7 +2284,9 @@ var X402Client = class {
2170
2284
  this.paidFetch = (0, import_fetch.wrapFetchWithPayment)(fetch, client);
2171
2285
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2172
2286
  const dailyLimit = config.dailySpendLimitUsdc ? BigInt(Math.round(parseFloat(config.dailySpendLimitUsdc) * 1e6)) : 0n;
2173
- this._spendingTracker = { date: today, totalBorrowed: 0n, dailyLimit };
2287
+ const restored = config.initialSpendingState;
2288
+ const restoredBorrowed = restored && restored.date === today ? BigInt(restored.totalBorrowed) : 0n;
2289
+ this._spendingTracker = { date: today, totalBorrowed: restoredBorrowed, dailyLimit };
2174
2290
  }
2175
2291
  async get(url, opts) {
2176
2292
  return this.request(url, { ...opts, method: "GET" });
@@ -2199,66 +2315,122 @@ var X402Client = class {
2199
2315
  return (Number(remaining > 0n ? remaining : 0n) / 1e6).toFixed(2);
2200
2316
  }
2201
2317
  /**
2202
- * Pay with auto-draw: Make an x402 request with automatic Morpho borrowing.
2318
+ * Pay with auto-funding: Make an x402 request with automatic USDC sourcing.
2203
2319
  *
2204
- * Flow:
2205
- * 1. Check USDC balance on AgentAccount
2206
- * 2. Probe the URL to discover payment amount (if 402)
2207
- * 3. If insufficient USDC, calculate deficit
2208
- * 4. Check spending limit
2209
- * 5. Borrow from Morpho via MorphoClient
2210
- * 6. Proceed with x402 payment
2320
+ * Uses a **plan-then-execute** approach: all reads happen first, and if the
2321
+ * full deficit can't be covered the method fails with NO side-effects
2322
+ * (no yield withdrawn, no USDC borrowed).
2323
+ *
2324
+ * Waterfall (when both autoYield + autoDraw enabled):
2325
+ * 1. Check USDC balance on AgentAccount
2326
+ * 2. Probe the URL to discover payment amount (if 402)
2327
+ * 3. If insufficient USDC — PLANNING PHASE (read-only):
2328
+ * a. Calculate total available yield across supply positions
2329
+ * b. Calculate max borrowable from Morpho markets
2330
+ * c. Verify yield + borrow can cover full deficit — if not, fail immediately
2331
+ * 4. EXECUTION PHASE (only if plan is feasible):
2332
+ * a. Withdraw needed yield from supply positions
2333
+ * b. Borrow remaining deficit from Morpho
2334
+ * 5. Proceed with x402 payment
2211
2335
  */
2212
2336
  async payWithAutoDraw(url, opts) {
2213
2337
  const { morphoClient, ...fetchOpts } = opts || {};
2214
- if (!this.config.autoDraw || !morphoClient) {
2338
+ if (!this.config.autoDraw && !this.config.autoYield || !morphoClient) {
2215
2339
  return this.request(url, fetchOpts);
2216
2340
  }
2217
2341
  try {
2218
2342
  const usdcBalance = await morphoClient.getUsdcBalance();
2219
- console.log(` [auto-draw] AgentAccount USDC balance: ${(Number(usdcBalance) / 1e6).toFixed(2)}`);
2343
+ console.log(` [auto-fund] AgentAccount USDC balance: ${(Number(usdcBalance) / 1e6).toFixed(2)}`);
2220
2344
  const paymentAmount = await this._probePaymentAmount(url, fetchOpts);
2221
2345
  if (paymentAmount !== null) {
2222
- console.log(` [auto-draw] Payment required: ${(Number(paymentAmount) / 1e6).toFixed(6)} USDC`);
2346
+ console.log(` [auto-fund] Payment required: ${(Number(paymentAmount) / 1e6).toFixed(6)} USDC`);
2223
2347
  const bufferStr = this.config.autoDrawBuffer || "0.5";
2224
2348
  const buffer = BigInt(Math.round(parseFloat(bufferStr) * 1e6));
2225
2349
  const needed = paymentAmount + buffer;
2226
2350
  if (usdcBalance < needed) {
2227
- const deficit = needed - usdcBalance;
2228
- console.log(` [auto-draw] Insufficient balance. Need to borrow ${(Number(deficit) / 1e6).toFixed(2)} USDC`);
2229
- const limitCheck = await this._checkSpendingLimit(deficit, morphoClient);
2230
- if (!limitCheck.allowed) {
2231
- return {
2232
- success: false,
2233
- error: `Auto-draw blocked: ${limitCheck.reason}`
2234
- };
2351
+ const totalDeficit = needed - usdcBalance;
2352
+ console.log(` [auto-fund] Insufficient balance. Deficit: ${(Number(totalDeficit) / 1e6).toFixed(2)} USDC`);
2353
+ const yieldPlan = [];
2354
+ let totalYieldAvailable = 0n;
2355
+ if (this.config.autoYield) {
2356
+ try {
2357
+ const positions = await morphoClient.getSupplyPositions();
2358
+ const withYield = positions.filter((p) => parseFloat(p.earnedYield) > 1e-6).sort((a, b) => parseFloat(b.earnedYield) - parseFloat(a.earnedYield));
2359
+ for (const pos of withYield) {
2360
+ const yieldRaw = BigInt(Math.floor(parseFloat(pos.earnedYield) * 1e6));
2361
+ if (yieldRaw > 0n) {
2362
+ yieldPlan.push({ collateralToken: pos.collateralToken, amount: yieldRaw });
2363
+ totalYieldAvailable += yieldRaw;
2364
+ }
2365
+ }
2366
+ console.log(` [auto-fund] Plan: ${(Number(totalYieldAvailable) / 1e6).toFixed(6)} USDC available from yield`);
2367
+ } catch (e) {
2368
+ console.warn(` [auto-fund] Plan: yield check failed: ${e instanceof Error ? e.message : String(e)}`);
2369
+ }
2235
2370
  }
2236
- const maxBorrowable = await morphoClient.getMaxBorrowable();
2237
- if (maxBorrowable.total < deficit) {
2371
+ const yieldToUse = totalYieldAvailable < totalDeficit ? totalYieldAvailable : totalDeficit;
2372
+ const deficitAfterYield = totalDeficit - yieldToUse;
2373
+ let canBorrow = 0n;
2374
+ if (this.config.autoDraw && deficitAfterYield > 0n) {
2375
+ const limitCheck = await this._checkSpendingLimit(deficitAfterYield, morphoClient);
2376
+ if (!limitCheck.allowed) {
2377
+ return { success: false, error: `Auto-fund blocked: ${limitCheck.reason}` };
2378
+ }
2379
+ const maxBorrowable = await morphoClient.getMaxBorrowable();
2380
+ canBorrow = maxBorrowable.total;
2381
+ console.log(` [auto-fund] Plan: ${(Number(canBorrow) / 1e6).toFixed(2)} USDC borrowable from Morpho`);
2382
+ }
2383
+ const totalAvailable = yieldToUse + canBorrow;
2384
+ if (totalAvailable < totalDeficit) {
2385
+ const parts = [];
2386
+ if (totalYieldAvailable > 0n) parts.push(`yield: $${(Number(totalYieldAvailable) / 1e6).toFixed(2)}`);
2387
+ if (canBorrow > 0n) parts.push(`borrowable: $${(Number(canBorrow) / 1e6).toFixed(2)}`);
2388
+ if (!this.config.autoYield) parts.push("autoYield: off");
2389
+ if (!this.config.autoDraw) parts.push("autoDraw: off");
2238
2390
  return {
2239
2391
  success: false,
2240
- 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.`
2392
+ 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)}.`
2241
2393
  };
2242
2394
  }
2243
- const borrowAmount = (Number(deficit) / 1e6).toFixed(6);
2244
- console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
2245
- const borrowResult = await morphoClient.borrow(borrowAmount);
2246
- console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
2247
- this._trackSpending(deficit);
2248
- const result = await this.request(url, fetchOpts);
2249
- return {
2250
- ...result,
2251
- autoDrawInfo: {
2252
- borrowed: borrowAmount,
2253
- borrowTx: borrowResult.tx,
2254
- reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2255
- }
2395
+ const drawInfo = {
2396
+ reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2256
2397
  };
2398
+ if (yieldToUse > 0n) {
2399
+ let remaining = yieldToUse;
2400
+ let totalWithdrawn = 0n;
2401
+ let lastTx = "";
2402
+ for (const plan of yieldPlan) {
2403
+ if (remaining <= 0n) break;
2404
+ const toWithdraw = plan.amount < remaining ? plan.amount : remaining;
2405
+ const withdrawStr = (Number(toWithdraw) / 1e6).toFixed(6);
2406
+ console.log(` [auto-yield] Withdrawing $${withdrawStr} yield from ${plan.collateralToken} market`);
2407
+ const wr = await morphoClient.withdrawSupply(withdrawStr, plan.collateralToken);
2408
+ lastTx = wr.tx;
2409
+ totalWithdrawn += toWithdraw;
2410
+ remaining -= toWithdraw;
2411
+ }
2412
+ if (totalWithdrawn > 0n) {
2413
+ drawInfo.yieldWithdrawn = (Number(totalWithdrawn) / 1e6).toFixed(6);
2414
+ drawInfo.yieldTx = lastTx;
2415
+ console.log(` [auto-yield] Withdrawn: $${drawInfo.yieldWithdrawn}`);
2416
+ }
2417
+ }
2418
+ if (deficitAfterYield > 0n && this.config.autoDraw) {
2419
+ const borrowAmount = (Number(deficitAfterYield) / 1e6).toFixed(6);
2420
+ console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
2421
+ const borrowResult = await morphoClient.borrow(borrowAmount);
2422
+ console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
2423
+ drawInfo.borrowed = borrowAmount;
2424
+ drawInfo.borrowTx = borrowResult.tx;
2425
+ this._trackSpending(deficitAfterYield);
2426
+ }
2427
+ const result = await this.request(url, fetchOpts);
2428
+ return { ...result, autoDrawInfo: drawInfo };
2257
2429
  }
2258
2430
  }
2259
2431
  return this.request(url, fetchOpts);
2260
2432
  } catch (error) {
2261
- console.log(` [auto-draw] Auto-draw check failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
2433
+ console.log(` [auto-fund] Failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
2262
2434
  return this.request(url, fetchOpts);
2263
2435
  }
2264
2436
  }
@@ -2359,48 +2531,26 @@ var X402Client = class {
2359
2531
  };
2360
2532
  }
2361
2533
  }
2362
- /** Track a new spending amount */
2534
+ /** Track a new spending amount and notify the caller for persistence */
2363
2535
  _trackSpending(amount) {
2364
2536
  this._resetTrackerIfNewDay();
2365
2537
  this._spendingTracker.totalBorrowed += amount;
2366
- }
2367
- /**
2368
- * Check if a borrow amount is within spending limits.
2369
- * Considers both fixed daily limits and yield-limited spending.
2370
- */
2371
- async _checkSpendingLimit(amount, morphoClient) {
2372
- this._resetTrackerIfNewDay();
2373
- if (this.config.yieldLimitedSpending) {
2538
+ if (this.config.onSpendingUpdate) {
2374
2539
  try {
2375
- const status = await morphoClient.getStatus();
2376
- let totalDailyYieldUsdc = 0;
2377
- for (const pos of status.positions) {
2378
- if (parseFloat(pos.collateral) > 0) {
2379
- try {
2380
- const estimate = await morphoClient.getYieldEstimate(
2381
- pos.collateralToken,
2382
- pos.collateral,
2383
- 1
2384
- // 1 day
2385
- );
2386
- totalDailyYieldUsdc += estimate.estimatedYieldUsd;
2387
- } catch (e) {
2388
- console.warn(`[agether] yield calc failed for ${pos.collateralToken}:`, e instanceof Error ? e.message : e);
2389
- }
2390
- }
2391
- }
2392
- const yieldLimit = BigInt(Math.round(totalDailyYieldUsdc * 1e6));
2393
- const newTotal = this._spendingTracker.totalBorrowed + amount;
2394
- if (yieldLimit > 0n && newTotal > yieldLimit) {
2395
- return {
2396
- allowed: false,
2397
- 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)}`
2398
- };
2399
- }
2540
+ this.config.onSpendingUpdate({
2541
+ date: this._spendingTracker.date,
2542
+ totalBorrowed: this._spendingTracker.totalBorrowed.toString()
2543
+ });
2400
2544
  } catch (e) {
2401
- console.warn("[agether] yield-limited spending check failed, falling through to fixed limit:", e instanceof Error ? e.message : e);
2545
+ console.warn("[agether] onSpendingUpdate callback failed:", e instanceof Error ? e.message : e);
2402
2546
  }
2403
2547
  }
2548
+ }
2549
+ /**
2550
+ * Check if a borrow amount is within the fixed daily spending limit.
2551
+ */
2552
+ async _checkSpendingLimit(amount, _morphoClient) {
2553
+ this._resetTrackerIfNewDay();
2404
2554
  if (this._spendingTracker.dailyLimit > 0n) {
2405
2555
  const newTotal = this._spendingTracker.totalBorrowed + amount;
2406
2556
  if (newTotal > this._spendingTracker.dailyLimit) {