@agether/sdk 2.5.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
@@ -537,6 +537,50 @@ var init_MorphoClient = __esm({
537
537
  this._refreshSigner();
538
538
  return { tx: receipt.hash, amount: usdcAmount, agentAccount: acctAddr };
539
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
+ }
540
584
  // ════════════════════════════════════════════════════════
541
585
  // Market Discovery (Morpho GraphQL API)
542
586
  // ════════════════════════════════════════════════════════
@@ -1998,7 +2042,9 @@ var init_X402Client = __esm({
1998
2042
  this.paidFetch = (0, import_fetch.wrapFetchWithPayment)(fetch, client);
1999
2043
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2000
2044
  const dailyLimit = config.dailySpendLimitUsdc ? BigInt(Math.round(parseFloat(config.dailySpendLimitUsdc) * 1e6)) : 0n;
2001
- 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 };
2002
2048
  }
2003
2049
  async get(url, opts) {
2004
2050
  return this.request(url, { ...opts, method: "GET" });
@@ -2027,66 +2073,122 @@ var init_X402Client = __esm({
2027
2073
  return (Number(remaining > 0n ? remaining : 0n) / 1e6).toFixed(2);
2028
2074
  }
2029
2075
  /**
2030
- * 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.
2031
2077
  *
2032
- * Flow:
2033
- * 1. Check USDC balance on AgentAccount
2034
- * 2. Probe the URL to discover payment amount (if 402)
2035
- * 3. If insufficient USDC, calculate deficit
2036
- * 4. Check spending limit
2037
- * 5. Borrow from Morpho via MorphoClient
2038
- * 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
2039
2093
  */
2040
2094
  async payWithAutoDraw(url, opts) {
2041
2095
  const { morphoClient, ...fetchOpts } = opts || {};
2042
- if (!this.config.autoDraw || !morphoClient) {
2096
+ if (!this.config.autoDraw && !this.config.autoYield || !morphoClient) {
2043
2097
  return this.request(url, fetchOpts);
2044
2098
  }
2045
2099
  try {
2046
2100
  const usdcBalance = await morphoClient.getUsdcBalance();
2047
- 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)}`);
2048
2102
  const paymentAmount = await this._probePaymentAmount(url, fetchOpts);
2049
2103
  if (paymentAmount !== null) {
2050
- 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`);
2051
2105
  const bufferStr = this.config.autoDrawBuffer || "0.5";
2052
2106
  const buffer = BigInt(Math.round(parseFloat(bufferStr) * 1e6));
2053
2107
  const needed = paymentAmount + buffer;
2054
2108
  if (usdcBalance < needed) {
2055
- const deficit = needed - usdcBalance;
2056
- console.log(` [auto-draw] Insufficient balance. Need to borrow ${(Number(deficit) / 1e6).toFixed(2)} USDC`);
2057
- const limitCheck = await this._checkSpendingLimit(deficit, morphoClient);
2058
- if (!limitCheck.allowed) {
2059
- return {
2060
- success: false,
2061
- error: `Auto-draw blocked: ${limitCheck.reason}`
2062
- };
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
+ }
2128
+ }
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`);
2063
2140
  }
2064
- const maxBorrowable = await morphoClient.getMaxBorrowable();
2065
- if (maxBorrowable.total < deficit) {
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");
2066
2148
  return {
2067
2149
  success: false,
2068
- 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)}.`
2069
2151
  };
2070
2152
  }
2071
- const borrowAmount = (Number(deficit) / 1e6).toFixed(6);
2072
- console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
2073
- const borrowResult = await morphoClient.borrow(borrowAmount);
2074
- console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
2075
- this._trackSpending(deficit);
2076
- const result = await this.request(url, fetchOpts);
2077
- return {
2078
- ...result,
2079
- autoDrawInfo: {
2080
- borrowed: borrowAmount,
2081
- borrowTx: borrowResult.tx,
2082
- reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2083
- }
2153
+ const drawInfo = {
2154
+ reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2084
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 };
2085
2187
  }
2086
2188
  }
2087
2189
  return this.request(url, fetchOpts);
2088
2190
  } catch (error) {
2089
- 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.`);
2090
2192
  return this.request(url, fetchOpts);
2091
2193
  }
2092
2194
  }
@@ -2187,48 +2289,26 @@ var init_X402Client = __esm({
2187
2289
  };
2188
2290
  }
2189
2291
  }
2190
- /** Track a new spending amount */
2292
+ /** Track a new spending amount and notify the caller for persistence */
2191
2293
  _trackSpending(amount) {
2192
2294
  this._resetTrackerIfNewDay();
2193
2295
  this._spendingTracker.totalBorrowed += amount;
2194
- }
2195
- /**
2196
- * Check if a borrow amount is within spending limits.
2197
- * Considers both fixed daily limits and yield-limited spending.
2198
- */
2199
- async _checkSpendingLimit(amount, morphoClient) {
2200
- this._resetTrackerIfNewDay();
2201
- if (this.config.yieldLimitedSpending) {
2296
+ if (this.config.onSpendingUpdate) {
2202
2297
  try {
2203
- const status = await morphoClient.getStatus();
2204
- let totalDailyYieldUsdc = 0;
2205
- for (const pos of status.positions) {
2206
- if (parseFloat(pos.collateral) > 0) {
2207
- try {
2208
- const estimate = await morphoClient.getYieldEstimate(
2209
- pos.collateralToken,
2210
- pos.collateral,
2211
- 1
2212
- // 1 day
2213
- );
2214
- totalDailyYieldUsdc += estimate.estimatedYieldUsd;
2215
- } catch (e) {
2216
- console.warn(`[agether] yield calc failed for ${pos.collateralToken}:`, e instanceof Error ? e.message : e);
2217
- }
2218
- }
2219
- }
2220
- const yieldLimit = BigInt(Math.round(totalDailyYieldUsdc * 1e6));
2221
- const newTotal = this._spendingTracker.totalBorrowed + amount;
2222
- if (yieldLimit > 0n && newTotal > yieldLimit) {
2223
- return {
2224
- allowed: false,
2225
- 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)}`
2226
- };
2227
- }
2298
+ this.config.onSpendingUpdate({
2299
+ date: this._spendingTracker.date,
2300
+ totalBorrowed: this._spendingTracker.totalBorrowed.toString()
2301
+ });
2228
2302
  } catch (e) {
2229
- 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);
2230
2304
  }
