@agether/sdk 2.5.0 → 2.6.1

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,120 @@ 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`);
2051
- const bufferStr = this.config.autoDrawBuffer || "0.5";
2052
- const buffer = BigInt(Math.round(parseFloat(bufferStr) * 1e6));
2053
- const needed = paymentAmount + buffer;
2104
+ console.log(` [auto-fund] Payment required: ${(Number(paymentAmount) / 1e6).toFixed(6)} USDC`);
2105
+ const needed = paymentAmount;
2054
2106
  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
- };
2107
+ const totalDeficit = needed - usdcBalance;
2108
+ console.log(` [auto-fund] Insufficient balance. Deficit: ${(Number(totalDeficit) / 1e6).toFixed(2)} USDC`);
2109
+ const yieldPlan = [];
2110
+ let totalYieldAvailable = 0n;
2111
+ if (this.config.autoYield) {
2112
+ try {
2113
+ const positions = await morphoClient.getSupplyPositions();
2114
+ const withYield = positions.filter((p) => parseFloat(p.earnedYield) > 1e-6).sort((a, b) => parseFloat(b.earnedYield) - parseFloat(a.earnedYield));
2115
+ for (const pos of withYield) {
2116
+ const yieldRaw = BigInt(Math.floor(parseFloat(pos.earnedYield) * 1e6));
2117
+ if (yieldRaw > 0n) {
2118
+ yieldPlan.push({ collateralToken: pos.collateralToken, amount: yieldRaw });
2119
+ totalYieldAvailable += yieldRaw;
2120
+ }
2121
+ }
2122
+ console.log(` [auto-fund] Plan: ${(Number(totalYieldAvailable) / 1e6).toFixed(6)} USDC available from yield`);
2123
+ } catch (e) {
2124
+ console.warn(` [auto-fund] Plan: yield check failed: ${e instanceof Error ? e.message : String(e)}`);
2125
+ }
2126
+ }
2127
+ const yieldToUse = totalYieldAvailable < totalDeficit ? totalYieldAvailable : totalDeficit;
2128
+ const deficitAfterYield = totalDeficit - yieldToUse;
2129
+ let canBorrow = 0n;
2130
+ if (this.config.autoDraw && deficitAfterYield > 0n) {
2131
+ const limitCheck = await this._checkSpendingLimit(deficitAfterYield, morphoClient);
2132
+ if (!limitCheck.allowed) {
2133
+ return { success: false, error: `Auto-fund blocked: ${limitCheck.reason}` };
2134
+ }
2135
+ const maxBorrowable = await morphoClient.getMaxBorrowable();
2136
+ canBorrow = maxBorrowable.total;
2137
+ console.log(` [auto-fund] Plan: ${(Number(canBorrow) / 1e6).toFixed(2)} USDC borrowable from Morpho`);
2063
2138
  }
2064
- const maxBorrowable = await morphoClient.getMaxBorrowable();
2065
- if (maxBorrowable.total < deficit) {
2139
+ const totalAvailable = yieldToUse + canBorrow;
2140
+ if (totalAvailable < totalDeficit) {
2141
+ const parts = [];
2142
+ if (totalYieldAvailable > 0n) parts.push(`yield: $${(Number(totalYieldAvailable) / 1e6).toFixed(2)}`);
2143
+ if (canBorrow > 0n) parts.push(`borrowable: $${(Number(canBorrow) / 1e6).toFixed(2)}`);
2144
+ if (!this.config.autoYield) parts.push("autoYield: off");
2145
+ if (!this.config.autoDraw) parts.push("autoDraw: off");
2066
2146
  return {
2067
2147
  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.`
2148
+ 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
2149
  };
2070
2150
  }
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
- }
2151
+ const drawInfo = {
2152
+ reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2084
2153
  };
