@agether/agether 2.1.0 → 2.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.
@@ -2,7 +2,7 @@
2
2
  "id": "agether",
3
3
  "name": "Agether Credit",
4
4
  "description": "Onchain credit protocol for AI agents — Morpho-backed overcollateralized credit, ERC-8004 identity, x402 payments. Private key and RPC keys are read from OpenClaw secrets (env vars); no plaintext config needed.",
5
- "version": "2.0.1",
5
+ "version": "2.3.0",
6
6
  "skills": ["skills/agether"],
7
7
  "configSchema": {
8
8
  "type": "object",
@@ -17,16 +17,16 @@
17
17
  "description": "Auto-borrow from Morpho credit line when USDC balance is low for x402 payments",
18
18
  "default": false
19
19
  },
20
+ "autoYield": {
21
+ "type": "boolean",
22
+ "description": "Auto-withdraw earned yield from supply positions before borrowing. Waterfall: balance → yield → borrow",
23
+ "default": false
24
+ },
20
25
  "dailySpendLimitUsdc": {
21
26
  "type": "number",
22
- "description": "Daily USDC spending cap for x402 payments (0 = unlimited)",
27
+ "description": "Daily USDC spending cap for x402 auto-draw payments (0 = unlimited). Persisted to cache file so it survives restarts.",
23
28
  "default": 0
24
29
  },
