@agether/openclaw-plugin 1.4.0 → 1.5.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 +145 -575
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.0",
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,23 @@ 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
- }
93
-
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 };
65
+ function createClient(cfg: PluginConfig, status: any): MorphoCreditClient {
66
+ return new MorphoCreditClient({
67
+ privateKey: cfg.privateKey,
68
+ rpcUrl: cfg.rpcUrl,
69
+ agentId: cfg.agentId,
70
+ contracts: {
71
+ morphoCredit: status.contracts.morphoCredit,
72
+ accountFactory: status.contracts.accountFactory,
73
+ usdc: status.contracts.usdc,
74
+ agentRegistry: status.contracts.agentRegistry,
75
+ },
76
+ });
97
77
  }
98
78
 
99
79
  // ─── Plugin Entry ─────────────────────────────────────────
@@ -110,38 +90,9 @@ export default function register(api: any) {
110
90
  async execute() {
111
91
  try {
112
92
  const cfg = getConfig(api);
113
- const signer = getSigner(cfg);
114
93
  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
-
94
+ const client = createClient(cfg, status);
95
+ const result = await client.getBalances();
145
96
  return ok(JSON.stringify(result, null, 2));
146
97
  } catch (e) { return fail(e); }
147
98
  },
@@ -153,7 +104,7 @@ export default function register(api: any) {
153
104
  api.registerTool({
154
105
  name: "agether_register",
155
106
  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.",
107
+ "Register a new ERC-8004 agent identity on Base and create an AgentAccount. Returns the new agentId.",
157
108
  parameters: {
158
109
  type: "object",
159
110
  properties: {
@@ -164,82 +115,15 @@ export default function register(api: any) {
164
115
  async execute(_id: string, params: { name: string }) {
165
116
  try {
166
117
  const cfg = getConfig(api);
167
- const signer = getSigner(cfg);
168
118
  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
-
119
+ const client = createClient(cfg, status);
120
+ const result = await client.register(params.name);
237
121
  return ok(JSON.stringify({
238
- status: "registered",
239
- agentId: agentId.toString(),
240
- address: signer.address,
241
- agentAccount: accountAddr,
242
- tx: tx.hash,
122
+ status: result.alreadyRegistered ? "already_registered" : "registered",
123
+ agentId: result.agentId,
124
+ address: result.address,
125
+ agentAccount: result.agentAccount,
126
+ tx: result.tx ? txLink(result.tx) : undefined,
243
127
  }));
244
128
  } catch (e) { return fail(e); }
245
129
  },
@@ -256,33 +140,10 @@ export default function register(api: any) {
256
140
  async execute() {
257
141
  try {
258
142
  const cfg = getConfig(api);
259
- const signer = getSigner(cfg);
260
143
  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));
144
+ const client = createClient(cfg, status);
145
+ const result = await client.getStatus();
146
+ return ok(JSON.stringify(result, null, 2));
286
147
  } catch (e) { return fail(e); }
287
148
  },
288
149
  });
@@ -305,55 +166,22 @@ export default function register(api: any) {
305
166
  async execute(_id: string, params: { amount: string; token: string }) {
306
167
  try {
307
168
  const cfg = getConfig(api);
308
- const signer = getSigner(cfg);
309
169
  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
-
170
+ const client = createClient(cfg, status);
171
+ const result = await client.deposit(params.token, params.amount);
344
172
  return ok(JSON.stringify({
345
173
  status: "deposited",
346
174
  amount: `${params.amount} ${params.token}`,
347
- totalCollateral: `${ethers.formatUnits(pos.collateralAmount, tokenInfo.decimals)} ${params.token}`,
348
- agentAccount: accountAddr,
349
- tx: depositTx.hash,
175
+ totalCollateral: result.totalCollateral.toString(),
176
+ agentAccount: result.agentAccount,
177
+ tx: txLink(result.tx),
350
178
  }));
351
179
  } catch (e) { return fail(e); }
352
180
  },
353
181
  });
354
182
 
355
183
  // ═══════════════════════════════════════════════════════
356
- // TOOL: morpho_deposit_and_borrow (Flow 2)
184
+ // TOOL: morpho_deposit_and_borrow
357
185
  // ═══════════════════════════════════════════════════════
358
186
  api.registerTool({
359
187
  name: "morpho_deposit_and_borrow",
@@ -371,66 +199,24 @@ export default function register(api: any) {
371
199
  async execute(_id: string, params: { collateralAmount: string; token: string; borrowAmount: string }) {
372
200
  try {
373
201
  const cfg = getConfig(api);
374
- const signer = getSigner(cfg);
375
202
  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
-
203
+ const client = createClient(cfg, status);
204
+ const result = await client.depositAndBorrow(params.token, params.collateralAmount, params.borrowAmount);
418
205
  return ok(JSON.stringify({
419
206
  status: "deposited_and_borrowed",
420
207
  collateral: `${params.collateralAmount} ${params.token}`,
421
208
  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,
209
+ totalDebt: result.totalDebt.toString(),
210
+ agentAccount: result.agentAccount,
211
+ depositTx: txLink(result.depositTx),
212
+ borrowTx: txLink(result.borrowTx),
427
213
  }));
428
214
  } catch (e) { return fail(e); }
429
215
  },
430
216
  });
431
217
 
432
218
  // ═══════════════════════════════════════════════════════
433
- // TOOL: morpho_sponsor (Flows 4-7: Human sponsors agent)
219
+ // TOOL: morpho_sponsor
434
220
  // ═══════════════════════════════════════════════════════
435
221
  api.registerTool({
436
222
  name: "morpho_sponsor",
@@ -443,88 +229,31 @@ export default function register(api: any) {
443
229
  agentAddress: { type: "string", description: "Target AgentAccount address (alternative to agentId)" },
444
230
  amount: { type: "string", description: "Collateral amount (e.g. '0.05')" },
445
231
  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." },
232
+ borrow: { type: "string", description: "Optional: USDC amount to borrow for the agent" },
447
233
  },
448
234
  required: ["amount", "token"],
449
235
  },
450
236
  async execute(_id: string, params: { agentId?: string; agentAddress?: string; amount: string; token: string; borrow?: string }) {
451
237
  try {
238
+ if (!params.agentId && !params.agentAddress) return fail("Provide either agentId or agentAddress");
452
239
  const cfg = getConfig(api);
453
- const signer = getSigner(cfg);
454
240
  const status = await getBackendStatus(cfg);
241
+ const client = createClient(cfg, status);
455
242
 
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
- }
485
-
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();
243
+ const target = params.agentId
244
+ ? { agentId: params.agentId }
245
+ : { address: params.agentAddress! };
493
246
 
494
- const result: any = {
247
+ const result = await client.sponsor(target, params.token, params.amount, params.borrow);
248
+ return ok(JSON.stringify({
495
249
  status: "sponsored",
496
- target: accountAddr,
497
- targetAgentId: params.agentId || "by address",
250
+ target: result.targetAccount,
251
+ targetAgentId: result.targetAgentId || "by address",
498
252
  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));
253
+ borrowed: result.borrowed ? `$${params.borrow}` : undefined,
254
+ depositTx: txLink(result.depositTx),
255
+ borrowTx: result.borrowTx ? txLink(result.borrowTx) : undefined,
256
+ }));
528
257
  } catch (e) { return fail(e); }
529
258
  },
530
259
  });
@@ -546,45 +275,16 @@ export default function register(api: any) {
546
275
  async execute(_id: string, params: { amount: string }) {
547
276
  try {
548
277
  const cfg = getConfig(api);
549
- const signer = getSigner(cfg);
550
278
  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
-
279
+ const client = createClient(cfg, status);
280
+ const result = await client.borrow(params.amount);
581
281
  return ok(JSON.stringify({
582
282
  status: "borrowed",
583
283
  amount: `$${params.amount}`,
584
- collateral: activeToken,
585
- totalDebt: `$${ethers.formatUnits(totalDebt, 6)}`,
586
- agentAccount: accountAddr,
587
- tx: tx.hash,
284
+ collateral: result.collateralToken,
285
+ totalDebt: result.totalDebt.toString(),
286
+ agentAccount: result.agentAccount,
287
+ tx: txLink(result.tx),
588
288
  }));
589
289
  } catch (e) { return fail(e); }
590
290
  },
@@ -607,53 +307,14 @@ export default function register(api: any) {
607
307
  async execute(_id: string, params: { amount: string }) {
608
308
  try {
609
309
  const cfg = getConfig(api);
610
- const signer = getSigner(cfg);
611
310
  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
-
311
+ const client = createClient(cfg, status);
312
+ const result = await client.repay(params.amount);
652
313
  return ok(JSON.stringify({
653
314
  status: "repaid",
654
315
  amount: `$${params.amount}`,
655
- remainingDebt: `$${ethers.formatUnits(totalDebt, 6)}`,
656
- tx: tx.hash,
316
+ remainingDebt: result.remainingDebt.toString(),
317
+ tx: txLink(result.tx),
657
318
  }));
658
319
  } catch (e) { return fail(e); }
659
320
  },
@@ -677,56 +338,23 @@ export default function register(api: any) {
677
338
  async execute(_id: string, params: { amount: string; token: string }) {
678
339
  try {
679
340
  const cfg = getConfig(api);
680
- const signer = getSigner(cfg);
681
341
  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
-
342
+ const client = createClient(cfg, status);
343
+ const result = await client.withdraw(params.token, params.amount);
716
344
  return ok(JSON.stringify({
717
345
  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,
346
+ amount: result.amount.toString(),
347
+ token: result.token,
348
+ remainingCollateral: result.remainingCollateral.toString(),
349
+ destination: result.destination,
350
+ tx: txLink(result.tx),
723
351
  }));
724
352
  } catch (e) { return fail(e); }
725
353
  },
726
354
  });
727
355
 
728
356
  // ═══════════════════════════════════════════════════════
729
- // TOOL: morpho_compare
357
+ // TOOL: morpho_compare (backend API)
730
358
  // ═══════════════════════════════════════════════════════
731
359
  api.registerTool({
732
360
  name: "morpho_compare",
@@ -743,27 +371,20 @@ export default function register(api: any) {
743
371
  try {
744
372
  const cfg = getConfig(api);
745
373
  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
-
374
+ const status = await getBackendStatus(cfg);
375
+ const client = createClient(cfg, status);
376
+ const balances = await client.getBalances();
756
377
  return ok(JSON.stringify({
757
378
  borrowAmount: `$${params.amount}`,
758
379
  estimates: data,
759
- walletBalances: balances,
380
+ walletBalances: { eth: balances.eth, usdc: balances.usdc },
760
381
  }, null, 2));
761
382
  } catch (e) { return fail(e); }
762
383
  },
763
384
  });
764
385
 
765
386
  // ═══════════════════════════════════════════════════════
766
- // TOOL: morpho_markets
387
+ // TOOL: morpho_markets (backend API)
767
388
  // ═══════════════════════════════════════════════════════
768
389
  api.registerTool({
769
390
  name: "morpho_markets",
@@ -779,7 +400,7 @@ export default function register(api: any) {
779
400
  });
780
401
 
781
402
  // ═══════════════════════════════════════════════════════
782
- // TOOL: agether_score
403
+ // TOOL: agether_score (backend API)
783
404
  // ═══════════════════════════════════════════════════════
784
405
  api.registerTool({
785
406
  name: "agether_score",
@@ -788,9 +409,9 @@ export default function register(api: any) {
788
409
  async execute() {
789
410
  try {
790
411
  const cfg = getConfig(api);
791
- const signer = getSigner(cfg);
792
412
  const status = await getBackendStatus(cfg);
793
- const agentId = await resolveAgentId(cfg, signer, status);
413
+ const client = createClient(cfg, status);
414
+ const agentId = await client.getAgentId();
794
415
  const { data } = await axios.get(`${cfg.backendUrl}/credit/score/${agentId}`);
795
416
  return ok(JSON.stringify(data, null, 2));
796
417
  } catch (e) { return fail(e); }
@@ -813,97 +434,66 @@ export default function register(api: any) {
813
434
  async execute(_id: string, params: { amount: string }) {
814
435
  try {
815
436
  const cfg = getConfig(api);
816
- const signer = getSigner(cfg);
817
437
  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
-
438
+ const client = createClient(cfg, status);
439
+ const result = await client.fundAccount(params.amount);
832
440
  return ok(JSON.stringify({
833
441
  status: "funded",
834
442
  amount: `$${params.amount}`,
835
- agentAccount: accountAddr,
836
- tx: tx.hash,
443
+ agentAccount: result.agentAccount,
444
+ tx: txLink(result.tx),
837
445
  }));
838
446
  } catch (e) { return fail(e); }
839
447
  },
840
448
  });
841
449
 
842
450
  // ═══════════════════════════════════════════════════════
843
- // TOOL: x402_pay (Flows 8 & 9 — uses SDK X402Client)
451
+ // TOOL: x402_pay
844
452
  // ═══════════════════════════════════════════════════════
845
453
  api.registerTool({
846
454
  name: "x402_pay",
847
455
  description:
848
456
  "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.",
457
+ "If autoDraw is enabled, automatically borrows USDC from Morpho credit line when balance is insufficient.",
851
458
  parameters: {
852
459
  type: "object",
853
460
  properties: {
854
461
  url: { type: "string", description: "API endpoint URL" },
855
462
  method: { type: "string", enum: ["GET", "POST"], description: "HTTP method (default: GET)" },
856
463
  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)" },
464
+ autoDraw: { type: "boolean", description: "Auto-borrow from Morpho if USDC insufficient" },
858
465
  },
859
466
  required: ["url"],
860
467
  },
861
468
  async execute(_id: string, params: { url: string; method?: string; body?: string; autoDraw?: boolean }) {
862
469
  try {
863
470
  const cfg = getConfig(api);
864
- const signer = getSigner(cfg);
865
471
  const status = await getBackendStatus(cfg);
472
+ const client = createClient(cfg, status);
866
473
 
867
- // Resolve AgentAccount address for x402 payments
868
474
  let accountAddress: string | undefined;
869
475
  let agentId: string | undefined;
870
476
  try {
871
- agentId = await resolveAgentId(cfg, signer, status);
872
- accountAddress = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
477
+ agentId = await client.getAgentId();
478
+ accountAddress = await client.getAccountAddress();
873
479
  } catch { /* no account, pay from EOA */ }
874
480
 
875
- // Auto-draw: if enabled, check USDC balance and borrow if needed
481
+ // Auto-draw: borrow $10 if balance < $1
876
482
  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
- }
483
+ if (shouldAutoDraw && accountAddress) {
484
+ try {
485
+ const accUsdc = await client.getAccountUSDC();
486
+ if (accUsdc < 1_000_000n) {
487
+ await client.borrow("10");
488
+ }
489
+ } catch { /* auto-draw failed, proceed anyway */ }
899
490
  }
900
491
 
901
- // Use SDK's X402Client directly (no execSync)
902
492
  const x402 = new X402Client({
903
493
  privateKey: cfg.privateKey,
904
- rpcUrl: cfg.rpcUrl!,
905
- backendUrl: cfg.backendUrl!,
906
- agentId: agentId,
494
+ rpcUrl: cfg.rpcUrl,
495
+ backendUrl: cfg.backendUrl,
496
+ agentId,
907
497
  accountAddress,
908
498
  });
909
499
 
@@ -914,13 +504,14 @@ export default function register(api: any) {
914
504
  result = await x402.get(params.url);
915
505
  }
916
506
 
917
- if (!result.success) {
918
- return fail(result.error || "x402 payment failed");
919
- }
507
+ if (!result.success) return fail(result.error || "x402 payment failed");
920
508
 
921
509
  const response: any = { status: "paid", data: result.data };
922
510
  if (result.paymentInfo) {
923
- response.payment = result.paymentInfo;
511
+ response.payment = {
512
+ ...result.paymentInfo,
513
+ tx: result.paymentInfo.txHash ? txLink(result.paymentInfo.txHash) : undefined,
514
+ };
924
515
  }
925
516
  return ok(JSON.stringify(response, null, 2));
926
517
  } catch (e) { return fail(e); }
@@ -928,7 +519,7 @@ export default function register(api: any) {
928
519
  });
929
520
 
930
521
  // ═══════════════════════════════════════════════════════
931
- // AUTO-REPLY COMMANDS (no AI needed)
522
+ // SLASH COMMANDS (no AI needed)
932
523
  // ═══════════════════════════════════════════════════════
933
524
 
934
525
  api.registerCommand({
@@ -937,29 +528,17 @@ export default function register(api: any) {
937
528
  handler: async () => {
938
529
  try {
939
530
  const cfg = getConfig(api);
940
- const signer = getSigner(cfg);
941
- const provider = signer.provider!;
942
- const ethBal = await provider.getBalance(signer.address);
943
531
  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 */ }
532
+ const client = createClient(cfg, status);
533
+ const b = await client.getBalances();
534
+
535
+ let text = `💰 Agent #${b.agentId}\n`;
536
+ text += `Address: ${b.address}\n`;
537
+ text += `ETH: ${parseFloat(b.eth).toFixed(6)}\n`;
538
+ text += `USDC: $${b.usdc}`;
539
+ if (b.agentAccount) {
540
+ text += `\n\n🏦 AgentAccount: ${b.agentAccount.address}\nUSDC: $${b.agentAccount.usdc}`;
961
541
  }
962
-
963
542
  return { text };
964
543
  } catch (e: any) {
965
544
  return { text: `❌ ${e.message}` };
@@ -973,23 +552,14 @@ export default function register(api: any) {
973
552
  handler: async () => {
974
553
  try {
975
554
  const cfg = getConfig(api);
976
- const signer = getSigner(cfg);
977
555
  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);
556
+ const client = createClient(cfg, status);
557
+ const s = await client.getStatus();
983
558
 
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
- }
559
+ let text = `📊 Morpho\nAccount: ${s.agentAccount}\nTotal debt: ${s.totalDebt}\n`;
560
+ for (const p of s.positions) {
561
+ text += `\n${p.token}: ${p.collateral} collateral, ${p.debt} debt`;
991
562
  }
992
-
993
563
  return { text };
994
564
  } catch (e: any) {
995
565
  return { text: `❌ ${e.message}` };