2231
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();
2232
2312
  if (this._spendingTracker.dailyLimit > 0n) {
2233
2313
  const newTotal = this._spendingTracker.totalBorrowed + amount;
2234
2314
  if (newTotal > this._spendingTracker.dailyLimit) {
package/dist/index.d.mts CHANGED
@@ -347,6 +347,12 @@ interface FundResult {
347
347
  amount: string;
348
348
  agentAccount: string;
349
349
  }
350
+ interface WithdrawFromAccountResult {
351
+ tx: string;
352
+ token: string;
353
+ amount: string;
354
+ destination: string;
355
+ }
350
356
  interface SupplyAssetResult {
351
357
  tx: string;
352
358
  amount: string;
@@ -432,6 +438,21 @@ declare class MorphoClient {
432
438
  getBalances(): Promise<BalancesResult>;
433
439
  /** Transfer USDC from EOA to AgentAccount. */
434
440
  fundAccount(usdcAmount: string): Promise<FundResult>;
441
+ /**
442
+ * Withdraw (transfer) a token from AgentAccount to EOA.
443
+ * Executes an ERC-20 transfer via Safe UserOp.
444
+ *
445
+ * @param tokenSymbol - Token to withdraw (e.g. 'USDC', 'WETH', 'wstETH')
446
+ * @param amount - Amount to withdraw (human-readable, e.g. '100' for 100 USDC, or 'all')
447
+ */
448
+ withdrawToken(tokenSymbol: string, amount: string): Promise<WithdrawFromAccountResult>;
449
+ /**
450
+ * Withdraw ETH from AgentAccount to EOA.
451
+ * Executes a native ETH transfer via Safe UserOp.
452
+ *
453
+ * @param amount - ETH amount (e.g. '0.01' or 'all')
454
+ */
455
+ withdrawEth(amount: string): Promise<WithdrawFromAccountResult>;
435
456
  /**
436
457
  * Fetch USDC borrow markets on Base from Morpho API.
437
458
  * Caches results for 5 minutes.
@@ -708,9 +729,14 @@ declare class MorphoClient {
708
729
  * Auto-Draw: When autoDraw is enabled and USDC balance is insufficient,
709
730
  * the client automatically borrows from Morpho Blue before paying.
710
731
  *
711
- * Spending Limits: Optional daily spending cap (dailySpendLimitUsdc) and
712
- * yield-limited spending (yieldLimitedSpending) to keep borrows within
713
- * theoretical collateral yield.
732
+ * Auto-Yield: When autoYield is enabled, the client first tries to cover
733
+ * the deficit from earned supply yield (principal untouched) before borrowing.
734
+ *
735
+ * Waterfall when both enabled: balance → yield → borrow
736
+ *
737
+ * Spending Limits: Optional daily spending cap (dailySpendLimitUsdc) with
738
+ * persistent state via onSpendingUpdate callback. The caller (e.g. plugin)
739
+ * is responsible for persisting and restoring state via initialSpendingState.
714
740
  *
715
741
  * Chain support: Base (8453), Base Sepolia (84532), Ethereum (1).
716
742
  */
@@ -733,21 +759,43 @@ interface X402BaseConfig {
733
759
  * Default: false
734
760
  */
735
761
  autoDraw?: boolean;
762
+ /**
763
+ * Auto-yield: when USDC is insufficient, try to cover the deficit from
764
+ * earned supply yield BEFORE borrowing. Withdraws only the yield portion
765
+ * (principal stays intact). Works independently or combined with autoDraw.
766
+ *
767
+ * Waterfall when both enabled: balance → yield → borrow
768
+ * Default: false
769
+ */
770
+ autoYield?: boolean;
736
771
  /**
737
772
  * Daily spending limit in USDC (e.g. '100' for $100/day).
738
773
  * Tracks cumulative daily borrows and rejects auto-draw if exceeded.
739
774
  */
740
775
  dailySpendLimitUsdc?: string;
741
- /**
742
- * When true, auto-calculates the daily spending limit based on
743
- * theoretical yield of deposited collateral. Overrides dailySpendLimitUsdc.
744
- */
745
- yieldLimitedSpending?: boolean;
746
776
  /**
747
777
  * Safety margin: borrow this much extra beyond what's needed (in USDC, e.g. '1').
748
778
  * Helps avoid rounding issues. Default: '0.5'
749
779
  */
750
780
  autoDrawBuffer?: string;
781
+ /**
782
+ * Pre-loaded spending state from a previous session.
783
+ * Pass this to resume the daily spending tracker after a restart.
784
+ * If the date doesn't match today, it's ignored (fresh day).
785
+ */
786
+ initialSpendingState?: {
787
+ date: string;
788
+ totalBorrowed: string;
789
+ };
790
+ /**
791
+ * Called after every auto-draw borrow so the caller can persist the
792
+ * updated spending state (e.g. to a cache file). Receives the full
793
+ * SpendingTracker with the current date and cumulative amount.
794
+ */
795
+ onSpendingUpdate?: (state: {
796
+ date: string;
797
+ totalBorrowed: string;
798
+ }) => void;
751
799
  /**
752
800
  * ERC-7579 validator module address (e.g. Agether8004ValidationModule).
753
801
  * Required for Safe7579 smart wallets so that `isValidSignature` calls
@@ -790,8 +838,10 @@ interface X402Response<T = unknown> {
790
838
  txHash?: string;
791
839
  };
792
840
  autoDrawInfo?: {
793
- borrowed: string;
794
- borrowTx: string;
841
+ borrowed?: string;
842
+ borrowTx?: string;
843
+ yieldWithdrawn?: string;
844
+ yieldTx?: string;
795
845
  reason: string;
796
846
  };
797
847
  }
@@ -828,15 +878,23 @@ declare class X402Client {
828
878
  /** Get remaining daily spending allowance in USDC (human-readable) */
829
879
  getRemainingDailyAllowance(): string;
830
880
  /**
831
- * Pay with auto-draw: Make an x402 request with automatic Morpho borrowing.
881
+ * Pay with auto-funding: Make an x402 request with automatic USDC sourcing.
832
882
  *
833
- * Flow:
834
- * 1. Check USDC balance on AgentAccount
835
- * 2. Probe the URL to discover payment amount (if 402)
836
- * 3. If insufficient USDC, calculate deficit
837
- * 4. Check spending limit
838
- * 5. Borrow from Morpho via MorphoClient
839
- * 6. Proceed with x402 payment
883
+ * Uses a **plan-then-execute** approach: all reads happen first, and if the
884
+ * full deficit can't be covered the method fails with NO side-effects
885
+ * (no yield withdrawn, no USDC borrowed).
886
+ *
887
+ * Waterfall (when both autoYield + autoDraw enabled):
888
+ * 1. Check USDC balance on AgentAccount
889
+ * 2. Probe the URL to discover payment amount (if 402)
890
+ * 3. If insufficient USDC — PLANNING PHASE (read-only):
891
+ * a. Calculate total available yield across supply positions
892
+ * b. Calculate max borrowable from Morpho markets
893
+ * c. Verify yield + borrow can cover full deficit — if not, fail immediately
894
+ * 4. EXECUTION PHASE (only if plan is feasible):
895
+ * a. Withdraw needed yield from supply positions
896
+ * b. Borrow remaining deficit from Morpho
897
+ * 5. Proceed with x402 payment
840
898
  */
841
899
  payWithAutoDraw<T = unknown>(url: string, opts?: RequestInit & {
842
900
  morphoClient?: any;
@@ -850,11 +908,10 @@ declare class X402Client {
850
908
  private _probePaymentAmount;
851
909
  /** Reset spending tracker if it's a new day */
852
910
  private _resetTrackerIfNewDay;
853
- /** Track a new spending amount */
911
+ /** Track a new spending amount and notify the caller for persistence */
854
912
  private _trackSpending;
855
913
  /**
856
- * Check if a borrow amount is within spending limits.
857
- * Considers both fixed daily limits and yield-limited spending.
914
+ * Check if a borrow amount is within the fixed daily spending limit.
858
915
  */
859
916
  private _checkSpendingLimit;
860
917
  }
@@ -1227,4 +1284,4 @@ declare function getDefaultConfig(chainId: ChainId): AgetherConfig;
1227
1284
  */
1228
1285
  declare function createConfig(chainId: ChainId, options?: Partial<AgetherConfig>): AgetherConfig;
1229
1286
 
1230
- export { ACCOUNT_FACTORY_ABI, AGENT_REPUTATION_ABI, AGETHER_4337_FACTORY_ABI, AGETHER_8004_SCORER_ABI, AGETHER_8004_VALIDATION_MODULE_ABI, AGETHER_HOOK_MULTIPLEXER_ABI, AgentIdentityClient, type AgentIdentityClientOptions, AgentNotApprovedError, AgetherClient, type AgetherClientOptions, type AgetherConfig, AgetherError, type AgetherSigner, type AgetherViemWallet, type BalancesResult, type BorrowResult, ChainId, type ContractAddresses, type DepositAndBorrowResult, type DepositResult, ENTRYPOINT_V07_ABI, ERC20_ABI, ERC8004_VALIDATION_MODULE_ABI, type FundResult, HOOK_MULTIPLEXER_ABI, IDENTITY_REGISTRY_ABI, InsufficientBalanceError, MORPHO_BLUE_ABI, MorphoClient, type MorphoClientConfig, type MorphoMarketInfo, type MorphoMarketParams, type MorphoPosition, type PayFromYieldResult, type PaymentRequirements, type PositionResult, type RegisterResult, type RepayResult, SAFE7579_ACCOUNT_ABI, SAFE_AGENT_FACTORY_ABI, type ScoreAttestation, type ScoreResult, ScoringClient, type ScoringClientConfig, ScoringRejectedError, type SpendingTracker, type StatusResult, type SupplyAssetResult, type SupplyPositionResult, type TransactionResult, VALIDATION_REGISTRY_ABI, type WithdrawResult, type WithdrawSupplyResult, X402Client, type X402Config, type X402PaymentRequest, type X402PaymentResult, type X402Response, bpsToRate, createConfig, formatAPR, formatAddress, formatHealthFactor, formatPercent, formatTimestamp, formatUSD, formatUnits, getDefaultConfig, parseUnits, rateToBps };
1287
+ export { ACCOUNT_FACTORY_ABI, AGENT_REPUTATION_ABI, AGETHER_4337_FACTORY_ABI, AGETHER_8004_SCORER_ABI, AGETHER_8004_VALIDATION_MODULE_ABI, AGETHER_HOOK_MULTIPLEXER_ABI, AgentIdentityClient, type AgentIdentityClientOptions, AgentNotApprovedError, AgetherClient, type AgetherClientOptions, type AgetherConfig, AgetherError, type AgetherSigner, type AgetherViemWallet, type BalancesResult, type BorrowResult, ChainId, type ContractAddresses, type DepositAndBorrowResult, type DepositResult, ENTRYPOINT_V07_ABI, ERC20_ABI, ERC8004_VALIDATION_MODULE_ABI, type FundResult, HOOK_MULTIPLEXER_ABI, IDENTITY_REGISTRY_ABI, InsufficientBalanceError, MORPHO_BLUE_ABI, MorphoClient, type MorphoClientConfig, type MorphoMarketInfo, type MorphoMarketParams, type MorphoPosition, type PayFromYieldResult, type PaymentRequirements, type PositionResult, type RegisterResult, type RepayResult, SAFE7579_ACCOUNT_ABI, SAFE_AGENT_FACTORY_ABI, type ScoreAttestation, type ScoreResult, ScoringClient, type ScoringClientConfig, ScoringRejectedError, type SpendingTracker, type StatusResult, type SupplyAssetResult, type SupplyPositionResult, type TransactionResult, VALIDATION_REGISTRY_ABI, type WithdrawFromAccountResult, type WithdrawResult, type WithdrawSupplyResult, X402Client, type X402Config, type X402PaymentRequest, type X402PaymentResult, type X402Response, bpsToRate, createConfig, formatAPR, formatAddress, formatHealthFactor, formatPercent, formatTimestamp, formatUSD, formatUnits, getDefaultConfig, parseUnits, rateToBps };
package/dist/index.d.ts CHANGED
@@ -347,6 +347,12 @@ interface FundResult {
347
347
  amount: string;
348
348
  agentAccount: string;
349
349
  }
350
+ interface WithdrawFromAccountResult {
351
+ tx: string;
352
+ token: string;
353
+ amount: string;
354
+ destination: string;
355
+ }
350
356
  interface SupplyAssetResult {
351
357
  tx: string;
352
358
  amount: string;
@@ -432,6 +438,21 @@ declare class MorphoClient {
432
438
  getBalances(): Promise<BalancesResult>;
433
439
  /** Transfer USDC from EOA to AgentAccount. */
434
440
  fundAccount(usdcAmount: string): Promise<FundResult>;
441
+ /**
442
+ * Withdraw (transfer) a token from AgentAccount to EOA.
443
+ * Executes an ERC-20 transfer via Safe UserOp.
444
+ *
445
+ * @param tokenSymbol - Token to withdraw (e.g. 'USDC', 'WETH', 'wstETH')
446
+ * @param amount - Amount to withdraw (human-readable, e.g. '100' for 100 USDC, or 'all')
447
+ */
448
+ withdrawToken(tokenSymbol: string, amount: string): Promise<WithdrawFromAccountResult>;
449
+ /**
450
+ * Withdraw ETH from AgentAccount to EOA.
451
+ * Executes a native ETH transfer via Safe UserOp.
452
+ *
453
+ * @param amount - ETH amount (e.g. '0.01' or 'all')
454
+ */
455
+ withdrawEth(amount: string): Promise<WithdrawFromAccountResult>;
435
456
  /**
436
457
  * Fetch USDC borrow markets on Base from Morpho API.
437
458
  * Caches results for 5 minutes.
@@ -708,9 +729,14 @@ declare class MorphoClient {
708
729
  * Auto-Draw: When autoDraw is enabled and USDC balance is insufficient,
709
730
  * the client automatically borrows from Morpho Blue before paying.
710
731
  *
711
- * Spending Limits: Optional daily spending cap (dailySpendLimitUsdc) and
712
- * yield-limited spending (yieldLimitedSpending) to keep borrows within
713
- * theoretical collateral yield.
732
+ * Auto-Yield: When autoYield is enabled, the client first tries to cover
733
+ * the deficit from earned supply yield (principal untouched) before borrowing.
734
+ *
735
+ * Waterfall when both enabled: balance → yield → borrow
736
+ *
737
+ * Spending Limits: Optional daily spending cap (dailySpendLimitUsdc) with
738
+ * persistent state via onSpendingUpdate callback. The caller (e.g. plugin)
739
+ * is responsible for persisting and restoring state via initialSpendingState.
714
740
  *
715
741
  * Chain support: Base (8453), Base Sepolia (84532), Ethereum (1).
716
742
  */
@@ -733,21 +759,43 @@ interface X402BaseConfig {
733
759
  * Default: false
734
760
  */
735
761
  autoDraw?: boolean;
762
+ /**
763
+ * Auto-yield: when USDC is insufficient, try to cover the deficit from
764
+ * earned supply yield BEFORE borrowing. Withdraws only the yield portion
765
+ * (principal stays intact). Works independently or combined with autoDraw.
766
+ *
767
+ * Waterfall when both enabled: balance → yield → borrow
768
+ * Default: false
769
+ */
770
+ autoYield?: boolean;
736
771
  /**
737
772
  * Daily spending limit in USDC (e.g. '100' for $100/day).
738
773
  * Tracks cumulative daily borrows and rejects auto-draw if exceeded.
739
774
  */
740
775
  dailySpendLimitUsdc?: string;
741
- /**
742
- * When true, auto-calculates the daily spending limit based on
743
- * theoretical yield of deposited collateral. Overrides dailySpendLimitUsdc.
744
- */
745
- yieldLimitedSpending?: boolean;
746
776
  /**
747
777
  * Safety margin: borrow this much extra beyond what's needed (in USDC, e.g. '1').
748
778
  * Helps avoid rounding issues. Default: '0.5'
749
779
  */
750
780
  autoDrawBuffer?: string;
781
+ /**
782
+ * Pre-loaded spending state from a previous session.
783
+ * Pass this to resume the daily spending tracker after a restart.
784
+ * If the date doesn't match today, it's ignored (fresh day).
785
+ */
786
+ initialSpendingState?: {
787
+ date: string;
788
+ totalBorrowed: string;
789
+ };
790
+ /**
791
+ * Called after every auto-draw borrow so the caller can persist the
792
+ * updated spending state (e.g. to a cache file). Receives the full
793
+ * SpendingTracker with the current date and cumulative amount.
794
+ */
795
+ onSpendingUpdate?: (state: {
796
+ date: string;
797
+ totalBorrowed: string;
798
+ }) => void;
751
799
  /**
752
800
  * ERC-7579 validator module address (e.g. Agether8004ValidationModule).
753
801
  * Required for Safe7579 smart wallets so that `isValidSignature` calls
@@ -790,8 +838,10 @@ interface X402Response<T = unknown> {
790
838
  txHash?: string;
791
839
  };
792
840
  autoDrawInfo?: {
793
- borrowed: string;
794
- borrowTx: string;
841
+ borrowed?: string;
842
+ borrowTx?: string;
843
+ yieldWithdrawn?: string;
844
+ yieldTx?: string;
795
845
  reason: string;
796
846
  };
797
847
  }
@@ -828,15 +878,23 @@ declare class X402Client {
828
878
  /** Get remaining daily spending allowance in USDC (human-readable) */
829
879
  getRemainingDailyAllowance(): string;
830
880
  /**
831
- * Pay with auto-draw: Make an x402 request with automatic Morpho borrowing.
881
+ * Pay with auto-funding: Make an x402 request with automatic USDC sourcing.
832
882
  *
833
- * Flow:
834
- * 1. Check USDC balance on AgentAccount
835
- * 2. Probe the URL to discover payment amount (if 402)
836
- * 3. If insufficient USDC, calculate deficit
837
- * 4. Check spending limit
838
- * 5. Borrow from Morpho via MorphoClient
839
- * 6. Proceed with x402 payment
883
+ * Uses a **plan-then-execute** approach: all reads happen first, and if the
884
+ * full deficit can't be covered the method fails with NO side-effects
885
+ * (no yield withdrawn, no USDC borrowed).
886
+ *
887
+ * Waterfall (when both autoYield + autoDraw enabled):
888
+ * 1. Check USDC balance on AgentAccount
889
+ * 2. Probe the URL to discover payment amount (if 402)
890
+ * 3. If insufficient USDC — PLANNING PHASE (read-only):
891
+ * a. Calculate total available yield across supply positions
892
+ * b. Calculate max borrowable from Morpho markets
893
+ * c. Verify yield + borrow can cover full deficit — if not, fail immediately
894
+ * 4. EXECUTION PHASE (only if plan is feasible):
895
+ * a. Withdraw needed yield from supply positions
896
+ * b. Borrow remaining deficit from Morpho
897
+ * 5. Proceed with x402 payment
840
898
  */
841
899
  payWithAutoDraw<T = unknown>(url: string, opts?: RequestInit & {
842
900
  morphoClient?: any;
@@ -850,11 +908,10 @@ declare class X402Client {
850
908
  private _probePaymentAmount;
851
909
  /** Reset spending tracker if it's a new day */
852
910
  private _resetTrackerIfNewDay;
853
- /** Track a new spending amount */
911
+ /** Track a new spending amount and notify the caller for persistence */
854
912
  private _trackSpending;
855
913
  /**
856
- * Check if a borrow amount is within spending limits.
857
- * Considers both fixed daily limits and yield-limited spending.
914
+ * Check if a borrow amount is within the fixed daily spending limit.
858
915
  */
859
916
  private _checkSpendingLimit;
860
917
  }
@@ -1227,4 +1284,4 @@ declare function getDefaultConfig(chainId: ChainId): AgetherConfig;
1227
1284
  */
1228
1285
  declare function createConfig(chainId: ChainId, options?: Partial<AgetherConfig>): AgetherConfig;
1229
1286
 
1230
- export { ACCOUNT_FACTORY_ABI, AGENT_REPUTATION_ABI, AGETHER_4337_FACTORY_ABI, AGETHER_8004_SCORER_ABI, AGETHER_8004_VALIDATION_MODULE_ABI, AGETHER_HOOK_MULTIPLEXER_ABI, AgentIdentityClient, type AgentIdentityClientOptions, AgentNotApprovedError, AgetherClient, type AgetherClientOptions, type AgetherConfig, AgetherError, type AgetherSigner, type AgetherViemWallet, type BalancesResult, type BorrowResult, ChainId, type ContractAddresses, type DepositAndBorrowResult, type DepositResult, ENTRYPOINT_V07_ABI, ERC20_ABI, ERC8004_VALIDATION_MODULE_ABI, type FundResult, HOOK_MULTIPLEXER_ABI, IDENTITY_REGISTRY_ABI, InsufficientBalanceError, MORPHO_BLUE_ABI, MorphoClient, type MorphoClientConfig, type MorphoMarketInfo, type MorphoMarketParams, type MorphoPosition, type PayFromYieldResult, type PaymentRequirements, type PositionResult, type RegisterResult, type RepayResult, SAFE7579_ACCOUNT_ABI, SAFE_AGENT_FACTORY_ABI, type ScoreAttestation, type ScoreResult, ScoringClient, type ScoringClientConfig, ScoringRejectedError, type SpendingTracker, type StatusResult, type SupplyAssetResult, type SupplyPositionResult, type TransactionResult, VALIDATION_REGISTRY_ABI, type WithdrawResult, type WithdrawSupplyResult, X402Client, type X402Config, type X402PaymentRequest, type X402PaymentResult, type X402Response, bpsToRate, createConfig, formatAPR, formatAddress, formatHealthFactor, formatPercent, formatTimestamp, formatUSD, formatUnits, getDefaultConfig, parseUnits, rateToBps };
1287
+ export { ACCOUNT_FACTORY_ABI, AGENT_REPUTATION_ABI, AGETHER_4337_FACTORY_ABI, AGETHER_8004_SCORER_ABI, AGETHER_8004_VALIDATION_MODULE_ABI, AGETHER_HOOK_MULTIPLEXER_ABI, AgentIdentityClient, type AgentIdentityClientOptions, AgentNotApprovedError, AgetherClient, type AgetherClientOptions, type AgetherConfig, AgetherError, type AgetherSigner, type AgetherViemWallet, type BalancesResult, type BorrowResult, ChainId, type ContractAddresses, type DepositAndBorrowResult, type DepositResult, ENTRYPOINT_V07_ABI, ERC20_ABI, ERC8004_VALIDATION_MODULE_ABI, type FundResult, HOOK_MULTIPLEXER_ABI, IDENTITY_REGISTRY_ABI, InsufficientBalanceError, MORPHO_BLUE_ABI, MorphoClient, type MorphoClientConfig, type MorphoMarketInfo, type MorphoMarketParams, type MorphoPosition, type PayFromYieldResult, type PaymentRequirements, type PositionResult, type RegisterResult, type RepayResult, SAFE7579_ACCOUNT_ABI, SAFE_AGENT_FACTORY_ABI, type ScoreAttestation, type ScoreResult, ScoringClient, type ScoringClientConfig, ScoringRejectedError, type SpendingTracker, type StatusResult, type SupplyAssetResult, type SupplyPositionResult, type TransactionResult, VALIDATION_REGISTRY_ABI, type WithdrawFromAccountResult, type WithdrawResult, type WithdrawSupplyResult, X402Client, type X402Config, type X402PaymentRequest, type X402PaymentResult, type X402Response, bpsToRate, createConfig, formatAPR, formatAddress, formatHealthFactor, formatPercent, formatTimestamp, formatUSD, formatUnits, getDefaultConfig, parseUnits, rateToBps };
package/dist/index.js CHANGED
@@ -786,6 +786,50 @@ var MorphoClient = class {
786
786
  this._refreshSigner();
787
787
  return { tx: receipt.hash, amount: usdcAmount, agentAccount: acctAddr };
788
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
+ }
789
833
  // ════════════════════════════════════════════════════════
790
834
  // Market Discovery (Morpho GraphQL API)
791
835
  // ════════════════════════════════════════════════════════
@@ -2240,7 +2284,9 @@ var X402Client = class {
2240
2284
  this.paidFetch = (0, import_fetch.wrapFetchWithPayment)(fetch, client);
2241
2285
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2242
2286
  const dailyLimit = config.dailySpendLimitUsdc ? BigInt(Math.round(parseFloat(config.dailySpendLimitUsdc) * 1e6)) : 0n;
2243
- 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 };
2244
2290
  }
2245
2291
  async get(url, opts) {
2246
2292
  return this.request(url, { ...opts, method: "GET" });
@@ -2269,66 +2315,122 @@ var X402Client = class {
2269
2315
  return (Number(remaining > 0n ? remaining : 0n) / 1e6).toFixed(2);
2270
2316
  }
2271
2317
  /**
2272
- * 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.
2273
2319
  *
2274
- * Flow:
2275
- * 1. Check USDC balance on AgentAccount
2276
- * 2. Probe the URL to discover payment amount (if 402)
2277
- * 3. If insufficient USDC, calculate deficit
2278
- * 4. Check spending limit
2279
- * 5. Borrow from Morpho via MorphoClient
2280
- * 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
2281
2335
  */
2282
2336
  async payWithAutoDraw(url, opts) {
2283
2337
  const { morphoClient, ...fetchOpts } = opts || {};
2284
- if (!this.config.autoDraw || !morphoClient) {
2338
+ if (!this.config.autoDraw && !this.config.autoYield || !morphoClient) {
2285
2339
  return this.request(url, fetchOpts);
2286
2340
  }
2287
2341
  try {
2288
2342
  const usdcBalance = await morphoClient.getUsdcBalance();
2289
- 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)}`);
2290
2344
  const paymentAmount = await this._probePaymentAmount(url, fetchOpts);
2291
2345
  if (paymentAmount !== null) {
2292
- 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`);
2293
2347
  const bufferStr = this.config.autoDrawBuffer || "0.5";
2294
2348
  const buffer = BigInt(Math.round(parseFloat(bufferStr) * 1e6));
2295
2349
  const needed = paymentAmount + buffer;
2296
2350
  if (usdcBalance < needed) {
2297
- const deficit = needed - usdcBalance;
2298
- console.log(` [auto-draw] Insufficient balance. Need to borrow ${(Number(deficit) / 1e6).toFixed(2)} USDC`);
2299
- const limitCheck = await this._checkSpendingLimit(deficit, morphoClient);
2300
- if (!limitCheck.allowed) {
2301
- return {
2302
- success: false,
2303
- error: `Auto-draw blocked: ${limitCheck.reason}`
2304
- };
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
+ }
2370
+ }
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`);
2305
2382
  }
2306
- const maxBorrowable = await morphoClient.getMaxBorrowable();
2307
- if (maxBorrowable.total < deficit) {
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");
2308
2390
  return {
2309
2391
  success: false,
2310
- 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)}.`
2311
2393
  };
2312
2394
  }
2313
- const borrowAmount = (Number(deficit) / 1e6).toFixed(6);
2314
- console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
2315
- const borrowResult = await morphoClient.borrow(borrowAmount);
2316
- console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
2317
- this._trackSpending(deficit);
2318
- const result = await this.request(url, fetchOpts);
2319
- return {
2320
- ...result,
2321
- autoDrawInfo: {
2322
- borrowed: borrowAmount,
2323
- borrowTx: borrowResult.tx,
2324
- reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2325
- }
2395
+ const drawInfo = {
2396
+ reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2326
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 };
2327
2429
  }
2328
2430
  }
2329
2431
  return this.request(url, fetchOpts);
2330
2432
  } catch (error) {
2331
- 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.`);
2332
2434
  return this.request(url, fetchOpts);
2333
2435
  }
2334
2436
  }
@@ -2429,48 +2531,26 @@ var X402Client = class {
2429
2531
  };
2430
2532
  }
2431
2533
  }
2432
- /** Track a new spending amount */
2534
+ /** Track a new spending amount and notify the caller for persistence */
2433
2535
  _trackSpending(amount) {
2434
2536
  this._resetTrackerIfNewDay();
2435
2537
  this._spendingTracker.totalBorrowed += amount;
2436
- }
2437
- /**
2438
- * Check if a borrow amount is within spending limits.
2439
- * Considers both fixed daily limits and yield-limited spending.
2440
- */
2441
- async _checkSpendingLimit(amount, morphoClient) {
2442
- this._resetTrackerIfNewDay();
2443
- if (this.config.yieldLimitedSpending) {
2538
+ if (this.config.onSpendingUpdate) {
2444
2539
  try {
2445
- const status = await morphoClient.getStatus();
2446
- let totalDailyYieldUsdc = 0;
2447
- for (const pos of status.positions) {
2448
- if (parseFloat(pos.collateral) > 0) {
2449
- try {
2450
- const estimate = await morphoClient.getYieldEstimate(
2451
- pos.collateralToken,
2452
- pos.collateral,
2453
- 1
2454
- // 1 day
2455
- );
2456
- totalDailyYieldUsdc += estimate.estimatedYieldUsd;
2457
- } catch (e) {
2458
- console.warn(`[agether] yield calc failed for ${pos.collateralToken}:`, e instanceof Error ? e.message : e);
2459
- }
2460
- }
2461
- }
2462
- const yieldLimit = BigInt(Math.round(totalDailyYieldUsdc * 1e6));
2463
- const newTotal = this._spendingTracker.totalBorrowed + amount;
2464
- if (yieldLimit > 0n && newTotal > yieldLimit) {
2465
- return {
2466
- allowed: false,
2467
- 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)}`
2468
- };
2469
- }
2540
+ this.config.onSpendingUpdate({
2541
+ date: this._spendingTracker.date,
2542
+ totalBorrowed: this._spendingTracker.totalBorrowed.toString()
2543
+ });
2470
2544
  } catch (e) {
2471
- 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);
2472
2546
  }
2473
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();
2474
2554
  if (this._spendingTracker.dailyLimit > 0n) {
2475
2555
  const newTotal = this._spendingTracker.totalBorrowed + amount;
2476
2556
  if (newTotal > this._spendingTracker.dailyLimit) {
package/dist/index.mjs CHANGED
@@ -714,6 +714,50 @@ var MorphoClient = class {
714
714
  this._refreshSigner();
715
715
  return { tx: receipt.hash, amount: usdcAmount, agentAccount: acctAddr };
716
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
+ }
717
761
  // ════════════════════════════════════════════════════════
718
762
  // Market Discovery (Morpho GraphQL API)
719
763
  // ════════════════════════════════════════════════════════
@@ -2168,7 +2212,9 @@ var X402Client = class {
2168
2212
  this.paidFetch = wrapFetchWithPayment(fetch, client);
2169
2213
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2170
2214
  const dailyLimit = config.dailySpendLimitUsdc ? BigInt(Math.round(parseFloat(config.dailySpendLimitUsdc) * 1e6)) : 0n;
2171
- 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 };
2172
2218
  }
2173
2219
  async get(url, opts) {
2174
2220
  return this.request(url, { ...opts, method: "GET" });
@@ -2197,66 +2243,122 @@ var X402Client = class {
2197
2243
  return (Number(remaining > 0n ? remaining : 0n) / 1e6).toFixed(2);
2198
2244
  }
2199
2245
  /**
2200
- * 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.
2201
2247
  *
2202
- * Flow:
2203
- * 1. Check USDC balance on AgentAccount
2204
- * 2. Probe the URL to discover payment amount (if 402)
2205
- * 3. If insufficient USDC, calculate deficit
2206
- * 4. Check spending limit
2207
- * 5. Borrow from Morpho via MorphoClient
2208
- * 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
2209
2263
  */
2210
2264
  async payWithAutoDraw(url, opts) {
2211
2265
  const { morphoClient, ...fetchOpts } = opts || {};
2212
- if (!this.config.autoDraw || !morphoClient) {
2266
+ if (!this.config.autoDraw && !this.config.autoYield || !morphoClient) {
2213
2267
  return this.request(url, fetchOpts);
2214
2268
  }
2215
2269
  try {
2216
2270
  const usdcBalance = await morphoClient.getUsdcBalance();
2217
- 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)}`);
2218
2272
  const paymentAmount = await this._probePaymentAmount(url, fetchOpts);
2219
2273
  if (paymentAmount !== null) {
2220
- 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`);
2221
2275
  const bufferStr = this.config.autoDrawBuffer || "0.5";
2222
2276
  const buffer = BigInt(Math.round(parseFloat(bufferStr) * 1e6));
2223
2277
  const needed = paymentAmount + buffer;
2224
2278
  if (usdcBalance < needed) {
2225
- const deficit = needed - usdcBalance;
2226
- console.log(` [auto-draw] Insufficient balance. Need to borrow ${(Number(deficit) / 1e6).toFixed(2)} USDC`);
2227
- const limitCheck = await this._checkSpendingLimit(deficit, morphoClient);
2228
- if (!limitCheck.allowed) {
2229
- return {
2230
- success: false,
2231
- error: `Auto-draw blocked: ${limitCheck.reason}`
2232
- };
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
+ }
2298
+ }
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`);
2233
2310
  }
2234
- const maxBorrowable = await morphoClient.getMaxBorrowable();
2235
- if (maxBorrowable.total < deficit) {
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");
2236
2318
  return {
2237
2319
  success: false,
2238
- 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)}.`
2239
2321
  };
2240
2322
  }
2241
- const borrowAmount = (Number(deficit) / 1e6).toFixed(6);
2242
- console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
2243
- const borrowResult = await morphoClient.borrow(borrowAmount);
2244
- console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
2245
- this._trackSpending(deficit);
2246
- const result = await this.request(url, fetchOpts);
2247
- return {
2248
- ...result,
2249
- autoDrawInfo: {
2250
- borrowed: borrowAmount,
2251
- borrowTx: borrowResult.tx,
2252
- reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2253
- }
2323
+ const drawInfo = {
2324
+ reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2254
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 };
2255
2357
  }
2256
2358
  }
2257
2359
  return this.request(url, fetchOpts);
2258
2360
  } catch (error) {
2259
- 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.`);
2260
2362
  return this.request(url, fetchOpts);
2261
2363
  }
2262
2364
  }
@@ -2357,48 +2459,26 @@ var X402Client = class {
2357
2459
  };
2358
2460
  }
2359
2461
  }
2360
- /** Track a new spending amount */
2462
+ /** Track a new spending amount and notify the caller for persistence */
2361
2463
  _trackSpending(amount) {
2362
2464
  this._resetTrackerIfNewDay();
2363
2465
  this._spendingTracker.totalBorrowed += amount;
2364
- }
2365
- /**
2366
- * Check if a borrow amount is within spending limits.
2367
- * Considers both fixed daily limits and yield-limited spending.
2368
- */
2369
- async _checkSpendingLimit(amount, morphoClient) {
2370
- this._resetTrackerIfNewDay();
2371
- if (this.config.yieldLimitedSpending) {
2466
+ if (this.config.onSpendingUpdate) {
2372
2467
  try {
2373
- const status = await morphoClient.getStatus();
2374
- let totalDailyYieldUsdc = 0;
2375
- for (const pos of status.positions) {
2376
- if (parseFloat(pos.collateral) > 0) {
2377
- try {
2378
- const estimate = await morphoClient.getYieldEstimate(
2379
- pos.collateralToken,
2380
- pos.collateral,
2381
- 1
2382
- // 1 day
2383
- );
2384
- totalDailyYieldUsdc += estimate.estimatedYieldUsd;
2385
- } catch (e) {
2386
- console.warn(`[agether] yield calc failed for ${pos.collateralToken}:`, e instanceof Error ? e.message : e);
2387
- }
2388
- }
2389
- }
2390
- const yieldLimit = BigInt(Math.round(totalDailyYieldUsdc * 1e6));
2391
- const newTotal = this._spendingTracker.totalBorrowed + amount;
2392
- if (yieldLimit > 0n && newTotal > yieldLimit) {
2393
- return {
2394
- allowed: false,
2395
- 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)}`
2396
- };
2397
- }
2468
+ this.config.onSpendingUpdate({
2469
+ date: this._spendingTracker.date,
2470
+ totalBorrowed: this._spendingTracker.totalBorrowed.toString()
2471
+ });
2398
2472
  } catch (e) {
2399
- 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);
2400
2474
  }
2401
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();
2402
2482
  if (this._spendingTracker.dailyLimit > 0n) {
2403
2483
  const newTotal = this._spendingTracker.totalBorrowed + amount;
2404
2484
  if (newTotal > this._spendingTracker.dailyLimit) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/sdk",
3
- "version": "2.5.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",