@agether/openclaw-plugin 1.4.0 → 1.5.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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/index.ts +150 -574
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/openclaw-plugin",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "OpenClaw plugin for Agether — on-chain credit for AI agents",
5
5
  "main": "src/index.ts",
6
6
  "openclaw": {
@@ -9,7 +9,7 @@
9
9
  ]
10
10
  },
11
11
  "dependencies": {
12
- "@agether/sdk": "^1.3.0",
12
+ "@agether/sdk": "^1.4.0",
13
13
  "axios": "^1.6.0",
14
14
  "ethers": "^6.9.0"
15
15
  },
package/src/index.ts CHANGED
@@ -1,19 +1,14 @@
1
1
  /**
2
- * Agether OpenClaw Plugin
2
+ * Agether OpenClaw Plugin — thin wrapper over @agether/sdk
3
3
  *
4
- * Registers on-chain credit tools for AI agents.
5
- * Uses @agether/sdk clients under the hood.
4
+ * Each tool = SDK call format result with txLink.
5
+ * Backend-only calls (score, compare, markets) use axios directly.
6
6
  */
7
7
 
8
- import { ethers } from "ethers";
9
8
  import axios from "axios";
10
9
  import {
11
- ACCOUNT_FACTORY_ABI,
12
- AGENT_ACCOUNT_ABI,
13
- MORPHO_CREDIT_ABI,
14
- ERC20_ABI,
10
+ MorphoCreditClient,
15
11
  X402Client,
16
- getDefaultConfig,
17
12
  } from "@agether/sdk";
18
13
 
19
14
  // ─── Helpers ──────────────────────────────────────────────
