@agether/agether 3.1.4 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/index.ts +529 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/agether",
3
- "version": "3.1.4",
3
+ "version": "3.3.0",
4
4
  "description": "OpenClaw plugin for Agether — onchain credit for AI agents on Ethereum & Base",
5
5
  "main": "src/index.ts",
6
6
  "openclaw": {
@@ -9,7 +9,7 @@
9
9
  ]
10
10
  },
11
11
  "dependencies": {
12
- "@agether/sdk": "file:../sdk",
12
+ "@agether/sdk": "^2.18.1",
13
13
  "axios": "^1.6.0",
14
14
  "ethers": "^6.9.0"
15
15
  },
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ import {
29
29
  ScoringClient,
30
30
  getContractAddresses,
31
31
  ChainId,
32
+ AaveClient,
32
33
  } from "@agether/sdk";
33
34
 
34
35
  // ─── Constants ────────────────────────────────────────────
@@ -355,6 +356,19 @@ function createMorphoClient(cfg: PluginConfig): MorphoClient {
355
356
  }
356
357
 
357
358
  /** @deprecated Use createAgetherClient or createMorphoClient instead */
359
+ function createAaveClient(cfg: PluginConfig): AaveClient {
360
+ const agentId = state.getAgentId(cfg.chainId) || cfg.agentId;
361
+ if (!agentId) {
362
+ throw new Error("agentId not set. Register first with agether_register, then try again.");
363
+ }
364
+ return new AaveClient({
365
+ privateKey: cfg.privateKey,
366
+ rpcUrl: cfg.rpcUrl,
367
+ agentId,
368
+ chainId: cfg.chainId,
369
+ });
370
+ }
371
+
358
372
  function createClient(cfg: PluginConfig): MorphoClient {
359
373
  return createMorphoClient(cfg);
360
374
  }
