@agether/agether 1.0.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.
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # @agether/openclaw-plugin
2
+
3
+ OpenClaw plugin for **Agether** — on-chain credit protocol for AI agents on Base.
4
+
5
+ ## Prerequisites
6
+
7
+ - [OpenClaw](https://docs.openclaw.ai) installed and running (`npm i -g openclaw`)
8
+ - A Telegram bot connected (or any other OpenClaw channel)
9
+ - A wallet private key with some ETH on **Base** (chain 8453) for gas
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ openclaw plugins install @agether/openclaw-plugin
15
+ ```
16
+
17
+ ## Configure
18
+
19
+ Edit `~/.openclaw/openclaw.json` and add the `agether` entry under `plugins.entries`:
20
+
21
+ ```jsonc
22
+ // ~/.openclaw/openclaw.json
23
+ {
24
+ "plugins": {
25
+ "entries": {
26
+ "agether": {
27
+ "enabled": true,
28
+ "config": {
29
+ "privateKey": "0x...", // required — your wallet PK
30
+ "rpcUrl": "https://base-rpc.publicnode.com", // optional (default)
31
+ "backendUrl": "http://95.179.189.214:3001", // optional (default)
32
+ "autoDraw": false // optional — auto-borrow for x402 payments
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ Then restart the gateway:
41
+
42
+ ```bash
43
+ openclaw gateway --force
44
+ ```
45
+
46
+ > ⚠️ **Never share your private key.** The `privateKey` field is marked as sensitive in the plugin manifest.
47
+
48
+ ## Verify
49
+
50
+ ```bash
51
+ openclaw plugins list
52
+ ```
53
+
54
+ You should see `Agether Credit` with status `loaded`.
55
+
56
+ ## Tools
57
+
58
+ Once installed, the following tools are available to your AI agent:
59
+
60
+ | Tool | Description |
61
+ |---|---|
62
+ | `agether_balance` | Check ETH & USDC balances (EOA + AgentAccount) |
63
+ | `agether_register` | Mint ERC-8004 identity & create AgentAccount |
64
+ | `agether_score` | Get Bayesian credit score with 5-factor breakdown |
65
+ | `morpho_status` | Show all Morpho credit positions |
66
+ | `morpho_deposit` | Deposit collateral (WETH, wstETH, cbETH) |
67
+ | `morpho_deposit_and_borrow` | Deposit collateral + borrow USDC in one step |
68
+ | `morpho_sponsor` | Human deposits collateral for an agent (by ID or address) |
69
+ | `morpho_borrow` | Borrow USDC against deposited collateral |
70
+ | `morpho_repay` | Repay USDC debt |
71
+ | `morpho_withdraw` | Withdraw collateral back to EOA |
72
+ | `morpho_compare` | Compare collateral options with current prices |
73
+ | `morpho_markets` | List supported Morpho Blue markets |
74
+ | `wallet_fund` | Transfer USDC from EOA into AgentAccount |
75
+ | `x402_pay` | Make paid API calls via x402 protocol (supports auto-draw) |
76
+
77
+ ## Commands
78
+
79
+ Quick commands that run without AI:
80
+
81
+ - `/balance` — wallet balances
82
+ - `/morpho` — Morpho positions overview
83
+
84
+ ## Quick Start
85
+
86
+ 1. Install the plugin: `openclaw plugins install @agether/openclaw-plugin`
87
+ 2. Add config to `~/.openclaw/openclaw.json` (see above)
88
+ 3. Restart: `openclaw gateway --force`
89
+ 4. Open Telegram and send to your bot: *"What is my balance?"*
90
+ 5. Ask: *"Register me as an agent on Agether"*
91
+ 6. Fund your wallet with ETH (for gas) and WETH/wstETH/cbETH (for collateral)
92
+ 7. Ask: *"Deposit 0.05 WETH and borrow $50 USDC"*
93
+
94
+ ## Getting Collateral on Base
95
+
96
+ - **ETH**: Bridge from Ethereum via [Base Bridge](https://bridge.base.org) or buy on any exchange that supports Base
97
+ - **WETH**: Wrap ETH on [Uniswap](https://app.uniswap.org) (Base) or use `weth.deposit()` directly
98
+ - **wstETH / cbETH**: Swap on [Uniswap](https://app.uniswap.org) or bridge from Ethereum
99
+
100
+ ## Config Options
101
+
102
+ | Option | Required | Default | Description |
103
+ |---|---|---|---|
104
+ | `privateKey` | ✅ | — | Wallet private key for signing transactions |
105
+ | `rpcUrl` | — | `https://base-rpc.publicnode.com` | Base RPC endpoint |
106
+ | `backendUrl` | — | `http://95.179.189.214:3001` | Agether backend API |
107
+ | `autoDraw` | — | `false` | Auto-borrow from credit line when USDC is insufficient for x402 payments |
108
+
109
+ ## Links
110
+
111
+ - [Agether SDK](https://www.npmjs.com/package/@agether/sdk) — CLI + TypeScript clients
112
+ - [ERC-8004 Standard](https://eips.ethereum.org/EIPS/eip-8004) — Agent Identity
113
+
114
+ ## License
115
+
116
+ MIT
@@ -0,0 +1,44 @@
1
+ {
2
+ "id": "agether",
3
+ "name": "Agether Credit",
4
+ "description": "On-chain credit protocol for AI agents — Morpho-backed overcollateralized credit, ERC-8004 identity, x402 payments",
5
+ "version": "1.2.0",
6
+ "skills": ["skills/agether"],
7
+ "configSchema": {
8
+ "type": "object",
9
+ "additionalProperties": false,
10
+ "properties": {
11
+ "privateKey": {
12
+ "type": "string",
13
+ "description": "Wallet private key for signing transactions"
14
+ },
15
+ "agentId": {
16
+ "type": "string",
17
+ "description": "ERC-8004 agent ID (set after registration)"
18
+ },
19
+ "rpcUrl": {
20
+ "type": "string",
21
+ "description": "Base RPC endpoint",
22
+ "default": "https://base-rpc.publicnode.com"
23
+ },
24
+ "backendUrl": {
25
+ "type": "string",
26
+ "description": "Agether backend URL",
27
+ "default": "http://95.179.189.214:3001"
28
+ },
29
+ "autoDraw": {
30
+ "type": "boolean",
31
+ "description": "Auto-borrow from Morpho credit line when USDC balance is low for x402 payments",
32
+ "default": false
33
+ }
34
+ },
35
+ "required": ["privateKey"]
36
+ },
37
+ "uiHints": {
38
+ "privateKey": { "label": "Private Key", "sensitive": true },
39
+ "agentId": { "label": "Agent ID", "placeholder": "17676" },
40
+ "rpcUrl": { "label": "RPC URL", "placeholder": "https://base-rpc.publicnode.com" },
41
+ "backendUrl": { "label": "Backend URL", "placeholder": "http://95.179.189.214:3001" },
42
+ "autoDraw": { "label": "Auto-Draw Credit", "placeholder": "false" }
43
+ }
44
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@agether/agether",
3
+ "version": "1.0.0",
4
+ "description": "OpenClaw plugin for Agether — on-chain credit for AI agents",
5
+ "main": "src/index.ts",
6
+ "openclaw": {
7
+ "extensions": [
8
+ "./src/index.ts"
9
+ ]
10
+ },
11
+ "dependencies": {
12
+ "@agether/sdk": "^1.6.5",
13
+ "axios": "^1.6.0",
14
+ "ethers": "^6.9.0"
15
+ },
16
+ "peerDependencies": {
17
+ "@sinclair/typebox": "*"
18
+ },
19
+ "keywords": [
20
+ "openclaw",
21
+ "agether",
22
+ "ai-agents",
23
+ "credit",
24
+ "defi"
25
+ ],
26
+ "license": "MIT",
27
+ "devDependencies": {
28
+ "typescript": "^5.9.3"
29
+ }
30
+ }
@@ -0,0 +1,241 @@
1
+ ---
2
+ name: agether
3
+ description: On-chain credit protocol for AI agents on Base. Morpho credit, x402 payments, ERC-8004 identity.
4
+ ---
5
+
6
+ # Agether — On-Chain Credit for AI Agents
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.
9
+
10
+ ---
11
+
12
+ ## 🚨 CRITICAL: THE USER CANNOT SEE TOOL RESULTS
13
+
14
+ Tool results are invisible to the user. They ONLY see your text messages.
15
+ After EVERY tool call, you MUST paste the important fields from the result into your reply:
16
+ - tx links (always full URL)
17
+ - agentId
18
+ - balances
19
+ - amounts
20
+ - errors
21
+
22
+ If you don't paste it, the user sees NOTHING. An empty colon ":" with no data after it is a bug.
23
+
24
+ ## ⚡ BEHAVIOR RULES
25
+
26
+ 1. **PASTE tool results into your reply.** Every tool returns JSON. Extract the key fields and write them in your message. The user cannot see tool output directly.
27
+ 2. **ALWAYS paste tx links.** Tools return `tx` field like `https://basescan.org/tx/0x...`. Copy it verbatim into your reply.
28
+ 3. **After on-chain actions, call `agether_balance` and paste the balances.**
29
+ 4. **On first message, call `agether_balance`.** If `agentId` is `"?"`, you need to set it up — see AGENT ID RESOLUTION below. If not registered at all, call `agether_register`.
30
+ 5. **Be proactive** — if the user asks to call a paid API, do the full flow without asking.
31
+ 6. **Never ask for private keys** — already configured.
32
+ 7. **Max LTV is 80%** (125% collateral ratio). To borrow $X, you need $X × 1.25 in collateral value.
33
+ 8. **When user says "register" → ALWAYS call `agether_register`.** A wallet CAN have multiple ERC-8004 identities. The tool handles everything. Never refuse to register because the wallet "already has an identity". Just call the tool.
34
+ 9. **Never guess about blockchain state.** If you're unsure, call the tool. Don't tell the user something is impossible — try it first.
35
+ 10. **Your EOA wallet IS the user's wallet.** Same private key, same address. If the user says "I have WETH", call `agether_balance` — you'll see it in the `collateral` field. You already have access to those tokens.
36
+ 11. **`morpho_deposit` and `morpho_deposit_and_borrow` transfer collateral from your EOA automatically.** You do NOT need anyone to "send you" tokens. If your EOA has WETH, just call the deposit tool directly. The SDK handles EOA→AgentAccount→Morpho in one flow.
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
+
39
+ ---
40
+
41
+ ## 🆔 AGENT ID RESOLUTION (CRITICAL)
42
+
43
+ If `agether_balance` returns `agentId: "?"`, ALL Morpho tools will fail. You MUST resolve this first:
44
+
45
+ ```
46
+ 1. Check your memory for a previously registered agentId
47
+ 2. IF you find an agentId in memory:
48
+ → Ask user: "I remember agent ID <X> from before. Should I use it, or register a new one?"
49
+ → IF user says yes → call `agether_set_agent(agentId: "<X>")`
50
+ → IF user says no → call `agether_register(name: "<ask name>")`
51
+ 3. IF no agentId in memory:
52
+ → call `agether_register(name: "<ask name>")`
53
+ 4. After either path → call `agether_balance` to confirm agentId is set
54
+ ```
55
+
56
+ Both `agether_set_agent` and `agether_register` save the agentId to config permanently (survives restarts).
57
+
58
+ ---
59
+
60
+ ## 🛠 YOUR TOOLS
61
+
62
+ ### Identity & Wallet
63
+ | Tool | What it does |
64
+ |------|-------------|
65
+ | `agether_balance` | Show ETH + USDC + collateral (WETH/wstETH/cbETH) balances for both EOA wallet and AgentAccount |
66
+ | `agether_register` | Register on-chain: mints ERC-8004 identity NFT + creates AgentAccount. Auto-saves agentId to config. |
67
+ | `agether_set_agent` | Set a known agentId (e.g. from memory) and save to config. Use when agentId is "?" but you remember it. |
68
+ | `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.** |
69
+ | `wallet_fund` | Transfer USDC from EOA wallet → AgentAccount |
70
+
71
+ ### Morpho Credit (Overcollateralized — instant, no approval)
72
+ | Tool | Params | What it does |
73
+ |------|--------|-------------|
74
+ | `morpho_markets` | none | List supported Morpho markets and parameters |
75
+ | `morpho_status` | none | Show all Morpho positions (collateral + debt) |
76
+ | `morpho_deposit` | `amount`, `token` | Deposit collateral from EOA → Morpho (no borrow) |
77
+ | `morpho_deposit_and_borrow` | `collateralAmount`, `token`, `borrowAmount` | Deposit + borrow in one flow. **Best for first-time setup** |
78
+ | `morpho_borrow` | `amount` (USD) | Borrow USDC against existing collateral → lands in AgentAccount |
79
+ | `morpho_repay` | `amount` (USD) | Repay USDC debt from AgentAccount → Morpho |
80
+ | `morpho_withdraw` | `amount` (or "all"), `token` | Withdraw collateral back to EOA wallet |
81
+ | `morpho_sponsor` | `agentId`/`agentAddress`, `amount`, `token`, `borrow?` | Deposit collateral for another agent |
82
+
83
+ ### x402 Payments
84
+ | Tool | Params | What it does |
85
+ |------|--------|-------------|
86
+ | `x402_pay` | `url`, `method?`, `body?`, `autoDraw?` | Make a paid API call. Pays with USDC via EIP-3009 signature |
87
+
88
+ ### Slash Commands (user types these)
89
+ | Command | What it does |
90
+ |---------|-------------|
91
+ | `/balance` | Quick balance check |
92
+ | `/morpho` | Quick Morpho position summary |
93
+
94
+ ---
95
+
96
+ ## 📋 DECISION TREES
97
+
98
+ ### User asks to call a paid API (x402)
99
+ ```
100
+ 1. agether_balance → check USDC in AgentAccount AND collateral on EOA
101
+ 2. IF AgentAccount has USDC ≥ $0.01:
102
+ → x402_pay(url)
103
+ 3. IF no USDC but has collateral deposited in Morpho:
104
+ → morpho_borrow(amount: "1") ← borrow $1
105
+ → x402_pay(url)
106
+ 4. IF no USDC AND no Morpho collateral BUT EOA has WETH/wstETH/cbETH:
107
+ → morpho_deposit_and_borrow(collateralAmount, token, borrowAmount: "1")
108
+ → x402_pay(url)
109
+ 5. IF no USDC AND no collateral anywhere:
110
+ → Tell user: "Need collateral (WETH/wstETH/cbETH) on Base to get credit. Send to <EOA address>"
111
+ ```
112
+
113
+ ### User wants to borrow USDC via Morpho
114
+ ```
115
+ 1. agether_balance ← check collateral on EOA
116
+ 2. morpho_deposit_and_borrow(collateralAmount, token, borrowAmount)
117
+ 3. agether_balance ← show the result with balance changes
118
+ 4. Show: tx hash, collateral deposited, USDC borrowed, new balances
119
+ ```
120
+
121
+ ### User wants to repay + withdraw
122
+ ```
123
+ 1. morpho_status ← check current debt and collateral
124
+ 2. morpho_repay(amount) ← repay USDC
125
+ 3. morpho_withdraw("all", token) ← get collateral back
126
+ 4. agether_balance ← show final balances
127
+ ```
128
+
129
+ ### User asks to check or refresh credit score
130
+ ```
131
+ 1. IF user says "refresh" / "update" / "get new score":
132
+ → agether_score(refresh: true) ← pays via x402, computes fresh score, writes on-chain
133
+ → Paste the score and tx link
134
+ 2. IF user says "check" / "what's my score":
135
+ → agether_score(refresh: false) ← free, reads current on-chain score
136
+ → Paste the score
137
+ 3. NEVER say "not registered" or "no score yet" without calling the tool first!
138
+ ```
139
+
140
+ ### User asks "what can I do?" or "what is agether?"
141
+ ```
142
+ Explain: You're an AI agent with on-chain credit on Base.
143
+ You can:
144
+ - Deposit WETH/wstETH/cbETH as collateral
145
+ - Borrow USDC instantly (125% collateral ratio)
146
+ - Pay for APIs using x402 protocol
147
+ - All on Base mainnet, all autonomous
148
+ ```
149
+
150
+ ---
151
+
152
+ ## 📊 RESPONSE FORMAT (MANDATORY)
153
+
154
+ You MUST format every on-chain action reply like this. Do NOT skip any field.
155
+
156
+ Example after repay:
157
+ ```
158
+ ✅ Repaid $1.50 USDC
159
+
160
+ � Tx: https://basescan.org/tx/0xabc123def456...
161
+ 💰 Balances:
162
+ EOA: 0.05 ETH, 0 USDC
163
+ AgentAccount: 0 ETH, $3.50 USDC
164
+ 📊 Debt remaining: $0.00
165
+ ```
166
+
167
+ Example after register:
168
+ ```
169
+ ✅ Registered!
170
+
171
+ 🆔 Agent ID: 18060
172
+ 📍 Wallet: 0x930C...
173
+ 📍 AgentAccount: 0x05d9...
174
+ 🔗 Tx: https://basescan.org/tx/0x177fdf...
175
+ ```
176
+
177
+ If something **fails**:
178
+ ```
179
+ ❌ Borrow failed: ExceedsMaxLtv
180
+ 💡 Deposit more collateral. Need $X × 1.25 in value.
181
+ ```
182
+
183
+ ---
184
+
185
+ ## 🔢 KEY NUMBERS
186
+
187
+ | Parameter | Value |
188
+ |-----------|-------|
189
+ | Chain | Base mainnet (8453) |
190
+ | Currency | USDC (6 decimals) |
191
+ | Max LTV | 80% (= 125% collateral ratio) |
192
+ | Collateral tokens | WETH, wstETH, cbETH |
193
+ | x402 cost | ~$0.001 per API call (WeatherXM) |
194
+ | Gas cost | ~$0.001–$0.01 per tx |
195
+
196
+ ---
197
+
198
+ ## ⚠️ COMMON ERRORS
199
+
200
+ | Error | Meaning | What to do |
201
+ |-------|---------|-----------|
202
+ | `ExceedsMaxLtv` | Collateral too low for borrow amount | Deposit more collateral or borrow less. LTV must be ≤ 80% |
203
+ | `Payment rejected (402)` | No USDC for x402 payment | Borrow USDC first: `morpho_borrow` or `morpho_deposit_and_borrow` |
204
+ | `No collateral deposited` | Trying to borrow without collateral | `morpho_deposit` first |
205
+ | `Insufficient WETH/wstETH/cbETH` | EOA doesn't have enough collateral | Tell user to send collateral to EOA address |
206
+ | `Insufficient USDC in AgentAccount` | Not enough USDC for repay | Need to fund AgentAccount or borrow more |
207
+ | `ExecutionFailed` | Smart contract call reverted | Check inner error, usually LTV or approval issue |
208
+
209
+ ---
210
+
211
+ ## 🏗 ARCHITECTURE (for context)
212
+
213
+ ```
214
+ EOA Wallet (private key)
215
+ ├── Owns ERC-8004 Identity NFT (agentId) ← can own MULTIPLE
216
+ └── Owns AgentAccount (smart wallet, 1 per agentId)
217
+ ├── Holds USDC (from borrows)
218
+ └── Executes Morpho Blue operations via executeBatch()
219
+
220
+ Morpho Blue (direct lending, overcollateralized 125%)
221
+ ├── supplyCollateral() → collateral into Morpho Blue market
222
+ ├── borrow() → USDC from Morpho Blue → AgentAccount
223
+ ├── repay() → USDC from AgentAccount → Morpho Blue
224
+ └── withdrawCollateral() → collateral back to EOA
225
+ ```
226
+
227
+ ---
228
+
229
+ ## 📍 CONTRACT ADDRESSES (Base Mainnet)
230
+
231
+ | Contract | Address |
232
+ |----------|---------|
233
+ | AccountFactory | `0xb5b09213d0718f3FD56F1b111C8A83FDFcBdd5d8` |
234
+ | ValidationRegistry | `0x867F4F6f749Dc422aeFEcF4273cD22015d2CBCc2` |
235
+ | AgentReputation | `0x352883c396bc7e88891a7D343ba550A7638256c0` |
236
+ | ERC-8004 Identity | `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` |
237
+ | Morpho Blue | `0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb` |
238
+ | USDC | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
239
+ | WETH | `0x4200000000000000000000000000000000000006` |
240
+ | wstETH | `0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452` |
241
+ | cbETH | `0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22` |
package/src/index.ts ADDED
@@ -0,0 +1,744 @@
1
+ /**
2
+ * Agether OpenClaw Plugin — thin wrapper over @agether/sdk
3
+ *
4
+ * Each tool = MorphoClient / X402Client call → format result with txLink.
5
+ * Market discovery via Morpho GraphQL API (no backend dependency for markets).
6
+ */
7
+
8
+ import axios from "axios";
9
+ import * as fs from "fs";
10
+ import * as path from "path";
11
+ import {
12
+ MorphoClient,
13
+ X402Client,
14
+ } from "@agether/sdk";
15
+
16
+ // ─── Helpers ──────────────────────────────────────────────
17
+
18
+ interface PluginConfig {
19
+ privateKey: string;
20
+ agentId?: string;
21
+ rpcUrl: string;
22
+ backendUrl: string;
23
+ }
24
+
25
+ const BASESCAN = "https://basescan.org/tx";
26
+ function txLink(hash: string): string {
27
+ return hash ? `${BASESCAN}/${hash}` : "";
28
+ }
29
+
30
+ function ok(text: string) {
31
+ return { content: [{ type: "text" as const, text }] };
32
+ }
33
+
34
+ function fail(err: unknown) {
35
+ let msg = err instanceof Error ? err.message : String(err);
36
+ if (msg.includes("0xda04aecc")) {
37
+ msg = "ExceedsMaxLtv — collateral too low for this borrow amount";
38
+ } else if (msg.includes("0xfeca99cb")) {
39
+ msg = "ExecutionFailed — the inner contract call reverted. Likely ExceedsMaxLtv or insufficient collateral.";
40
+ } else if (msg.includes("0xa920ef9f")) {
41
+ msg = "PositionNotActive — no collateral deposited for this token";
42
+ }
43
+ if (msg.length > 300) msg = msg.slice(0, 250) + "…";
44
+ return { content: [{ type: "text" as const, text: `❌ ${msg}` }], isError: true };
45
+ }
46
+
47
+ function getConfig(api: any): PluginConfig {
48
+ const cfg = api.config?.plugins?.entries?.["agether"]?.config;
49
+ if (!cfg?.privateKey) {
50
+ throw new Error("Agether plugin not configured: missing privateKey in plugins.entries.agether.config");
51
+ }
52
+ return {
53
+ privateKey: cfg.privateKey,
54
+ agentId: cfg.agentId,
55
+ rpcUrl: cfg.rpcUrl || "https://base-rpc.publicnode.com",
56
+ backendUrl: cfg.backendUrl || "http://95.179.189.214:3001",
57
+ };
58
+ }
59
+
60
+ // Module-level cache
61
+ let cachedAgentId: string | undefined;
62
+
63
+ function createClient(cfg: PluginConfig): MorphoClient {
64
+ const agentId = cachedAgentId || cfg.agentId;
65
+ return new MorphoClient({
66
+ privateKey: cfg.privateKey,
67
+ rpcUrl: cfg.rpcUrl,
68
+ agentId,
69
+ });
70
+ }
71
+
72
+ /** Persist agentId to openclaw.json so it survives restarts. */
73
+ function persistAgentId(agentId: string): string {
74
+ cachedAgentId = agentId;
75
+ try {
76
+ const home = process.env.HOME || process.env.USERPROFILE || "/root";
77
+ const cfgPath = path.join(home, ".openclaw", "openclaw.json");
78
+ const raw = fs.readFileSync(cfgPath, "utf-8");
79
+ const cfg = JSON.parse(raw);
80
+ if (cfg?.plugins?.entries?.agether?.config) {
81
+ cfg.plugins.entries.agether.config.agentId = agentId;
82
+ fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
83
+ return "saved";
84
+ }
85
+ return "config structure not found";
86
+ } catch (e) {
87
+ return `write failed: ${e instanceof Error ? e.message : String(e)}`;
88
+ }
89
+ }
90
+
91
+ // ─── Plugin Entry ─────────────────────────────────────────
92
+
93
+ export default function register(api: any) {
94
+ // ═══════════════════════════════════════════════════════
95
+ // TOOL: agether_balance
96
+ // ═══════════════════════════════════════════════════════
97
+ api.registerTool({
98
+ name: "agether_balance",
99
+ description:
100
+ "Check ETH and USDC balances for the agent's EOA wallet and AgentAccount on Base.",
101
+ parameters: { type: "object", properties: {}, required: [] },
102
+ async execute() {
103
+ try {
104
+ const cfg = getConfig(api);
105
+ const client = createClient(cfg);
106
+ const result = await client.getBalances();
107
+ return ok(JSON.stringify(result, null, 2));
108
+ } catch (e) { return fail(e); }
109
+ },
110
+ });
111
+
112
+ // ═══════════════════════════════════════════════════════
113
+ // TOOL: agether_register
114
+ // ═══════════════════════════════════════════════════════
115
+ api.registerTool({
116
+ name: "agether_register",
117
+ description:
118
+ "Register a new ERC-8004 agent identity on Base and create an AgentAccount. Returns the new agentId.",
119
+ parameters: {
120
+ type: "object",
121
+ properties: {
122
+ name: { type: "string", description: "Agent display name" },
123
+ },
124
+ required: ["name"],
125
+ },
126
+ async execute(_id: string, params: { name: string }) {
127
+ try {
128
+ const cfg = getConfig(api);
129
+ const client = createClient(cfg);
130
+ const result = await client.register(params.name);
131
+ const persistStatus = persistAgentId(result.agentId);
132
+ return ok(JSON.stringify({
133
+ status: result.alreadyRegistered ? "already_registered" : "registered",
134
+ agentId: result.agentId,
135
+ address: result.address,
136
+ agentAccount: result.agentAccount,
137
+ tx: result.tx ? txLink(result.tx) : undefined,
138
+ configSaved: persistStatus,
139
+ }));
140
+ } catch (e) { return fail(e); }
141
+ },
142
+ });
143
+
144
+ // ═══════════════════════════════════════════════════════
145
+ // TOOL: agether_set_agent
146
+ // ═══════════════════════════════════════════════════════
147
+ api.registerTool({
148
+ name: "agether_set_agent",
149
+ description:
150
+ "Set the active agentId and save it to config. Use this when you know the agentId " +
151
+ "(e.g. from memory) but the tools return agentId '?'. This persists across restarts.",
152
+ parameters: {
153
+ type: "object",
154
+ properties: {
155
+ agentId: { type: "string", description: "The ERC-8004 agent ID (e.g. '18425')" },
156
+ },
157
+ required: ["agentId"],
158
+ },
159
+ async execute(_id: string, params: { agentId: string }) {
160
+ try {
161
+ const cfg = getConfig(api);
162
+ // Verify on-chain that this agentId exists and belongs to this wallet
163
+ const client = new MorphoClient({
164
+ privateKey: cfg.privateKey,
165
+ rpcUrl: cfg.rpcUrl,
166
+ agentId: params.agentId,
167
+ });
168
+ const acctAddr = await client.getAccountAddress();
169
+ const persistStatus = persistAgentId(params.agentId);
170
+ return ok(JSON.stringify({
171
+ status: "agent_set",
172
+ agentId: params.agentId,
173
+ agentAccount: acctAddr,
174
+ configSaved: persistStatus,
175
+ }));
176
+ } catch (e) { return fail(e); }
177
+ },
178
+ });
179
+
180
+ // ═══════════════════════════════════════════════════════
181
+ // TOOL: morpho_status
182
+ // ═══════════════════════════════════════════════════════
183
+ api.registerTool({
184
+ name: "morpho_status",
185
+ description:
186
+ "Show all Morpho Blue positions — collateral deposited, USDC borrowed, debt, for each market.",
187
+ parameters: { type: "object", properties: {}, required: [] },
188
+ async execute() {
189
+ try {
190
+ const cfg = getConfig(api);
191
+ const client = createClient(cfg);
192
+ const result = await client.getStatus();
193
+ return ok(JSON.stringify(result, null, 2));
194
+ } catch (e) { return fail(e); }
195
+ },
196
+ });
197
+
198
+ // ═══════════════════════════════════════════════════════
199
+ // TOOL: morpho_deposit
200
+ // ═══════════════════════════════════════════════════════
201
+ api.registerTool({
202
+ name: "morpho_deposit",
203
+ description:
204
+ "Deposit collateral (WETH, wstETH, or cbETH) into Morpho Blue via AgentAccount. Enables borrowing USDC.",
205
+ parameters: {
206
+ type: "object",
207
+ properties: {
208
+ amount: { type: "string", description: "Amount of collateral tokens (e.g. '0.05')" },
209
+ token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
210
+ },
211
+ required: ["amount", "token"],
212
+ },
213
+ async execute(_id: string, params: { amount: string; token: string }) {
214
+ try {
215
+ const cfg = getConfig(api);
216
+ const client = createClient(cfg);
217
+ const result = await client.supplyCollateral(params.token, params.amount);
218
+ return ok(JSON.stringify({
219
+ status: "deposited",
220
+ amount: `${params.amount} ${params.token}`,
221
+ agentAccount: result.agentAccount,
222
+ tx: txLink(result.tx),
223
+ }));
224
+ } catch (e) { return fail(e); }
225
+ },
226
+ });
227
+
228
+ // ═══════════════════════════════════════════════════════
229
+ // TOOL: morpho_deposit_and_borrow
230
+ // ═══════════════════════════════════════════════════════
231
+ api.registerTool({
232
+ name: "morpho_deposit_and_borrow",
233
+ description:
234
+ "Deposit collateral AND borrow USDC in one batched transaction via AgentAccount.executeBatch. " +
235
+ "Deposits collateral from EOA into Morpho, then borrows USDC. All in one tx.",
236
+ parameters: {
237
+ type: "object",
238
+ properties: {
239
+ collateralAmount: { type: "string", description: "Amount of collateral (e.g. '0.05')" },
240
+ token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
241
+ borrowAmount: { type: "string", description: "USDC amount to borrow (e.g. '50')" },
242
+ },
243
+ required: ["collateralAmount", "token", "borrowAmount"],
244
+ },
245
+ async execute(_id: string, params: { collateralAmount: string; token: string; borrowAmount: string }) {
246
+ try {
247
+ const cfg = getConfig(api);
248
+ const client = createClient(cfg);
249
+ const result = await client.depositAndBorrow(
250
+ params.token,
251
+ params.collateralAmount,
252
+ params.borrowAmount,
253
+ );
254
+ return ok(JSON.stringify({
255
+ status: "deposited_and_borrowed",
256
+ collateral: `${params.collateralAmount} ${params.token}`,
257
+ borrowed: `$${params.borrowAmount}`,
258
+ agentAccount: result.agentAccount,
259
+ tx: txLink(result.tx),
260
+ }));
261
+ } catch (e) { return fail(e); }
262
+ },
263
+ });
264
+
265
+ // ═══════════════════════════════════════════════════════
266
+ // TOOL: morpho_sponsor
267
+ // ═══════════════════════════════════════════════════════
268
+ api.registerTool({
269
+ name: "morpho_sponsor",
270
+ description:
271
+ "Transfer collateral tokens to another agent's AgentAccount. " +
272
+ "The target agent can then supply the collateral to Morpho Blue themselves.",
273
+ parameters: {
274
+ type: "object",
275
+ properties: {
276
+ agentId: { type: "string", description: "Target agent's ERC-8004 ID (e.g. '17676')" },
277
+ agentAddress: { type: "string", description: "Target AgentAccount address (alternative to agentId)" },
278
+ amount: { type: "string", description: "Collateral amount (e.g. '0.05')" },
279
+ token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
280
+ },
281
+ required: ["amount", "token"],
282
+ },
283
+ async execute(_id: string, params: { agentId?: string; agentAddress?: string; amount: string; token: string }) {
284
+ try {
285
+ if (!params.agentId && !params.agentAddress) return fail("Provide either agentId or agentAddress");
286
+ const cfg = getConfig(api);
287
+ const client = createClient(cfg);
288
+
289
+ const target = params.agentId
290
+ ? { agentId: params.agentId }
291
+ : { address: params.agentAddress! };
292
+
293
+ const result = await client.sponsor(target, params.token, params.amount);
294
+ return ok(JSON.stringify({
295
+ status: "sponsored",
296
+ target: result.targetAccount,
297
+ targetAgentId: result.targetAgentId || "by address",
298
+ collateral: `${params.amount} ${params.token}`,
299
+ tx: txLink(result.tx),
300
+ }));
301
+ } catch (e) { return fail(e); }
302
+ },
303
+ });
304
+
305
+ // ═══════════════════════════════════════════════════════
306
+ // TOOL: morpho_borrow
307
+ // ═══════════════════════════════════════════════════════
308
+ api.registerTool({
309
+ name: "morpho_borrow",
310
+ description:
311
+ "Borrow USDC against deposited collateral via Morpho Blue. " +
312
+ "USDC lands in AgentAccount. Requires collateral deposited first.",
313
+ parameters: {
314
+ type: "object",
315
+ properties: {
316
+ amount: { type: "string", description: "Amount in USD to borrow (e.g. '100')" },
317
+ token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token (optional, auto-detected)" },
318
+ },
319
+ required: ["amount"],
320
+ },
321
+ async execute(_id: string, params: { amount: string; token?: string }) {
322
+ try {
323
+ const cfg = getConfig(api);
324
+ const client = createClient(cfg);
325
+ const result = await client.borrow(params.amount, params.token);
326
+ return ok(JSON.stringify({
327
+ status: "borrowed",
328
+ amount: `$${params.amount}`,
329
+ collateral: result.collateralToken,
330
+ agentAccount: result.agentAccount,
331
+ tx: txLink(result.tx),
332
+ }));
333
+ } catch (e) { return fail(e); }
334
+ },
335
+ });
336
+
337
+ // ═══════════════════════════════════════════════════════
338
+ // TOOL: morpho_repay
339
+ // ═══════════════════════════════════════════════════════
340
+ api.registerTool({
341
+ name: "morpho_repay",
342
+ description:
343
+ "Repay borrowed USDC back to Morpho Blue from AgentAccount. Reduces debt. " +
344
+ "Use amount 'all' to repay full debt and clear dust shares.",
345
+ parameters: {
346
+ type: "object",
347
+ properties: {
348
+ amount: { type: "string", description: "Amount in USD to repay (e.g. '50' or 'all' for full repayment)" },
349
+ token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token (optional, auto-detected)" },
350
+ },
351
+ required: ["amount"],
352
+ },
353
+ async execute(_id: string, params: { amount: string; token?: string }) {
354
+ try {
355
+ const cfg = getConfig(api);
356
+ const client = createClient(cfg);
357
+ const result = await client.repay(params.amount, params.token);
358
+ return ok(JSON.stringify({
359
+ status: "repaid",
360
+ amount: `$${params.amount}`,
361
+ remainingDebt: result.remainingDebt,
362
+ tx: txLink(result.tx),
363
+ }));
364
+ } catch (e) { return fail(e); }
365
+ },
366
+ });
367
+
368
+ // ═══════════════════════════════════════════════════════
369
+ // TOOL: morpho_withdraw
370
+ // ═══════════════════════════════════════════════════════
371
+ api.registerTool({
372
+ name: "morpho_withdraw",
373
+ description:
374
+ "Withdraw collateral from Morpho Blue back to EOA wallet. " +
375
+ "Use amount 'all' to withdraw maximum. Must maintain collateral ratio if debt remains.",
376
+ parameters: {
377
+ type: "object",
378
+ properties: {
379
+ amount: { type: "string", description: "Amount to withdraw (e.g. '0.05' or 'all')" },
380
+ token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
381
+ },
382
+ required: ["amount", "token"],
383
+ },
384
+ async execute(_id: string, params: { amount: string; token: string }) {
385
+ try {
386
+ const cfg = getConfig(api);
387
+ const client = createClient(cfg);
388
+ const result = await client.withdrawCollateral(params.token, params.amount);
389
+ return ok(JSON.stringify({
390
+ status: "withdrawn",
391
+ amount: result.amount,
392
+ token: result.token,
393
+ remainingCollateral: result.remainingCollateral,
394
+ destination: result.destination,
395
+ tx: txLink(result.tx),
396
+ }));
397
+ } catch (e) { return fail(e); }
398
+ },
399
+ });
400
+
401
+ // ═══════════════════════════════════════════════════════
402
+ // TOOL: morpho_markets (Morpho GraphQL API — no backend)
403
+ // ═══════════════════════════════════════════════════════
404
+ api.registerTool({
405
+ name: "morpho_markets",
406
+ description:
407
+ "List available Morpho Blue USDC borrow markets on Base with liquidity, APY, and collateral info.",
408
+ parameters: { type: "object", properties: {}, required: [] },
409
+ async execute() {
410
+ try {
411
+ const cfg = getConfig(api);
412
+ const client = createClient(cfg);
413
+ const markets = await client.getMarkets(true);
414
+
415
+ const formatted = markets
416
+ .filter((m) => m.collateralAsset.address !== "0x0000000000000000000000000000000000000000")
417
+ .map((m) => ({
418
+ collateral: m.collateralAsset.symbol,
419
+ loan: m.loanAsset.symbol,
420
+ lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
421
+ totalSupply: `$${(Number(m.totalSupplyAssets) / 1e6).toFixed(0)}`,
422
+ totalBorrow: `$${(Number(m.totalBorrowAssets) / 1e6).toFixed(0)}`,
423
+ utilization: `${(m.utilization * 100).toFixed(1)}%`,
424
+ marketId: m.uniqueKey.slice(0, 18) + "…",
425
+ }));
426
+
427
+ return ok(JSON.stringify(formatted, null, 2));
428
+ } catch (e) { return fail(e); }
429
+ },
430
+ });
431
+
432
+ // ═══════════════════════════════════════════════════════
433
+ // TOOL: agether_score (backend API — x402 gated)
434
+ // ═══════════════════════════════════════════════════════
435
+ api.registerTool({
436
+ name: "agether_score",
437
+ description:
438
+ "Get the agent's current on-chain credit score. " +
439
+ "Use 'refresh' param to request a fresh score computation (costs USDC via x402).",
440
+ parameters: {
441
+ type: "object",
442
+ properties: {
443
+ refresh: { type: "boolean", description: "Request fresh score via x402 payment (default: false)" },
444
+ },
445
+ required: [],
446
+ },
447
+ async execute(_id: string, params: { refresh?: boolean }) {
448
+ try {
449
+ const cfg = getConfig(api);
450
+ const client = createClient(cfg);
451
+ const agentId = client.getAgentId();
452
+
453
+ if (params.refresh) {
454
+ // x402-gated fresh score
455
+ const x402 = new X402Client({
456
+ privateKey: cfg.privateKey,
457
+ rpcUrl: cfg.rpcUrl,
458
+ backendUrl: cfg.backendUrl,
459
+ agentId,
460
+ accountAddress: await client.getAccountAddress().catch(() => undefined),
461
+ });
462
+ const result = await x402.get(`${cfg.backendUrl}/score/${agentId}`);
463
+ if (!result.success) return fail(result.error || "Score request failed");
464
+ return ok(JSON.stringify(result.data, null, 2));
465
+ }
466
+
467
+ // Free: read current on-chain score
468
+ const { data } = await axios.get(`${cfg.backendUrl}/score/${agentId}/current`);
469
+ return ok(JSON.stringify(data, null, 2));
470
+ } catch (e) { return fail(e); }
471
+ },
472
+ });
473
+
474
+ // ═══════════════════════════════════════════════════════
475
+ // TOOL: wallet_fund
476
+ // ═══════════════════════════════════════════════════════
477
+ api.registerTool({
478
+ name: "wallet_fund",
479
+ description: "Transfer USDC from EOA wallet into AgentAccount.",
480
+ parameters: {
481
+ type: "object",
482
+ properties: {
483
+ amount: { type: "string", description: "USDC amount (e.g. '50')" },
484
+ },
485
+ required: ["amount"],
486
+ },
487
+ async execute(_id: string, params: { amount: string }) {
488
+ try {
489
+ const cfg = getConfig(api);
490
+ const client = createClient(cfg);
491
+ const result = await client.fundAccount(params.amount);
492
+ return ok(JSON.stringify({
493
+ status: "funded",
494
+ amount: `$${params.amount}`,
495
+ agentAccount: result.agentAccount,
496
+ tx: txLink(result.tx),
497
+ }));
498
+ } catch (e) { return fail(e); }
499
+ },
500
+ });
501
+
502
+ // ═══════════════════════════════════════════════════════
503
+ // TOOL: x402_pay (with auto-draw support)
504
+ // ═══════════════════════════════════════════════════════
505
+ api.registerTool({
506
+ name: "x402_pay",
507
+ description:
508
+ "Make a paid API call using x402 protocol. Pays with USDC from AgentAccount via EIP-3009 signature. " +
509
+ "Set autoDraw=true to automatically borrow from Morpho Blue if USDC balance is insufficient (opt-in).",
510
+ parameters: {
511
+ type: "object",
512
+ properties: {
513
+ url: { type: "string", description: "API endpoint URL" },
514
+ method: { type: "string", enum: ["GET", "POST"], description: "HTTP method (default: GET)" },
515
+ body: { type: "string", description: "JSON body for POST requests" },
516
+ autoDraw: { type: "boolean", description: "Auto-borrow from Morpho if USDC insufficient (default: false)" },
517
+ },
518
+ required: ["url"],
519
+ },
520
+ async execute(_id: string, params: { url: string; method?: string; body?: string; autoDraw?: boolean }) {
521
+ try {
522
+ const cfg = getConfig(api);
523
+ const agetherCfg = api.config?.plugins?.entries?.agether?.config || {};
524
+ const client = createClient(cfg);
525
+
526
+ let accountAddress: string | undefined;
527
+ let agentId: string | undefined;
528
+ try {
529
+ agentId = client.getAgentId();
530
+ accountAddress = await client.getAccountAddress();
531
+ } catch { /* no account, pay from EOA */ }
532
+
533
+ const autoDrawEnabled = params.autoDraw === true; // default false — opt-in only
534
+ const x402 = new X402Client({
535
+ privateKey: cfg.privateKey,
536
+ rpcUrl: cfg.rpcUrl,
537
+ backendUrl: cfg.backendUrl,
538
+ agentId,
539
+ accountAddress,
540
+ autoDraw: autoDrawEnabled,
541
+ dailySpendLimitUsdc: agetherCfg.dailySpendLimitUsdc,
542
+ yieldLimitedSpending: agetherCfg.yieldLimitedSpending,
543
+ autoDrawBuffer: agetherCfg.autoDrawBuffer,
544
+ });
545
+
546
+ let result;
547
+ if (autoDrawEnabled && agentId) {
548
+ // Use payWithAutoDraw which handles balance check + borrow
549
+ const fetchOpts: any = {
550
+ morphoClient: client,
551
+ };
552
+ if (params.method?.toUpperCase() === "POST" && params.body) {
553
+ fetchOpts.method = "POST";
554
+ fetchOpts.body = JSON.stringify(JSON.parse(params.body));
555
+ fetchOpts.headers = { "Content-Type": "application/json" };
556
+ }
557
+ result = await x402.payWithAutoDraw(params.url, fetchOpts);
558
+ } else {
559
+ if (params.method?.toUpperCase() === "POST" && params.body) {
560
+ result = await x402.post(params.url, JSON.parse(params.body));
561
+ } else {
562
+ result = await x402.get(params.url);
563
+ }
564
+ }
565
+
566
+ if (!result.success) return fail(result.error || "x402 payment failed");
567
+
568
+ const response: any = { status: "paid", data: result.data };
569
+ if (result.paymentInfo) {
570
+ response.payment = {
571
+ ...result.paymentInfo,
572
+ tx: result.paymentInfo.txHash ? txLink(result.paymentInfo.txHash) : undefined,
573
+ };
574
+ }
575
+ if (result.autoDrawInfo) {
576
+ response.autoDraw = {
577
+ ...result.autoDrawInfo,
578
+ borrowTx: result.autoDrawInfo.borrowTx ? txLink(result.autoDrawInfo.borrowTx) : undefined,
579
+ };
580
+ }
581
+ return ok(JSON.stringify(response, null, 2));
582
+ } catch (e) { return fail(e); }
583
+ },
584
+ });
585
+
586
+ // ═══════════════════════════════════════════════════════
587
+ // TOOL: morpho_rates
588
+ // ═══════════════════════════════════════════════════════
589
+ api.registerTool({
590
+ name: "morpho_rates",
591
+ description:
592
+ "Show current Morpho Blue market rates — supply APY, borrow APY, utilization for USDC markets. " +
593
+ "Note: collateral does NOT earn yield on Morpho; supply APY is what lenders earn.",
594
+ parameters: {
595
+ type: "object",
596
+ properties: {
597
+ token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Filter by collateral token (optional)" },
598
+ },
599
+ required: [],
600
+ },
601
+ async execute(_id: string, params: { token?: string }) {
602
+ try {
603
+ const cfg = getConfig(api);
604
+ const client = createClient(cfg);
605
+ const rates = await client.getMarketRates(params.token);
606
+
607
+ if (rates.length === 0) return ok("No markets found.");
608
+
609
+ const formatted = rates.map((r: any) => ({
610
+ collateral: r.collateralToken,
611
+ loan: r.loanToken,
612
+ supplyApy: `${(r.supplyApy * 100).toFixed(2)}%`,
613
+ borrowApy: `${(r.borrowApy * 100).toFixed(2)}%`,
614
+ utilization: `${(r.utilization * 100).toFixed(1)}%`,
615
+ totalSupply: `$${r.totalSupplyUsd.toFixed(0)}`,
616
+ totalBorrow: `$${r.totalBorrowUsd.toFixed(0)}`,
617
+ lltv: r.lltv,
618
+ }));
619
+
620
+ return ok(JSON.stringify(formatted, null, 2));
621
+ } catch (e) { return fail(e); }
622
+ },
623
+ });
624
+
625
+ // ═══════════════════════════════════════════════════════
626
+ // TOOL: morpho_yield_estimate
627
+ // ═══════════════════════════════════════════════════════
628
+ api.registerTool({
629
+ name: "morpho_yield_estimate",
630
+ description:
631
+ "Estimate theoretical yield for collateral deposited in Morpho Blue. " +
632
+ "⚠️ Collateral does NOT actually earn yield on Morpho. This estimates " +
633
+ "what it WOULD earn if it were supplied as a lender instead. " +
634
+ "Useful for setting spending caps based on theoretical earnings.",
635
+ parameters: {
636
+ type: "object",
637
+ properties: {
638
+ token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
639
+ amount: { type: "string", description: "Collateral amount (e.g. '1.5')" },
640
+ periodDays: { type: "number", description: "Period in days (default: 1)" },
641
+ ethPriceUsd: { type: "number", description: "ETH price in USD (optional, uses oracle if not provided)" },
642
+ },
643
+ required: ["token", "amount"],
644
+ },
645
+ async execute(_id: string, params: { token: string; amount: string; periodDays?: number; ethPriceUsd?: number }) {
646
+ try {
647
+ const cfg = getConfig(api);
648
+ const client = createClient(cfg);
649
+ const result = await client.getYieldEstimate(
650
+ params.token,
651
+ params.amount,
652
+ params.periodDays || 1,
653
+ params.ethPriceUsd,
654
+ );
655
+
656
+ return ok(JSON.stringify({
657
+ collateral: `${result.amount} ${result.collateralToken}`,
658
+ collateralValueUsd: `$${result.collateralValueUsd.toFixed(2)}`,
659
+ theoreticalSupplyApy: `${(result.theoreticalSupplyApy * 100).toFixed(2)}%`,
660
+ estimatedYieldUsd: `$${result.estimatedYieldUsd.toFixed(4)}`,
661
+ period: `${result.periodDays} day(s)`,
662
+ disclaimer: result.disclaimer,
663
+ }, null, 2));
664
+ } catch (e) { return fail(e); }
665
+ },
666
+ });
667
+
668
+ // ═══════════════════════════════════════════════════════
669
+ // TOOL: morpho_max_borrowable
670
+ // ═══════════════════════════════════════════════════════
671
+ api.registerTool({
672
+ name: "morpho_max_borrowable",
673
+ description:
674
+ "Calculate maximum additional USDC that can be borrowed given current collateral and debt. " +
675
+ "Shows borrowing headroom per market based on LLTV and oracle prices.",
676
+ parameters: { type: "object", properties: {}, required: [] },
677
+ async execute() {
678
+ try {
679
+ const cfg = getConfig(api);
680
+ const client = createClient(cfg);
681
+ const result = await client.getMaxBorrowable();
682
+
683
+ const formatted = {
684
+ totalMaxBorrowable: `$${(Number(result.total) / 1e6).toFixed(2)}`,
685
+ markets: result.byMarket.map((m: any) => ({
686
+ collateral: m.collateralToken,
687
+ maxAdditionalBorrow: `$${(Number(m.maxAdditional) / 1e6).toFixed(2)}`,
688
+ currentDebt: `$${(Number(m.currentDebt) / 1e6).toFixed(2)}`,
689
+ collateralValueUsdc: `$${(Number(m.collateralValue) / 1e6).toFixed(2)}`,
690
+ })),
691
+ };
692
+
693
+ return ok(JSON.stringify(formatted, null, 2));
694
+ } catch (e) { return fail(e); }
695
+ },
696
+ });
697
+
698
+ // ═══════════════════════════════════════════════════════
699
+ // SLASH COMMANDS (no AI needed)
700
+ // ═══════════════════════════════════════════════════════
701
+
702
+ api.registerCommand({
703
+ name: "balance",
704
+ description: "Show agent wallet balances (no AI)",
705
+ handler: async () => {
706
+ try {
707
+ const cfg = getConfig(api);
708
+ const client = createClient(cfg);
709
+ const b = await client.getBalances();
710
+
711
+ let text = `💰 Agent #${b.agentId}\n`;
712
+ text += `Address: ${b.address}\n`;
713
+ text += `ETH: ${parseFloat(b.eth).toFixed(6)}\n`;
714
+ text += `USDC: $${b.usdc}`;
715
+ if (b.agentAccount) {
716
+ text += `\n\n🏦 AgentAccount: ${b.agentAccount.address}\nUSDC: $${b.agentAccount.usdc}`;
717
+ }
718
+ return { text };
719
+ } catch (e: any) {
720
+ return { text: `❌ ${e.message}` };
721
+ }
722
+ },
723
+ });
724
+
725
+ api.registerCommand({
726
+ name: "morpho",
727
+ description: "Show Morpho positions (no AI)",
728
+ handler: async () => {
729
+ try {
730
+ const cfg = getConfig(api);
731
+ const client = createClient(cfg);
732
+ const s = await client.getStatus();
733
+
734
+ let text = `📊 Morpho\nAccount: ${s.agentAccount}\nTotal debt: $${s.totalDebt}\n`;
735
+ for (const p of s.positions) {
736
+ text += `\n${p.collateralToken}: ${p.collateral} collateral, $${p.debt} debt`;
737
+ }
738
+ return { text };
739
+ } catch (e: any) {
740
+ return { text: `❌ ${e.message}` };
741
+ }
742
+ },
743
+ });
744
+ }