2154
+ if (yieldToUse > 0n) {
2155
+ let remaining = yieldToUse;
2156
+ let totalWithdrawn = 0n;
2157
+ let lastTx = "";
2158
+ for (const plan of yieldPlan) {
2159
+ if (remaining <= 0n) break;
2160
+ const toWithdraw = plan.amount < remaining ? plan.amount : remaining;
2161
+ const withdrawStr = (Number(toWithdraw) / 1e6).toFixed(6);
2162
+ console.log(` [auto-yield] Withdrawing $${withdrawStr} yield from ${plan.collateralToken} market`);
2163
+ const wr = await morphoClient.withdrawSupply(withdrawStr, plan.collateralToken);
2164
+ lastTx = wr.tx;
2165
+ totalWithdrawn += toWithdraw;
2166
+ remaining -= toWithdraw;
2167
+ }
2168
+ if (totalWithdrawn > 0n) {
2169
+ drawInfo.yieldWithdrawn = (Number(totalWithdrawn) / 1e6).toFixed(6);
2170
+ drawInfo.yieldTx = lastTx;
2171
+ console.log(` [auto-yield] Withdrawn: $${drawInfo.yieldWithdrawn}`);
2172
+ }
2173
+ }
2174
+ if (deficitAfterYield > 0n && this.config.autoDraw) {
2175
+ const borrowAmount = (Number(deficitAfterYield) / 1e6).toFixed(6);
2176
+ console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
2177
+ const borrowResult = await morphoClient.borrow(borrowAmount);
2178
+ console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
2179
+ drawInfo.borrowed = borrowAmount;
2180
+ drawInfo.borrowTx = borrowResult.tx;
2181
+ this._trackSpending(deficitAfterYield);
2182
+ }
2183
+ const result = await this.request(url, fetchOpts);
2184
+ return { ...result, autoDrawInfo: drawInfo };
2085
2185
  }
2086
2186
  }
2087
2187
  return this.request(url, fetchOpts);
2088
2188
  } catch (error) {
2089
- console.log(` [auto-draw] Auto-draw check failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
2189
+ console.log(` [auto-fund] Failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
2090
2190
  return this.request(url, fetchOpts);
2091
2191
  }
2092
2192
  }
@@ -2187,48 +2287,26 @@ var init_X402Client = __esm({
2187
2287
  };
2188
2288
  }
2189
2289
  }
2190
- /** Track a new spending amount */
2290
+ /** Track a new spending amount and notify the caller for persistence */
2191
2291
  _trackSpending(amount) {
2192
2292
  this._resetTrackerIfNewDay();
2193
2293
  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) {
2294
+ if (this.config.onSpendingUpdate) {
2202
2295
  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
- }
2296
+ this.config.onSpendingUpdate({
2297
+ date: this._spendingTracker.date,
2298
+ totalBorrowed: this._spendingTracker.totalBorrowed.toString()
2299
+ });
2228
2300
  } catch (e) {
2229
- console.warn("[agether] yield-limited spending check failed, falling through to fixed limit:", e instanceof Error ? e.message : e);
2301
+ console.warn("[agether] onSpendingUpdate callback failed:", e instanceof Error ? e.message : e);
2230
2302
  }
2231
2303
  }
