@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.
- package/openclaw.plugin.json +6 -12
- package/package.json +2 -2
- package/skills/agether/SKILL.md +158 -52
- package/src/index.ts +234 -162
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
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
|
-
"
|
|
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": "
|
|
22
|
+
"description": "Auto-withdraw earned yield from supply positions before borrowing. Waterfall: balance → yield → borrow",
|
|
28
23
|
"default": false
|
|
29
24
|
},
|
|
30
|
-
"
|
|
25
|
+
"dailySpendLimitUsdc": {
|
|
31
26
|
"type": "number",
|
|
32
|
-
"description": "
|
|
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.
|
|
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.
|
|
12
|
+
"@agether/sdk": "^2.6.1",
|
|
13
13
|
"axios": "^1.6.0",
|
|
14
14
|
"ethers": "^6.9.0"
|
|
15
15
|
},
|
package/skills/agether/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
73
|
+
### Identity
|
|
71
74
|
| Tool | What it does |
|
|
72
75
|
|------|-------------|
|
|
73
|
-
| `agether_balance` | Show ETH + USDC + collateral
|
|
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
|
-
| `
|
|
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
|
-
###
|
|
83
|
+
### Wallet (move funds between EOA ↔ AgentAccount)
|
|
80
84
|
| Tool | Params | What it does |
|
|
81
85
|
|------|--------|-------------|
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
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
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
|
|
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
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
242
|
+
### User wants to compare markets or check rates
|
|
166
243
|
```
|
|
167
|
-
1.
|
|
168
|
-
2.
|
|
169
|
-
3.
|
|
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
|
|
201
|
-
-
|
|
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 |
|
|
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
|
|
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
|
|
264
|
-
| `Insufficient USDC in AgentAccount` | Not enough USDC for repay |
|
|
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
|
|
389
|
+
└── Executes operations via ERC-4337 UserOps through EntryPoint
|
|
293
390
|
|
|
294
391
|
Morpho Blue (direct lending, overcollateralized 125%)
|
|
295
|
-
├──
|
|
296
|
-
├──
|
|
297
|
-
├──
|
|
298
|
-
|
|
392
|
+
├── BORROWER side:
|
|
393
|
+
│ ├── supplyCollateral() → collateral into Morpho Blue market
|
|
394
|
+
│ ├── borrow() → USDC from Morpho Blue → AgentAccount
|
|
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
|
|
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
|
|
331
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
749
|
+
// TOOL: morpho_markets (Morpho GraphQL API — no backend)
|
|
668
750
|
// ═══════════════════════════════════════════════════════
|
|
669
751
|
api.registerTool({
|
|
670
|
-
name: "
|
|
752
|
+
name: "morpho_markets",
|
|
671
753
|
description:
|
|
672
|
-
"
|
|
673
|
-
"
|
|
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
|
-
|
|
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: [
|
|
761
|
+
required: [],
|
|
687
762
|
},
|
|
688
|
-
async execute(_id: string, params: {
|
|
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
|
|
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
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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:
|
|
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
|
-
"
|
|
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
|
|
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 =
|
|
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
|
-
|
|
853
|
-
|
|
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
|
|
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
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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}
|
|
1182
|
-
text +=
|
|
1183
|
-
text +=
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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}` };
|