@@ -672,7 +686,7 @@ export default function register(api: any) {
672
686
  // TOOL: morpho_deposit
673
687
  // ═══════════════════════════════════════════════════════
674
688
  api.registerTool({
675
- name: "morpho_deposit_collateral",
689
+ name: "morpho_deposit",
676
690
  description: "Deposit collateral into Morpho Blue to enable borrowing. Validates balance first.",
677
691
  parameters: {
678
692
  type: "object",
@@ -2125,6 +2139,520 @@ export default function register(api: any) {
2125
2139
  });
2126
2140
 
2127
2141
 
2142
+
2143
+ // ═══════════════════════════════════════════════════════
2144
+ // TOOL: morpho_repay_and_withdraw
2145
+ // ═══════════════════════════════════════════════════════
2146
+ api.registerTool({
2147
+ name: "morpho_repay_and_withdraw",
2148
+ description: "Repay all debt and withdraw all collateral from Morpho in one atomic batch. Full position exit.",
2149
+ parameters: {
2150
+ type: "object",
2151
+ properties: {
2152
+ token: { type: "string", description: "Collateral token (optional — auto-detects active position)" },
2153
+ loanToken: { type: "string", description: "Loan token (optional)" },
2154
+ toEoa: { type: "boolean", description: "Send withdrawn collateral to EOA wallet (default: true)" },
2155
+ },
2156
+ },
2157
+ async execute(_id: string, params: { token?: string; loanToken?: string; toEoa?: boolean }) {
2158
+ try {
2159
+ const cfg = getConfig(api);
2160
+ requireChain(cfg);
2161
+ const client = createMorphoClient(cfg);
2162
+ const result = await client.repayAndWithdraw(params.token, params.loanToken, params.toEoa ?? true);
2163
+ const explorer = cfg.chainId === 8453 ? 'https://basescan.org/tx/' : 'https://etherscan.io/tx/';
2164
+ return ok(JSON.stringify({
2165
+ status: "position_closed",
2166
+ repaid: result.repaid,
2167
+ withdrawn: result.withdrawn + ' ' + result.collateralToken,
2168
+ loanToken: result.loanToken,
2169
+ tx: explorer + result.tx,
2170
+ }));
2171
+ } catch (e) { return fail(e); }
2172
+ },
2173
+ });
2174
+
2175
+ // ═══════════════════════════════════════════════════════
2176
+ // AAVE V3 TOOLS
2177
+ // ═══════════════════════════════════════════════════════
2178
+
2179
+ // ═══════════════════════════════════════════════════════
2180
+ // TOOL: aave_markets
2181
+ // ═══════════════════════════════════════════════════════
2182
+ api.registerTool({
2183
+ name: "aave_markets",
2184
+ description: "List Aave V3 reserves with supply/borrow APY, LTV, prices. Shows all available markets on the current chain.",
2185
+ parameters: { type: "object", properties: {} },
2186
+ async execute() {
2187
+ try {
2188
+ const cfg = getConfig(api);
2189
+ requireChain(cfg);
2190
+ const client = createAaveClient(cfg);
2191
+ const reserves = await client.getReserves();
2192
+
2193
+ if (reserves.length === 0) {
2194
+ return ok(JSON.stringify({ status: "no_reserves", message: "No Aave V3 reserves found on this chain." }));
2195
+ }
2196
+
2197
+ const summary = reserves.map(r => ({
2198
+ asset: r.symbol,
2199
+ supplyApy: r.supplyApy.toFixed(2) + '%',
2200
+ borrowApy: r.borrowApy.toFixed(2) + '%',
2201
+ maxLtv: r.ltv.toFixed(0) + '%',
2202
+ liquidationThreshold: r.liquidationThreshold.toFixed(0) + '%',
2203
+ priceUsd: '$' + r.priceUsd.toFixed(2),
2204
+ borrowingEnabled: r.borrowingEnabled,
2205
+ active: r.isActive,
2206
+ }));
2207
+
2208
+ return ok(JSON.stringify({ status: "aave_reserves", chain: cfg.chainId === 8453 ? "Base" : "Ethereum", reserves: summary }));
2209
+ } catch (e) { return fail(e); }
2210
+ },
2211
+ });
2212
+
2213
+ // ═══════════════════════════════════════════════════════
2214
+ // TOOL: aave_status
2215
+ // ═══════════════════════════════════════════════════════
2216
+ api.registerTool({
2217
+ name: "aave_status",
2218
+ description: "Show Aave V3 account overview: total collateral, debt, health factor, and per-asset positions with APY.",
2219
+ parameters: { type: "object", properties: {} },
2220
+ async execute() {
2221
+ try {
2222
+ const cfg = getConfig(api);
2223
+ requireChain(cfg);
2224
+ const client = createAaveClient(cfg);
2225
+ const data = await client.getAccountData();
2226
+
2227
+ return ok(JSON.stringify({
2228
+ status: "aave_account",
2229
+ chain: cfg.chainId === 8453 ? "Base" : "Ethereum",
2230
+ totalCollateralUsd: '$' + data.totalCollateralUsd.toFixed(2),
2231
+ totalDebtUsd: '$' + data.totalDebtUsd.toFixed(2),
2232
+ availableBorrowUsd: '$' + data.availableBorrowUsd.toFixed(2),
2233
+ currentLtv: data.currentLtv.toFixed(2) + '%',
2234
+ liquidationThreshold: data.liquidationThreshold.toFixed(2) + '%',
2235
+ healthFactor: data.healthFactor === Infinity ? 'Safe (no debt)' : data.healthFactor.toFixed(3),
2236
+ positions: data.positions.map(p => ({
2237
+ asset: p.asset,
2238
+ supplied: p.supplied > 0 ? p.supplied.toFixed(6) + ' (' + '$' + p.suppliedUsd.toFixed(2) + ')' : '0',
2239
+ borrowed: p.borrowed > 0 ? p.borrowed.toFixed(6) + ' (' + '$' + p.borrowedUsd.toFixed(2) + ')' : '0',
2240
+ usedAsCollateral: p.usedAsCollateral,
2241
+ supplyApy: p.supplyApy.toFixed(2) + '%',
2242
+ borrowApy: p.borrowApy.toFixed(2) + '%',
2243
+ })),
2244
+ }));
2245
+ } catch (e) { return fail(e); }
2246
+ },
2247
+ });
2248
+
2249
+ // ═══════════════════════════════════════════════════════
2250
+ // TOOL: aave_supply
2251
+ // ═══════════════════════════════════════════════════════
2252
+ api.registerTool({
2253
+ name: "aave_deposit",
2254
+ description: "Deposit an asset to Aave V3 as collateral or to earn yield.",
2255
+ parameters: {
2256
+ type: "object",
2257
+ properties: {
2258
+ token: { type: "string", description: "Token to supply (e.g. 'WETH', 'USDC', 'wstETH')" },
2259
+ amount: { type: "string", description: "Amount to supply" },
2260
+ },
2261
+ required: ["token", "amount"],
2262
+ },
2263
+ async execute(_id: string, params: { token: string; amount: string }) {
2264
+ try {
2265
+ const cfg = getConfig(api);
2266
+ requireChain(cfg);
2267
+ const client = createAaveClient(cfg);
2268
+ const receipt = await client.supply(params.token, params.amount);
2269
+ const explorer = cfg.chainId === 8453 ? 'https://basescan.org/tx/' : 'https://etherscan.io/tx/';
2270
+
2271
+ return ok(JSON.stringify({
2272
+ status: "supplied",
2273
+ token: params.token,
2274
+ amount: params.amount,
2275
+ tx: explorer + receipt.hash,
2276
+ }));
2277
+ } catch (e) { return fail(e); }
2278
+ },
2279
+ });
2280
+
2281
+ // ═══════════════════════════════════════════════════════
2282
+ // TOOL: aave_withdraw
2283
+ // ═══════════════════════════════════════════════════════
2284
+ api.registerTool({
2285
+ name: "aave_withdraw",
2286
+ description: "Withdraw a supplied asset from Aave V3.",
2287
+ parameters: {
2288
+ type: "object",
2289
+ properties: {
2290
+ token: { type: "string", description: "Token to withdraw (e.g. 'WETH', 'USDC')" },
2291
+ amount: { type: "string", description: "Amount to withdraw. Use 'all' for full balance." },
2292
+ },
2293
+ required: ["token", "amount"],
2294
+ },
2295
+ async execute(_id: string, params: { token: string; amount: string }) {
2296
+ try {
2297
+ const cfg = getConfig(api);
2298
+ requireChain(cfg);
2299
+ const client = createAaveClient(cfg);
2300
+ const receipt = await client.withdraw(params.token, params.amount);
2301
+ const explorer = cfg.chainId === 8453 ? 'https://basescan.org/tx/' : 'https://etherscan.io/tx/';
2302
+
2303
+ return ok(JSON.stringify({
2304
+ status: "withdrawn",
2305
+ token: params.token,
2306
+ amount: params.amount,
2307
+ tx: explorer + receipt.hash,
2308
+ }));
2309
+ } catch (e) { return fail(e); }
2310
+ },
2311
+ });
2312
+
2313
+ // ═══════════════════════════════════════════════════════
2314
+ // TOOL: aave_borrow
2315
+ // ═══════════════════════════════════════════════════════
2316
+ api.registerTool({
2317
+ name: "aave_borrow",
2318
+ description: "Borrow an asset from Aave V3 against supplied collateral. Uses variable rate.",
2319
+ parameters: {
2320
+ type: "object",
2321
+ properties: {
2322
+ token: { type: "string", description: "Token to borrow (e.g. 'USDC', 'WETH')" },
2323
+ amount: { type: "string", description: "Amount to borrow" },
2324
+ },
2325
+ required: ["token", "amount"],
2326
+ },
2327
+ async execute(_id: string, params: { token: string; amount: string }) {
2328
+ try {
2329
+ const cfg = getConfig(api);
2330
+ requireChain(cfg);
2331
+ const client = createAaveClient(cfg);
2332
+ const receipt = await client.borrow(params.token, params.amount);
2333
+ const explorer = cfg.chainId === 8453 ? 'https://basescan.org/tx/' : 'https://etherscan.io/tx/';
2334
+
2335
+ return ok(JSON.stringify({
2336
+ status: "borrowed",
2337
+ token: params.token,
2338
+ amount: params.amount,
2339
+ tx: explorer + receipt.hash,
2340
+ }));
2341
+ } catch (e) { return fail(e); }
2342
+ },
2343
+ });
2344
+
2345
+ // ═══════════════════════════════════════════════════════
2346
+ // TOOL: aave_repay
2347
+ // ═══════════════════════════════════════════════════════
2348
+ api.registerTool({
2349
+ name: "aave_repay",
2350
+ description: "Repay borrowed asset on Aave V3. Use amount 'all' to repay full debt.",
2351
+ parameters: {
2352
+ type: "object",
2353
+ properties: {
2354
+ token: { type: "string", description: "Token to repay (e.g. 'USDC', 'WETH')" },
2355
+ amount: { type: "string", description: "Amount to repay. Use 'all' for full debt." },
2356
+ },
2357
+ required: ["token", "amount"],
2358
+ },
2359
+ async execute(_id: string, params: { token: string; amount: string }) {
2360
+ try {
2361
+ const cfg = getConfig(api);
2362
+ requireChain(cfg);
2363
+ const client = createAaveClient(cfg);
2364
+ const receipt = await client.repay(params.token, params.amount);
2365
+ const explorer = cfg.chainId === 8453 ? 'https://basescan.org/tx/' : 'https://etherscan.io/tx/';
2366
+
2367
+ return ok(JSON.stringify({
2368
+ status: "repaid",
2369
+ token: params.token,
2370
+ amount: params.amount,
2371
+ tx: explorer + receipt.hash,
2372
+ }));
2373
+ } catch (e) { return fail(e); }
2374
+ },
2375
+ });
2376
+
2377
+ // ═══════════════════════════════════════════════════════
2378
+ // TOOL: aave_set_collateral
2379
+ // ═══════════════════════════════════════════════════════
2380
+ api.registerTool({
2381
+ name: "aave_set_collateral",
2382
+ description: "Enable or disable a supplied asset as collateral on Aave V3.",
2383
+ parameters: {
2384
+ type: "object",
2385
+ properties: {
2386
+ token: { type: "string", description: "Token to toggle (e.g. 'WETH', 'wstETH')" },
2387
+ enabled: { type: "boolean", description: "true to enable as collateral, false to disable" },
2388
+ },
2389
+ required: ["token", "enabled"],
2390
+ },
2391
+ async execute(_id: string, params: { token: string; enabled: boolean }) {
2392
+ try {
2393
+ const cfg = getConfig(api);
2394
+ requireChain(cfg);
2395
+ const client = createAaveClient(cfg);
2396
+ const receipt = await client.setCollateral(params.token, params.enabled);
2397
+ const explorer = cfg.chainId === 8453 ? 'https://basescan.org/tx/' : 'https://etherscan.io/tx/';
2398
+
2399
+ return ok(JSON.stringify({
2400
+ status: "collateral_updated",
2401
+ token: params.token,
2402
+ enabled: params.enabled,
2403
+ tx: explorer + receipt.hash,
2404
+ }));
2405
+ } catch (e) { return fail(e); }
2406
+ },
2407
+ });
2408
+
2409
+
2410
+
2411
+ // ═══════════════════════════════════════════════════════
2412
+ // TOOL: aave_supply_and_borrow
2413
+ // ═══════════════════════════════════════════════════════
2414
+ api.registerTool({
2415
+ name: "aave_deposit_and_borrow",
2416
+ description: "Deposit collateral and borrow in one atomic batch on Aave V3. Preferred over separate deposit + borrow.",
2417
+ parameters: {
2418
+ type: "object",
2419
+ properties: {
2420
+ supplyToken: { type: "string", description: "Token to supply as collateral (e.g. 'wstETH', 'WETH')" },
2421
+ supplyAmount: { type: "string", description: "Amount to supply" },
2422
+ borrowToken: { type: "string", description: "Token to borrow (e.g. 'USDC', 'WETH')" },
2423
+ borrowAmount: { type: "string", description: "Amount to borrow" },
2424
+ },
2425
+ required: ["supplyToken", "supplyAmount", "borrowToken", "borrowAmount"],
2426
+ },
2427
+ async execute(_id: string, params: { supplyToken: string; supplyAmount: string; borrowToken: string; borrowAmount: string }) {
2428
+ try {
2429
+ const cfg = getConfig(api);
2430
+ requireChain(cfg);
2431
+ const client = createAaveClient(cfg);
2432
+ const receipt = await client.supplyAndBorrow(params.supplyToken, params.supplyAmount, params.borrowToken, params.borrowAmount);
2433
+ const explorer = cfg.chainId === 8453 ? 'https://basescan.org/tx/' : 'https://etherscan.io/tx/';
2434
+ return ok(JSON.stringify({
2435
+ status: "supplied_and_borrowed",
2436
+ supplied: params.supplyAmount + ' ' + params.supplyToken,
2437
+ borrowed: params.borrowAmount + ' ' + params.borrowToken,
2438
+ tx: explorer + receipt.hash,
2439
+ }));
2440
+ } catch (e) { return fail(e); }
2441
+ },
2442
+ });
2443
+
2444
+ // ═══════════════════════════════════════════════════════
2445
+ // TOOL: aave_search
2446
+ // ═══════════════════════════════════════════════════════
2447
+ api.registerTool({
2448
+ name: "aave_search",
2449
+ description: "Search Aave V3 reserves by token name or symbol.",
2450
+ parameters: {
2451
+ type: "object",
2452
+ properties: {
2453
+ token: { type: "string", description: "Token to search for (e.g. 'wstETH', 'USDC')" },
2454
+ },
2455
+ required: ["token"],
2456
+ },
2457
+ async execute(_id: string, params: { token: string }) {
2458
+ try {
2459
+ const cfg = getConfig(api);
2460
+ requireChain(cfg);
2461
+ const client = createAaveClient(cfg);
2462
+ const results = await client.searchReserves(params.token);
2463
+ if (results.length === 0) {
2464
+ return ok(JSON.stringify({ status: "no_results", message: `No Aave reserves match '${params.token}'` }));
2465
+ }
2466
+ return ok(JSON.stringify({
2467
+ status: "search_results",
2468
+ reserves: results.map(r => ({
2469
+ symbol: r.symbol,
2470
+ supplyApy: r.supplyApy.toFixed(2) + '%',
2471
+ borrowApy: r.borrowApy.toFixed(2) + '%',
2472
+ ltv: r.ltv.toFixed(0) + '%',
2473
+ price: '$' + r.priceUsd.toFixed(2),
2474
+ borrowingEnabled: r.borrowingEnabled,
2475
+ })),
2476
+ }));
2477
+ } catch (e) { return fail(e); }
2478
+ },
2479
+ });
2480
+
2481
+ // ═══════════════════════════════════════════════════════
2482
+ // TOOL: aave_borrowing_options
2483
+ // ═══════════════════════════════════════════════════════
2484
+ api.registerTool({
2485
+ name: "aave_borrowing_options",
2486
+ description: "Show what you can borrow on Aave V3. Without args: scans wallet for collateral tokens. With collateral: shows options for that token.",
2487
+ parameters: {
2488
+ type: "object",
2489
+ properties: {
2490
+ collateral: { type: "string", description: "Collateral token to check (optional — omit to scan wallet)" },
2491
+ },
2492
+ },
2493
+ async execute(_id: string, params: { collateral?: string }) {
2494
+ try {
2495
+ const cfg = getConfig(api);
2496
+ requireChain(cfg);
2497
+ const client = createAaveClient(cfg);
2498
+ const options = await client.getBorrowingOptions(params.collateral);
2499
+ if (options.length === 0) {
2500
+ return ok(JSON.stringify({
2501
+ status: "no_options",
2502
+ message: params.collateral
2503
+ ? `No Aave collateral options for ${params.collateral}`
2504
+ : "No tokens found in wallet that can be used as Aave collateral",
2505
+ }));
2506
+ }
2507
+ return ok(JSON.stringify({
2508
+ status: "borrowing_options",
2509
+ options: options.map(o => ({
2510
+ collateral: o.collateral,
2511
+ balance: o.collateralBalance.toFixed(6),
2512
+ valueUsd: '$' + o.collateralValueUsd.toFixed(2),
2513
+ maxBorrowUsd: '$' + o.maxBorrowUsd.toFixed(2),
2514
+ borrowableTokens: o.borrowable.map(b => ({
2515
+ token: b.symbol,
2516
+ borrowApy: b.borrowApy.toFixed(2) + '%',
2517
+ price: '$' + b.priceUsd.toFixed(2),
2518
+ })),
2519
+ })),
2520
+ }));
2521
+ } catch (e) { return fail(e); }
2522
+ },
2523
+ });
2524
+
2525
+ // ═══════════════════════════════════════════════════════
2526
+ // TOOL: aave_max_borrowable
2527
+ // ═══════════════════════════════════════════════════════
2528
+ api.registerTool({
2529
+ name: "aave_max_borrowable",
2530
+ description: "Calculate max additional borrowable on Aave V3 given current collateral and debt.",
2531
+ parameters: { type: "object", properties: {} },
2532
+ async execute() {
2533
+ try {
2534
+ const cfg = getConfig(api);
2535
+ requireChain(cfg);
2536
+ const client = createAaveClient(cfg);
2537
+ const data = await client.getMaxBorrowable();
2538
+ return ok(JSON.stringify({
2539
+ status: "max_borrowable",
2540
+ availableBorrowUsd: '$' + data.availableBorrowUsd.toFixed(2),
2541
+ currentLtv: data.currentLtv.toFixed(2) + '%',
2542
+ liquidationThreshold: data.liquidationThreshold.toFixed(2) + '%',
2543
+ totalCollateralUsd: '$' + data.totalCollateralUsd.toFixed(2),
2544
+ totalDebtUsd: '$' + data.totalDebtUsd.toFixed(2),
2545
+ }));
2546
+ } catch (e) { return fail(e); }
2547
+ },
2548
+ });
2549
+
2550
+ // ═══════════════════════════════════════════════════════
2551
+ // TOOL: aave_wallet_tokens
2552
+ // ═══════════════════════════════════════════════════════
2553
+ api.registerTool({
2554
+ name: "aave_wallet_tokens",
2555
+ description: "Scan wallet for tokens that exist as Aave V3 reserves. Shows balances in EOA and AgentAccount.",
2556
+ parameters: { type: "object", properties: {} },
2557
+ async execute() {
2558
+ try {
2559
+ const cfg = getConfig(api);
2560
+ requireChain(cfg);
2561
+ const client = createAaveClient(cfg);
2562
+ const tokens = await client.getWalletTokens();
2563
+ if (tokens.length === 0) {
2564
+ return ok(JSON.stringify({ status: "no_tokens", message: "No Aave-compatible tokens found in wallet." }));
2565
+ }
2566
+ return ok(JSON.stringify({
2567
+ status: "wallet_tokens",
2568
+ tokens: tokens.map(t => ({
2569
+ symbol: t.symbol,
2570
+ eoaBalance: t.eoaBalance.toFixed(6),
2571
+ safeBalance: t.safeBalance.toFixed(6),
2572
+ priceUsd: '$' + t.priceUsd.toFixed(2),
2573
+ totalValueUsd: '$' + t.valueUsd.toFixed(2),
2574
+ canSupply: t.canSupply,
2575
+ canBorrow: t.canBorrow,
2576
+ })),
2577
+ }));
2578
+ } catch (e) { return fail(e); }
2579
+ },
2580
+ });
2581
+
2582
+ // ═══════════════════════════════════════════════════════
2583
+ // TOOL: aave_yield_spread
2584
+ // ═══════════════════════════════════════════════════════
2585
+ api.registerTool({
2586
+ name: "aave_yield_spread",
2587
+ description: "Analyze LST/LRT yield vs Aave V3 borrow rates for carry trades. Live APYs from DeFi Llama.",
2588
+ parameters: {
2589
+ type: "object",
2590
+ properties: {
2591
+ token: { type: "string", description: "Filter by collateral token (optional)" },
2592
+ },
2593
+ },
2594
+ async execute(_id: string, params: { token?: string }) {
2595
+ try {
2596
+ const cfg = getConfig(api);
2597
+ requireChain(cfg);
2598
+ const client = createAaveClient(cfg);
2599
+ const spreads = await client.getYieldSpread();
2600
+ const filtered = params.token
2601
+ ? spreads.filter((s: any) => s.collateralToken.toLowerCase() === params.token!.toLowerCase())
2602
+ : spreads;
2603
+
2604
+ if (filtered.length === 0) {
2605
+ return ok(JSON.stringify({ status: "no_lst_markets", message: "No LST carry trade opportunities on Aave." }));
2606
+ }
2607
+
2608
+ return ok(JSON.stringify({
2609
+ status: "aave_yield_spread",
2610
+ markets: filtered.map((s: any) => ({
2611
+ collateral: s.collateralToken,
2612
+ loan: s.loanToken,
2613
+ collateralYield: s.collateralYield + '%',
2614
+ borrowRate: s.borrowRate.toFixed(2) + '%',
2615
+ netSpread: (s.netSpread > 0 ? '+' : '') + s.netSpread.toFixed(2) + '%',
2616
+ profitable: s.profitable ? 'yes' : 'no',
2617
+ maxLeverage: s.maxSafeLeverage + 'x',
2618
+ })),
2619
+ }));
2620
+ } catch (e) { return fail(e); }
2621
+ },
2622
+ });
2623
+
2624
+ // ═══════════════════════════════════════════════════════
2625
+ // TOOL: aave_repay_and_withdraw
2626
+ // ═══════════════════════════════════════════════════════
2627
+ api.registerTool({
2628
+ name: "aave_repay_and_withdraw",
2629
+ description: "Repay all debt and withdraw all collateral from Aave V3 in one atomic batch. Full position exit.",
2630
+ parameters: {
2631
+ type: "object",
2632
+ properties: {
2633
+ repayToken: { type: "string", description: "Token to repay (e.g. 'USDC')" },
2634
+ withdrawToken: { type: "string", description: "Token to withdraw (e.g. 'wstETH')" },
2635
+ },
2636
+ required: ["repayToken", "withdrawToken"],
2637
+ },
2638
+ async execute(_id: string, params: { repayToken: string; withdrawToken: string }) {
2639
+ try {
2640
+ const cfg = getConfig(api);
2641
+ requireChain(cfg);
2642
+ const client = createAaveClient(cfg);
2643
+ const receipt = await client.repayAndWithdraw(params.repayToken, params.withdrawToken);
2644
+ const explorer = cfg.chainId === 8453 ? 'https://basescan.org/tx/' : 'https://etherscan.io/tx/';
2645
+ return ok(JSON.stringify({
2646
+ status: "position_closed",
2647
+ repaid: 'all ' + params.repayToken,
2648
+ withdrawn: 'all ' + params.withdrawToken,
2649
+ tx: explorer + receipt.hash,
2650
+ }));
2651
+ } catch (e) { return fail(e); }
2652
+ },
2653
+ });
2654
+
2655
+
2128
2656
  // ═══════════════════════════════════════════════════════
2129
2657
  // SLASH COMMANDS (no AI needed)
2130
2658
  // ═══════════════════════════════════════════════════════