2304
+ }
2305
+ /**
2306
+ * Check if a borrow amount is within the fixed daily spending limit.
2307
+ */
2308
+ async _checkSpendingLimit(amount, _morphoClient) {
2309
+ this._resetTrackerIfNewDay();
2232
2310
  if (this._spendingTracker.dailyLimit > 0n) {
2233
2311
  const newTotal = this._spendingTracker.totalBorrowed + amount;
2234
2312
  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,38 @@ 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
776
  /**
742
- * When true, auto-calculates the daily spending limit based on
743
- * theoretical yield of deposited collateral. Overrides dailySpendLimitUsdc.
777
+ * Pre-loaded spending state from a previous session.
778
+ * Pass this to resume the daily spending tracker after a restart.
779
+ * If the date doesn't match today, it's ignored (fresh day).
744
780
  */
745
- yieldLimitedSpending?: boolean;
781
+ initialSpendingState?: {
782
+ date: string;
783
+ totalBorrowed: string;
784
+ };
746
785
  /**
747
- * Safety margin: borrow this much extra beyond what's needed (in USDC, e.g. '1').
748
- * Helps avoid rounding issues. Default: '0.5'
786
+ * Called after every auto-draw borrow so the caller can persist the
787
+ * updated spending state (e.g. to a cache file). Receives the full
788
+ * SpendingTracker with the current date and cumulative amount.
749
789
  */
750
- autoDrawBuffer?: string;
790
+ onSpendingUpdate?: (state: {
791
+ date: string;
792
+ totalBorrowed: string;
793
+ }) => void;
751
794
  /**
752
795
  * ERC-7579 validator module address (e.g. Agether8004ValidationModule).
753
796
  * Required for Safe7579 smart wallets so that `isValidSignature` calls
@@ -790,8 +833,10 @@ interface X402Response<T = unknown> {
790
833
  txHash?: string;
791
834
  };
792
835
  autoDrawInfo?: {
793
- borrowed: string;
794
- borrowTx: string;
836
+ borrowed?: string;
837
+ borrowTx?: string;
838
+ yieldWithdrawn?: string;
839
+ yieldTx?: string;
795
840
  reason: string;
796
841
  };
797
842
  }
@@ -828,15 +873,23 @@ declare class X402Client {
828
873
  /** Get remaining daily spending allowance in USDC (human-readable) */
829
874
  getRemainingDailyAllowance(): string;
830
875
  /**
831
- * Pay with auto-draw: Make an x402 request with automatic Morpho borrowing.
876
+ * Pay with auto-funding: Make an x402 request with automatic USDC sourcing.
832
877
  *
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
878
+ * Uses a **plan-then-execute** approach: all reads happen first, and if the
879
+ * full deficit can't be covered the method fails with NO side-effects
880
+ * (no yield withdrawn, no USDC borrowed).
881
+ *
882
+ * Waterfall (when both autoYield + autoDraw enabled):
883
+ * 1. Check USDC balance on AgentAccount
884
+ * 2. Probe the URL to discover payment amount (if 402)
885
+ * 3. If insufficient USDC — PLANNING PHASE (read-only):
886
+ * a. Calculate total available yield across supply positions
887
+ * b. Calculate max borrowable from Morpho markets
888
+ * c. Verify yield + borrow can cover full deficit — if not, fail immediately
889
+ * 4. EXECUTION PHASE (only if plan is feasible):
890
+ * a. Withdraw needed yield from supply positions
891
+ * b. Borrow remaining deficit from Morpho
892
+ * 5. Proceed with x402 payment
840
893
  */
841
894
  payWithAutoDraw<T = unknown>(url: string, opts?: RequestInit & {
842
895
  morphoClient?: any;
@@ -850,11 +903,10 @@ declare class X402Client {
850
903
  private _probePaymentAmount;
851
904
  /** Reset spending tracker if it's a new day */
852
905
  private _resetTrackerIfNewDay;
853
- /** Track a new spending amount */
906
+ /** Track a new spending amount and notify the caller for persistence */
854
907
  private _trackSpending;
855
908
  /**
856
- * Check if a borrow amount is within spending limits.
857
- * Considers both fixed daily limits and yield-limited spending.
909
+ * Check if a borrow amount is within the fixed daily spending limit.
858
910
  */
859
911
  private _checkSpendingLimit;
860
912
  }
@@ -1227,4 +1279,4 @@ declare function getDefaultConfig(chainId: ChainId): AgetherConfig;
1227
1279
  */
1228
1280
  declare function createConfig(chainId: ChainId, options?: Partial<AgetherConfig>): AgetherConfig;
1229
1281
 
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 };
1282
+ 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,38 @@ 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
776
  /**
742
- * When true, auto-calculates the daily spending limit based on
743
- * theoretical yield of deposited collateral. Overrides dailySpendLimitUsdc.
777
+ * Pre-loaded spending state from a previous session.
778
+ * Pass this to resume the daily spending tracker after a restart.
779
+ * If the date doesn't match today, it's ignored (fresh day).
744
780
  */
745
- yieldLimitedSpending?: boolean;
781
+ initialSpendingState?: {
782
+ date: string;
783
+ totalBorrowed: string;
784
+ };
746
785
  /**
747
- * Safety margin: borrow this much extra beyond what's needed (in USDC, e.g. '1').
748
- * Helps avoid rounding issues. Default: '0.5'
786
+ * Called after every auto-draw borrow so the caller can persist the
787
+ * updated spending state (e.g. to a cache file). Receives the full
788
+ * SpendingTracker with the current date and cumulative amount.
749
789
  */
750
- autoDrawBuffer?: string;
790
+ onSpendingUpdate?: (state: {
791
+ date: string;
792
+ totalBorrowed: string;
793
+ }) => void;
751
794
  /**
752
795
  * ERC-7579 validator module address (e.g. Agether8004ValidationModule).
753
796
  * Required for Safe7579 smart wallets so that `isValidSignature` calls
@@ -790,8 +833,10 @@ interface X402Response<T = unknown> {
790
833
  txHash?: string;
791
834
  };
792
835
  autoDrawInfo?: {
793
- borrowed: string;
794
- borrowTx: string;
836
+ borrowed?: string;
837
+ borrowTx?: string;
838
+ yieldWithdrawn?: string;
839
+ yieldTx?: string;
795
840
  reason: string;
796
841
  };
797
842
  }
@@ -828,15 +873,23 @@ declare class X402Client {
828
873
  /** Get remaining daily spending allowance in USDC (human-readable) */
829
874
  getRemainingDailyAllowance(): string;
830
875
  /**
831
- * Pay with auto-draw: Make an x402 request with automatic Morpho borrowing.
876
+ * Pay with auto-funding: Make an x402 request with automatic USDC sourcing.
832
877
  *
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
878
+ * Uses a **plan-then-execute** approach: all reads happen first, and if the
879
+ * full deficit can't be covered the method fails with NO side-effects
880
+ * (no yield withdrawn, no USDC borrowed).
881
+ *
882
+ * Waterfall (when both autoYield + autoDraw enabled):
883
+ * 1. Check USDC balance on AgentAccount
884
+ * 2. Probe the URL to discover payment amount (if 402)
885
+ * 3. If insufficient USDC — PLANNING PHASE (read-only):
886
+ * a. Calculate total available yield across supply positions
887
+ * b. Calculate max borrowable from Morpho markets
888
+ * c. Verify yield + borrow can cover full deficit — if not, fail immediately
889
+ * 4. EXECUTION PHASE (only if plan is feasible):
890
+ * a. Withdraw needed yield from supply positions
891
+ * b. Borrow remaining deficit from Morpho
892
+ * 5. Proceed with x402 payment
840
893
  */
841
894
  payWithAutoDraw<T = unknown>(url: string, opts?: RequestInit & {
842
895
  morphoClient?: any;
@@ -850,11 +903,10 @@ declare class X402Client {
850
903
  private _probePaymentAmount;
851
904
  /** Reset spending tracker if it's a new day */
852
905
  private _resetTrackerIfNewDay;
853
- /** Track a new spending amount */
906
+ /** Track a new spending amount and notify the caller for persistence */
854
907
  private _trackSpending;
855
908
  /**
856
- * Check if a borrow amount is within spending limits.
857
- * Considers both fixed daily limits and yield-limited spending.
909
+ * Check if a borrow amount is within the fixed daily spending limit.
858
910
  */
859
911
  private _checkSpendingLimit;
860
912
  }
@@ -1227,4 +1279,4 @@ declare function getDefaultConfig(chainId: ChainId): AgetherConfig;
1227
1279
  */
1228
1280
  declare function createConfig(chainId: ChainId, options?: Partial<AgetherConfig>): AgetherConfig;
1229
1281
 
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 };
1282
+ 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,120 @@ 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`);
2293
- const bufferStr = this.config.autoDrawBuffer || "0.5";
2294
- const buffer = BigInt(Math.round(parseFloat(bufferStr) * 1e6));
2295
- const needed = paymentAmount + buffer;
2346
+ console.log(` [auto-fund] Payment required: ${(Number(paymentAmount) / 1e6).toFixed(6)} USDC`);
2347
+ const needed = paymentAmount;
2296
2348
  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
- };
2349
+ const totalDeficit = needed - usdcBalance;
2350
+ console.log(` [auto-fund] Insufficient balance. Deficit: ${(Number(totalDeficit) / 1e6).toFixed(2)} USDC`);
2351
+ const yieldPlan = [];
2352
+ let totalYieldAvailable = 0n;
2353
+ if (this.config.autoYield) {
2354
+ try {
2355
+ const positions = await morphoClient.getSupplyPositions();
2356
+ const withYield = positions.filter((p) => parseFloat(p.earnedYield) > 1e-6).sort((a, b) => parseFloat(b.earnedYield) - parseFloat(a.earnedYield));
2357
+ for (const pos of withYield) {
2358
+ const yieldRaw = BigInt(Math.floor(parseFloat(pos.earnedYield) * 1e6));
2359
+ if (yieldRaw > 0n) {
2360
+ yieldPlan.push({ collateralToken: pos.collateralToken, amount: yieldRaw });
2361
+ totalYieldAvailable += yieldRaw;
2362
+ }
2363
+ }
2364
+ console.log(` [auto-fund] Plan: ${(Number(totalYieldAvailable) / 1e6).toFixed(6)} USDC available from yield`);
2365
+ } catch (e) {
2366
+ console.warn(` [auto-fund] Plan: yield check failed: ${e instanceof Error ? e.message : String(e)}`);
2367
+ }
2368
+ }
2369
+ const yieldToUse = totalYieldAvailable < totalDeficit ? totalYieldAvailable : totalDeficit;
2370
+ const deficitAfterYield = totalDeficit - yieldToUse;
2371
+ let canBorrow = 0n;
2372
+ if (this.config.autoDraw && deficitAfterYield > 0n) {
2373
+ const limitCheck = await this._checkSpendingLimit(deficitAfterYield, morphoClient);
2374
+ if (!limitCheck.allowed) {
2375
+ return { success: false, error: `Auto-fund blocked: ${limitCheck.reason}` };
2376
+ }
2377
+ const maxBorrowable = await morphoClient.getMaxBorrowable();
2378
+ canBorrow = maxBorrowable.total;
2379
+ console.log(` [auto-fund] Plan: ${(Number(canBorrow) / 1e6).toFixed(2)} USDC borrowable from Morpho`);
2305
2380
  }
2306
- const maxBorrowable = await morphoClient.getMaxBorrowable();
2307
- if (maxBorrowable.total < deficit) {
2381
+ const totalAvailable = yieldToUse + canBorrow;
2382
+ if (totalAvailable < totalDeficit) {
2383
+ const parts = [];
2384
+ if (totalYieldAvailable > 0n) parts.push(`yield: $${(Number(totalYieldAvailable) / 1e6).toFixed(2)}`);
2385
+ if (canBorrow > 0n) parts.push(`borrowable: $${(Number(canBorrow) / 1e6).toFixed(2)}`);
2386
+ if (!this.config.autoYield) parts.push("autoYield: off");
2387
+ if (!this.config.autoDraw) parts.push("autoDraw: off");
2308
2388
  return {
2309
2389
  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.`
2390
+ 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
2391
  };
2312
2392
  }
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
- }
2393
+ const drawInfo = {
2394
+ reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2326
2395
  };
2396
+ if (yieldToUse > 0n) {
2397
+ let remaining = yieldToUse;
2398
+ let totalWithdrawn = 0n;
2399
+ let lastTx = "";
2400
+ for (const plan of yieldPlan) {
2401
+ if (remaining <= 0n) break;
2402
+ const toWithdraw = plan.amount < remaining ? plan.amount : remaining;
2403
+ const withdrawStr = (Number(toWithdraw) / 1e6).toFixed(6);
2404
+ console.log(` [auto-yield] Withdrawing $${withdrawStr} yield from ${plan.collateralToken} market`);
2405
+ const wr = await morphoClient.withdrawSupply(withdrawStr, plan.collateralToken);
2406
+ lastTx = wr.tx;
2407
+ totalWithdrawn += toWithdraw;
2408
+ remaining -= toWithdraw;
2409
+ }
2410
+ if (totalWithdrawn > 0n) {
2411
+ drawInfo.yieldWithdrawn = (Number(totalWithdrawn) / 1e6).toFixed(6);
2412
+ drawInfo.yieldTx = lastTx;
2413
+ console.log(` [auto-yield] Withdrawn: $${drawInfo.yieldWithdrawn}`);
2414
+ }
2415
+ }
2416
+ if (deficitAfterYield > 0n && this.config.autoDraw) {
2417
+ const borrowAmount = (Number(deficitAfterYield) / 1e6).toFixed(6);
2418
+ console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
2419
+ const borrowResult = await morphoClient.borrow(borrowAmount);
2420
+ console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
2421
+ drawInfo.borrowed = borrowAmount;
2422
+ drawInfo.borrowTx = borrowResult.tx;
2423
+ this._trackSpending(deficitAfterYield);
2424
+ }
2425
+ const result = await this.request(url, fetchOpts);
2426
+ return { ...result, autoDrawInfo: drawInfo };
2327
2427
  }
