@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 +153 -75
- package/dist/index.d.mts +75 -23
- package/dist/index.d.ts +75 -23
- package/dist/index.js +153 -75
- package/dist/index.mjs +153 -75
- package/package.json +1 -1
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
|
-
|
|
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-
|
|
2076
|
+
* Pay with auto-funding: Make an x402 request with automatic USDC sourcing.
|
|
2031
2077
|
*
|
|
2032
|
-
*
|
|
2033
|
-
*
|
|
2034
|
-
*
|
|
2035
|
-
*
|
|
2036
|
-
*
|
|
2037
|
-
*
|
|
2038
|
-
*
|
|
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-
|
|
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-
|
|
2051
|
-
const
|
|
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
|
|
2056
|
-
console.log(` [auto-
|
|
2057
|
-
const
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
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
|
|
2065
|
-
if (
|
|
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: `
|
|
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
|
|
2072
|
-
|
|
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-
|
|
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
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
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]
|
|
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
|
-
*
|
|
712
|
-
* yield
|
|
713
|
-
*
|
|
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
|
-
*
|
|
743
|
-
*
|
|
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
|
-
|
|
781
|
+
initialSpendingState?: {
|
|
782
|
+
date: string;
|
|
783
|
+
totalBorrowed: string;
|
|
784
|
+
};
|
|
746
785
|
/**
|
|
747
|
-
*
|
|
748
|
-
*
|
|
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
|
-
|
|
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
|
|
794
|
-
borrowTx
|
|
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-
|
|
876
|
+
* Pay with auto-funding: Make an x402 request with automatic USDC sourcing.
|
|
832
877
|
*
|
|
833
|
-
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
839
|
-
*
|
|
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
|
|
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
|
-
*
|
|
712
|
-
* yield
|
|
713
|
-
*
|
|
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
|
-
*
|
|
743
|
-
*
|
|
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
|
-
|
|
781
|
+
initialSpendingState?: {
|
|
782
|
+
date: string;
|
|
783
|
+
totalBorrowed: string;
|
|
784
|
+
};
|
|
746
785
|
/**
|
|
747
|
-
*
|
|
748
|
-
*
|
|
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
|
-
|
|
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
|
|
794
|
-
borrowTx
|
|
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-
|
|
876
|
+
* Pay with auto-funding: Make an x402 request with automatic USDC sourcing.
|
|
832
877
|
*
|
|
833
|
-
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
839
|
-
*
|
|
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
|
|
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
|
-
|
|
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-
|
|
2318
|
+
* Pay with auto-funding: Make an x402 request with automatic USDC sourcing.
|
|
2273
2319
|
*
|
|
2274
|
-
*
|
|
2275
|
-
*
|
|
2276
|
-
*
|
|
2277
|
-
*
|
|
2278
|
-
*
|
|
2279
|
-
*
|
|
2280
|
-
*
|
|
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-
|
|
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-
|
|
2293
|
-
const
|
|
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
|
|
2298
|
-
console.log(` [auto-
|
|
2299
|
-
const
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
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
|
|
2307
|
-
if (
|
|
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: `
|
|
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
|
|
2314
|
-
|
|
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-
|
|
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
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
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]
|
|
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
|
-
|
|
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-
|
|
2246
|
+
* Pay with auto-funding: Make an x402 request with automatic USDC sourcing.
|
|
2201
2247
|
*
|
|
2202
|
-
*
|
|
2203
|
-
*
|
|
2204
|
-
*
|
|
2205
|
-
*
|
|
2206
|
-
*
|
|
2207
|
-
*
|
|
2208
|
-
*
|
|
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-
|
|
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-
|
|
2221
|
-
const
|
|
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
|
|
2226
|
-
console.log(` [auto-
|
|
2227
|
-
const
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
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
|
|
2235
|
-
if (
|
|
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: `
|
|
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
|
|
2242
|
-
|
|
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-
|
|
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
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
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]
|
|
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) {
|