@@ -21,23 +16,32 @@ import {
21
16
  interface PluginConfig {
22
17
  privateKey: string;
23
18
  agentId?: string;
24
- rpcUrl?: string;
25
- backendUrl?: string;
19
+ rpcUrl: string;
20
+ backendUrl: string;
26
21
  autoDraw?: boolean;
27
22
  }
28
23
 
29
- const ERC8004_ABI = [
30
- "function register(string agentURI) returns (uint256)",
31
- "function ownerOf(uint256 tokenId) view returns (address)",
32
- "function balanceOf(address owner) view returns (uint256)",
33
- "function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)",
34
- ];
24
+ const BASESCAN = "https://basescan.org/tx";
25
+ function txLink(hash: string): string {
26
+ return hash ? `${BASESCAN}/${hash}` : "";
27
+ }
28
+
29
+ function ok(text: string) {
30
+ return { content: [{ type: "text" as const, text }] };
31
+ }
35
32
 
36
- const COLLATERAL_TOKENS: Record<string, { address: string; decimals: number }> = {
37
- WETH: { address: "0x4200000000000000000000000000000000000006", decimals: 18 },
38
- wstETH: { address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", decimals: 18 },
39
- cbETH: { address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", decimals: 18 },
40
- };
33
+ function fail(err: unknown) {
34
+ let msg = err instanceof Error ? err.message : String(err);
35
+ if (msg.includes("0xda04aecc")) {
36
+ msg = "ExceedsMaxLtv collateral too low for this borrow amount (max 80% LTV / 125% collateral ratio)";
37
+ } else if (msg.includes("0xfeca99cb")) {
38
+ msg = "ExecutionFailed — the inner contract call reverted. Likely ExceedsMaxLtv or insufficient collateral.";
39
+ } else if (msg.includes("0xa920ef9f")) {
40
+ msg = "PositionNotActive — no collateral deposited for this token";
41
+ }
42
+ if (msg.length > 300) msg = msg.slice(0, 250) + "…";
43
+ return { content: [{ type: "text" as const, text: `❌ ${msg}` }], isError: true };
44
+ }
41
45
 
42
46
  function getConfig(api: any): PluginConfig {
43
47
  const cfg = api.config?.plugins?.entries?.agether?.config;
@@ -53,47 +57,27 @@ function getConfig(api: any): PluginConfig {
53
57
  };
54
58
  }
55
59
 
56
- function getSigner(cfg: PluginConfig) {
57
- const provider = new ethers.JsonRpcProvider(cfg.rpcUrl);
58
- return new ethers.Wallet(cfg.privateKey, provider);
59
- }
60
-
61
60
  async function getBackendStatus(cfg: PluginConfig) {
62
61
  const { data } = await axios.get(`${cfg.backendUrl}/status`);
63
62
  return data;
64
63
  }
65
64
 
66
- async function getAccountAddr(signer: ethers.Wallet, factoryAddr: string, agentId: string) {
67
- const factory = new ethers.Contract(factoryAddr, ACCOUNT_FACTORY_ABI, signer);
68
- const addr = await factory.getAccount(agentId);
69
- if (addr === ethers.ZeroAddress) throw new Error("No AgentAccount. Register first.");
70
- return addr;
71
- }
72
-
73
- // ─── Auto-resolve agentId from chain ──────────────────────
74
- let _cachedAgentId: string | null = null;
75
-
76
- async function resolveAgentId(cfg: PluginConfig, signer: ethers.Wallet, status: any): Promise<string> {
77
- // 1. Config takes priority
78
- if (cfg.agentId && cfg.agentId !== "0") return cfg.agentId;
79
- // 2. Session cache
80
- if (_cachedAgentId) return _cachedAgentId;
81
- // 3. Look up on-chain: does this wallet own any ERC-8004 token?
82
- const agentRegistry = new ethers.Contract(status.contracts.agentRegistry, ERC8004_ABI, signer.provider!);
83
- const balance: bigint = await agentRegistry.balanceOf(signer.address);
84
- if (balance === 0n) throw new Error("No agent registered. Use agether_register first.");
85
- const tokenId: bigint = await agentRegistry.tokenOfOwnerByIndex(signer.address, 0);
86
- _cachedAgentId = tokenId.toString();
87
- return _cachedAgentId;
88
- }
89
-
90
- function ok(text: string) {
91
- return { content: [{ type: "text" as const, text }] };
92
- }
65
+ // Module-level cache: survives across tool calls within the same process
66
+ let cachedAgentId: string | undefined;
93
67
 
94
- function fail(err: unknown) {
95
- const msg = err instanceof Error ? err.message : String(err);
96
- return { content: [{ type: "text" as const, text: `❌ ${msg}` }], isError: true };
68
+ function createClient(cfg: PluginConfig, status: any): MorphoCreditClient {
69
+ const agentId = cachedAgentId || cfg.agentId;
70
+ return new MorphoCreditClient({
71
+ privateKey: cfg.privateKey,
72
+ rpcUrl: cfg.rpcUrl,
73
+ agentId,
74
+ contracts: {
75
+ morphoCredit: status.contracts.morphoCredit,
76
+ accountFactory: status.contracts.accountFactory,
77
+ usdc: status.contracts.usdc,
78
+ agentRegistry: status.contracts.agentRegistry,
79
+ },
80
+ });
97
81
  }
98
82
 
99
83
  // ─── Plugin Entry ─────────────────────────────────────────
@@ -110,38 +94,9 @@ export default function register(api: any) {
110
94
  async execute() {
111
95
  try {
112
96
  const cfg = getConfig(api);
113
- const signer = getSigner(cfg);
114
97
  const status = await getBackendStatus(cfg);
115
- const address = signer.address;
116
-
117
- const provider = signer.provider!;
118
- const ethBal = await provider.getBalance(address);
119
- const usdc = new ethers.Contract(status.contracts.usdc, ERC20_ABI, provider);
120
- const usdcBal = await usdc.balanceOf(address);
121
-
122
- const result: any = {
123
- address,
124
- agentId: cfg.agentId || _cachedAgentId || "not registered",
125
- eth: ethers.formatEther(ethBal),
126
- usdc: ethers.formatUnits(usdcBal, 6),
127
- };
128
-
129
- // AgentAccount balances — try to auto-resolve agentId
130
- try {
131
- const agentId = await resolveAgentId(cfg, signer, status);
132
- result.agentId = agentId;
133
- if (status.contracts.accountFactory) {
134
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
135
- const accEth = await provider.getBalance(accountAddr);
136
- const accUsdc = await usdc.balanceOf(accountAddr);
137
- result.agentAccount = {
138
- address: accountAddr,
139
- eth: ethers.formatEther(accEth),
140
- usdc: ethers.formatUnits(accUsdc, 6),
141
- };
142
- }
143
- } catch { /* no agent registered yet */ }
144
-
98
+ const client = createClient(cfg, status);
99
+ const result = await client.getBalances();
145
100
  return ok(JSON.stringify(result, null, 2));
146
101
  } catch (e) { return fail(e); }
147
102
  },
@@ -153,7 +108,7 @@ export default function register(api: any) {
153
108
  api.registerTool({
154
109
  name: "agether_register",
155
110
  description:
156
- "Register a new ERC-8004 agent identity on Base and create an AgentAccount. Returns the new agentId. If agentId is already configured, returns the existing one.",
111
+ "Register a new ERC-8004 agent identity on Base and create an AgentAccount. Returns the new agentId.",
157
112
  parameters: {
158
113
  type: "object",
159
114
  properties: {
@@ -164,82 +119,17 @@ export default function register(api: any) {
164
119
  async execute(_id: string, params: { name: string }) {
165
120
  try {
166
121
  const cfg = getConfig(api);
167
- const signer = getSigner(cfg);
168
122
  const status = await getBackendStatus(cfg);
169
-
170
- const agentRegistry = new ethers.Contract(status.contracts.agentRegistry, ERC8004_ABI, signer);
171
-
172
- let agentId: bigint;
173
-
174
- // If agentId already set, verify and use
175
- if (cfg.agentId && cfg.agentId !== "0") {
176
- agentId = BigInt(cfg.agentId);
177
- const owner = await agentRegistry.ownerOf(agentId);
178
- if (owner.toLowerCase() !== signer.address.toLowerCase()) {
179
- return fail("agentId in config does not belong to this wallet");
180
- }
181
- // Ensure AgentAccount exists
182
- if (status.contracts.accountFactory) {
183
- const factory = new ethers.Contract(status.contracts.accountFactory, ACCOUNT_FACTORY_ABI, signer);
184
- const exists = await factory.accountExists(agentId);
185
- if (!exists) {
186
- const tx = await factory.createAccount(agentId);
187
- await tx.wait();
188
- }
189
- }
190
- return ok(JSON.stringify({
191
- status: "already_registered",
192
- agentId: agentId.toString(),
193
- address: signer.address,
194
- }));
195
- }
196
-
197
- // Mint new ERC-8004 identity
198
- const registrationFile = JSON.stringify({
199
- type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
200
- name: params.name,
201
- description: "AI agent registered via Agether OpenClaw plugin",
202
- active: true,
203
- registrations: [{
204
- agentId: 0,
205
- agentRegistry: `eip155:${status.chainId}:${status.contracts.agentRegistry}`,
206
- }],
207
- });
208
- const agentURI = `data:application/json;base64,${Buffer.from(registrationFile).toString("base64")}`;
209
-
210
- const tx = await agentRegistry["register(string)"](agentURI);
211
- const receipt = await tx.wait();
212
-
213
- // Parse agentId from Transfer event
214
- const transferTopic = ethers.id("Transfer(address,address,uint256)");
215
- const transferLog = receipt.logs.find((l: any) => l.topics[0] === transferTopic);
216
- if (transferLog?.topics?.length >= 4) {
217
- agentId = BigInt(transferLog.topics[3]);
218
- } else {
219
- return fail("Could not parse agentId from receipt");
220
- }
221
-
222
- // Create AgentAccount
223
- let accountAddr = "";
224
- if (status.contracts.accountFactory) {
225
- const factory = new ethers.Contract(status.contracts.accountFactory, ACCOUNT_FACTORY_ABI, signer);
226
- const exists = await factory.accountExists(agentId);
227
- if (!exists) {
228
- const accTx = await factory.createAccount(agentId);
229
- await accTx.wait();
230
- }
231
- accountAddr = await factory.getAccount(agentId);
232
- }
233
-
234
- // Cache agentId for the session so all subsequent tools work
235
- _cachedAgentId = agentId.toString();
236
-
123
+ const client = createClient(cfg, status);
124
+ const result = await client.register(params.name);
125
+ // Cache agentId for subsequent tool calls
126
+ cachedAgentId = result.agentId;
237
127
  return ok(JSON.stringify({
238
- status: "registered",
239
- agentId: agentId.toString(),
240
- address: signer.address,
241
- agentAccount: accountAddr,
242
- tx: tx.hash,
128
+ status: result.alreadyRegistered ? "already_registered" : "registered",
129
+ agentId: result.agentId,
130
+ address: result.address,
131
+ agentAccount: result.agentAccount,
132
+ tx: result.tx ? txLink(result.tx) : undefined,
243
133
  }));
244
134
  } catch (e) { return fail(e); }
245
135
  },
@@ -256,33 +146,10 @@ export default function register(api: any) {
256
146
  async execute() {
257
147
  try {
258
148
  const cfg = getConfig(api);
259
- const signer = getSigner(cfg);
260
149
  const status = await getBackendStatus(cfg);
261
- const agentId = await resolveAgentId(cfg, signer, status);
262
-
263
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
264
- const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
265
-
266
- const positions: any[] = [];
267
- for (const [symbol, info] of Object.entries(COLLATERAL_TOKENS)) {
268
- const pos = await morpho.getPosition(accountAddr, info.address);
269
- if (pos.isActive || pos.collateralAmount > 0n || pos.borrowedAmount > 0n) {
270
- positions.push({
271
- token: symbol,
272
- collateral: ethers.formatUnits(pos.collateralAmount, info.decimals),
273
- debt: `$${ethers.formatUnits(pos.borrowedAmount, 6)}`,
274
- active: pos.isActive,
275
- });
276
- }
277
- }
278
-
279
- const totalDebt = await morpho.getTotalDebt(accountAddr);
280
-
281
- return ok(JSON.stringify({
282
- agentAccount: accountAddr,
283
- totalDebt: `$${ethers.formatUnits(totalDebt, 6)}`,
284
- positions: positions.length > 0 ? positions : "No active positions",
285
- }, null, 2));
150
+ const client = createClient(cfg, status);
151
+ const result = await client.getStatus();
152
+ return ok(JSON.stringify(result, null, 2));
286
153
  } catch (e) { return fail(e); }
287
154
  },
288
155
  });
@@ -305,55 +172,22 @@ export default function register(api: any) {
305
172
  async execute(_id: string, params: { amount: string; token: string }) {
306
173
  try {
307
174
  const cfg = getConfig(api);
308
- const signer = getSigner(cfg);
309
175
  const status = await getBackendStatus(cfg);
310
- const agentId = await resolveAgentId(cfg, signer, status);
311
-
312
- const tokenInfo = COLLATERAL_TOKENS[params.token];
313
- if (!tokenInfo) return fail(`Unsupported token: ${params.token}. Use WETH, wstETH, or cbETH`);
314
-
315
- const morphoCreditAddr = status.contracts.morphoCredit;
316
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
317
- const amount = ethers.parseUnits(params.amount, tokenInfo.decimals);
318
-
319
- // Check balance
320
- const token = new ethers.Contract(tokenInfo.address, ERC20_ABI, signer);
321
- const balance = await token.balanceOf(signer.address);
322
- if (balance < amount) {
323
- return fail(`Insufficient ${params.token}: have ${ethers.formatUnits(balance, tokenInfo.decimals)}, need ${params.amount}`);
324
- }
325
-
326
- // Approve
327
- const approveTx = await token.approve(morphoCreditAddr, amount);
328
- await approveTx.wait();
329
-
330
- // Deposit for AgentAccount
331
- const morpho = new ethers.Contract(morphoCreditAddr, MORPHO_CREDIT_ABI, signer);
332
- const depositTx = await morpho.depositCollateralFor(accountAddr, tokenInfo.address, amount);
333
- await depositTx.wait();
334
-
335
- // Approve credit provider on AgentAccount
336
- const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
337
- try {
338
- const cpTx = await account.approveCreditProvider(morphoCreditAddr);
339
- await cpTx.wait();
340
- } catch { /* already approved */ }
341
-
342
- const pos = await morpho.getPosition(accountAddr, tokenInfo.address);
343
-
176
+ const client = createClient(cfg, status);
177
+ const result = await client.deposit(params.token, params.amount);
344
178
  return ok(JSON.stringify({
345
179
  status: "deposited",
346
180
  amount: `${params.amount} ${params.token}`,
347
- totalCollateral: `${ethers.formatUnits(pos.collateralAmount, tokenInfo.decimals)} ${params.token}`,
348
- agentAccount: accountAddr,
349
- tx: depositTx.hash,
181
+ totalCollateral: result.totalCollateral.toString(),
182
+ agentAccount: result.agentAccount,
183
+ tx: txLink(result.tx),
350
184
  }));
351
185
  } catch (e) { return fail(e); }
352
186
  },
353
187
  });
354
188
 
355
189
  // ═══════════════════════════════════════════════════════
356
- // TOOL: morpho_deposit_and_borrow (Flow 2)
190
+ // TOOL: morpho_deposit_and_borrow
357
191
  // ═══════════════════════════════════════════════════════
358
192
  api.registerTool({
359
193
  name: "morpho_deposit_and_borrow",
@@ -371,66 +205,24 @@ export default function register(api: any) {
371
205
  async execute(_id: string, params: { collateralAmount: string; token: string; borrowAmount: string }) {
372
206
  try {
373
207
  const cfg = getConfig(api);
374
- const signer = getSigner(cfg);
375
208
  const status = await getBackendStatus(cfg);
376
- const agentId = await resolveAgentId(cfg, signer, status);
377
-
378
- const tokenInfo = COLLATERAL_TOKENS[params.token];
379
- if (!tokenInfo) return fail(`Unsupported token: ${params.token}`);
380
-
381
- const morphoCreditAddr = status.contracts.morphoCredit;
382
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
383
- const collateralWei = ethers.parseUnits(params.collateralAmount, tokenInfo.decimals);
384
- const borrowWei = ethers.parseUnits(params.borrowAmount, 6);
385
-
386
- // Check collateral balance
387
- const token = new ethers.Contract(tokenInfo.address, ERC20_ABI, signer);
388
- const balance = await token.balanceOf(signer.address);
389
- if (balance < collateralWei) {
390
- return fail(`Insufficient ${params.token}: have ${ethers.formatUnits(balance, tokenInfo.decimals)}, need ${params.collateralAmount}`);
391
- }
392
-
393
- // Step 1: Approve collateral
394
- const approveTx = await token.approve(morphoCreditAddr, collateralWei);
395
- await approveTx.wait();
396
-
397
- // Step 2: Deposit collateral for AgentAccount
398
- const morpho = new ethers.Contract(morphoCreditAddr, MORPHO_CREDIT_ABI, signer);
399
- const depositTx = await morpho.depositCollateralFor(accountAddr, tokenInfo.address, collateralWei);
400
- await depositTx.wait();
401
-
402
- // Step 3: Approve credit provider on AgentAccount
403
- const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
404
- try {
405
- const cpTx = await account.approveCreditProvider(morphoCreditAddr);
406
- await cpTx.wait();
407
- } catch { /* already approved */ }
408
-
409
- // Step 4: Borrow via AgentAccount.execute → drawWithCollateral
410
- const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
411
- const borrowCalldata = morphoIface.encodeFunctionData("drawWithCollateral", [tokenInfo.address, borrowWei]);
412
- const borrowTx = await account.execute(morphoCreditAddr, 0, borrowCalldata);
413
- await borrowTx.wait();
414
-
415
- const pos = await morpho.getPosition(accountAddr, tokenInfo.address);
416
- const totalDebt = await morpho.getTotalDebt(accountAddr);
417
-
209
+ const client = createClient(cfg, status);
210
+ const result = await client.depositAndBorrow(params.token, params.collateralAmount, params.borrowAmount);
418
211
  return ok(JSON.stringify({
419
212
  status: "deposited_and_borrowed",
420
213
  collateral: `${params.collateralAmount} ${params.token}`,
421
214
  borrowed: `$${params.borrowAmount}`,
422
- totalCollateral: `${ethers.formatUnits(pos.collateralAmount, tokenInfo.decimals)} ${params.token}`,
423
- totalDebt: `$${ethers.formatUnits(totalDebt, 6)}`,
424
- agentAccount: accountAddr,
425
- depositTx: depositTx.hash,
426
- borrowTx: borrowTx.hash,
215
+ totalDebt: result.totalDebt.toString(),
216
+ agentAccount: result.agentAccount,
217
+ depositTx: txLink(result.depositTx),
218
+ borrowTx: txLink(result.borrowTx),
427
219
  }));
428
220
  } catch (e) { return fail(e); }
429
221
  },
430
222
  });
431
223
 
432
224
  // ═══════════════════════════════════════════════════════
433
- // TOOL: morpho_sponsor (Flows 4-7: Human sponsors agent)
225
+ // TOOL: morpho_sponsor
434
226
  // ═══════════════════════════════════════════════════════
435
227
  api.registerTool({
436
228
  name: "morpho_sponsor",
@@ -443,88 +235,31 @@ export default function register(api: any) {
443
235
  agentAddress: { type: "string", description: "Target AgentAccount address (alternative to agentId)" },
444
236
  amount: { type: "string", description: "Collateral amount (e.g. '0.05')" },
445
237
  token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
446
- borrow: { type: "string", description: "Optional: USDC amount to borrow for the agent (e.g. '50'). Only works if caller owns the AgentAccount." },
238
+ borrow: { type: "string", description: "Optional: USDC amount to borrow for the agent" },
447
239
  },
448
240
  required: ["amount", "token"],
449
241
  },
450
242
  async execute(_id: string, params: { agentId?: string; agentAddress?: string; amount: string; token: string; borrow?: string }) {
451
243
  try {
244
+ if (!params.agentId && !params.agentAddress) return fail("Provide either agentId or agentAddress");
452
245
  const cfg = getConfig(api);
453
- const signer = getSigner(cfg);
454
246
  const status = await getBackendStatus(cfg);
247
+ const client = createClient(cfg, status);
455
248
 
456
- if (!params.agentId && !params.agentAddress) {
457
- return fail("Provide either agentId or agentAddress");
458
- }
459
-
460
- const tokenInfo = COLLATERAL_TOKENS[params.token];
461
- if (!tokenInfo) return fail(`Unsupported token: ${params.token}`);
462
-
463
- const morphoCreditAddr = status.contracts.morphoCredit;
464
- let accountAddr: string;
465
-
466
- // Resolve target AgentAccount
467
- if (params.agentAddress) {
468
- accountAddr = params.agentAddress;
469
- } else {
470
- const factory = new ethers.Contract(status.contracts.accountFactory, ACCOUNT_FACTORY_ABI, signer);
471
- accountAddr = await factory.getAccount(params.agentId!);
472
- if (accountAddr === ethers.ZeroAddress) {
473
- return fail(`No AgentAccount for agent #${params.agentId}`);
474
- }
475
- }
476
-
477
- const collateralWei = ethers.parseUnits(params.amount, tokenInfo.decimals);
478
-
479
- // Check sponsor's balance
480
- const token = new ethers.Contract(tokenInfo.address, ERC20_ABI, signer);
481
- const balance = await token.balanceOf(signer.address);
482
- if (balance < collateralWei) {
483
- return fail(`Insufficient ${params.token}: have ${ethers.formatUnits(balance, tokenInfo.decimals)}, need ${params.amount}`);
484
- }
249
+ const target = params.agentId
250
+ ? { agentId: params.agentId }
251
+ : { address: params.agentAddress! };
485
252
 
486
- // Approve + deposit for target
487
- const approveTx = await token.approve(morphoCreditAddr, collateralWei);
488
- await approveTx.wait();
489
-
490
- const morpho = new ethers.Contract(morphoCreditAddr, MORPHO_CREDIT_ABI, signer);
491
- const depositTx = await morpho.depositCollateralFor(accountAddr, tokenInfo.address, collateralWei);
492
- await depositTx.wait();
493
-
494
- const result: any = {
253
+ const result = await client.sponsor(target, params.token, params.amount, params.borrow);
254
+ return ok(JSON.stringify({
495
255
  status: "sponsored",
496
- target: accountAddr,
497
- targetAgentId: params.agentId || "by address",
256
+ target: result.targetAccount,
257
+ targetAgentId: result.targetAgentId || "by address",
498
258
  collateral: `${params.amount} ${params.token}`,
499
- depositTx: depositTx.hash,
500
- };
501
-
502
- // Optional: borrow for the agent (only works if caller owns the AgentAccount)
503
- if (params.borrow) {
504
- try {
505
- const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
506
- try {
507
- const cpTx = await account.approveCreditProvider(morphoCreditAddr);
508
- await cpTx.wait();
509
- } catch { /* already approved or not owner */ }
510
-
511
- const borrowWei = ethers.parseUnits(params.borrow, 6);
512
- const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
513
- const calldata = morphoIface.encodeFunctionData("drawWithCollateral", [tokenInfo.address, borrowWei]);
514
- const borrowTx = await account.execute(morphoCreditAddr, 0, calldata);
515
- await borrowTx.wait();
516
- result.borrowed = `$${params.borrow}`;
517
- result.borrowTx = borrowTx.hash;
518
- } catch (e: any) {
519
- result.borrowError = `Borrow failed (caller may not own this AgentAccount): ${e.message}`;
520
- }
521
- }
522
-
523
- const pos = await morpho.getPosition(accountAddr, tokenInfo.address);
524
- result.totalCollateral = `${ethers.formatUnits(pos.collateralAmount, tokenInfo.decimals)} ${params.token}`;
525
- result.totalDebt = `$${ethers.formatUnits(pos.borrowedAmount, 6)}`;
526
-
527
- return ok(JSON.stringify(result));
259
+ borrowed: result.borrowed ? `$${params.borrow}` : undefined,
260
+ depositTx: txLink(result.depositTx),
261
+ borrowTx: result.borrowTx ? txLink(result.borrowTx) : undefined,
262
+ }));
528
263
  } catch (e) { return fail(e); }
529
264
  },
530
265
  });
@@ -546,45 +281,16 @@ export default function register(api: any) {
546
281
  async execute(_id: string, params: { amount: string }) {
547
282
  try {
548
283
  const cfg = getConfig(api);
549
- const signer = getSigner(cfg);
550
284
  const status = await getBackendStatus(cfg);
551
- const agentId = await resolveAgentId(cfg, signer, status);
552
-
553
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
554
- const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
555
-
556
- // Find active collateral
557
- let activeToken: string | null = null;
558
- for (const [symbol, info] of Object.entries(COLLATERAL_TOKENS)) {
559
- const pos = await morpho.getPosition(accountAddr, info.address);
560
- if (pos.collateralAmount > 0n) { activeToken = symbol; break; }
561
- }
562
- if (!activeToken) return fail("No collateral deposited. Use morpho_deposit first.");
563
-
564
- const collateralAddr = COLLATERAL_TOKENS[activeToken].address;
565
- const amountWei = ethers.parseUnits(params.amount, 6);
566
-
567
- // Borrow via AgentAccount.execute → drawWithCollateral
568
- const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
569
-
570
- // Ensure credit provider approved (one-time, idempotent)
571
- try { const cpTx = await account.approveCreditProvider(status.contracts.morphoCredit); await cpTx.wait(); } catch { /* already approved */ }
572
-
573
- const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
574
- const calldata = morphoIface.encodeFunctionData("drawWithCollateral", [collateralAddr, amountWei]);
575
-
576
- const tx = await account.execute(status.contracts.morphoCredit, 0, calldata);
577
- await tx.wait();
578
-
579
- const totalDebt = await morpho.getTotalDebt(accountAddr);
580
-
285
+ const client = createClient(cfg, status);
286
+ const result = await client.borrow(params.amount);
581
287
  return ok(JSON.stringify({
582
288
  status: "borrowed",
583
289
  amount: `$${params.amount}`,
584
- collateral: activeToken,
585
- totalDebt: `$${ethers.formatUnits(totalDebt, 6)}`,
586
- agentAccount: accountAddr,
587
- tx: tx.hash,
290
+ collateral: result.collateralToken,
291
+ totalDebt: result.totalDebt.toString(),
292
+ agentAccount: result.agentAccount,
293
+ tx: txLink(result.tx),
588
294
  }));
589
295
  } catch (e) { return fail(e); }
590
296
  },
@@ -607,53 +313,14 @@ export default function register(api: any) {
607
313
  async execute(_id: string, params: { amount: string }) {
608
314
  try {
609
315
  const cfg = getConfig(api);
610
- const signer = getSigner(cfg);
611
316
  const status = await getBackendStatus(cfg);
612
- const agentId = await resolveAgentId(cfg, signer, status);
613
-
614
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
615
- const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
616
-
617
- // Find active collateral with debt
618
- let activeToken: string | null = null;
619
- for (const [symbol, info] of Object.entries(COLLATERAL_TOKENS)) {
620
- const pos = await morpho.getPosition(accountAddr, info.address);
621
- if (pos.borrowedAmount > 0n) { activeToken = symbol; break; }
622
- }
623
- if (!activeToken) return fail("No Morpho debt to repay");
624
-
625
- const collateralAddr = COLLATERAL_TOKENS[activeToken].address;
626
- const amountWei = ethers.parseUnits(params.amount, 6);
627
-
628
- // Check USDC in AgentAccount
629
- const usdc = new ethers.Contract(status.contracts.usdc, ERC20_ABI, signer.provider!);
630
- const accBalance = await usdc.balanceOf(accountAddr);
631
- if (accBalance < amountWei) {
632
- return fail(`Insufficient USDC in AgentAccount: have $${ethers.formatUnits(accBalance, 6)}, need $${params.amount}`);
633
- }
634
-
635
- const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
636
- const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
637
- const erc20Iface = new ethers.Interface(ERC20_ABI);
638
-
639
- // Single tx via executeBatch: approve USDC + repayWithCollateral
640
- const tx = await account.executeBatch(
641
- [status.contracts.usdc, status.contracts.morphoCredit],
642
- [0, 0],
643
- [
644
- erc20Iface.encodeFunctionData("approve", [status.contracts.morphoCredit, amountWei]),
645
- morphoIface.encodeFunctionData("repayWithCollateral", [collateralAddr, amountWei]),
646
- ],
647
- );
648
- await tx.wait();
649
-
650
- const totalDebt = await morpho.getTotalDebt(accountAddr);
651
-
317
+ const client = createClient(cfg, status);
318
+ const result = await client.repay(params.amount);
652
319
  return ok(JSON.stringify({
653
320
  status: "repaid",
654
321
  amount: `$${params.amount}`,
655
- remainingDebt: `$${ethers.formatUnits(totalDebt, 6)}`,
656
- tx: tx.hash,
322
+ remainingDebt: result.remainingDebt.toString(),
323
+ tx: txLink(result.tx),
657
324
  }));
658
325
  } catch (e) { return fail(e); }
659
326
  },
@@ -677,56 +344,23 @@ export default function register(api: any) {
677
344
  async execute(_id: string, params: { amount: string; token: string }) {
678
345
  try {
679
346
  const cfg = getConfig(api);
680
- const signer = getSigner(cfg);
681
347
  const status = await getBackendStatus(cfg);
682
- const agentId = await resolveAgentId(cfg, signer, status);
683
-
684
- const tokenInfo = COLLATERAL_TOKENS[params.token];
685
- if (!tokenInfo) return fail(`Unsupported token: ${params.token}`);
686
-
687
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
688
- const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
689
- const pos = await morpho.getPosition(accountAddr, tokenInfo.address);
690
-
691
- if (pos.collateralAmount === 0n) return fail(`No ${params.token} collateral deposited`);
692
-
693
- const withdrawAmount = params.amount.toLowerCase() === "all" ? pos.collateralAmount : ethers.parseUnits(params.amount, tokenInfo.decimals);
694
-
695
- if (withdrawAmount > pos.collateralAmount) {
696
- return fail(`Cannot withdraw more than deposited: max ${ethers.formatUnits(pos.collateralAmount, tokenInfo.decimals)} ${params.token}`);
697
- }
698
-
699
- const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
700
- const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
701
- const erc20Iface = new ethers.Interface(ERC20_ABI);
702
-
703
- // Single tx via executeBatch: withdrawCollateral + transfer to EOA
704
- const tx = await account.executeBatch(
705
- [status.contracts.morphoCredit, tokenInfo.address],
706
- [0, 0],
707
- [
708
- morphoIface.encodeFunctionData("withdrawCollateral", [tokenInfo.address, withdrawAmount]),
709
- erc20Iface.encodeFunctionData("transfer", [signer.address, withdrawAmount]),
710
- ],
711
- );
712
- await tx.wait();
713
-
714
- const newPos = await morpho.getPosition(accountAddr, tokenInfo.address);
715
-
348
+ const client = createClient(cfg, status);
349
+ const result = await client.withdraw(params.token, params.amount);
716
350
  return ok(JSON.stringify({
717
351
  status: "withdrawn",
718
- amount: `${ethers.formatUnits(withdrawAmount, tokenInfo.decimals)} ${params.token}`,
719
- remainingCollateral: `${ethers.formatUnits(newPos.collateralAmount, tokenInfo.decimals)} ${params.token}`,
720
- remainingDebt: `$${ethers.formatUnits(newPos.borrowedAmount, 6)}`,
721
- destination: signer.address,
722
- tx: tx.hash,
352
+ amount: result.amount.toString(),
353
+ token: result.token,
354
+ remainingCollateral: result.remainingCollateral.toString(),
355
+ destination: result.destination,
356
+ tx: txLink(result.tx),
723
357
  }));
724
358
  } catch (e) { return fail(e); }
725
359
  },
726
360
  });
727
361
 
728
362
  // ═══════════════════════════════════════════════════════
729
- // TOOL: morpho_compare
363
+ // TOOL: morpho_compare (backend API)
730
364
  // ═══════════════════════════════════════════════════════
731
365
  api.registerTool({
732
366
  name: "morpho_compare",
@@ -743,27 +377,20 @@ export default function register(api: any) {
743
377
  try {
744
378
  const cfg = getConfig(api);
745
379
  const { data } = await axios.get(`${cfg.backendUrl}/morpho/estimate/${params.amount}`);
746
- const signer = getSigner(cfg);
747
-
748
- // Get wallet balances
749
- const balances: Record<string, string> = {};
750
- for (const [symbol, info] of Object.entries(COLLATERAL_TOKENS)) {
751
- const token = new ethers.Contract(info.address, ERC20_ABI, signer.provider!);
752
- const bal = await token.balanceOf(signer.address);
753
- balances[symbol] = ethers.formatUnits(bal, info.decimals);
754
- }
755
-
380
+ const status = await getBackendStatus(cfg);
381
+ const client = createClient(cfg, status);
382
+ const balances = await client.getBalances();
756
383
  return ok(JSON.stringify({
757
384
  borrowAmount: `$${params.amount}`,
758
385
  estimates: data,
759
- walletBalances: balances,
386
+ walletBalances: { eth: balances.eth, usdc: balances.usdc },
760
387
  }, null, 2));
761
388
  } catch (e) { return fail(e); }
762
389
  },
763
390
  });
764
391
 
765
392
  // ═══════════════════════════════════════════════════════
766
- // TOOL: morpho_markets
393
+ // TOOL: morpho_markets (backend API)
767
394
  // ═══════════════════════════════════════════════════════
768
395
  api.registerTool({
769
396
  name: "morpho_markets",
@@ -779,7 +406,7 @@ export default function register(api: any) {
779
406
  });
780
407
 
781
408
  // ═══════════════════════════════════════════════════════
782
- // TOOL: agether_score
409
+ // TOOL: agether_score (backend API)
783
410
  // ═══════════════════════════════════════════════════════
784
411
  api.registerTool({
785
412
  name: "agether_score",
@@ -788,9 +415,9 @@ export default function register(api: any) {
788
415
  async execute() {
789
416
  try {
790
417
  const cfg = getConfig(api);
791
- const signer = getSigner(cfg);
792
418
  const status = await getBackendStatus(cfg);
793
- const agentId = await resolveAgentId(cfg, signer, status);
419
+ const client = createClient(cfg, status);
420
+ const agentId = await client.getAgentId();
794
421
  const { data } = await axios.get(`${cfg.backendUrl}/credit/score/${agentId}`);
795
422
  return ok(JSON.stringify(data, null, 2));
796
423
  } catch (e) { return fail(e); }
@@ -813,97 +440,66 @@ export default function register(api: any) {
813
440
  async execute(_id: string, params: { amount: string }) {
814
441
  try {
815
442
  const cfg = getConfig(api);
816
- const signer = getSigner(cfg);
817
443
  const status = await getBackendStatus(cfg);
818
- const agentId = await resolveAgentId(cfg, signer, status);
819
-
820
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
821
- const amountWei = ethers.parseUnits(params.amount, 6);
822
-
823
- const usdc = new ethers.Contract(status.contracts.usdc, ERC20_ABI, signer);
824
- const balance = await usdc.balanceOf(signer.address);
825
- if (balance < amountWei) {
826
- return fail(`Insufficient USDC: have $${ethers.formatUnits(balance, 6)}, need $${params.amount}`);
827
- }
828
-
829
- const tx = await usdc.transfer(accountAddr, amountWei);
830
- await tx.wait();
831
-
444
+ const client = createClient(cfg, status);
445
+ const result = await client.fundAccount(params.amount);
832
446
  return ok(JSON.stringify({
833
447
  status: "funded",
834
448
  amount: `$${params.amount}`,
835
- agentAccount: accountAddr,
836
- tx: tx.hash,
449
+ agentAccount: result.agentAccount,
450
+ tx: txLink(result.tx),
837
451
  }));
838
452
  } catch (e) { return fail(e); }
839
453
  },
840
454
  });
841
455
 
842
456
  // ═══════════════════════════════════════════════════════
843
- // TOOL: x402_pay (Flows 8 & 9 — uses SDK X402Client)
457
+ // TOOL: x402_pay
844
458
  // ═══════════════════════════════════════════════════════
845
459
  api.registerTool({
846
460
  name: "x402_pay",
847
461
  description:
848
462
  "Make a paid API call using x402 protocol. Pays with USDC from AgentAccount via EIP-3009 signature. " +
849
- "If autoDraw is enabled in config (or --auto-draw param is true), automatically borrows USDC from Morpho " +
850
- "credit line when AgentAccount balance is insufficient.",
463
+ "If autoDraw is enabled, automatically borrows USDC from Morpho credit line when balance is insufficient.",
851
464
  parameters: {
852
465
  type: "object",
853
466
  properties: {
854
467
  url: { type: "string", description: "API endpoint URL" },
855
468
  method: { type: "string", enum: ["GET", "POST"], description: "HTTP method (default: GET)" },
856
469
  body: { type: "string", description: "JSON body for POST requests" },
857
- autoDraw: { type: "boolean", description: "Auto-borrow from Morpho if USDC insufficient (default: uses config autoDraw setting)" },
470
+ autoDraw: { type: "boolean", description: "Auto-borrow from Morpho if USDC insufficient" },
858
471
  },
859
472
  required: ["url"],
860
473
  },
861
474
  async execute(_id: string, params: { url: string; method?: string; body?: string; autoDraw?: boolean }) {
862
475
  try {
863
476
  const cfg = getConfig(api);
864
- const signer = getSigner(cfg);
865
477
  const status = await getBackendStatus(cfg);
478
+ const client = createClient(cfg, status);
866
479
 
867
- // Resolve AgentAccount address for x402 payments
868
480
  let accountAddress: string | undefined;
869
481
  let agentId: string | undefined;
870
482
  try {
871
- agentId = await resolveAgentId(cfg, signer, status);
872
- accountAddress = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
483
+ agentId = await client.getAgentId();
484
+ accountAddress = await client.getAccountAddress();
873
485
  } catch { /* no account, pay from EOA */ }
874
486
 
875
- // Auto-draw: if enabled, check USDC balance and borrow if needed
487
+ // Auto-draw: borrow $10 if balance < $1
876
488
  const shouldAutoDraw = params.autoDraw ?? cfg.autoDraw;
877
- if (shouldAutoDraw && accountAddress && agentId) {
878
- const usdc = new ethers.Contract(status.contracts.usdc, ERC20_ABI, signer.provider!);
879
- const accBalance = await usdc.balanceOf(accountAddress);
880
- // If balance < $1, try to auto-borrow $10 (enough for several x402 calls)
881
- if (accBalance < ethers.parseUnits("1", 6)) {
882
- try {
883
- const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
884
- let collateralAddr: string | null = null;
885
- for (const [, info] of Object.entries(COLLATERAL_TOKENS)) {
886
- const pos = await morpho.getPosition(accountAddress, info.address);
887
- if (pos.collateralAmount > 0n) { collateralAddr = info.address; break; }
888
- }
889
- if (collateralAddr) {
890
- const account = new ethers.Contract(accountAddress, AGENT_ACCOUNT_ABI, signer);
891
- const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
892
- const drawAmount = ethers.parseUnits("10", 6); // $10 auto-draw
893
- const calldata = morphoIface.encodeFunctionData("drawWithCollateral", [collateralAddr, drawAmount]);
894
- const tx = await account.execute(status.contracts.morphoCredit, 0, calldata);
895
- await tx.wait();
896
- }
897
- } catch { /* auto-draw failed — proceed anyway, x402 might still work from EOA */ }
898
- }
489
+ if (shouldAutoDraw && accountAddress) {
490
+ try {
491
+ const accUsdc = await client.getAccountUSDC();
492
+ if (accUsdc < 1_000_000n) {
493
+ await client.borrow("10");
494
+ }
495
+ } catch { /* auto-draw failed, proceed anyway */ }
899
496
  }
900
497
 
901
- // Use SDK's X402Client directly (no execSync)
902
498
  const x402 = new X402Client({
903
499
  privateKey: cfg.privateKey,
904
- rpcUrl: cfg.rpcUrl!,
905
- backendUrl: cfg.backendUrl!,
906
- agentId: agentId,
500
+ rpcUrl: cfg.rpcUrl,
501
+ backendUrl: cfg.backendUrl,
502
+ agentId,
907
503
  accountAddress,
908
504
  });
909
505
 
@@ -914,13 +510,14 @@ export default function register(api: any) {
914
510
  result = await x402.get(params.url);
915
511
  }
916
512
 
917
- if (!result.success) {
918
- return fail(result.error || "x402 payment failed");
919
- }
513
+ if (!result.success) return fail(result.error || "x402 payment failed");
920
514
 
921
515
  const response: any = { status: "paid", data: result.data };
922
516
  if (result.paymentInfo) {
923
- response.payment = result.paymentInfo;
517
+ response.payment = {
518
+ ...result.paymentInfo,
519
+ tx: result.paymentInfo.txHash ? txLink(result.paymentInfo.txHash) : undefined,
520
+ };
924
521
  }
925
522
  return ok(JSON.stringify(response, null, 2));
926
523
  } catch (e) { return fail(e); }
@@ -928,7 +525,7 @@ export default function register(api: any) {
928
525
  });
929
526
 
930
527
  // ═══════════════════════════════════════════════════════
931
- // AUTO-REPLY COMMANDS (no AI needed)
528
+ // SLASH COMMANDS (no AI needed)
932
529
  // ═══════════════════════════════════════════════════════
933
530
 
934
531
  api.registerCommand({
@@ -937,29 +534,17 @@ export default function register(api: any) {
937
534
  handler: async () => {
938
535
  try {
939
536
  const cfg = getConfig(api);
940
- const signer = getSigner(cfg);
941
- const provider = signer.provider!;
942
- const ethBal = await provider.getBalance(signer.address);
943
537
  const status = await getBackendStatus(cfg);
944
- const usdc = new ethers.Contract(status.contracts.usdc, ERC20_ABI, provider);
945
- const usdcBal = await usdc.balanceOf(signer.address);
946
-
947
- let agentId: string | undefined;
948
- try { agentId = await resolveAgentId(cfg, signer, status); } catch {}
949
-
950
- let text = `💰 Agent #${agentId || "?"}\n`;
951
- text += `Address: ${signer.address}\n`;
952
- text += `ETH: ${parseFloat(ethers.formatEther(ethBal)).toFixed(6)}\n`;
953
- text += `USDC: $${ethers.formatUnits(usdcBal, 6)}`;
954
-
955
- if (agentId && status.contracts.accountFactory) {
956
- try {
957
- const accAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
958
- const accUsdc = await usdc.balanceOf(accAddr);
959
- text += `\n\n🏦 AgentAccount: ${accAddr}\nUSDC: $${ethers.formatUnits(accUsdc, 6)}`;
960
- } catch { /* no account */ }
538
+ const client = createClient(cfg, status);
539
+ const b = await client.getBalances();
540
+
541
+ let text = `💰 Agent #${b.agentId}\n`;
542
+ text += `Address: ${b.address}\n`;
543
+ text += `ETH: ${parseFloat(b.eth).toFixed(6)}\n`;
544
+ text += `USDC: $${b.usdc}`;
545
+ if (b.agentAccount) {
546
+ text += `\n\n🏦 AgentAccount: ${b.agentAccount.address}\nUSDC: $${b.agentAccount.usdc}`;
961
547
  }
962
-
963
548
  return { text };
964
549
  } catch (e: any) {
965
550
  return { text: `❌ ${e.message}` };
@@ -973,23 +558,14 @@ export default function register(api: any) {
973
558
  handler: async () => {
974
559
  try {
975
560
  const cfg = getConfig(api);
976
- const signer = getSigner(cfg);
977
561
  const status = await getBackendStatus(cfg);
978
- const agentId = await resolveAgentId(cfg, signer, status);
979
-
980
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
981
- const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
982
- const totalDebt = await morpho.getTotalDebt(accountAddr);
562
+ const client = createClient(cfg, status);
563
+ const s = await client.getStatus();
983
564
 
984
- let text = `📊 Morpho — Agent #${agentId}\nAccount: ${accountAddr}\nTotal debt: $${ethers.formatUnits(totalDebt, 6)}\n`;
985
-
986
- for (const [symbol, info] of Object.entries(COLLATERAL_TOKENS)) {
987
- const pos = await morpho.getPosition(accountAddr, info.address);
988
- if (pos.collateralAmount > 0n || pos.borrowedAmount > 0n) {
989
- text += `\n${symbol}: ${ethers.formatUnits(pos.collateralAmount, info.decimals)} collateral, $${ethers.formatUnits(pos.borrowedAmount, 6)} debt`;
990
- }
565
+ let text = `📊 Morpho\nAccount: ${s.agentAccount}\nTotal debt: ${s.totalDebt}\n`;
566
+ for (const p of s.positions) {
567
+ text += `\n${p.token}: ${p.collateral} collateral, ${p.debt} debt`;
991
568
  }
992
-
993
569
  return { text };
994
570
  } catch (e: any) {
995
571
  return { text: `❌ ${e.message}` };