2328
2428
  }
2329
2429
  return this.request(url, fetchOpts);
2330
2430
  } catch (error) {
2331
- console.log(` [auto-draw] Auto-draw check failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
2431
+ console.log(` [auto-fund] Failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
2332
2432
  return this.request(url, fetchOpts);
2333
2433
  }
2334
2434
  }
@@ -2429,48 +2529,26 @@ var X402Client = class {
2429
2529
  };
2430
2530
  }
2431
2531
  }
2432
- /** Track a new spending amount */
2532
+ /** Track a new spending amount and notify the caller for persistence */
2433
2533
  _trackSpending(amount) {
2434
2534
  this._resetTrackerIfNewDay();
2435
2535
  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) {
2536
+ if (this.config.onSpendingUpdate) {
2444
2537
  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
- }
2538
+ this.config.onSpendingUpdate({
2539
+ date: this._spendingTracker.date,
2540
+ totalBorrowed: this._spendingTracker.totalBorrowed.toString()
2541
+ });
2470
2542
  } catch (e) {
2471
- console.warn("[agether] yield-limited spending check failed, falling through to fixed limit:", e instanceof Error ? e.message : e);
2543
+ console.warn("[agether] onSpendingUpdate callback failed:", e instanceof Error ? e.message : e);
2472
2544
  }
