@agether/agether 2.2.0 → 2.3.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.
@@ -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.1",
6
6
  "skills": ["skills/agether"],
7
7
  "configSchema": {
8
8
  "type": "object",
@@ -17,19 +17,14 @@
17
17
  "description": "Auto-borrow from Morpho credit line when USDC balance is low for x402 payments",
18
18
  "default": false
19
19
  },
20
- "dailySpendLimitUsdc": {
21
- "type": "number",
22
- "description": "Daily USDC spending cap for x402 payments (0 = unlimited)",
23
- "default": 0
24
- },
25
- "yieldLimitedSpending": {
20
+ "autoYield": {
26
21
  "type": "boolean",
27
- "description": "Limit auto-draw spending to theoretical yield on deposited collateral",
22
+ "description": "Auto-withdraw earned yield from supply positions before borrowing. Waterfall: balance → yield borrow",
28
23
  "default": false
29
24
  },
30
- "autoDrawBuffer": {
25
+ "dailySpendLimitUsdc": {
31
26
  "type": "number",
32
- "description": "Extra USDC buffer when auto-drawing from Morpho (e.g. 0.5 = borrow $0.50 extra)",
27
+ "description": "Daily USDC spending cap for x402 auto-draw payments (0 = unlimited). Persisted to cache file so it survives restarts.",
33
28
  "default": 0
34
29
  },
35
30
  "healthAlertThreshold": {
@@ -43,9 +38,8 @@
43
38
  "uiHints": {
44
39
  "agentId": { "label": "Agent ID", "placeholder": "17676" },
45
40
  "autoDraw": { "label": "Auto-Draw Credit", "placeholder": "false" },
41
+ "autoYield": { "label": "Auto-Yield (use yield before borrowing)", "placeholder": "false" },
46
42
  "dailySpendLimitUsdc": { "label": "Daily Spend Limit (USDC)", "placeholder": "0" },
47
- "yieldLimitedSpending": { "label": "Yield-Limited Spending", "placeholder": "false" },
48
- "autoDrawBuffer": { "label": "Auto-Draw Buffer (USDC)", "placeholder": "0" },
49
43
  "healthAlertThreshold": { "label": "Health Alert Threshold (%)", "placeholder": "70" }
50
44
  }
51
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/agether",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
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.5.0",
12
+ "@agether/sdk": "^2.6.1",
13
13
  "axios": "^1.6.0",
14
14
  "ethers": "^6.9.0"
15
15
  },
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: agether
3
- description: On-chain credit protocol for AI agents on Base. Morpho credit, x402 payments, ERC-8004 identity.
3
+ description: On-chain credit protocol for AI agents on Base. Morpho credit & lending, x402 payments, ERC-8004 identity.
4
4
  ---
5
5
 
6
6
  # Agether — On-Chain Credit for AI Agents
7
7
 
8
- You are an AI agent with on-chain financial capabilities on **Base mainnet**. You can earn credit, borrow USDC, and pay for APIs autonomously.
8
+ You are an AI agent with on-chain financial capabilities on **Base mainnet**. You can borrow USDC, earn yield by lending, and pay for APIs autonomously.
9
9
 
10
10
  ---
11
11
 
@@ -37,6 +37,9 @@ If you don't paste it, the user sees NOTHING. An empty colon ":" with no data af
37
37
  12. **To get USDC for x402:** call `morpho_deposit_and_borrow` with collateral from your EOA. Do NOT ask the user to send tokens — check your balance first.
38
38
  13. **Check health before large actions.** Before borrowing, depositing, or withdrawing significant amounts, call `agether_health` to see current LTV and headroom. This prevents failed transactions.
39
39
  14. **Batch awareness:** `morpho_deposit_and_borrow` is a batched operation (deposit + borrow in one tx). Always prefer it over separate `morpho_deposit` then `morpho_borrow` when doing both — it saves gas and is atomic.
40
+ 15. **Collateral tokens are dynamic.** Don't hardcode "WETH/wstETH/cbETH". Call `morpho_markets` to discover which tokens are available. The SDK auto-discovers tokens from Morpho GraphQL API.
41
+ 16. **Withdrawals stay in AgentAccount by default.** `morpho_withdraw` and `morpho_withdraw_supply` keep funds in the Safe account unless `toEoa: true` is passed. Use `wallet_withdraw_token` / `wallet_withdraw_eth` to move any token or ETH from AgentAccount to EOA.
42
+ 17. **Always show Health Factor when reporting positions.** HF = LLTV / LTV. HF > 1.15 = safe 🟢, HF 1.0–1.15 = warning 🟡, HF ≤ 1.0 = liquidation 🔴. `morpho_status` returns HF per position. Always paste it so the user understands their risk.
40
43
 
41
44
  ---
42
45
 
@@ -67,42 +70,84 @@ Both `agether_set_agent` and `agether_register` save the agentId to config perma
67
70
  | `agether_health` | **START HERE.** Comprehensive check: balances, all positions with LTV & liquidation risk, borrowing headroom, alerts — all in one call. |
68
71
  | `agether_preflight` | Setup readiness check: private key present? RPC working? Agent registered? Balances OK? Returns pass/fail checklist. |
69
72
 
70
- ### Identity & Wallet
73
+ ### Identity
71
74
  | Tool | What it does |
72
75
  |------|-------------|
73
- | `agether_balance` | Show ETH + USDC + collateral (WETH/wstETH/cbETH) balances for both EOA wallet and Safe account |
76
+ | `agether_balance` | Show ETH + USDC + all collateral token balances for both EOA wallet and AgentAccount (Safe). Tokens auto-discovered from markets. |
74
77
  | `agether_register` | Register on-chain: mints ERC-8004 identity NFT + creates Safe account (via Safe7579). Auto-saves agentId to config. |
75
78
  | `agether_set_agent` | Set a known agentId (e.g. from memory) and save to config. Use when agentId is "?" but you remember it. |
76
79
  | `agether_score` | Get credit score. `refresh: false` = free read. `refresh: true` = pays x402, computes fresh score on-chain. **Always call this when user asks about score.** |
77
- | `wallet_fund` | Transfer USDC from EOA wallet Safe account |
80
+ | `agether_kya_status` | Check if KYA code verification is required for this deployment. |
81
+ | `agether_set_kya` | Enable or disable KYA verification gate (owner only). |
78
82
 
79
- ### Morpho Credit (Overcollateralized instant, no approval)
83
+ ### Wallet (move funds between EOA ↔ AgentAccount)
80
84
  | Tool | Params | What it does |
81
85
  |------|--------|-------------|
82
- | `morpho_markets` | none | List supported Morpho markets and parameters |
83
- | `morpho_status` | none | Show all Morpho positions (collateral + debt) |
84
- | `morpho_rates` | `token?` | Current market rates supply APY, borrow APY, utilization |
86
+ | `wallet_fund` | `amount` | Transfer USDC from EOA AgentAccount |
87
+ | `wallet_withdraw_token` | `token`, `amount` | Withdraw any ERC-20 token from AgentAccount EOA. Use `amount: "all"` for full balance. |
88
+ | `wallet_withdraw_eth` | `amount` | Withdraw ETH from AgentAccount EOA. Use `amount: "all"` for full balance. |
89
+
90
+ ### Morpho Borrowing (Overcollateralized — instant, no approval needed)
91
+ | Tool | Params | What it does |
92
+ |------|--------|-------------|
93
+ | `morpho_status` | none | Show all Morpho positions — collateral, debt, LTV, **Health Factor** (HF), headroom, risk level per market |
94
+ | `morpho_markets` | `token?` | List Morpho Blue USDC markets — supply/borrow APY, utilization, LLTV, liquidity. Optional token filter. |
85
95
  | `morpho_max_borrowable` | none | Calculate max additional USDC borrowable given current collateral/debt |
86
- | `morpho_yield_estimate` | `token`, `amount`, `periodDays?` | Estimate theoretical yield for collateral |
87
- | `morpho_deposit` | `amount`, `token` | Deposit collateral from EOA Morpho (no borrow) |
88
- | `morpho_deposit_and_borrow` | `collateralAmount`, `token`, `borrowAmount` | **PREFERRED** Deposit + borrow in one batched tx. Best for first-time setup |
89
- | `morpho_borrow` | `amount` (USD) | Borrow USDC against existing collateral lands in Safe account |
90
- | `morpho_repay` | `amount` (USD or "all") | Repay USDC debt from Safe accountMorpho |
91
- | `morpho_withdraw` | `amount` (or "all"), `token` | Withdraw collateral back to EOA wallet |
92
- | `morpho_sponsor` | `agentId`/`agentAddress`, `amount`, `token` | Deposit collateral for another agent |
96
+ | `morpho_deposit` | `amount`, `token` | Deposit collateral from EOA Morpho (no borrow). Token auto-discovered. |
97
+ | `morpho_deposit_and_borrow` | `collateralAmount`, `token`, `borrowAmount` | **PREFERRED** — Deposit + borrow in one batched tx (ERC-7579 batch mode). Best for first-time setup. |
98
+ | `morpho_borrow` | `amount`, `token?` | Borrow USDC against existing collateral lands in AgentAccount. Token auto-detected. |
99
+ | `morpho_repay` | `amount`, `token?` | Repay USDC debt. Use `amount: "all"` to clear dust shares. |
100
+ | `morpho_withdraw` | `amount`, `token`, `toEoa?` | Withdraw collateral from Morpho. Default: stays in AgentAccount. `toEoa: true` send to EOA wallet. |
101
+ | `morpho_sponsor` | `agentId`/`agentAddress`, `amount`, `token` | Transfer collateral to another agent's AgentAccount. |
102
+
103
+ ### Morpho Lending (Supply-side — earn yield)
104
+ | Tool | Params | What it does |
105
+ |------|--------|-------------|
106
+ | `morpho_supply` | `amount`, `market?` | Supply USDC as a LENDER to earn yield from borrowers. Auto-picks highest APY market if no market specified. |
107
+ | `morpho_supply_status` | `market?` | Show supply (lending) positions with earned yield. Tracks yield on-chain — no database needed. |
108
+ | `morpho_withdraw_supply` | `amount`, `market?`, `toEoa?` | Withdraw supplied USDC (principal + earned interest). Default: stays in AgentAccount. `toEoa: true` → send to EOA. |
93
109
 
94
110
  ### x402 Payments
95
111
  | Tool | Params | What it does |
96
112
  |------|--------|-------------|
97
- | `x402_pay` | `url`, `method?`, `body?`, `autoDraw?` | Make a paid API call. Pays with USDC via EIP-3009 signature |
113
+ | `x402_pay` | `url`, `method?`, `body?` | Make a paid API call via x402 protocol. Pays with USDC via EIP-3009 from AgentAccount. If USDC is low, auto-sources: yield first (autoYield), then borrow (autoDraw). |
98
114
 
99
115
  ### Slash Commands (user types these — no AI invocation)
100
116
  | Command | What it does |
101
117
  |---------|-------------|
102
- | `/balance` | Quick balance check |
103
- | `/morpho` | Quick Morpho position summary |
118
+ | `/balance` | Quick balance check (shows all non-zero balances) |
119
+ | `/morpho` | Quick Morpho position summary with Health Factor |
104
120
  | `/health` | Quick position health: LTV, liquidation risk, balances |
105
- | `/rates` | Current Morpho market rates |
121
+ | `/rates` | Current Morpho market rates (supply APY, borrow APY, utilization) |
122
+
123
+ ---
124
+
125
+ ## ⚙️ PLUGIN CONFIGURATION
126
+
127
+ These settings are in `openclaw.json` under `plugins.entries.agether.config`:
128
+
129
+ | Setting | Type | Default | Description |
130
+ |---------|------|---------|-------------|
131
+ | `agentId` | string | — | ERC-8004 agent ID (auto-saved by register/set_agent tools) |
132
+ | `autoDraw` | boolean | `false` | Auto-borrow from Morpho when USDC is low for x402 payments |
133
+ | `autoYield` | boolean | `false` | Auto-withdraw earned yield before borrowing. Waterfall: balance → yield → borrow |
134
+ | `dailySpendLimitUsdc` | number | `0` | Daily spending cap for x402 auto-draw (0 = unlimited). Persisted to cache file. |
135
+ | `healthAlertThreshold` | number | `70` | LTV % threshold for health warnings in `agether_health` and `/health` (liquidation at 80%). Also controls 🟡 vs 🟢 icons. |
136
+
137
+ ### x402 Auto-Funding Waterfall
138
+ When `autoYield` and/or `autoDraw` are enabled, `x402_pay` automatically sources USDC:
139
+ ```
140
+ 1. Check AgentAccount USDC balance
141
+ → If enough → pay directly (no extra tx)
142
+ 2. If autoYield enabled AND supply positions have earned yield:
143
+ → Withdraw yield from supply position (1 tx)
144
+ → Pay with that
145
+ 3. If still not enough AND autoDraw enabled AND collateral deposited in Morpho:
146
+ → Borrow from Morpho credit line (1 tx)
147
+ → Pay with that
148
+ 4. If dailySpendLimitUsdc > 0:
149
+ → Enforce daily cap (resets at midnight UTC, persisted to disk)
150
+ ```
106
151
 
107
152
  ---
108
153
 
@@ -120,18 +165,24 @@ Both `agether_set_agent` and `agether_register` save the agentId to config perma
120
165
 
121
166
  ### User asks to call a paid API (x402)
122
167
  ```
123
- 1. agether_health ← get full picture in one call
124
- 2. Look at balances.agentAccount.usdc:
125
- IF USDC $0.01:
126
- → x402_pay(url)
127
- 3. IF no USDC but positions exist with headroom:
128
- morpho_borrow(amount: "1") borrow $1
129
- x402_pay(url)
130
- 4. IF no USDC AND no positions BUT EOA has WETH/wstETH/cbETH:
131
- morpho_deposit_and_borrow(collateralAmount, token, borrowAmount: "1")
132
- x402_pay(url)
133
- 5. IF nothing at all:
134
- Tell user: "Need collateral (WETH/wstETH/cbETH) on Base. Send to <EOA address>"
168
+ IF autoYield or autoDraw are configured:
169
+ x402_pay(url) ← the waterfall handles everything automatically
170
+ Paste: response data, payment info, any auto-fund actions taken
171
+
172
+ IF no auto-funding configured:
173
+ 1. agether_health get full picture
174
+ 2. Look at balances.agentAccount.usdc:
175
+ IF USDC $0.01:
176
+ x402_pay(url)
177
+ 3. IF no USDC but positions exist with headroom:
178
+ morpho_borrow(amount: "1") ← borrow $1
179
+ x402_pay(url)
180
+ 4. IF no USDC AND no positions BUT EOA has collateral:
181
+ → morpho_deposit_and_borrow(collateralAmount, token, borrowAmount: "1")
182
+ → x402_pay(url)
183
+ 5. IF nothing at all:
184
+ → Tell user: "Need collateral on Base. Send to <EOA address>"
185
+ → Call morpho_markets to show which tokens are accepted
135
186
  ```
136
187
 
137
188
  ### User wants to borrow USDC via Morpho
@@ -145,14 +196,40 @@ Both `agether_set_agent` and `agether_register` save the agentId to config perma
145
196
  5. Show: tx hash, collateral deposited, USDC borrowed, new balances, new LTV
146
197
  ```
147
198
 
199
+ ### User wants to earn yield (supply-side lending)
200
+ ```
201
+ 1. Check USDC availability:
202
+ - agether_health for AgentAccount USDC
203
+ - If no USDC in AgentAccount but exists in EOA → wallet_fund first
204
+ 2. morpho_markets ← show supply APYs to pick best market
205
+ 3. morpho_supply(amount, market?) ← supply USDC to earn yield
206
+ 4. Later: morpho_supply_status ← check earned yield
207
+ 5. To exit: morpho_withdraw_supply("all") ← principal + earned interest
208
+ ```
209
+
148
210
  ### User wants to repay + withdraw
149
211
  ```
150
212
  1. agether_health ← check current debt, collateral, LTV
151
213
  2. morpho_repay(amount or "all") ← repay USDC
152
- 3. morpho_withdraw("all", token) ← get collateral back
214
+ 3. morpho_withdraw("all", token, toEoa: true) ← get collateral back to EOA
153
215
  4. agether_balance ← show final balances
154
216
  ```
155
217
 
218
+ ### User wants to move funds to EOA
219
+ ```
220
+ For tokens (USDC, WETH, etc.):
221
+ → wallet_withdraw_token(token, amount) ← any ERC-20
222
+
223
+ For ETH:
224
+ → wallet_withdraw_eth(amount)
225
+
226
+ For Morpho collateral:
227
+ → morpho_withdraw(amount, token, toEoa: true) ← must use toEoa flag
228
+
229
+ For supply positions (lent USDC):
230
+ → morpho_withdraw_supply(amount, market?, toEoa: true) ← must use toEoa flag
231
+ ```
232
+
156
233
  ### User asks about position health or liquidation risk
157
234
  ```
158
235
  1. agether_health ← returns LTV, risk level, alerts per position
@@ -162,12 +239,11 @@ Both `agether_set_agent` and `agether_register` save the agentId to config perma
162
239
  → Show: morpho_max_borrowable for headroom context
163
240
  ```
164
241
 
165
- ### User wants to optimize rates or compare markets
242
+ ### User wants to compare markets or check rates
166
243
  ```
167
- 1. morpho_rates get all market rates
168
- 2. morpho_yield_estimate(token, amount) estimate yield
169
- 3. Present: which market has lowest borrow APY, highest supply APY
170
- 4. Suggest optimal strategy based on their holdings
244
+ 1. morpho_markets supply APY, borrow APY, utilization, LLTV, liquidity per market
245
+ 2. Present: which market has lowest borrow APY, highest supply APY
246
+ 3. Suggest optimal strategy based on their holdings
171
247
  ```
172
248
 
173
249
  ### User asks to check or refresh credit score
@@ -182,6 +258,13 @@ Both `agether_set_agent` and `agether_register` save the agentId to config perma
182
258
  3. NEVER say "not registered" or "no score yet" without calling the tool first!
183
259
  ```
184
260
 
261
+ ### User wants to sponsor another agent
262
+ ```
263
+ 1. morpho_sponsor(agentId or agentAddress, amount, token)
264
+ → Transfers collateral from your EOA to another agent's AgentAccount
265
+ 2. The target agent can then deposit that collateral into Morpho themselves
266
+ ```
267
+
185
268
  ### Something seems wrong / setup issues
186
269
  ```
187
270
  1. agether_preflight ← run the diagnostic checklist
@@ -197,10 +280,13 @@ Both `agether_set_agent` and `agether_register` save the agentId to config perma
197
280
  ```
198
281
  Explain: You're an AI agent with on-chain credit on Base.
199
282
  You can:
200
- - Deposit WETH/wstETH/cbETH as collateral
201
- - Borrow USDC instantly (125% collateral ratio)
202
- - Pay for APIs using x402 protocol
283
+ - Deposit collateral and borrow USDC instantly (125% collateral ratio)
284
+ - Earn yield by lending USDC to Morpho markets
285
+ - Pay for APIs autonomously using x402 protocol
286
+ - Auto-fund payments from yield or credit (autoYield + autoDraw)
287
+ - Sponsor other agents with collateral
203
288
  - Monitor position health and avoid liquidation
289
+ - Move funds freely between AgentAccount and EOA wallet
204
290
  - All on Base mainnet, all autonomous
205
291
  ```
206
292
 
@@ -231,6 +317,15 @@ Example after register:
231
317
  🔗 Tx: https://basescan.org/tx/0x177fdf...
232
318
  ```
233
319
 
320
+ Example after supply (lending):
321
+ ```
322
+ ✅ Supplied $500 USDC to WETH/USDC market
323
+
324
+ 🔗 Tx: https://basescan.org/tx/0x789abc...
325
+ 📈 Earning ~3.2% APY from borrowers
326
+ 💰 AgentAccount: $50 USDC remaining
327
+ ```
328
+
234
329
  If something **fails**:
235
330
  ```
236
331
  ❌ Borrow failed: ExceedsMaxLtv
@@ -246,7 +341,7 @@ If something **fails**:
246
341
  | Chain | Base mainnet (8453) |
247
342
  | Currency | USDC (6 decimals) |
248
343
  | Max LTV | 80% (= 125% collateral ratio) |
249
- | Collateral tokens | WETH, wstETH, cbETH |
344
+ | Collateral tokens | Dynamically discovered from Morpho markets (call `morpho_markets`) |
250
345
  | x402 cost | ~$0.001 per API call (WeatherXM) |
251
346
  | Gas cost | ~$0.001–$0.01 per tx |
252
347
 
@@ -258,11 +353,12 @@ If something **fails**:
258
353
  |-------|---------|-----------|
259
354
  | `Missing AGETHER_PRIVATE_KEY` | Private key not set in secrets | Run `openclaw secrets configure` → source: env → id: AGETHER_PRIVATE_KEY |
260
355
  | `ExceedsMaxLtv` | Collateral too low for borrow amount | Deposit more collateral or borrow less. LTV must be ≤ 80% |
261
- | `Payment rejected (402)` | No USDC for x402 payment | Borrow USDC first: `morpho_borrow` or `morpho_deposit_and_borrow` |
356
+ | `Payment rejected (402)` | No USDC for x402 payment | Borrow USDC first, or enable autoDraw/autoYield in config |
262
357
  | `No collateral deposited` | Trying to borrow without collateral | `morpho_deposit` first |
263
- | `Insufficient WETH/wstETH/cbETH` | EOA doesn't have enough collateral | Tell user to send collateral to EOA address |
264
- | `Insufficient USDC in AgentAccount` | Not enough USDC for repay | Need to fund Safe account or borrow more |
358
+ | `Insufficient collateral` | EOA doesn't have enough of that token | Tell user to send collateral to EOA address |
359
+ | `Insufficient USDC in AgentAccount` | Not enough USDC for repay/pay | Fund via borrow, yield withdrawal, or `wallet_fund` |
265
360
  | `ExecutionFailed` | Smart contract call reverted | Check inner error, usually LTV or approval issue |
361
+ | `PositionNotActive` | No collateral deposited for this token | Deposit collateral with `morpho_deposit` first |
266
362
 
267
363
  ---
268
364
 
@@ -287,15 +383,26 @@ If no RPC key is set, the plugin falls back to `https://base-rpc.publicnode.com`
287
383
  EOA Wallet (private key from AGETHER_PRIVATE_KEY)
288
384
  ├── Owns ERC-8004 Identity NFT (agentId) ← can own MULTIPLE
289
385
  └── Owns Safe Account (via Safe7579 + ERC-4337, 1 per agentId)
290
- ├── Holds USDC (from borrows)
386
+ ├── Holds USDC (from borrows or fund transfers)
387
+ ├── Holds any ERC-20 tokens (collateral, etc.)
291
388
  ├── ERC8004ValidationModule validates ownership + KYA gate
292
- └── Executes Morpho Blue operations via ERC-4337 UserOps through EntryPoint
389
+ └── Executes operations via ERC-4337 UserOps through EntryPoint
293
390
 
294
391
  Morpho Blue (direct lending, overcollateralized 125%)
295
- ├── supplyCollateral() → collateral into Morpho Blue market
296
- ├── borrow() USDC from Morpho Blue → Safe Account
297
- ├── repay() → USDC from Safe AccountMorpho Blue
298
- └── withdrawCollateral() collateral back to EOA
392
+ ├── BORROWER side:
393
+ ├── supplyCollateral() collateral into Morpho Blue market
394
+ ├── borrow() → USDC from Morpho BlueAgentAccount
395
+ │ ├── repay() USDC from AgentAccount → Morpho Blue
396
+ │ └── withdrawCollateral() → collateral back (to AgentAccount or EOA)
397
+ └── LENDER side:
398
+ ├── supply() → USDC into Morpho Blue market (earn yield)
399
+ ├── withdraw() → USDC + earned interest back (to AgentAccount or EOA)
400
+ └── Yield tracked via Morpho GraphQL API (no database needed)
401
+
402
+ Wallet transfers:
403
+ ├── wallet_fund → USDC from EOA → AgentAccount
404
+ ├── wallet_withdraw_token → any ERC-20 from AgentAccount → EOA
405
+ └── wallet_withdraw_eth → ETH from AgentAccount → EOA
299
406
  ```
300
407
 
301
408
  ---
@@ -308,7 +415,6 @@ Morpho Blue (direct lending, overcollateralized 125%)
308
415
  | Agether8004ValidationModule | `0xde896C58163b5f6cAC5B16C1b0109843f26106F6` |
309
416
  | AgetherHookMultiplexer | `0x4AB6DaD0f7360fa8d8c75889A5c206B65d7CbeDb` |
310
417
  | Agether7579Bootstrap | `0xCc83AA714c05B7141B21a17e80EB21bD09652b27` |
311
- | ValidationRegistry | Not deployed yet |
312
418
  | Agether8004Scorer | `0x56c7D35A976fac67b1993b66b861fCA32f59104F` |
313
419
  | TimelockController | `0xc600e7AAB8a230326C714CE66f356fdf6aC021d8` |
314
420
  | Safe Singleton | `0x41675C099F32341bf84BFc5382aF534df5C7461a` |
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 {
@@ -321,14 +359,50 @@ export default function register(api: any) {
321
359
  api.registerTool({
322
360
  name: "morpho_status",
323
361
  description:
324
- "Show all Morpho Blue positions — collateral deposited, USDC borrowed, debt, for each market.",
362
+ "Show all Morpho Blue positions — collateral, debt, LTV, and Health Factor for each market.",
325
363
  parameters: { type: "object", properties: {}, required: [] },
326
364
  async execute() {
327
365
  try {
328
366
  const cfg = getConfig(api);
329
367
  const client = createClient(cfg);
330
- const result = await client.getStatus();
331
- return ok(JSON.stringify(result, null, 2));
368
+ const [status, maxBorrow] = await Promise.all([
369
+ client.getStatus(),
370
+ client.getMaxBorrowable(),
371
+ ]);
372
+
373
+ const positions = status.positions.map((p: any) => {
374
+ const mm = maxBorrow.byMarket.find((m: any) => m.collateralToken === p.collateralToken);
375
+ const collateralValueUsd = mm ? Number(mm.collateralValue) / 1e6 : 0;
376
+ const debt = parseFloat(p.debt);
377
+ const lltv = 0.80; // Morpho LLTV
378
+ const ltv = collateralValueUsd > 0 ? debt / collateralValueUsd : 0;
379
+ // HF = LLTV / LTV — >1 is safe, <1 is liquidatable
380
+ const hf = ltv > 0 ? lltv / ltv : Infinity;
381
+ const headroom = mm ? Number(mm.maxAdditional) / 1e6 : 0;
382
+
383
+ let risk: string;
384
+ if (debt === 0) risk = "🟢 no debt";
385
+ else if (hf <= 1.0) risk = "🔴 LIQUIDATION ZONE";
386
+ else if (hf <= 1.15) risk = "🟡 WARNING";
387
+ else risk = "🟢 healthy";
388
+
389
+ return {
390
+ collateralToken: p.collateralToken,
391
+ collateral: p.collateral,
392
+ collateralValueUsd: `$${collateralValueUsd.toFixed(2)}`,
393
+ debt: `$${debt.toFixed(2)}`,
394
+ ltv: `${(ltv * 100).toFixed(1)}%`,
395
+ healthFactor: debt === 0 ? "∞" : hf.toFixed(2),
396
+ headroom: `$${headroom.toFixed(2)}`,
397
+ risk,
398
+ };
399
+ });
400
+
401
+ return ok(JSON.stringify({
402
+ agentAccount: status.agentAccount,
403
+ totalDebt: `$${status.totalDebt}`,
404
+ positions,
405
+ }, null, 2));
332
406
  } catch (e) { return fail(e); }
333
407
  },
334
408
  });
@@ -509,21 +583,25 @@ export default function register(api: any) {
509
583
  api.registerTool({
510
584
  name: "morpho_withdraw",
511
585
  description:
512
- "Withdraw collateral from Morpho Blue back to EOA wallet. " +
586
+ "Withdraw collateral from Morpho Blue. " +
587
+ "By default keeps collateral in AgentAccount. Set toEoa=true to send to EOA wallet. " +
513
588
  "Use amount 'all' to withdraw maximum. Must maintain collateral ratio if debt remains.",
514
589
  parameters: {
515
590
  type: "object",
516
591
  properties: {
517
592
  amount: { type: "string", description: "Amount to withdraw (e.g. '0.05' or 'all')" },
518
593
  token: { type: "string", description: "Collateral token" },
594
+ toEoa: { type: "boolean", description: "Send to EOA wallet instead of keeping in AgentAccount (default: false)" },
519
595
  },
520
596
  required: ["amount", "token"],
521
597
  },
522
- async execute(_id: string, params: { amount: string; token: string }) {
598
+ async execute(_id: string, params: { amount: string; token: string; toEoa?: boolean }) {
523
599
  try {
524
600
  const cfg = getConfig(api);
525
601
  const client = createClient(cfg);
526
- const result = await client.withdrawCollateral(params.token, params.amount);
602
+ // Default: keep in AgentAccount. toEoa=true → send to EOA.
603
+ const receiver = params.toEoa ? await client.getSignerAddress() : await client.getAccountAddress();
604
+ const result = await client.withdrawCollateral(params.token, params.amount, undefined, receiver);
527
605
  return ok(JSON.stringify({
528
606
  status: "withdrawn",
529
607
  amount: result.amount,
@@ -634,6 +712,7 @@ export default function register(api: any) {
634
712
  name: "morpho_withdraw_supply",
635
713
  description:
636
714
  "Withdraw supplied USDC (principal + earned interest) from a Morpho Blue lending position. " +
715
+ "By default keeps USDC in AgentAccount. Set toEoa=true to send to EOA wallet. " +
637
716
  "Use amount 'all' to withdraw the full position. Different from morpho_withdraw which withdraws collateral.",
638
717
  parameters: {
639
718
  type: "object",
@@ -644,14 +723,17 @@ export default function register(api: any) {
644
723
 
645
724
  description: "Market collateral token (optional — auto-detects)",
646
725
  },
726
+ toEoa: { type: "boolean", description: "Send to EOA wallet instead of keeping in AgentAccount (default: false)" },
647
727
  },
648
728
  required: ["amount"],
649
729
  },
650
- async execute(_id: string, params: { amount: string; market?: string }) {
730
+ async execute(_id: string, params: { amount: string; market?: string; toEoa?: boolean }) {
651
731
  try {
652
732
  const cfg = getConfig(api);
653
733
  const client = createClient(cfg);
654
- const result = await client.withdrawSupply(params.amount, params.market);
734
+ // Default: keep in AgentAccount. toEoa=true → send to EOA.
735
+ const receiver = params.toEoa ? await client.getSignerAddress() : await client.getAccountAddress();
736
+ const result = await client.withdrawSupply(params.amount, params.market, receiver);
655
737
  return ok(JSON.stringify({
656
738
  status: "withdrawn",
657
739
  amount: params.amount === "all" ? "all (full position)" : `$${params.amount} USDC`,
@@ -664,70 +746,38 @@ export default function register(api: any) {
664
746
  });
665
747
 
666
748
  // ═══════════════════════════════════════════════════════
667
- // TOOL: morpho_pay_from_yield (pay using only earned yield)
749
+ // TOOL: morpho_markets (Morpho GraphQL API no backend)
668
750
  // ═══════════════════════════════════════════════════════
669
751
  api.registerTool({
670
- name: "morpho_pay_from_yield",
752
+ name: "morpho_markets",
671
753
  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.",
754
+ "List available Morpho Blue USDC markets on Base liquidity, supply/borrow APY, utilization, LLTV. " +
755
+ "Optionally filter by collateral token.",
675
756
  parameters: {
676
757
  type: "object",
677
758
  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
-
683
- description: "Which supply position to use (optional — auto-picks highest yield)",
684
- },
759
+ token: { type: "string", description: "Filter by collateral token (optional)" },
685
760
  },
686
- required: ["recipient", "amount"],
761
+ required: [],
687
762
  },
688
- async execute(_id: string, params: { recipient: string; amount: string; market?: string }) {
763
+ async execute(_id: string, params: { token?: string }) {
689
764
  try {
690
765
  const cfg = getConfig(api);
691
766
  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
- });
767
+ const rates = await client.getMarketRates(params.token);
705
768
 
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
- }));
769
+ if (rates.length === 0) return ok("No markets found.");
770
+
771
+ const formatted = rates.map((r: any) => ({
772
+ collateral: r.collateralToken,
773
+ loan: r.loanToken,
774
+ lltv: r.lltv,
775
+ supplyApy: `${(r.supplyApy * 100).toFixed(2)}%`,
776
+ borrowApy: `${(r.borrowApy * 100).toFixed(2)}%`,
777
+ utilization: `${(r.utilization * 100).toFixed(1)}%`,
778
+ totalSupply: `$${r.totalSupplyUsd.toFixed(0)}`,
779
+ totalBorrow: `$${r.totalBorrowUsd.toFixed(0)}`,
780
+ }));
731
781
 
732
782
  return ok(JSON.stringify(formatted, null, 2));
733
783
  } catch (e) { return fail(e); }
@@ -808,24 +858,88 @@ export default function register(api: any) {
808
858
  });
809
859
 
810
860
  // ═══════════════════════════════════════════════════════
811
- // TOOL: x402_pay (with auto-draw support)
861
+ // TOOL: wallet_withdraw_token
862
+ // ═══════════════════════════════════════════════════════
863
+ api.registerTool({
864
+ name: "wallet_withdraw_token",
865
+ description:
866
+ "Withdraw (transfer) a token from AgentAccount to EOA wallet. " +
867
+ "Works with any ERC-20 token (USDC, WETH, wstETH, cbBTC, etc.). " +
868
+ "Use amount 'all' to withdraw the full balance.",
869
+ parameters: {
870
+ type: "object",
871
+ properties: {
872
+ token: { type: "string", description: "Token to withdraw (e.g. 'USDC', 'WETH')" },
873
+ amount: { type: "string", description: "Amount to withdraw (e.g. '100' or 'all')" },
874
+ },
875
+ required: ["token", "amount"],
876
+ },
877
+ async execute(_id: string, params: { token: string; amount: string }) {
878
+ try {
879
+ const cfg = getConfig(api);
880
+ const client = createClient(cfg);
881
+ const result = await client.withdrawToken(params.token, params.amount);
882
+ return ok(JSON.stringify({
883
+ status: "withdrawn",
884
+ token: result.token,
885
+ amount: result.amount,
886
+ destination: result.destination,
887
+ tx: txLink(result.tx),
888
+ }));
889
+ } catch (e) { return fail(e); }
890
+ },
891
+ });
892
+
893
+ // ═══════════════════════════════════════════════════════
894
+ // TOOL: wallet_withdraw_eth
895
+ // ═══════════════════════════════════════════════════════
896
+ api.registerTool({
897
+ name: "wallet_withdraw_eth",
898
+ description:
899
+ "Withdraw ETH from AgentAccount to EOA wallet. " +
900
+ "Use amount 'all' to withdraw the full ETH balance.",
901
+ parameters: {
902
+ type: "object",
903
+ properties: {
904
+ amount: { type: "string", description: "ETH amount to withdraw (e.g. '0.01' or 'all')" },
905
+ },
906
+ required: ["amount"],
907
+ },
908
+ async execute(_id: string, params: { amount: string }) {
909
+ try {
910
+ const cfg = getConfig(api);
911
+ const client = createClient(cfg);
912
+ const result = await client.withdrawEth(params.amount);
913
+ return ok(JSON.stringify({
914
+ status: "withdrawn",
915
+ token: "ETH",
916
+ amount: `${result.amount} ETH`,
917
+ destination: result.destination,
918
+ tx: txLink(result.tx),
919
+ }));
920
+ } catch (e) { return fail(e); }
921
+ },
922
+ });
923
+
924
+ // ═══════════════════════════════════════════════════════
925
+ // TOOL: x402_pay (with auto-yield + auto-draw waterfall)
812
926
  // ═══════════════════════════════════════════════════════
813
927
  api.registerTool({
814
928
  name: "x402_pay",
815
929
  description:
816
930
  "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).",
931
+ "If USDC is insufficient, auto-sources funds: yield first (autoYield), then borrow (autoDraw). " +
932
+ "Both are configured in plugin settings.",
818
933
  parameters: {
819
934
  type: "object",
820
935
  properties: {
821
936
  url: { type: "string", description: "API endpoint URL" },
822
937
  method: { type: "string", enum: ["GET", "POST"], description: "HTTP method (default: GET)" },
823
938
  body: { type: "string", description: "JSON body for POST requests" },
824
- autoDraw: { type: "boolean", description: "Auto-borrow from Morpho if USDC insufficient (default: false)" },
825
939
  },
826
940
  required: ["url"],
827
941
  },
828
- async execute(_id: string, params: { url: string; method?: string; body?: string; autoDraw?: boolean }) {
942
+ async execute(_id: string, params: { url: string; method?: string; body?: string }) {
829
943
  try {
830
944
  const cfg = getConfig(api);
831
945
  const agetherCfg = api.config?.plugins?.entries?.agether?.config || {};
@@ -840,7 +954,8 @@ export default function register(api: any) {
840
954
  console.warn('[agether] x402_pay: getAccountAddress failed, paying from EOA:', e instanceof Error ? e.message : e);
841
955
  }
842
956
 
843
- const autoDrawEnabled = params.autoDraw === true; // default false — opt-in only
957
+ const autoDrawEnabled = agetherCfg.autoDraw === true;
958
+ const autoYieldEnabled = agetherCfg.autoYield === true;
844
959
  const x402 = new X402Client({
845
960
  privateKey: cfg.privateKey,
846
961
  rpcUrl: cfg.rpcUrl,
@@ -848,16 +963,19 @@ export default function register(api: any) {
848
963
  agentId,
849
964
  accountAddress,
850
965
  autoDraw: autoDrawEnabled,
966
+ autoYield: autoYieldEnabled,
851
967
  dailySpendLimitUsdc: agetherCfg.dailySpendLimitUsdc,
852
- yieldLimitedSpending: agetherCfg.yieldLimitedSpending,
853
- autoDrawBuffer: agetherCfg.autoDrawBuffer,
968
+ // Restore spending state from cache so limit survives restarts
969
+ initialSpendingState: loadSpendCache(),
970
+ // Persist every spending update to cache file
971
+ onSpendingUpdate: (state: any) => saveSpendCache(state),
854
972
  // Safe7579 needs validator prefix for ERC-1271 isValidSignature routing
855
973
  validatorModule: "0xde896C58163b5f6cAC5B16C1b0109843f26106F6",
856
974
  });
857
975
 
858
976
  let result;
859
- if (autoDrawEnabled && agentId) {
860
- // Use payWithAutoDraw which handles balance check + borrow
977
+ if ((autoDrawEnabled || autoYieldEnabled) && agentId) {
978
+ // Use payWithAutoDraw which handles waterfall: balance yield borrow
861
979
  const fetchOpts: any = {
862
980
  morphoClient: client,
863
981
  };
@@ -885,98 +1003,22 @@ export default function register(api: any) {
885
1003
  };
886
1004
  }
887
1005
  if (result.autoDrawInfo) {
888
- response.autoDraw = {
889
- ...result.autoDrawInfo,
890
- borrowTx: result.autoDrawInfo.borrowTx ? txLink(result.autoDrawInfo.borrowTx) : undefined,
891
- };
1006
+ const info: any = { reason: result.autoDrawInfo.reason };
1007
+ if (result.autoDrawInfo.yieldWithdrawn) {
1008
+ info.yieldWithdrawn = `$${result.autoDrawInfo.yieldWithdrawn} USDC`;
1009
+ if (result.autoDrawInfo.yieldTx) info.yieldTx = txLink(result.autoDrawInfo.yieldTx);
1010
+ }
1011
+ if (result.autoDrawInfo.borrowed) {
1012
+ info.borrowed = `$${result.autoDrawInfo.borrowed} USDC`;
1013
+ if (result.autoDrawInfo.borrowTx) info.borrowTx = txLink(result.autoDrawInfo.borrowTx);
1014
+ }
1015
+ response.autoFund = info;
892
1016
  }
893
1017
  return ok(JSON.stringify(response, null, 2));
894
1018
  } catch (e) { return fail(e); }
895
1019
  },
896
1020
  });
897
1021
 
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", 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", 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
1022
  // ═══════════════════════════════════════════════════════
981
1023
  // TOOL: morpho_max_borrowable
982
1024
  // ═══════════════════════════════════════════════════════
@@ -1177,13 +1219,32 @@ export default function register(api: any) {
1177
1219
  const client = createClient(cfg);
1178
1220
  const b = await client.getBalances();
1179
1221
 
1222
+ const nz = (v: string) => parseFloat(v) > 0;
1223
+
1180
1224
  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}`;
1225
+ text += `Address: ${b.address}`;
1226
+ if (nz(b.eth)) text += `\nETH: ${parseFloat(b.eth).toFixed(6)}`;
1227
+ if (nz(b.usdc)) text += `\nUSDC: $${b.usdc}`;
1228
+ for (const [sym, val] of Object.entries(b.collateral ?? {})) {
1229
+ if (nz(val)) text += `\n${sym}: ${parseFloat(val).toFixed(6)}`;
1230
+ }
1231
+
1184
1232
  if (b.agentAccount) {
1185
- text += `\n\n🏦 AgentAccount: ${b.agentAccount.address}\nUSDC: $${b.agentAccount.usdc}`;
1233
+ const a = b.agentAccount;
1234
+ const hasAny = nz(a.eth) || nz(a.usdc) ||
1235
+ Object.values(a.collateral ?? {}).some(nz);
1236
+ if (hasAny) {
1237
+ text += `\n\n🏦 AgentAccount: ${a.address}`;
1238
+ if (nz(a.eth)) text += `\nETH: ${parseFloat(a.eth).toFixed(6)}`;
1239
+ if (nz(a.usdc)) text += `\nUSDC: $${a.usdc}`;
1240
+ for (const [sym, val] of Object.entries(a.collateral ?? {})) {
1241
+ if (nz(val)) text += `\n${sym}: ${parseFloat(val).toFixed(6)}`;
1242
+ }
1243
+ } else {
1244
+ text += `\n\n🏦 AgentAccount: ${a.address}\n(empty)`;
1245
+ }
1186
1246
  }
1247
+
1187
1248
  return { text };
1188
1249
  } catch (e: any) {
1189
1250
  return { text: `❌ ${e.message}` };
@@ -1193,17 +1254,28 @@ export default function register(api: any) {
1193
1254
 
1194
1255
  api.registerCommand({
1195
1256
  name: "morpho",
1196
- description: "Show Morpho positions (no AI)",
1257
+ description: "Show Morpho positions with Health Factor (no AI)",
1197
1258
  handler: async () => {
1198
1259
  try {
1199
1260
  const cfg = getConfig(api);
1200
1261
  const client = createClient(cfg);
1201
- const s = await client.getStatus();
1262
+ const [s, maxBorrow] = await Promise.all([
1263
+ client.getStatus(),
1264
+ client.getMaxBorrowable(),
1265
+ ]);
1202
1266
 
1203
1267
  let text = `📊 Morpho\nAccount: ${s.agentAccount}\nTotal debt: $${s.totalDebt}\n`;
1204
1268
  for (const p of s.positions) {
1205
- text += `\n${p.collateralToken}: ${p.collateral} collateral, $${p.debt} debt`;
1269
+ const mm = maxBorrow.byMarket.find((m: any) => m.collateralToken === p.collateralToken);
1270
+ const cv = mm ? Number(mm.collateralValue) / 1e6 : 0;
1271
+ const debt = parseFloat(p.debt);
1272
+ const ltv = cv > 0 ? debt / cv : 0;
1273
+ const hf = ltv > 0 ? 0.80 / ltv : Infinity;
1274
+ const hfStr = debt === 0 ? "∞" : hf.toFixed(2);
1275
+ const icon = debt === 0 ? "🟢" : hf <= 1.0 ? "🔴" : hf <= 1.15 ? "🟡" : "🟢";
1276
+ text += `\n${icon} ${p.collateralToken}: ${p.collateral} col, $${p.debt} debt, HF ${hfStr}`;
1206
1277
  }
1278
+ if (s.positions.length === 0) text += "\nNo active positions.";
1207
1279
  return { text };
1208
1280
  } catch (e: any) {
1209
1281
  return { text: `❌ ${e.message}` };