@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.mjs CHANGED
@@ -460,11 +460,6 @@ var AgetherClient = class _AgetherClient {
460
460
  // src/clients/MorphoClient.ts
461
461
  import { ethers as ethers2, Contract as Contract2 } from "ethers";
462
462
  import axios from "axios";
463
- var BASE_COLLATERALS = {
464
- WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
465
- wstETH: { address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", symbol: "wstETH", decimals: 18 },
466
- cbETH: { address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", symbol: "cbETH", decimals: 18 }
467
- };
468
463
  var MORPHO_API_URL = "https://api.morpho.org/graphql";
469
464
  var MODE_SINGLE = "0x0000000000000000000000000000000000000000000000000000000000000000";
470
465
  var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
@@ -473,6 +468,8 @@ var erc20Iface = new ethers2.Interface(ERC20_ABI);
473
468
  var MorphoClient = class {
474
469
  constructor(config) {
475
470
  this._marketCache = /* @__PURE__ */ new Map();
471
+ /** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
472
+ this._tokenCache = /* @__PURE__ */ new Map();
476
473
  this._discoveredAt = 0;
477
474
  const chainId = config.chainId ?? 8453 /* Base */;
478
475
  const defaultCfg = getDefaultConfig(chainId);
@@ -659,15 +656,16 @@ var MorphoClient = class {
659
656
  const usdc = new Contract2(this.config.contracts.usdc, ERC20_ABI, this.provider);
660
657
  const ethBal = await this.provider.getBalance(eoaAddr);
661
658
  const usdcBal = await usdc.balanceOf(eoaAddr);
659
+ const discoveredTokens = await this._getDiscoveredTokens();
662
660
  const eoaCollateral = {};
663
- for (const [symbol, info] of Object.entries(BASE_COLLATERALS)) {
661
+ for (const info of discoveredTokens) {
664
662
  try {
665
663
  const token = new Contract2(info.address, ERC20_ABI, this.provider);
666
664
  const bal = await token.balanceOf(eoaAddr);
667
- eoaCollateral[symbol] = ethers2.formatUnits(bal, info.decimals);
665
+ eoaCollateral[info.symbol] = ethers2.formatUnits(bal, info.decimals);
668
666
  } catch (e) {
669
- console.warn(`[agether] EOA collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
670
- eoaCollateral[symbol] = "0";
667
+ console.warn(`[agether] EOA collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
668
+ eoaCollateral[info.symbol] = "0";
671
669
  }
672
670
  }
673
671
  const result = {
@@ -682,14 +680,14 @@ var MorphoClient = class {
682
680
  const acctEth = await this.provider.getBalance(acctAddr);
683
681
  const acctUsdc = await usdc.balanceOf(acctAddr);
684
682
  const acctCollateral = {};
685
- for (const [symbol, info] of Object.entries(BASE_COLLATERALS)) {
683
+ for (const info of discoveredTokens) {
686
684
  try {
687
685
  const token = new Contract2(info.address, ERC20_ABI, this.provider);
688
686
  const bal = await token.balanceOf(acctAddr);
689
- acctCollateral[symbol] = ethers2.formatUnits(bal, info.decimals);
687
+ acctCollateral[info.symbol] = ethers2.formatUnits(bal, info.decimals);
690
688
  } catch (e) {
691
- console.warn(`[agether] AgentAccount collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
692
- acctCollateral[symbol] = "0";
689
+ console.warn(`[agether] AgentAccount collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
690
+ acctCollateral[info.symbol] = "0";
693
691
  }
694
692
  }
695
693
  result.agentAccount = {
@@ -716,6 +714,50 @@ var MorphoClient = class {
716
714
  this._refreshSigner();
717
715
  return { tx: receipt.hash, amount: usdcAmount, agentAccount: acctAddr };
718
716
  }
717
+ /**
718
+ * Withdraw (transfer) a token from AgentAccount to EOA.
719
+ * Executes an ERC-20 transfer via Safe UserOp.
720
+ *
721
+ * @param tokenSymbol - Token to withdraw (e.g. 'USDC', 'WETH', 'wstETH')
722
+ * @param amount - Amount to withdraw (human-readable, e.g. '100' for 100 USDC, or 'all')
723
+ */
724
+ async withdrawToken(tokenSymbol, amount) {
725
+ const acctAddr = await this.getAccountAddress();
726
+ const eoaAddr = await this.getSignerAddress();
727
+ const tokenInfo = await this._resolveToken(tokenSymbol);
728
+ const tokenContract = new Contract2(tokenInfo.address, ERC20_ABI, this.provider);
729
+ let weiAmount;
730
+ if (amount === "all") {
731
+ weiAmount = await tokenContract.balanceOf(acctAddr);
732
+ if (weiAmount === 0n) throw new AgetherError(`No ${tokenSymbol} in AgentAccount`, "INSUFFICIENT_BALANCE");
733
+ } else {
734
+ weiAmount = ethers2.parseUnits(amount, tokenInfo.decimals);
735
+ }
736
+ const data = erc20Iface.encodeFunctionData("transfer", [eoaAddr, weiAmount]);
737
+ const receipt = await this.exec(tokenInfo.address, data);
738
+ const actualAmount = amount === "all" ? ethers2.formatUnits(weiAmount, tokenInfo.decimals) : amount;
739
+ return { tx: receipt.hash, token: tokenSymbol, amount: actualAmount, destination: eoaAddr };
740
+ }
741
+ /**
742
+ * Withdraw ETH from AgentAccount to EOA.
743
+ * Executes a native ETH transfer via Safe UserOp.
744
+ *
745
+ * @param amount - ETH amount (e.g. '0.01' or 'all')
746
+ */
747
+ async withdrawEth(amount) {
748
+ const acctAddr = await this.getAccountAddress();
749
+ const eoaAddr = await this.getSignerAddress();
750
+ let weiAmount;
751
+ if (amount === "all") {
752
+ weiAmount = await this.provider.getBalance(acctAddr);
753
+ if (weiAmount === 0n) throw new AgetherError("No ETH in AgentAccount", "INSUFFICIENT_BALANCE");
754
+ } else {
755
+ weiAmount = ethers2.parseEther(amount);
756
+ }
757
+ const receipt = await this.exec(eoaAddr, "0x", weiAmount);
758
+ const actualAmount = amount === "all" ? ethers2.formatEther(weiAmount) : amount;
759
+ return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: eoaAddr };
760
+ }
719
761
  // ════════════════════════════════════════════════════════
720
762
  // Market Discovery (Morpho GraphQL API)
721
763
  // ════════════════════════════════════════════════════════
@@ -775,6 +817,28 @@ var MorphoClient = class {
775
817
  irm: mi.irm,
776
818
  lltv: mi.lltv
777
819
  });
820
+ this._tokenCache.set(mi.collateralAsset.symbol.toUpperCase(), {
821
+ address: mi.collateralAsset.address,
822
+ symbol: mi.collateralAsset.symbol,
823
+ decimals: mi.collateralAsset.decimals
824
+ });
825
+ this._tokenCache.set(mi.collateralAsset.address.toLowerCase(), {
826
+ address: mi.collateralAsset.address,
827
+ symbol: mi.collateralAsset.symbol,
828
+ decimals: mi.collateralAsset.decimals
829
+ });
830
+ }
831
+ if (mi.loanAsset.address !== ethers2.ZeroAddress) {
832
+ this._tokenCache.set(mi.loanAsset.symbol.toUpperCase(), {
833
+ address: mi.loanAsset.address,
834
+ symbol: mi.loanAsset.symbol,
835
+ decimals: mi.loanAsset.decimals
836
+ });
837
+ this._tokenCache.set(mi.loanAsset.address.toLowerCase(), {
838
+ address: mi.loanAsset.address,
839
+ symbol: mi.loanAsset.symbol,
840
+ decimals: mi.loanAsset.decimals
841
+ });
778
842
  }
779
843
  }
780
844
  return this._discoveredMarkets;
@@ -788,8 +852,17 @@ var MorphoClient = class {
788
852
  * Tries cache → API → onchain idToMarketParams.
789
853
  */
790
854
  async findMarketForCollateral(collateralSymbolOrAddress) {
791
- const colInfo = BASE_COLLATERALS[collateralSymbolOrAddress];
792
- const colAddr = (colInfo?.address ?? collateralSymbolOrAddress).toLowerCase();
855
+ let colAddr;
856
+ if (collateralSymbolOrAddress.startsWith("0x")) {
857
+ colAddr = collateralSymbolOrAddress.toLowerCase();
858
+ } else {
859
+ try {
860
+ const resolved = await this._resolveToken(collateralSymbolOrAddress);
861
+ colAddr = resolved.address.toLowerCase();
862
+ } catch {
863
+ colAddr = collateralSymbolOrAddress.toLowerCase();
864
+ }
865
+ }
793
866
  const cached = this._marketCache.get(colAddr);
794
867
  if (cached) return cached;
795
868
  await this.getMarkets();
@@ -948,8 +1021,17 @@ var MorphoClient = class {
948
1021
  const usdcAddr = this.config.contracts.usdc.toLowerCase();
949
1022
  let collateralFilter = "";
950
1023
  if (collateralSymbolOrAddress) {
951
- const colInfo = BASE_COLLATERALS[collateralSymbolOrAddress];
952
- const colAddr = (colInfo?.address ?? collateralSymbolOrAddress).toLowerCase();
1024
+ let colAddr;
1025
+ if (collateralSymbolOrAddress.startsWith("0x")) {
1026
+ colAddr = collateralSymbolOrAddress.toLowerCase();
1027
+ } else {
1028
+ try {
1029
+ const resolved = await this._resolveToken(collateralSymbolOrAddress);
1030
+ colAddr = resolved.address.toLowerCase();
1031
+ } catch {
1032
+ colAddr = collateralSymbolOrAddress.toLowerCase();
1033
+ }
1034
+ }
953
1035
  collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
954
1036
  }
955
1037
  const query = `{
@@ -1008,8 +1090,7 @@ var MorphoClient = class {
1008
1090
  * @returns Estimated yield in USD for the period
1009
1091
  */
1010
1092
  async getYieldEstimate(collateralSymbol, amount, periodDays = 1, ethPriceUsd) {
1011
- const colInfo = BASE_COLLATERALS[collateralSymbol];
1012
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${collateralSymbol}`, "UNKNOWN_COLLATERAL");
1093
+ const colInfo = await this._resolveToken(collateralSymbol);
1013
1094
  const rates = await this.getMarketRates(collateralSymbol);
1014
1095
  if (rates.length === 0) {
1015
1096
  throw new AgetherError(`No market found for ${collateralSymbol}`, "MARKET_NOT_FOUND");
@@ -1311,8 +1392,7 @@ var MorphoClient = class {
1311
1392
  */
1312
1393
  async supplyCollateral(tokenSymbol, amount, marketParams) {
1313
1394
  const acctAddr = await this.getAccountAddress();
1314
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1315
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1395
+ const colInfo = await this._resolveToken(tokenSymbol);
1316
1396
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1317
1397
  const weiAmount = ethers2.parseUnits(amount, colInfo.decimals);
1318
1398
  const morphoAddr = this.config.contracts.morphoBlue;
@@ -1433,8 +1513,7 @@ var MorphoClient = class {
1433
1513
  */
1434
1514
  async depositAndBorrow(tokenSymbol, collateralAmount, borrowUsdcAmount, marketParams) {
1435
1515
  const acctAddr = await this.getAccountAddress();
1436
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1437
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1516
+ const colInfo = await this._resolveToken(tokenSymbol);
1438
1517
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1439
1518
  const colWei = ethers2.parseUnits(collateralAmount, colInfo.decimals);
1440
1519
  const borrowWei = ethers2.parseUnits(borrowUsdcAmount, 6);
@@ -1602,8 +1681,7 @@ var MorphoClient = class {
1602
1681
  */
1603
1682
  async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
1604
1683
  const acctAddr = await this.getAccountAddress();
1605
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1606
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1684
+ const colInfo = await this._resolveToken(tokenSymbol);
1607
1685
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1608
1686
  const morphoAddr = this.config.contracts.morphoBlue;
1609
1687
  const usdcAddr = this.config.contracts.usdc;
@@ -1700,8 +1778,7 @@ var MorphoClient = class {
1700
1778
  * (The agent must then supplyCollateral themselves via their own account.)
1701
1779
  */
1702
1780
  async sponsor(target, tokenSymbol, amount) {
1703
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1704
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1781
+ const colInfo = await this._resolveToken(tokenSymbol);
1705
1782
  let targetAddr;
1706
1783
  if (target.address) {
1707
1784
  targetAddr = target.address;
@@ -1957,6 +2034,43 @@ var MorphoClient = class {
1957
2034
  }
1958
2035
  throw new AgetherError("No active supply position found", "NO_SUPPLY");
1959
2036
  }
2037
+ /**
2038
+ * Resolve a token symbol or address to { address, symbol, decimals }.
2039
+ *
2040
+ * Uses the dynamic `_tokenCache` populated by `getMarkets()` from the
2041
+ * Morpho GraphQL API — no hardcoded token list needed.
2042
+ *
2043
+ * @param symbolOrAddress - e.g. 'WETH', 'wstETH', or '0x4200...'
2044
+ */
2045
+ async _resolveToken(symbolOrAddress) {
2046
+ const key = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
2047
+ const cached = this._tokenCache.get(key);
2048
+ if (cached) return cached;
2049
+ await this.getMarkets();
2050
+ const fromApi = this._tokenCache.get(key);
2051
+ if (fromApi) return fromApi;
2052
+ throw new AgetherError(
2053
+ `Unknown token: ${symbolOrAddress}. No Morpho market found with this collateral.`,
2054
+ "UNKNOWN_COLLATERAL"
2055
+ );
2056
+ }
2057
+ /**
2058
+ * Get all discovered collateral tokens (for balance iteration, etc.).
2059
+ * Returns unique tokens from the Morpho API market discovery.
2060
+ */
2061
+ async _getDiscoveredTokens() {
2062
+ await this.getMarkets();
2063
+ const seen = /* @__PURE__ */ new Set();
2064
+ const tokens = [];
2065
+ for (const [key, info] of this._tokenCache) {
2066
+ if (key.startsWith("0x")) continue;
2067
+ if (seen.has(info.address.toLowerCase())) continue;
2068
+ seen.add(info.address.toLowerCase());
2069
+ if (info.symbol === "USDC" || info.symbol === "USDbC") continue;
2070
+ tokens.push(info);
2071
+ }
2072
+ return tokens;
2073
+ }
1960
2074
  /**
1961
2075
  * Compute net deposited amounts per market using Morpho GraphQL API.
1962
2076
  *
@@ -2098,7 +2212,9 @@ var X402Client = class {
2098
2212
  this.paidFetch = wrapFetchWithPayment(fetch, client);
2099
2213
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2100
2214
  const dailyLimit = config.dailySpendLimitUsdc ? BigInt(Math.round(parseFloat(config.dailySpendLimitUsdc) * 1e6)) : 0n;
2101
- this._spendingTracker = { date: today, totalBorrowed: 0n, dailyLimit };
2215
+ const restored = config.initialSpendingState;
2216
+ const restoredBorrowed = restored && restored.date === today ? BigInt(restored.totalBorrowed) : 0n;
2217
+ this._spendingTracker = { date: today, totalBorrowed: restoredBorrowed, dailyLimit };
2102
2218
  }
2103
2219
  async get(url, opts) {
2104
2220
  return this.request(url, { ...opts, method: "GET" });
@@ -2127,66 +2243,122 @@ var X402Client = class {
2127
2243
  return (Number(remaining > 0n ? remaining : 0n) / 1e6).toFixed(2);
2128
2244
  }
2129
2245
  /**
2130
- * Pay with auto-draw: Make an x402 request with automatic Morpho borrowing.
2246
+ * Pay with auto-funding: Make an x402 request with automatic USDC sourcing.
2131
2247
  *
2132
- * Flow:
2133
- * 1. Check USDC balance on AgentAccount
2134
- * 2. Probe the URL to discover payment amount (if 402)
2135
- * 3. If insufficient USDC, calculate deficit
2136
- * 4. Check spending limit
2137
- * 5. Borrow from Morpho via MorphoClient
2138
- * 6. Proceed with x402 payment
2248
+ * Uses a **plan-then-execute** approach: all reads happen first, and if the
2249
+ * full deficit can't be covered the method fails with NO side-effects
2250
+ * (no yield withdrawn, no USDC borrowed).
2251
+ *
2252
+ * Waterfall (when both autoYield + autoDraw enabled):
2253
+ * 1. Check USDC balance on AgentAccount
2254
+ * 2. Probe the URL to discover payment amount (if 402)
2255
+ * 3. If insufficient USDC — PLANNING PHASE (read-only):
2256
+ * a. Calculate total available yield across supply positions
2257
+ * b. Calculate max borrowable from Morpho markets
2258
+ * c. Verify yield + borrow can cover full deficit — if not, fail immediately
2259
+ * 4. EXECUTION PHASE (only if plan is feasible):
2260
+ * a. Withdraw needed yield from supply positions
2261
+ * b. Borrow remaining deficit from Morpho
2262
+ * 5. Proceed with x402 payment
2139
2263
  */
2140
2264
  async payWithAutoDraw(url, opts) {
2141
2265
  const { morphoClient, ...fetchOpts } = opts || {};
2142
- if (!this.config.autoDraw || !morphoClient) {
2266
+ if (!this.config.autoDraw && !this.config.autoYield || !morphoClient) {
2143
2267
  return this.request(url, fetchOpts);
2144
2268
  }
2145
2269
  try {
2146
2270
  const usdcBalance = await morphoClient.getUsdcBalance();
2147
- console.log(` [auto-draw] AgentAccount USDC balance: ${(Number(usdcBalance) / 1e6).toFixed(2)}`);
2271
+ console.log(` [auto-fund] AgentAccount USDC balance: ${(Number(usdcBalance) / 1e6).toFixed(2)}`);
2148
2272
  const paymentAmount = await this._probePaymentAmount(url, fetchOpts);
2149
2273
  if (paymentAmount !== null) {
2150
- console.log(` [auto-draw] Payment required: ${(Number(paymentAmount) / 1e6).toFixed(6)} USDC`);
2274
+ console.log(` [auto-fund] Payment required: ${(Number(paymentAmount) / 1e6).toFixed(6)} USDC`);
2151
2275
  const bufferStr = this.config.autoDrawBuffer || "0.5";
2152
2276
  const buffer = BigInt(Math.round(parseFloat(bufferStr) * 1e6));
2153
2277
  const needed = paymentAmount + buffer;
2154
2278
  if (usdcBalance < needed) {
2155
- const deficit = needed - usdcBalance;
2156
- console.log(` [auto-draw] Insufficient balance. Need to borrow ${(Number(deficit) / 1e6).toFixed(2)} USDC`);
2157
- const limitCheck = await this._checkSpendingLimit(deficit, morphoClient);
2158
- if (!limitCheck.allowed) {
2159
- return {
2160
- success: false,
2161
- error: `Auto-draw blocked: ${limitCheck.reason}`
2162
- };
2279
+ const totalDeficit = needed - usdcBalance;
2280
+ console.log(` [auto-fund] Insufficient balance. Deficit: ${(Number(totalDeficit) / 1e6).toFixed(2)} USDC`);
2281
+ const yieldPlan = [];
2282
+ let totalYieldAvailable = 0n;
2283
+ if (this.config.autoYield) {
2284
+ try {
2285
+ const positions = await morphoClient.getSupplyPositions();
2286
+ const withYield = positions.filter((p) => parseFloat(p.earnedYield) > 1e-6).sort((a, b) => parseFloat(b.earnedYield) - parseFloat(a.earnedYield));
2287
+ for (const pos of withYield) {
2288
+ const yieldRaw = BigInt(Math.floor(parseFloat(pos.earnedYield) * 1e6));
2289
+ if (yieldRaw > 0n) {
2290
+ yieldPlan.push({ collateralToken: pos.collateralToken, amount: yieldRaw });
2291
+ totalYieldAvailable += yieldRaw;
2292
+ }
2293
+ }
2294
+ console.log(` [auto-fund] Plan: ${(Number(totalYieldAvailable) / 1e6).toFixed(6)} USDC available from yield`);
2295
+ } catch (e) {
2296
+ console.warn(` [auto-fund] Plan: yield check failed: ${e instanceof Error ? e.message : String(e)}`);
2297
+ }
2163
2298
  }
2164
- const maxBorrowable = await morphoClient.getMaxBorrowable();
2165
- if (maxBorrowable.total < deficit) {
2299
+ const yieldToUse = totalYieldAvailable < totalDeficit ? totalYieldAvailable : totalDeficit;
2300
+ const deficitAfterYield = totalDeficit - yieldToUse;
2301
+ let canBorrow = 0n;
2302
+ if (this.config.autoDraw && deficitAfterYield > 0n) {
2303
+ const limitCheck = await this._checkSpendingLimit(deficitAfterYield, morphoClient);
2304
+ if (!limitCheck.allowed) {
2305
+ return { success: false, error: `Auto-fund blocked: ${limitCheck.reason}` };
2306
+ }
2307
+ const maxBorrowable = await morphoClient.getMaxBorrowable();
2308
+ canBorrow = maxBorrowable.total;
2309
+ console.log(` [auto-fund] Plan: ${(Number(canBorrow) / 1e6).toFixed(2)} USDC borrowable from Morpho`);
2310
+ }
2311
+ const totalAvailable = yieldToUse + canBorrow;
2312
+ if (totalAvailable < totalDeficit) {
2313
+ const parts = [];
2314
+ if (totalYieldAvailable > 0n) parts.push(`yield: $${(Number(totalYieldAvailable) / 1e6).toFixed(2)}`);
2315
+ if (canBorrow > 0n) parts.push(`borrowable: $${(Number(canBorrow) / 1e6).toFixed(2)}`);
2316
+ if (!this.config.autoYield) parts.push("autoYield: off");
2317
+ if (!this.config.autoDraw) parts.push("autoDraw: off");
2166
2318
  return {
2167
2319
  success: false,
2168
- 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.`
2320
+ 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)}.`
2169
2321
  };
2170
2322
  }
2171
- const borrowAmount = (Number(deficit) / 1e6).toFixed(6);
2172
- console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
2173
- const borrowResult = await morphoClient.borrow(borrowAmount);
2174
- console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
2175
- this._trackSpending(deficit);
2176
- const result = await this.request(url, fetchOpts);
2177
- return {
2178
- ...result,
2179
- autoDrawInfo: {
2180
- borrowed: borrowAmount,
2181
- borrowTx: borrowResult.tx,
2182
- reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2183
- }
2323
+ const drawInfo = {
2324
+ reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2184
2325
  };
2326
+ if (yieldToUse > 0n) {
2327
+ let remaining = yieldToUse;
2328
+ let totalWithdrawn = 0n;
2329
+ let lastTx = "";
2330
+ for (const plan of yieldPlan) {
2331
+ if (remaining <= 0n) break;
2332
+ const toWithdraw = plan.amount < remaining ? plan.amount : remaining;
2333
+ const withdrawStr = (Number(toWithdraw) / 1e6).toFixed(6);
2334
+ console.log(` [auto-yield] Withdrawing $${withdrawStr} yield from ${plan.collateralToken} market`);
2335
+ const wr = await morphoClient.withdrawSupply(withdrawStr, plan.collateralToken);
2336
+ lastTx = wr.tx;
2337
+ totalWithdrawn += toWithdraw;
2338
+ remaining -= toWithdraw;
2339
+ }
2340
+ if (totalWithdrawn > 0n) {
2341
+ drawInfo.yieldWithdrawn = (Number(totalWithdrawn) / 1e6).toFixed(6);
2342
+ drawInfo.yieldTx = lastTx;
2343
+ console.log(` [auto-yield] Withdrawn: $${drawInfo.yieldWithdrawn}`);
2344
+ }
2345
+ }
2346
+ if (deficitAfterYield > 0n && this.config.autoDraw) {
2347
+ const borrowAmount = (Number(deficitAfterYield) / 1e6).toFixed(6);
2348
+ console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
2349
+ const borrowResult = await morphoClient.borrow(borrowAmount);
2350
+ console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
2351
+ drawInfo.borrowed = borrowAmount;
2352
+ drawInfo.borrowTx = borrowResult.tx;
2353
+ this._trackSpending(deficitAfterYield);
2354
+ }
2355
+ const result = await this.request(url, fetchOpts);
2356
+ return { ...result, autoDrawInfo: drawInfo };
2185
2357
  }
2186
2358
  }
2187
2359
  return this.request(url, fetchOpts);
2188
2360
  } catch (error) {
2189
- console.log(` [auto-draw] Auto-draw check failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
2361
+ console.log(` [auto-fund] Failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
2190
2362
  return this.request(url, fetchOpts);
2191
2363
  }
2192
2364
  }
@@ -2287,48 +2459,26 @@ var X402Client = class {
2287
2459
  };
2288
2460
  }
2289
2461
  }
2290
- /** Track a new spending amount */
2462
+ /** Track a new spending amount and notify the caller for persistence */
2291
2463
  _trackSpending(amount) {
2292
2464
  this._resetTrackerIfNewDay();
2293
2465
  this._spendingTracker.totalBorrowed += amount;
2294
- }
2295
- /**
2296
- * Check if a borrow amount is within spending limits.
2297
- * Considers both fixed daily limits and yield-limited spending.
2298
- */
2299
- async _checkSpendingLimit(amount, morphoClient) {
2300
- this._resetTrackerIfNewDay();
2301
- if (this.config.yieldLimitedSpending) {
2466
+ if (this.config.onSpendingUpdate) {
2302
2467
  try {
2303
- const status = await morphoClient.getStatus();
2304
- let totalDailyYieldUsdc = 0;
2305
- for (const pos of status.positions) {
2306
- if (parseFloat(pos.collateral) > 0) {
2307
- try {
2308
- const estimate = await morphoClient.getYieldEstimate(
2309
- pos.collateralToken,
2310
- pos.collateral,
2311
- 1
2312
- // 1 day
2313
- );
2314
- totalDailyYieldUsdc += estimate.estimatedYieldUsd;
2315
- } catch (e) {
2316
- console.warn(`[agether] yield calc failed for ${pos.collateralToken}:`, e instanceof Error ? e.message : e);
2317
- }
2318
- }
2319
- }
2320
- const yieldLimit = BigInt(Math.round(totalDailyYieldUsdc * 1e6));
2321
- const newTotal = this._spendingTracker.totalBorrowed + amount;
2322
- if (yieldLimit > 0n && newTotal > yieldLimit) {
2323
- return {
2324
- allowed: false,
2325
- 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)}`
2326
- };
2327
- }
2468
+ this.config.onSpendingUpdate({
2469
+ date: this._spendingTracker.date,
2470
+ totalBorrowed: this._spendingTracker.totalBorrowed.toString()
2471
+ });
2328
2472
  } catch (e) {
2329
- console.warn("[agether] yield-limited spending check failed, falling through to fixed limit:", e instanceof Error ? e.message : e);
2473
+ console.warn("[agether] onSpendingUpdate callback failed:", e instanceof Error ? e.message : e);
2330
2474
  }
2331
2475
  }
2476
+ }
2477
+ /**
2478
+ * Check if a borrow amount is within the fixed daily spending limit.
2479
+ */
2480
+ async _checkSpendingLimit(amount, _morphoClient) {
2481
+ this._resetTrackerIfNewDay();
2332
2482
  if (this._spendingTracker.dailyLimit > 0n) {
2333
2483
  const newTotal = this._spendingTracker.totalBorrowed + amount;
2334
2484
  if (newTotal > this._spendingTracker.dailyLimit) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/sdk",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "TypeScript SDK for Agether - autonomous credit for AI agents on Base",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",