2473
2545
  }
2546
+ }
2547
+ /**
2548
+ * Check if a borrow amount is within the fixed daily spending limit.
2549
+ */
2550
+ async _checkSpendingLimit(amount, _morphoClient) {
2551
+ this._resetTrackerIfNewDay();
2474
2552
  if (this._spendingTracker.dailyLimit > 0n) {
2475
2553
  const newTotal = this._spendingTracker.totalBorrowed + amount;
2476
2554
  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,120 @@ 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`);
2221
- const bufferStr = this.config.autoDrawBuffer || "0.5";
2222
- const buffer = BigInt(Math.round(parseFloat(bufferStr) * 1e6));
2223
- const needed = paymentAmount + buffer;
2274
+ console.log(` [auto-fund] Payment required: ${(Number(paymentAmount) / 1e6).toFixed(6)} USDC`);
2275
+ const needed = paymentAmount;
2224
2276
  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
- };
2277
+ const totalDeficit = needed - usdcBalance;
2278
+ console.log(` [auto-fund] Insufficient balance. Deficit: ${(Number(totalDeficit) / 1e6).toFixed(2)} USDC`);
2279
+ const yieldPlan = [];
2280
+ let totalYieldAvailable = 0n;
2281
+ if (this.config.autoYield) {
2282
+ try {
2283
+ const positions = await morphoClient.getSupplyPositions();
2284
+ const withYield = positions.filter((p) => parseFloat(p.earnedYield) > 1e-6).sort((a, b) => parseFloat(b.earnedYield) - parseFloat(a.earnedYield));
2285
+ for (const pos of withYield) {
2286
+ const yieldRaw = BigInt(Math.floor(parseFloat(pos.earnedYield) * 1e6));
2287
+ if (yieldRaw > 0n) {
2288
+ yieldPlan.push({ collateralToken: pos.collateralToken, amount: yieldRaw });
2289
+ totalYieldAvailable += yieldRaw;
2290
+ }
2291
+ }
2292
+ console.log(` [auto-fund] Plan: ${(Number(totalYieldAvailable) / 1e6).toFixed(6)} USDC available from yield`);
2293
+ } catch (e) {
2294
+ console.warn(` [auto-fund] Plan: yield check failed: ${e instanceof Error ? e.message : String(e)}`);
2295
+ }
2296
+ }
2297
+ const yieldToUse = totalYieldAvailable < totalDeficit ? totalYieldAvailable : totalDeficit;
2298
+ const deficitAfterYield = totalDeficit - yieldToUse;
2299
+ let canBorrow = 0n;
2300
+ if (this.config.autoDraw && deficitAfterYield > 0n) {
2301
+ const limitCheck = await this._checkSpendingLimit(deficitAfterYield, morphoClient);
2302
+ if (!limitCheck.allowed) {
2303
+ return { success: false, error: `Auto-fund blocked: ${limitCheck.reason}` };
2304
+ }
2305
+ const maxBorrowable = await morphoClient.getMaxBorrowable();
2306
+ canBorrow = maxBorrowable.total;
2307
+ console.log(` [auto-fund] Plan: ${(Number(canBorrow) / 1e6).toFixed(2)} USDC borrowable from Morpho`);
2233
2308
  }
2234
- const maxBorrowable = await morphoClient.getMaxBorrowable();
2235
- if (maxBorrowable.total < deficit) {
2309
+ const totalAvailable = yieldToUse + canBorrow;
2310
+ if (totalAvailable < totalDeficit) {
2311
+ const parts = [];
2312
+ if (totalYieldAvailable > 0n) parts.push(`yield: $${(Number(totalYieldAvailable) / 1e6).toFixed(2)}`);
2313
+ if (canBorrow > 0n) parts.push(`borrowable: $${(Number(canBorrow) / 1e6).toFixed(2)}`);
2314
+ if (!this.config.autoYield) parts.push("autoYield: off");
2315
+ if (!this.config.autoDraw) parts.push("autoDraw: off");
2236
2316
  return {
2237
2317
  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.`
2318
+ 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
2319
  };
2240
2320
  }
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
- }
2321
+ const drawInfo = {
2322
+ reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
2254
2323
  };