25
- "yieldLimitedSpending": {
26
- "type": "boolean",
27
- "description": "Limit auto-draw spending to theoretical yield on deposited collateral",
28
- "default": false
29
- },
30
30
  "autoDrawBuffer": {
31
31
  "type": "number",
32
32
  "description": "Extra USDC buffer when auto-drawing from Morpho (e.g. 0.5 = borrow $0.50 extra)",
@@ -43,8 +43,8 @@
43
43
  "uiHints": {
44
44
  "agentId": { "label": "Agent ID", "placeholder": "17676" },
45
45
  "autoDraw": { "label": "Auto-Draw Credit", "placeholder": "false" },
46
+ "autoYield": { "label": "Auto-Yield (use yield before borrowing)", "placeholder": "false" },
46
47
  "dailySpendLimitUsdc": { "label": "Daily Spend Limit (USDC)", "placeholder": "0" },
47
- "yieldLimitedSpending": { "label": "Yield-Limited Spending", "placeholder": "false" },
48
48
  "autoDrawBuffer": { "label": "Auto-Draw Buffer (USDC)", "placeholder": "0" },
49
49
  "healthAlertThreshold": { "label": "Health Alert Threshold (%)", "placeholder": "70" }
50
50
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/agether",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "OpenClaw plugin for Agether — onchain 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": "^2.4.0",
12
+ "@agether/sdk": "^2.6.0",
13
13
  "axios": "^1.6.0",
14
14
  "ethers": "^6.9.0"
15
15
  },
package/src/index.ts CHANGED
@@ -29,6 +29,44 @@ const BACKEND_URL = "http://95.179.189.214:3001";
29
29
  const DEFAULT_RPC = "https://base-rpc.publicnode.com";
30
30
  const BASESCAN = "https://basescan.org/tx";
31
31
 
32
+ // ─── Spending Cache (persists dailySpendLimit tracker across restarts) ────
33
+
34
+ const SPEND_CACHE_FILE = path.join(
35
+ process.env.HOME || process.env.USERPROFILE || "/root",
36
+ ".openclaw",
37
+ ".agether-spend-cache.json",
38
+ );
39
+
40
+ interface SpendCacheData {
41
+ date: string; // YYYY-MM-DD
42
+ totalBorrowed: string; // raw bigint string (6-decimal USDC units)
43
+ }
44
+
45
+ function loadSpendCache(): SpendCacheData | undefined {
46
+ try {
47
+ if (fs.existsSync(SPEND_CACHE_FILE)) {
48
+ const raw = fs.readFileSync(SPEND_CACHE_FILE, "utf-8");
49
+ const data: SpendCacheData = JSON.parse(raw);
50
+ // Only return if it's today's data
51
+ const today = new Date().toISOString().split("T")[0];
52
+ if (data.date === today && data.totalBorrowed) return data;
53
+ }
54
+ } catch (e) {
55
+ console.warn("[agether] failed to load spend cache:", e instanceof Error ? e.message : String(e));
56
+ }
57
+ return undefined;
58
+ }
59
+
60
+ function saveSpendCache(state: SpendCacheData): void {
61
+ try {
62
+ const dir = path.dirname(SPEND_CACHE_FILE);
63
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
64
+ fs.writeFileSync(SPEND_CACHE_FILE, JSON.stringify(state, null, 2));
65
+ } catch (e) {
66
+ console.warn("[agether] failed to save spend cache:", e instanceof Error ? e.message : String(e));
67
+ }
68
+ }
69
+
32
70
  // ─── Helpers ──────────────────────────────────────────────
33
71
 
34
72
  interface PluginConfig {
@@ -339,12 +377,12 @@ export default function register(api: any) {
339
377
  api.registerTool({
340
378
  name: "morpho_deposit",
341
379
  description:
342
- "Deposit collateral (WETH, wstETH, or cbETH) into Morpho Blue via AgentAccount. Enables borrowing USDC.",
380
+ "Deposit collateral into Morpho Blue via AgentAccount. Enables borrowing USDC. Token is auto-discovered from available markets.",
343
381
  parameters: {
344
382
  type: "object",
345
383
  properties: {
346
384
  amount: { type: "string", description: "Amount of collateral tokens (e.g. '0.05')" },
347
- token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
385
+ token: { type: "string", description: "Collateral token" },
348
386
  },
349
387
  required: ["amount", "token"],
350
388
  },
@@ -375,7 +413,7 @@ export default function register(api: any) {
375
413
  type: "object",
376
414
  properties: {
377
415
  collateralAmount: { type: "string", description: "Amount of collateral (e.g. '0.05')" },
378
- token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
416
+ token: { type: "string", description: "Collateral token" },
379
417
  borrowAmount: { type: "string", description: "USDC amount to borrow (e.g. '50')" },
380
418
  },
381
419
  required: ["collateralAmount", "token", "borrowAmount"],
@@ -414,7 +452,7 @@ export default function register(api: any) {
414
452
  agentId: { type: "string", description: "Target agent's ERC-8004 ID (e.g. '17676')" },
415
453
  agentAddress: { type: "string", description: "Target AgentAccount address (alternative to agentId)" },
416
454
  amount: { type: "string", description: "Collateral amount (e.g. '0.05')" },
417
- token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
455
+ token: { type: "string", description: "Collateral token" },
418
456
  },
419
457
  required: ["amount", "token"],
420
458
  },
@@ -452,7 +490,7 @@ export default function register(api: any) {
452
490
  type: "object",
453
491
  properties: {
454
492
  amount: { type: "string", description: "Amount in USD to borrow (e.g. '100')" },
455
- token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token (optional, auto-detected)" },
493
+ token: { type: "string", description: "Collateral token (optional, auto-detected)" },
456
494
  },
457
495
  required: ["amount"],
458
496
  },
@@ -484,7 +522,7 @@ export default function register(api: any) {
484
522
  type: "object",
485
523
  properties: {
486
524
  amount: { type: "string", description: "Amount in USD to repay (e.g. '50' or 'all' for full repayment)" },
487
- token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token (optional, auto-detected)" },
525
+ token: { type: "string", description: "Collateral token (optional, auto-detected)" },
488
526
  },
489
527
  required: ["amount"],
490
528
  },
@@ -509,21 +547,25 @@ export default function register(api: any) {
509
547
  api.registerTool({
510
548
  name: "morpho_withdraw",
511
549
  description:
512
- "Withdraw collateral from Morpho Blue back to EOA wallet. " +
550
+ "Withdraw collateral from Morpho Blue. " +
551
+ "By default keeps collateral in AgentAccount. Set toEoa=true to send to EOA wallet. " +
513
552
  "Use amount 'all' to withdraw maximum. Must maintain collateral ratio if debt remains.",
514
553
  parameters: {
515
554
  type: "object",
516
555
  properties: {
517
556
  amount: { type: "string", description: "Amount to withdraw (e.g. '0.05' or 'all')" },
518
- token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
557
+ token: { type: "string", description: "Collateral token" },
558
+ toEoa: { type: "boolean", description: "Send to EOA wallet instead of keeping in AgentAccount (default: false)" },
519
559
  },
520
560
  required: ["amount", "token"],
521
561
  },
522
- async execute(_id: string, params: { amount: string; token: string }) {
562
+ async execute(_id: string, params: { amount: string; token: string; toEoa?: boolean }) {
523
563
  try {
524
564
  const cfg = getConfig(api);
525
565
  const client = createClient(cfg);
526
- const result = await client.withdrawCollateral(params.token, params.amount);
566
+ // Default: keep in AgentAccount. toEoa=true → send to EOA.
567
+ const receiver = params.toEoa ? await client.getSignerAddress() : await client.getAccountAddress();
568
+ const result = await client.withdrawCollateral(params.token, params.amount, undefined, receiver);
527
569
  return ok(JSON.stringify({
528
570
  status: "withdrawn",
529
571
  amount: result.amount,
@@ -551,7 +593,7 @@ export default function register(api: any) {
551
593
  amount: { type: "string", description: "USDC amount to supply (e.g. '500')" },
552
594
  market: {
553
595
  type: "string",
554
- enum: ["WETH", "wstETH", "cbETH"],
596
+
555
597
  description: "Which market to supply to, identified by collateral token (optional — auto-picks highest APY)",
556
598
  },
557
599
  },
@@ -588,7 +630,7 @@ export default function register(api: any) {
588
630
  properties: {
589
631
  market: {
590
632
  type: "string",
591
- enum: ["WETH", "wstETH", "cbETH"],
633
+
592
634
  description: "Filter by market collateral token (optional)",
593
635
  },
594
636
  },
@@ -634,6 +676,7 @@ export default function register(api: any) {
634
676
  name: "morpho_withdraw_supply",
635
677
  description:
636
678
  "Withdraw supplied USDC (principal + earned interest) from a Morpho Blue lending position. " +
679
+ "By default keeps USDC in AgentAccount. Set toEoa=true to send to EOA wallet. " +
637
680
  "Use amount 'all' to withdraw the full position. Different from morpho_withdraw which withdraws collateral.",
638
681
  parameters: {
639
682
  type: "object",
@@ -641,17 +684,20 @@ export default function register(api: any) {
641
684
  amount: { type: "string", description: "USDC amount to withdraw (e.g. '100' or 'all')" },
642
685
  market: {
643
686
  type: "string",
644
- enum: ["WETH", "wstETH", "cbETH"],
687
+
645
688
  description: "Market collateral token (optional — auto-detects)",
646
689
  },
690
+ toEoa: { type: "boolean", description: "Send to EOA wallet instead of keeping in AgentAccount (default: false)" },
647
691
  },
648
692
  required: ["amount"],
649
693
  },
650
- async execute(_id: string, params: { amount: string; market?: string }) {
694
+ async execute(_id: string, params: { amount: string; market?: string; toEoa?: boolean }) {
651
695
  try {
652
696
  const cfg = getConfig(api);
653
697
  const client = createClient(cfg);
654
- const result = await client.withdrawSupply(params.amount, params.market);
698
+ // Default: keep in AgentAccount. toEoa=true → send to EOA.
699
+ const receiver = params.toEoa ? await client.getSignerAddress() : await client.getAccountAddress();
700
+ const result = await client.withdrawSupply(params.amount, params.market, receiver);
655
701
  return ok(JSON.stringify({
656
702
  status: "withdrawn",
657
703
  amount: params.amount === "all" ? "all (full position)" : `$${params.amount} USDC`,
@@ -664,70 +710,38 @@ export default function register(api: any) {
664
710
  });
665
711
 
666
712
  // ═══════════════════════════════════════════════════════
667
- // TOOL: morpho_pay_from_yield (pay using only earned yield)
713
+ // TOOL: morpho_markets (Morpho GraphQL API no backend)
668
714
  // ═══════════════════════════════════════════════════════
669
715
  api.registerTool({
670
- name: "morpho_pay_from_yield",
716
+ name: "morpho_markets",
671
717
  description:
672
- "Pay a recipient using ONLY earned yield from a supply position. " +
673
- "Your principal stays untouched — only the interest earned gets withdrawn and sent. " +
674
- "Fails if requested amount exceeds available yield.",
718
+ "List available Morpho Blue USDC markets on Base liquidity, supply/borrow APY, utilization, LLTV. " +
719
+ "Optionally filter by collateral token.",
675
720
  parameters: {
676
721
  type: "object",
677
722
  properties: {
678
- recipient: { type: "string", description: "Recipient address (0x...)" },
679
- amount: { type: "string", description: "USDC amount to pay from yield (e.g. '2.50')" },
680
- market: {
681
- type: "string",
682
- enum: ["WETH", "wstETH", "cbETH"],
683
- description: "Which supply position to use (optional — auto-picks highest yield)",
684
- },
723
+ token: { type: "string", description: "Filter by collateral token (optional)" },
685
724
  },
686
- required: ["recipient", "amount"],
725
+ required: [],
687
726
  },
688
- async execute(_id: string, params: { recipient: string; amount: string; market?: string }) {
727
+ async execute(_id: string, params: { token?: string }) {
689
728
  try {
690
729
  const cfg = getConfig(api);
691
730
  const client = createClient(cfg);
692
- const result = await client.payFromYield(params.recipient, params.amount, params.market);
693
- return ok(JSON.stringify({
694
- status: "paid_from_yield",
695
- amount: `$${params.amount} USDC`,
696
- recipient: params.recipient,
697
- remainingYield: `$${result.remainingYield} USDC`,
698
- remainingSupply: `$${result.remainingSupply} USDC`,
699
- note: "Paid from earned interest only — principal untouched",
700
- tx: txLink(result.tx),
701
- }));
702
- } catch (e) { return fail(e); }
703
- },
704
- });
731
+ const rates = await client.getMarketRates(params.token);
705
732
 
706
- // ═══════════════════════════════════════════════════════
707
- // TOOL: morpho_markets (Morpho GraphQL API — no backend)
708
- // ═══════════════════════════════════════════════════════
709
- api.registerTool({
710
- name: "morpho_markets",
711
- description:
712
- "List available Morpho Blue USDC borrow markets on Base with liquidity, APY, and collateral info.",
713
- parameters: { type: "object", properties: {}, required: [] },
714
- async execute() {
715
- try {
716
- const cfg = getConfig(api);
717
- const client = createClient(cfg);
718
- const markets = await client.getMarkets(true);
719
-
720
- const formatted = markets
721
- .filter((m) => m.collateralAsset.address !== "0x0000000000000000000000000000000000000000")
722
- .map((m) => ({
723
- collateral: m.collateralAsset.symbol,
724
- loan: m.loanAsset.symbol,
725
- lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
726
- totalSupply: `$${(Number(m.totalSupplyAssets) / 1e6).toFixed(0)}`,
727
- totalBorrow: `$${(Number(m.totalBorrowAssets) / 1e6).toFixed(0)}`,
728
- utilization: `${(m.utilization * 100).toFixed(1)}%`,
729
- marketId: m.uniqueKey.slice(0, 18) + "…",
730
- }));
733
+ if (rates.length === 0) return ok("No markets found.");
734
+
735
+ const formatted = rates.map((r: any) => ({
736
+ collateral: r.collateralToken,
737
+ loan: r.loanToken,
738
+ lltv: r.lltv,
739
+ supplyApy: `${(r.supplyApy * 100).toFixed(2)}%`,
740
+ borrowApy: `${(r.borrowApy * 100).toFixed(2)}%`,
741
+ utilization: `${(r.utilization * 100).toFixed(1)}%`,
742
+ totalSupply: `$${r.totalSupplyUsd.toFixed(0)}`,
743
+ totalBorrow: `$${r.totalBorrowUsd.toFixed(0)}`,
744
+ }));
731
745
 
732
746
  return ok(JSON.stringify(formatted, null, 2));
733
747
  } catch (e) { return fail(e); }
@@ -808,24 +822,88 @@ export default function register(api: any) {
808
822
  });
809
823
 
810
824
  // ═══════════════════════════════════════════════════════
811
- // TOOL: x402_pay (with auto-draw support)
825
+ // TOOL: wallet_withdraw_token
826
+ // ═══════════════════════════════════════════════════════
827
+ api.registerTool({
828
+ name: "wallet_withdraw_token",
829
+ description:
830
+ "Withdraw (transfer) a token from AgentAccount to EOA wallet. " +
831
+ "Works with any ERC-20 token (USDC, WETH, wstETH, cbBTC, etc.). " +
832
+ "Use amount 'all' to withdraw the full balance.",
833
+ parameters: {
834
+ type: "object",
835
+ properties: {
836
+ token: { type: "string", description: "Token to withdraw (e.g. 'USDC', 'WETH')" },
837
+ amount: { type: "string", description: "Amount to withdraw (e.g. '100' or 'all')" },
838
+ },
839
+ required: ["token", "amount"],
840
+ },
841
+ async execute(_id: string, params: { token: string; amount: string }) {
842
+ try {
843
+ const cfg = getConfig(api);
844
+ const client = createClient(cfg);
845
+ const result = await client.withdrawToken(params.token, params.amount);
846
+ return ok(JSON.stringify({
847
+ status: "withdrawn",
848
+ token: result.token,
849
+ amount: result.amount,
850
+ destination: result.destination,
851
+ tx: txLink(result.tx),
852
+ }));
853
+ } catch (e) { return fail(e); }
854
+ },
855
+ });
856
+
857
+ // ═══════════════════════════════════════════════════════
858
+ // TOOL: wallet_withdraw_eth
859
+ // ═══════════════════════════════════════════════════════
860
+ api.registerTool({
861
+ name: "wallet_withdraw_eth",
862
+ description:
863
+ "Withdraw ETH from AgentAccount to EOA wallet. " +
864
+ "Use amount 'all' to withdraw the full ETH balance.",
865
+ parameters: {
866
+ type: "object",
867
+ properties: {
868
+ amount: { type: "string", description: "ETH amount to withdraw (e.g. '0.01' or 'all')" },
869
+ },
870
+ required: ["amount"],
871
+ },
872
+ async execute(_id: string, params: { amount: string }) {
873
+ try {
874
+ const cfg = getConfig(api);
875
+ const client = createClient(cfg);
876
+ const result = await client.withdrawEth(params.amount);
877
+ return ok(JSON.stringify({
878
+ status: "withdrawn",
879
+ token: "ETH",
880
+ amount: `${result.amount} ETH`,
881
+ destination: result.destination,
882
+ tx: txLink(result.tx),
883
+ }));
884
+ } catch (e) { return fail(e); }
885
+ },
886
+ });
887
+
888
+ // ═══════════════════════════════════════════════════════
889
+ // TOOL: x402_pay (with auto-yield + auto-draw waterfall)
812
890
  // ═══════════════════════════════════════════════════════
813
891
  api.registerTool({
814
892
  name: "x402_pay",
815
893
  description:
816
894
  "Make a paid API call using x402 protocol. Pays with USDC from AgentAccount via EIP-3009 signature. " +
817
- "Set autoDraw=true to automatically borrow from Morpho Blue if USDC balance is insufficient (opt-in).",
895
+ "If USDC is insufficient, auto-sources funds: yield first (autoYield), then borrow (autoDraw). " +
896
+ "Both are configured in plugin settings.",
818
897
  parameters: {
819
898
  type: "object",
820
899
  properties: {
821
900
  url: { type: "string", description: "API endpoint URL" },
822
901
  method: { type: "string", enum: ["GET", "POST"], description: "HTTP method (default: GET)" },
823
902
  body: { type: "string", description: "JSON body for POST requests" },
824
- autoDraw: { type: "boolean", description: "Auto-borrow from Morpho if USDC insufficient (default: false)" },
825
903
  },
826
904
  required: ["url"],
827
905
  },
828
- async execute(_id: string, params: { url: string; method?: string; body?: string; autoDraw?: boolean }) {
906
+ async execute(_id: string, params: { url: string; method?: string; body?: string }) {
829
907
  try {
830
908
  const cfg = getConfig(api);
831
909
  const agetherCfg = api.config?.plugins?.entries?.agether?.config || {};
@@ -840,7 +918,8 @@ export default function register(api: any) {
840
918
  console.warn('[agether] x402_pay: getAccountAddress failed, paying from EOA:', e instanceof Error ? e.message : e);
841
919
  }
842
920
 
843
- const autoDrawEnabled = params.autoDraw === true; // default false — opt-in only
921
+ const autoDrawEnabled = agetherCfg.autoDraw === true;
922
+ const autoYieldEnabled = agetherCfg.autoYield === true;
844
923
  const x402 = new X402Client({
845
924
  privateKey: cfg.privateKey,
846
925
  rpcUrl: cfg.rpcUrl,
@@ -848,16 +927,20 @@ export default function register(api: any) {
848
927
  agentId,
849
928
  accountAddress,
850
929
  autoDraw: autoDrawEnabled,
930
+ autoYield: autoYieldEnabled,
851
931
  dailySpendLimitUsdc: agetherCfg.dailySpendLimitUsdc,
852
- yieldLimitedSpending: agetherCfg.yieldLimitedSpending,
853
932
  autoDrawBuffer: agetherCfg.autoDrawBuffer,
933
+ // Restore spending state from cache so limit survives restarts
934
+ initialSpendingState: loadSpendCache(),
935
+ // Persist every spending update to cache file
936
+ onSpendingUpdate: (state: any) => saveSpendCache(state),
854
937
  // Safe7579 needs validator prefix for ERC-1271 isValidSignature routing
855
938
  validatorModule: "0xde896C58163b5f6cAC5B16C1b0109843f26106F6",
856
939
  });
857
940
 
858
941
  let result;
859
- if (autoDrawEnabled && agentId) {
860
- // Use payWithAutoDraw which handles balance check + borrow
942
+ if ((autoDrawEnabled || autoYieldEnabled) && agentId) {
943
+ // Use payWithAutoDraw which handles waterfall: balance yield borrow
861
944
  const fetchOpts: any = {
862
945
  morphoClient: client,
863
946
  };
@@ -885,98 +968,22 @@ export default function register(api: any) {
885
968
  };
886
969
  }
887
970
  if (result.autoDrawInfo) {
888
- response.autoDraw = {
889
- ...result.autoDrawInfo,
890
- borrowTx: result.autoDrawInfo.borrowTx ? txLink(result.autoDrawInfo.borrowTx) : undefined,
891
- };
971
+ const info: any = { reason: result.autoDrawInfo.reason };
972
+ if (result.autoDrawInfo.yieldWithdrawn) {
973
+ info.yieldWithdrawn = `$${result.autoDrawInfo.yieldWithdrawn} USDC`;
974
+ if (result.autoDrawInfo.yieldTx) info.yieldTx = txLink(result.autoDrawInfo.yieldTx);
975
+ }
976
+ if (result.autoDrawInfo.borrowed) {
977
+ info.borrowed = `$${result.autoDrawInfo.borrowed} USDC`;
978
+ if (result.autoDrawInfo.borrowTx) info.borrowTx = txLink(result.autoDrawInfo.borrowTx);
979
+ }
980
+ response.autoFund = info;
892
981
  }
893
982
  return ok(JSON.stringify(response, null, 2));
894
983
  } catch (e) { return fail(e); }
895
984
  },
896
985
  });
897
986
 
898
- // ═══════════════════════════════════════════════════════
899
- // TOOL: morpho_rates
900
- // ═══════════════════════════════════════════════════════
901
- api.registerTool({
902
- name: "morpho_rates",
903
- description:
904
- "Show current Morpho Blue market rates — supply APY, borrow APY, utilization for USDC markets. " +
905
- "Note: collateral does NOT earn yield on Morpho; supply APY is what lenders earn.",
906
- parameters: {
907
- type: "object",
908
- properties: {
909
- token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Filter by collateral token (optional)" },
910
- },
911
- required: [],
912
- },
913
- async execute(_id: string, params: { token?: string }) {
914
- try {
915
- const cfg = getConfig(api);
916
- const client = createClient(cfg);
917
- const rates = await client.getMarketRates(params.token);
918
-
919
- if (rates.length === 0) return ok("No markets found.");
920
-
921
- const formatted = rates.map((r: any) => ({
922
- collateral: r.collateralToken,
923
- loan: r.loanToken,
924
- supplyApy: `${(r.supplyApy * 100).toFixed(2)}%`,
925
- borrowApy: `${(r.borrowApy * 100).toFixed(2)}%`,
926
- utilization: `${(r.utilization * 100).toFixed(1)}%`,
927
- totalSupply: `$${r.totalSupplyUsd.toFixed(0)}`,
928
- totalBorrow: `$${r.totalBorrowUsd.toFixed(0)}`,
929
- lltv: r.lltv,
930
- }));
931
-
932
- return ok(JSON.stringify(formatted, null, 2));
933
- } catch (e) { return fail(e); }
934
- },
935
- });
936
-
937
- // ═══════════════════════════════════════════════════════
938
- // TOOL: morpho_yield_estimate
939
- // ═══════════════════════════════════════════════════════
940
- api.registerTool({
941
- name: "morpho_yield_estimate",
942
- description:
943
- "Estimate theoretical yield for collateral deposited in Morpho Blue. " +
944
- "⚠️ Collateral does NOT actually earn yield on Morpho. This estimates " +
945
- "what it WOULD earn if it were supplied as a lender instead. " +
946
- "Useful for setting spending caps based on theoretical earnings.",
947
- parameters: {
948
- type: "object",
949
- properties: {
950
- token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
951
- amount: { type: "string", description: "Collateral amount (e.g. '1.5')" },
952
- periodDays: { type: "number", description: "Period in days (default: 1)" },
953
- ethPriceUsd: { type: "number", description: "ETH price in USD (optional, uses oracle if not provided)" },
954
- },
955
- required: ["token", "amount"],
956
- },
957
- async execute(_id: string, params: { token: string; amount: string; periodDays?: number; ethPriceUsd?: number }) {
958
- try {
959
- const cfg = getConfig(api);
960
- const client = createClient(cfg);
961
- const result = await client.getYieldEstimate(
962
- params.token,
963
- params.amount,
964
- params.periodDays || 1,
965
- params.ethPriceUsd,
966
- );
967
-
968
- return ok(JSON.stringify({
969
- collateral: `${result.amount} ${result.collateralToken}`,
970
- collateralValueUsd: `$${result.collateralValueUsd.toFixed(2)}`,
971
- theoreticalSupplyApy: `${(result.theoreticalSupplyApy * 100).toFixed(2)}%`,
972
- estimatedYieldUsd: `$${result.estimatedYieldUsd.toFixed(4)}`,
973
- period: `${result.periodDays} day(s)`,
974
- disclaimer: result.disclaimer,
975
- }, null, 2));
976
- } catch (e) { return fail(e); }
977
- },
978
- });
979
-
980
987
  // ═══════════════════════════════════════════════════════
981
988
  // TOOL: morpho_max_borrowable
982
989
  // ═══════════════════════════════════════════════════════
@@ -1177,13 +1184,32 @@ export default function register(api: any) {
1177
1184
  const client = createClient(cfg);
1178
1185
  const b = await client.getBalances();
1179
1186
 
1187
+ const nz = (v: string) => parseFloat(v) > 0;
1188
+
1180
1189
  let text = `💰 Agent #${b.agentId}\n`;
1181
- text += `Address: ${b.address}\n`;
1182
- text += `ETH: ${parseFloat(b.eth).toFixed(6)}\n`;
1183
- text += `USDC: $${b.usdc}`;
1190
+ text += `Address: ${b.address}`;
1191
+ if (nz(b.eth)) text += `\nETH: ${parseFloat(b.eth).toFixed(6)}`;
1192
+ if (nz(b.usdc)) text += `\nUSDC: $${b.usdc}`;
1193
+ for (const [sym, val] of Object.entries(b.collateral ?? {})) {
1194
+ if (nz(val)) text += `\n${sym}: ${parseFloat(val).toFixed(6)}`;
1195
+ }
1196
+
1184
1197
  if (b.agentAccount) {
1185
- text += `\n\n🏦 AgentAccount: ${b.agentAccount.address}\nUSDC: $${b.agentAccount.usdc}`;
1198
+ const a = b.agentAccount;
1199
+ const hasAny = nz(a.eth) || nz(a.usdc) ||
1200
+ Object.values(a.collateral ?? {}).some(nz);
1201
+ if (hasAny) {
1202
+ text += `\n\n🏦 AgentAccount: ${a.address}`;
1203
+ if (nz(a.eth)) text += `\nETH: ${parseFloat(a.eth).toFixed(6)}`;
1204
+ if (nz(a.usdc)) text += `\nUSDC: $${a.usdc}`;
1205
+ for (const [sym, val] of Object.entries(a.collateral ?? {})) {
1206
+ if (nz(val)) text += `\n${sym}: ${parseFloat(val).toFixed(6)}`;
1207
+ }
1208
+ } else {
1209
+ text += `\n\n🏦 AgentAccount: ${a.address}\n(empty)`;
1210
+ }
1186
1211
  }
1212
+
1187
1213
  return { text };
1188
1214
  } catch (e: any) {
1189
1215
  return { text: `❌ ${e.message}` };