@agether/agether 2.3.0 → 2.4.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,12 +2,18 @@
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.3.0",
5
+ "version": "2.3.1",
6
6
  "skills": ["skills/agether"],
7
7
  "configSchema": {
8
8
  "type": "object",
9
9
  "additionalProperties": false,
10
10
  "properties": {
11
+ "chain": {
12
+ "type": "number",
13
+ "description": "Chain ID: 1 = Ethereum (default), 8453 = Base",
14
+ "default": 1,
15
+ "enum": [1, 8453]
16
+ },
11
17
  "agentId": {
12
18
  "type": "string",
13
19
  "description": "ERC-8004 agent ID (set after registration, auto-saved by tools)"
@@ -27,11 +33,6 @@
27
33
  "description": "Daily USDC spending cap for x402 auto-draw payments (0 = unlimited). Persisted to cache file so it survives restarts.",
28
34
  "default": 0
29
35
  },
30
- "autoDrawBuffer": {
31
- "type": "number",
32
- "description": "Extra USDC buffer when auto-drawing from Morpho (e.g. 0.5 = borrow $0.50 extra)",
33
- "default": 0
34
- },
35
36
  "healthAlertThreshold": {
36
37
  "type": "number",
37
38
  "description": "LTV percentage threshold for health warnings (e.g. 70 = warn at 70% LTV, liquidation at 80%)",
@@ -41,11 +42,11 @@
41
42
  "required": []
42
43
  },
43
44
  "uiHints": {
45
+ "chain": { "label": "Chain", "placeholder": "1 (Ethereum)" },
44
46
  "agentId": { "label": "Agent ID", "placeholder": "17676" },
45
47
  "autoDraw": { "label": "Auto-Draw Credit", "placeholder": "false" },
46
48
  "autoYield": { "label": "Auto-Yield (use yield before borrowing)", "placeholder": "false" },
47
49
  "dailySpendLimitUsdc": { "label": "Daily Spend Limit (USDC)", "placeholder": "0" },
48
- "autoDrawBuffer": { "label": "Auto-Draw Buffer (USDC)", "placeholder": "0" },
49
50
  "healthAlertThreshold": { "label": "Health Alert Threshold (%)", "placeholder": "70" }
50
51
  }
51
52
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agether/agether",
3
- "version": "2.3.0",
4
- "description": "OpenClaw plugin for Agether — onchain credit for AI agents",
3
+ "version": "2.4.0",
4
+ "description": "OpenClaw plugin for Agether — onchain credit for AI agents on Ethereum & Base",
5
5
  "main": "src/index.ts",
6
6
  "openclaw": {
7
7
  "extensions": [
@@ -9,7 +9,7 @@
9
9
  ]
10
10
  },
11
11
  "dependencies": {
12
- "@agether/sdk": "^2.6.0",
12
+ "@agether/sdk": "^2.7.0",
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
@@ -4,15 +4,15 @@
4
4
  * Each tool = MorphoClient / X402Client call → format result with txLink.
5
5
  * Market discovery via Morpho GraphQL API (no backend dependency for markets).
6
6
  *
7
+ * Supported chains: Ethereum (1, default), Base (8453).
8
+ *
7
9
  * v2: Secrets-first config
8
10
  * ─────────────────────────
9
11
  * - privateKey → AGETHER_PRIVATE_KEY env var (set via `openclaw secrets configure` → crypto)
10
12
  * - rpcUrl → auto-resolved from ALCHEMY_API_KEY / ANKR_API_KEY / QUICKNODE_URL env vars,
11
- * falls back to https://base-rpc.publicnode.com
12
- * - backendUrl → hardcoded (no config needed)
13
+ * falls back to publicnode.com (chain-aware)
13
14
  * - agentId → optional plugin config field (auto-saved by tools)
14
- *
15
- * The plugin works with an empty `plugins.entries.agether.config: {}`.
15
+ * - chain → plugin config field (default: 1 = Ethereum)
16
16
  */
17
17
 
18
18
  import axios from "axios";
@@ -21,13 +21,27 @@ import * as path from "path";
21
21
  import {
22
22
  MorphoClient,
23
23
  X402Client,
24
+ getContractAddresses,
25
+ ChainId,
24
26
  } from "@agether/sdk";
25
27
 
26
28
  // ─── Constants ────────────────────────────────────────────
27
29
 
28
- const BACKEND_URL = "http://95.179.189.214:3001";
29
- const DEFAULT_RPC = "https://base-rpc.publicnode.com";
30
- const BASESCAN = "https://basescan.org/tx";
30
+ const BACKEND_URL = "https://api.agether.ai";
31
+
32
+ // Per-chain defaults
33
+ const CHAIN_DEFAULTS: Record<number, { rpc: string; explorer: string; chainName: string }> = {
34
+ [ChainId.Ethereum]: {
35
+ rpc: "https://ethereum-rpc.publicnode.com",
36
+ explorer: "https://etherscan.io/tx",
37
+ chainName: "Ethereum",
38
+ },
39
+ [ChainId.Base]: {
40
+ rpc: "https://base-rpc.publicnode.com",
41
+ explorer: "https://basescan.org/tx",
42
+ chainName: "Base",
43
+ },
44
+ };
31
45
 
32
46
  // ─── Spending Cache (persists dailySpendLimit tracker across restarts) ────
33
47
 
@@ -73,11 +87,13 @@ interface PluginConfig {
73
87
  privateKey: string;
74
88
  agentId?: string;
75
89
  rpcUrl: string;
76
- backendUrl: string;
90
+ chainId: ChainId;
77
91
  }
78
92
 
79
93
  function txLink(hash: string): string {
80
- return hash ? `${BASESCAN}/${hash}` : "";
94
+ if (!hash) return "";
95
+ const explorer = CHAIN_DEFAULTS[activeChainId]?.explorer ?? CHAIN_DEFAULTS[ChainId.Ethereum].explorer;
96
+ return `${explorer}/${hash}`;
81
97
  }
82
98
 
83
99
  function ok(text: string) {
@@ -102,18 +118,25 @@ function fail(err: unknown) {
102
118
  /**
103
119
  * Resolve RPC URL from environment secrets.
104
120
  * Priority: ALCHEMY_API_KEY → ANKR_API_KEY → QUICKNODE_URL → publicnode fallback.
121
+ * Chain-aware: uses the correct endpoint subdomain for the configured chain.
105
122
  */
106
- function resolveRpcUrl(): string {
123
+ function resolveRpcUrl(chainId: ChainId): string {
107
124
  const alchemy = process.env.ALCHEMY_API_KEY;
108
- if (alchemy) return `https://base-mainnet.g.alchemy.com/v2/${alchemy}`;
125
+ if (alchemy) {
126
+ const alchemyChain = chainId === ChainId.Base ? "base-mainnet" : "eth-mainnet";
127
+ return `https://${alchemyChain}.g.alchemy.com/v2/${alchemy}`;
128
+ }
109
129
 
110
130
  const ankr = process.env.ANKR_API_KEY;
111
- if (ankr) return `https://rpc.ankr.com/base/${ankr}`;
131
+ if (ankr) {
132
+ const ankrChain = chainId === ChainId.Base ? "base" : "eth";
133
+ return `https://rpc.ankr.com/${ankrChain}/${ankr}`;
134
+ }
112
135
 
113
136
  const quicknode = process.env.QUICKNODE_URL;
114
137
  if (quicknode) return quicknode;
115
138
 
116
- return DEFAULT_RPC;
139
+ return CHAIN_DEFAULTS[chainId]?.rpc ?? CHAIN_DEFAULTS[ChainId.Ethereum].rpc;
117
140
  }
118
141
 
119
142
  /**
@@ -134,16 +157,19 @@ function resolvePrivateKey(): string {
134
157
 
135
158
  function getConfig(api: any): PluginConfig {
136
159
  const cfg = api.config?.plugins?.entries?.["agether"]?.config ?? {};
160
+ const chainId = (cfg.chain as ChainId) || ChainId.Ethereum;
161
+ activeChainId = chainId; // Update module-level for txLink
137
162
  return {
138
163
  privateKey: resolvePrivateKey(),
139
164
  agentId: cfg.agentId,
140
- rpcUrl: resolveRpcUrl(),
141
- backendUrl: BACKEND_URL,
165
+ rpcUrl: resolveRpcUrl(chainId),
166
+ chainId,
142
167
  };
143
168
  }
144
169
 
145
170
  // Module-level cache
146
171
  let cachedAgentId: string | undefined;
172
+ let activeChainId: ChainId = ChainId.Ethereum;
147
173
 
148
174
  function createClient(cfg: PluginConfig): MorphoClient {
149
175
  const agentId = cachedAgentId || cfg.agentId;
@@ -151,6 +177,7 @@ function createClient(cfg: PluginConfig): MorphoClient {
151
177
  privateKey: cfg.privateKey,
152
178
  rpcUrl: cfg.rpcUrl,
153
179
  agentId,
180
+ chainId: cfg.chainId,
154
181
  });
155
182
  }
156
183
 
@@ -186,7 +213,7 @@ export default function register(api: any) {
186
213
  api.registerTool({
187
214
  name: "agether_balance",
188
215
  description:
189
- "Check ETH and USDC balances for the agent's EOA wallet and AgentAccount on Base.",
216
+ "Check ETH and USDC balances for the agent's EOA wallet and AgentAccount.",
190
217
  parameters: { type: "object", properties: {}, required: [] },
191
218
  async execute() {
192
219
  try {
@@ -204,7 +231,7 @@ export default function register(api: any) {
204
231
  api.registerTool({
205
232
  name: "agether_register",
206
233
  description:
207
- "Register a new ERC-8004 agent identity on Base and create a Safe smart account (via Safe7579). Returns the new agentId.",
234
+ "Register a new ERC-8004 agent identity and create a Safe smart account (via Safe7579). Returns the new agentId.",
208
235
  parameters: {
209
236
  type: "object",
210
237
  properties: {
@@ -359,14 +386,50 @@ export default function register(api: any) {
359
386
  api.registerTool({
360
387
  name: "morpho_status",
361
388
  description:
362
- "Show all Morpho Blue positions — collateral deposited, USDC borrowed, debt, for each market.",
389
+ "Show all Morpho Blue positions — collateral, debt, LTV, and Health Factor for each market.",
363
390
  parameters: { type: "object", properties: {}, required: [] },
364
391
  async execute() {
365
392
  try {
366
393
  const cfg = getConfig(api);
367
394
  const client = createClient(cfg);
368
- const result = await client.getStatus();
369
- return ok(JSON.stringify(result, null, 2));
395
+ const [status, maxBorrow] = await Promise.all([
396
+ client.getStatus(),
397
+ client.getMaxBorrowable(),
398
+ ]);
399
+
400
+ const positions = status.positions.map((p: any) => {
401
+ const mm = maxBorrow.byMarket.find((m: any) => m.collateralToken === p.collateralToken);
402
+ const collateralValueUsd = mm ? Number(mm.collateralValue) / 1e6 : 0;
403
+ const debt = parseFloat(p.debt);
404
+ const lltv = 0.80; // Morpho LLTV
405
+ const ltv = collateralValueUsd > 0 ? debt / collateralValueUsd : 0;
406
+ // HF = LLTV / LTV — >1 is safe, <1 is liquidatable
407
+ const hf = ltv > 0 ? lltv / ltv : Infinity;
408
+ const headroom = mm ? Number(mm.maxAdditional) / 1e6 : 0;
409
+
410
+ let risk: string;
411
+ if (debt === 0) risk = "🟢 no debt";
412
+ else if (hf <= 1.0) risk = "🔴 LIQUIDATION ZONE";
413
+ else if (hf <= 1.15) risk = "🟡 WARNING";
414
+ else risk = "🟢 healthy";
415
+
416
+ return {
417
+ collateralToken: p.collateralToken,
418
+ collateral: p.collateral,
419
+ collateralValueUsd: `$${collateralValueUsd.toFixed(2)}`,
420
+ debt: `$${debt.toFixed(2)}`,
421
+ ltv: `${(ltv * 100).toFixed(1)}%`,
422
+ healthFactor: debt === 0 ? "∞" : hf.toFixed(2),
423
+ headroom: `$${headroom.toFixed(2)}`,
424
+ risk,
425
+ };
426
+ });
427
+
428
+ return ok(JSON.stringify({
429
+ agentAccount: status.agentAccount,
430
+ totalDebt: `$${status.totalDebt}`,
431
+ positions,
432
+ }, null, 2));
370
433
  } catch (e) { return fail(e); }
371
434
  },
372
435
  });
@@ -715,7 +778,7 @@ export default function register(api: any) {
715
778
  api.registerTool({
716
779
  name: "morpho_markets",
717
780
  description:
718
- "List available Morpho Blue USDC markets on Base — liquidity, supply/borrow APY, utilization, LLTV. " +
781
+ "List available Morpho Blue USDC markets — liquidity, supply/borrow APY, utilization, LLTV. " +
719
782
  "Optionally filter by collateral token.",
720
783
  parameters: {
721
784
  type: "object",
@@ -772,22 +835,22 @@ export default function register(api: any) {
772
835
  if (params.refresh) {
773
836
  // x402-gated fresh score — account address required for payment
774
837
  const accountAddress = await client.getAccountAddress();
838
+ const contracts = getContractAddresses(cfg.chainId);
775
839
  const x402 = new X402Client({
776
840
  privateKey: cfg.privateKey,
777
841
  rpcUrl: cfg.rpcUrl,
778
- backendUrl: cfg.backendUrl,
779
842
  agentId,
780
843
  accountAddress,
781
844
  // Safe7579 needs validator prefix for ERC-1271 isValidSignature routing
782
- validatorModule: "0xde896C58163b5f6cAC5B16C1b0109843f26106F6",
845
+ validatorModule: contracts.erc8004ValidationModule,
783
846
  });
784
- const result = await x402.get(`${cfg.backendUrl}/score/${agentId}`);
847
+ const result = await x402.get(`${BACKEND_URL}/score/${agentId}?chain=${cfg.chainId}`);
785
848
  if (!result.success) return fail(result.error || "Score request failed");
786
849
  return ok(JSON.stringify(result.data, null, 2));
787
850
  }
788
851
 
789
852
  // Free: read current onchain score
790
- const { data } = await axios.get(`${cfg.backendUrl}/score/${agentId}/current`);
853
+ const { data } = await axios.get(`${BACKEND_URL}/score/${agentId}/current?chain=${cfg.chainId}`);
791
854
  return ok(JSON.stringify(data, null, 2));
792
855
  } catch (e) { return fail(e); }
793
856
  },
@@ -920,22 +983,21 @@ export default function register(api: any) {
920
983
 
921
984
  const autoDrawEnabled = agetherCfg.autoDraw === true;
922
985
  const autoYieldEnabled = agetherCfg.autoYield === true;
986
+ const contracts = getContractAddresses(cfg.chainId);
923
987
  const x402 = new X402Client({
924
988
  privateKey: cfg.privateKey,
925
989
  rpcUrl: cfg.rpcUrl,
926
- backendUrl: cfg.backendUrl,
927
990
  agentId,
928
991
  accountAddress,
929
992
  autoDraw: autoDrawEnabled,
930
993
  autoYield: autoYieldEnabled,
931
994
  dailySpendLimitUsdc: agetherCfg.dailySpendLimitUsdc,
932
- autoDrawBuffer: agetherCfg.autoDrawBuffer,
933
995
  // Restore spending state from cache so limit survives restarts
934
996
  initialSpendingState: loadSpendCache(),
935
997
  // Persist every spending update to cache file
936
998
  onSpendingUpdate: (state: any) => saveSpendCache(state),
937
999
  // Safe7579 needs validator prefix for ERC-1271 isValidSignature routing
938
- validatorModule: "0xde896C58163b5f6cAC5B16C1b0109843f26106F6",
1000
+ validatorModule: contracts.erc8004ValidationModule,
939
1001
  });
940
1002
 
941
1003
  let result;
@@ -1095,6 +1157,7 @@ export default function register(api: any) {
1095
1157
  totalBorrowingHeadroom: `$${(Number(maxBorrow.total) / 1e6).toFixed(2)}`,
1096
1158
  positions: positionHealth,
1097
1159
  alerts: alerts.length > 0 ? alerts : ["✅ All positions healthy"],
1160
+ chain: CHAIN_DEFAULTS[cfg.chainId]?.chainName ?? `Chain ${cfg.chainId}`,
1098
1161
  rpcSource: process.env.ALCHEMY_API_KEY ? "Alchemy" :
1099
1162
  process.env.ANKR_API_KEY ? "Ankr" :
1100
1163
  process.env.QUICKNODE_URL ? "QuickNode" : "PublicNode (free)",
@@ -1127,10 +1190,14 @@ export default function register(api: any) {
1127
1190
  }
1128
1191
 
1129
1192
  // 2. RPC URL
1130
- const rpcUrl = resolveRpcUrl();
1193
+ const pluginCfg = api.config?.plugins?.entries?.["agether"]?.config ?? {};
1194
+ const prefChainId = (pluginCfg.chain as ChainId) || ChainId.Ethereum;
1195
+ const chainName = CHAIN_DEFAULTS[prefChainId]?.chainName ?? "Unknown";
1196
+ const rpcUrl = resolveRpcUrl(prefChainId);
1131
1197
  const rpcSource = process.env.ALCHEMY_API_KEY ? "Alchemy" :
1132
1198
  process.env.ANKR_API_KEY ? "Ankr" :
1133
1199
  process.env.QUICKNODE_URL ? "QuickNode" : "PublicNode (free fallback)";
1200
+ checks.push({ check: "Chain", status: `✅ ${chainName} (${prefChainId})` });
1134
1201
  checks.push({ check: "RPC endpoint", status: `✅ ${rpcSource}`, detail: rpcUrl.replace(/\/[a-zA-Z0-9_-]{20,}$/, '/***') });
1135
1202
 
1136
1203
  // 3. Agent registration
@@ -1219,17 +1286,28 @@ export default function register(api: any) {
1219
1286
 
1220
1287
  api.registerCommand({
1221
1288
  name: "morpho",
1222
- description: "Show Morpho positions (no AI)",
1289
+ description: "Show Morpho positions with Health Factor (no AI)",
1223
1290
  handler: async () => {
1224
1291
  try {
1225
1292
  const cfg = getConfig(api);
1226
1293
  const client = createClient(cfg);
1227
- const s = await client.getStatus();
1294
+ const [s, maxBorrow] = await Promise.all([
1295
+ client.getStatus(),
1296
+ client.getMaxBorrowable(),
1297
+ ]);
1228
1298
 
1229
1299
  let text = `📊 Morpho\nAccount: ${s.agentAccount}\nTotal debt: $${s.totalDebt}\n`;
1230
1300
  for (const p of s.positions) {
1231
- text += `\n${p.collateralToken}: ${p.collateral} collateral, $${p.debt} debt`;
1301
+ const mm = maxBorrow.byMarket.find((m: any) => m.collateralToken === p.collateralToken);
1302
+ const cv = mm ? Number(mm.collateralValue) / 1e6 : 0;
1303
+ const debt = parseFloat(p.debt);
1304
+ const ltv = cv > 0 ? debt / cv : 0;
1305
+ const hf = ltv > 0 ? 0.80 / ltv : Infinity;
1306
+ const hfStr = debt === 0 ? "∞" : hf.toFixed(2);
1307
+ const icon = debt === 0 ? "🟢" : hf <= 1.0 ? "🔴" : hf <= 1.15 ? "🟡" : "🟢";
1308
+ text += `\n${icon} ${p.collateralToken}: ${p.collateral} col, $${p.debt} debt, HF ${hfStr}`;
1232
1309
  }
1310
+ if (s.positions.length === 0) text += "\nNo active positions.";
1233
1311
  return { text };
1234
1312
  } catch (e: any) {
1235
1313
  return { text: `❌ ${e.message}` };
@@ -1315,8 +1393,9 @@ export default function register(api: any) {
1315
1393
  const balances = await client.getBalances();
1316
1394
  const agentId = balances.agentId ?? "?";
1317
1395
  const safeUsdc = balances.agentAccount?.usdc ?? "0";
1396
+ const chainName = CHAIN_DEFAULTS[cfg.chainId]?.chainName ?? `Chain ${cfg.chainId}`;
1318
1397
  api.logger?.info?.(
1319
- `[agether] Session start — Agent #${agentId}, EOA: ${balances.eth} ETH / $${balances.usdc} USDC, Safe: $${safeUsdc} USDC`,
1398
+ `[agether] Session start — ${chainName}, Agent #${agentId}, EOA: ${balances.eth} ETH / $${balances.usdc} USDC, Safe: $${safeUsdc} USDC`,
1320
1399
  );
1321
1400
  } catch {
1322
1401
  // Silently fail — hook should not block conversations