2324
+ if (yieldToUse > 0n) {
2325
+ let remaining = yieldToUse;
2326
+ let totalWithdrawn = 0n;
2327
+ let lastTx = "";
2328
+ for (const plan of yieldPlan) {
2329
+ if (remaining <= 0n) break;
2330
+ const toWithdraw = plan.amount < remaining ? plan.amount : remaining;
2331
+ const withdrawStr = (Number(toWithdraw) / 1e6).toFixed(6);
2332
+ console.log(` [auto-yield] Withdrawing $${withdrawStr} yield from ${plan.collateralToken} market`);
2333
+ const wr = await morphoClient.withdrawSupply(withdrawStr, plan.collateralToken);
2334
+ lastTx = wr.tx;
2335
+ totalWithdrawn += toWithdraw;
2336
+ remaining -= toWithdraw;
2337
+ }
2338
+ if (totalWithdrawn > 0n) {
2339
+ drawInfo.yieldWithdrawn = (Number(totalWithdrawn) / 1e6).toFixed(6);
2340
+ drawInfo.yieldTx = lastTx;
2341
+ console.log(` [auto-yield] Withdrawn: $${drawInfo.yieldWithdrawn}`);
2342
+ }
2343
+ }
2344
+ if (deficitAfterYield > 0n && this.config.autoDraw) {
2345
+ const borrowAmount = (Number(deficitAfterYield) / 1e6).toFixed(6);
2346
+ console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
2347
+ const borrowResult = await morphoClient.borrow(borrowAmount);
2348
+ console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
2349
+ drawInfo.borrowed = borrowAmount;
2350
+ drawInfo.borrowTx = borrowResult.tx;
2351
+ this._trackSpending(deficitAfterYield);
2352
+ }
2353
+ const result = await this.request(url, fetchOpts);
2354
+ return { ...result, autoDrawInfo: drawInfo };
2255
2355
  }
2256
2356
  }
2257
2357
  return this.request(url, fetchOpts);
2258
2358
  } catch (error) {
2259
- console.log(` [auto-draw] Auto-draw check failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
2359
+ console.log(` [auto-fund] Failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
2260
2360
  return this.request(url, fetchOpts);
2261
2361
  }
2262
2362
  }
@@ -2357,48 +2457,26 @@ var X402Client = class {
2357
2457
  };
2358
2458
  }
2359
2459
  }
2360
- /** Track a new spending amount */
2460
+ /** Track a new spending amount and notify the caller for persistence */
2361
2461
  _trackSpending(amount) {
2362
2462
  this._resetTrackerIfNewDay();
2363
2463
  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) {
2464
+ if (this.config.onSpendingUpdate) {
2372
2465
  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
- }
2466
+ this.config.onSpendingUpdate({
2467
+ date: this._spendingTracker.date,
2468
+ totalBorrowed: this._spendingTracker.totalBorrowed.toString()
2469
+ });
2398
2470
  } catch (e) {
2399
- console.warn("[agether] yield-limited spending check failed, falling through to fixed limit:", e instanceof Error ? e.message : e);
2471
+ console.warn("[agether] onSpendingUpdate callback failed:", e instanceof Error ? e.message : e);
2400
2472
  }
2401
2473
  }
2474
+ }
2475
+ /**
2476
+ * Check if a borrow amount is within the fixed daily spending limit.
2477
+ */
2478
+ async _checkSpendingLimit(amount, _morphoClient) {
2479
+ this._resetTrackerIfNewDay();
2402
2480
  if (this._spendingTracker.dailyLimit > 0n) {
2403
2481
  const newTotal = this._spendingTracker.totalBorrowed + amount;
2404
2482
  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.1",
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",