@agether/openclaw-plugin 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.
@@ -0,0 +1,38 @@
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.0.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
+ },
30
+ "required": ["privateKey"]
31
+ },
32
+ "uiHints": {
33
+ "privateKey": { "label": "Private Key", "sensitive": true },
34
+ "agentId": { "label": "Agent ID", "placeholder": "17676" },
35
+ "rpcUrl": { "label": "RPC URL", "placeholder": "https://base-rpc.publicnode.com" },
36
+ "backendUrl": { "label": "Backend URL", "placeholder": "http://95.179.189.214:3001" }
37
+ }
38
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@agether/openclaw-plugin",
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.0.0",
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,471 @@
1
+ ---
2
+ name: agether
3
+ description: On-chain credit protocol for AI agents on Base. Two credit types, x402 payments, ERC-8004 identity.
4
+ metadata: { "openclaw": { "requires": { "bins": ["agether"], "env": ["AGETHER_PRIVATE_KEY"] }, "primaryEnv": "AGETHER_PRIVATE_KEY" } }
5
+ ---
6
+
7
+ # Agether — On-Chain Credit for AI Agents
8
+
9
+ **Chain**: Base mainnet (8453)
10
+ **Currency**: USDC (6 decimals)
11
+ **Backend**: http://95.179.189.214:3001
12
+ **RPC**: https://base-rpc.publicnode.com
13
+ **Admin Panel**: http://95.179.189.214/test
14
+
15
+ ---
16
+
17
+ ## 1. Setup (Required First)
18
+
19
+ ### ⚠️ CRITICAL: Private Key Rules
20
+ - **ALWAYS** use the key from `$AGETHER_PRIVATE_KEY` environment variable. **NEVER** generate, create, or derive a new private key.
21
+ - Do NOT call `ethers.Wallet.createRandom()` or any key-generation function.
22
+ - Do NOT invent or hardcode any private key.
23
+ - If `$AGETHER_PRIVATE_KEY` is not set, **stop and ask the user** — do not improvise.
24
+
25
+ ### 1.1 Initialize
26
+ ```bash
27
+ agether init $AGETHER_PRIVATE_KEY
28
+ ```
29
+ Saves config to `~/.agether/config.json`. Uses Base mainnet defaults.
30
+
31
+ Override with env vars:
32
+ ```bash
33
+ AGETHER_RPC_URL=https://base-rpc.publicnode.com AGETHER_BACKEND_URL=http://95.179.189.214:3001 agether init $AGETHER_PRIVATE_KEY
34
+ ```
35
+
36
+ ### 1.2 Register Agent
37
+ ```bash
38
+ agether register --name "MyAgent"
39
+ ```
40
+ Does 4 things on-chain:
41
+ 1. Mints ERC-8004 identity NFT on Base (assigns agentId)
42
+ 2. Creates AgentAccount smart wallet (isolated execution wallet)
43
+ 3. Registers code for KYA audit (Pending state)
44
+ 4. Fetches initial credit score
45
+
46
+ **Requires**: ~$0.01 ETH on Base for gas.
47
+
48
+ **Multiple agents per wallet**: YES, one wallet can own many agents. Each `register` mints a new ERC-8004 NFT with a unique agentId.
49
+
50
+ **If `--agent-id` was passed to `init`**: The CLI skips minting and uses the existing agent. To register a NEW agent on the same wallet, re-init without `--agent-id` first:
51
+ ```bash
52
+ agether init $AGETHER_PRIVATE_KEY # no --agent-id = fresh start
53
+ agether register --name "NewAgent" # mints new ERC-8004 token
54
+ # CLI prints new agentId — remember it for future init calls
55
+ ```
56
+
57
+ ### 1.3 Verify
58
+ ```bash
59
+ agether balance # Check ETH + USDC
60
+ agether status # Check credit line status
61
+ ```
62
+
63
+ ---
64
+
65
+ ## 2. Two Credit Paths
66
+
67
+ ### Path A: ReputationCredit (Undercollateralized)
68
+
69
+ **How it works**: Borrow USDC with only 80% collateral. Based on credit score and KYA code audit. Borrows from protocol LP Vault.
70
+
71
+ **Requirements**:
72
+ - Registered agent (agentId)
73
+ - KYA code audit approved by admin
74
+ - Credit application approved by admin
75
+ - Collateral deposit (80% of limit)
76
+
77
+ **Full flow**:
78
+ ```bash
79
+ # Step 1: Apply for credit
80
+ agether apply --limit 5000
81
+
82
+ # Step 2: Wait for admin approval at /test panel
83
+ # Admin approves KYA -> Admin approves credit line -> status becomes Active
84
+
85
+ # Step 3: Draw USDC (goes into AgentAccount)
86
+ agether draw --amount 1000
87
+
88
+ # Step 4: Use USDC for x402 payments or other purposes
89
+
90
+ # Step 5: Repay when done
91
+ agether repay --amount 1000
92
+ ```
93
+
94
+ **Credit tiers** (based on Bayesian score):
95
+ | Score Range | Max Limit | APR | Collateral |
96
+ |-------------|-----------|-----|------------|
97
+ | 800-1000 | $100,000 | 5% | 80% |
98
+ | 600-799 | $50,000 | 8% | 80% |
99
+ | 400-599 | $20,000 | 12% | 80% |
100
+ | 200-399 | $5,000 | 15% | 80% |
101
+ | 0-199 | Rejected | - | - |
102
+
103
+ **Payment schedule**: 30-day cycles with 7-day grace period. Miss payment -> overdue -> potential default.
104
+
105
+ ### Path B: MorphoCredit (Overcollateralized)
106
+
107
+ **How it works**: Deposit collateral (WETH/wstETH/cbETH), borrow USDC at 120% LTV via Morpho Blue. Instant, no admin approval needed.
108
+
109
+ **Requirements**:
110
+ - Registered agent (agentId + AgentAccount)
111
+ - Collateral tokens (WETH, wstETH, or cbETH) in EOA wallet
112
+ - No KYA needed, no admin approval needed
113
+
114
+ **Full flow**:
115
+ ```bash
116
+ # Step 1: Check what you need
117
+ agether morpho-compare --amount 100
118
+
119
+ # Step 2: Deposit collateral from EOA -> MorphoCredit -> Morpho Blue
120
+ agether morpho-deposit --amount 0.05 --token WETH
121
+
122
+ # Step 3: Borrow USDC (lands in AgentAccount)
123
+ agether morpho-borrow --amount 100
124
+
125
+ # Step 4: Use USDC for x402 payments
126
+
127
+ # Step 5: Repay
128
+ agether morpho-repay --amount 100
129
+
130
+ # Step 6: Withdraw collateral back to EOA
131
+ agether morpho-withdraw --amount all --token WETH
132
+ ```
133
+
134
+ **Supported collateral**: WETH, wstETH, cbETH (Base addresses)
135
+ **LTV**: 120% (need $120 collateral for $100 borrow)
136
+ **No daily limits, no payment schedule** — repay anytime.
137
+
138
+ ### Choosing Between Them
139
+
140
+ | Feature | ReputationCredit | MorphoCredit |
141
+ |---------|-----------------|--------------|
142
+ | Collateral | 80% (undercollat) | 120% (overcollat) |
143
+ | Speed | Needs admin approval | Instant |
144
+ | KYA required | Yes | No |
145
+ | Source of funds | LP Vault | Morpho Blue pools |
146
+ | Payment schedule | 30-day cycles | None (repay anytime) |
147
+ | Best for | Established agents | Quick access, has collateral |
148
+
149
+ ---
150
+
151
+ ## 3. x402 Payments
152
+
153
+ Make paid API calls. USDC is transferred on-chain via EIP-3009 transferWithAuthorization.
154
+
155
+ **Requirement**: USDC in your EOA wallet or AgentAccount. No USDC = payment rejected by facilitator.
156
+
157
+ ```bash
158
+ # GET request ($0.001 per call to WeatherXM)
159
+ agether x402 "https://agent.weatherxm.com/api/forecast?lat=40.71&lon=-74.00"
160
+
161
+ # POST request
162
+ agether x402 "https://api.example.com/data" --method POST --body '{"query":"test"}'
163
+ ```
164
+
165
+ **How it works internally**:
166
+ 1. CLI calls the API -> gets HTTP 402 with payment requirements
167
+ 2. CLI signs EIP-3009 transferWithAuthorization (off-chain signature, NOT a tx)
168
+ 3. CLI does risk check via backend (optional, non-blocking)
169
+ 4. CLI retries the API with PAYMENT-SIGNATURE header
170
+ 5. API facilitator verifies signature + settles USDC on-chain
171
+ 6. API returns the data
172
+
173
+ **Important**: Signing is free and always succeeds. The actual USDC transfer happens server-side by the facilitator. If wallet has 0 USDC, facilitator rejects.
174
+
175
+ ---
176
+
177
+ ## 4. Complete Command Reference
178
+
179
+ ### Setup and Identity
180
+ | Command | Args | Description |
181
+ |---------|------|-------------|
182
+ | `agether init <pk>` | `--agent-id <id>` (optional) | Initialize CLI with private key |
183
+ | `agether register` | `--name <name>` | Register ERC-8004 identity + AgentAccount |
184
+ | `agether balance` | none | Show ETH + USDC balances (EOA) |
185
+ | `agether status` | none | Show ReputationCredit line details |
186
+ | `agether score` | none | Show Bayesian credit score + subscores |
187
+ | `agether unified-status` | none | Show ALL credit lines (both types) |
188
+
189
+ ### ReputationCredit (Undercollateralized)
190
+ | Command | Args | Description |
191
+ |---------|------|-------------|
192
+ | `agether apply` | `--limit <usd>` | Apply for credit line (needs KYA first) |
193
+ | `agether draw` | `--amount <usd>` | Draw USDC from credit line into AgentAccount |
194
+ | `agether repay` | `--amount <usd>` | Repay debt (pulls USDC from EOA into AgentAccount then to credit) |
195
+
196
+ ### MorphoCredit (Overcollateralized)
197
+ | Command | Args | Description |
198
+ |---------|------|-------------|
199
+ | `agether morpho-compare` | `--amount <usd>` | Compare options, show exact collateral needed (uses oracle prices) |
200
+ | `agether morpho-balances` | none | Show all token balances + max borrow capacity |
201
+ | `agether morpho-markets` | none | List supported Morpho markets |
202
+ | `agether morpho-open` | `--collateral <TOKEN>` | Check or prepare Morpho position (WETH/wstETH/cbETH) |
203
+ | `agether morpho-deposit` | `--amount <n> --token <TOKEN>` | Deposit collateral from EOA into Morpho Blue |
204
+ | `agether morpho-borrow` | `--amount <usd>` | Borrow USDC into AgentAccount |
205
+ | `agether morpho-repay` | `--amount <usd>` | Repay USDC from AgentAccount to Morpho |
206
+ | `agether morpho-withdraw` | `--amount <n> --token <TOKEN>` | Withdraw collateral to EOA (use `--amount all` for max) |
207
+ | `agether morpho-status` | none | Show Morpho positions |
208
+
209
+ ### Agent Wallet (AgentAccount)
210
+ | Command | Args | Description |
211
+ |---------|------|-------------|
212
+ | `agether wallet-create` | none | Create AgentAccount (usually done by register) |
213
+ | `agether wallet-status` | none | Show AgentAccount balances (ETH + USDC) |
214
+ | `agether wallet-fund` | `--amount <usd>` | Transfer USDC from EOA into AgentAccount |
215
+ | `agether wallet-draw` | `--amount <usd>` | Draw from ReputationCredit into AgentAccount |
216
+ | `agether wallet-repay` | `--amount <usd>` | Repay from AgentAccount to ReputationCredit |
217
+
218
+ ### x402 Payments
219
+ | Command | Args | Description |
220
+ |---------|------|-------------|
221
+ | `agether x402 <url>` | `--method GET` or `--method POST` and `--body <json>` | Make paid API call |
222
+
223
+ ---
224
+
225
+ ## 5. Error Codes and Recovery
226
+
227
+ ### On-Chain Contract Errors (from ReputationCredit or MorphoCredit)
228
+ | Error | Meaning | Fix |
229
+ |-------|---------|-----|
230
+ | `CodeNotApproved` | KYA code audit not yet approved | Wait for admin to approve at /test panel |
231
+ | `DailyLimitExceeded` | Draw exceeds daily limit | Draw smaller amount or wait 24h |
232
+ | `CollateralRequired` | Need to deposit collateral first | `agether collateral --amount <usd>` |
233
+ | `ExceedsAvailable` | Draw exceeds remaining credit | Check `agether status` for available amount |
234
+ | `NotAgentOwner` | Wallet does not own this agentId | Check private key matches registered wallet |
235
+ | `AlreadyHasCreditLine` | Agent already applied | Check status: `agether status` |
236
+ | `BelowMinimum` | Amount below $100 minimum | Use at least $100 |
237
+ | `AboveMaximum` | Amount above $100,000 maximum | Use at most $100,000 |
238
+ | `InsufficientLiquidity` | LP Vault has no USDC | Try MorphoCredit instead, or wait for LP deposits |
239
+ | `Undercollateralized` | LTV too high for Morpho | Deposit more collateral |
240
+ | `AgentDefaulted` | Agent has been defaulted | Cannot use protocol anymore |
241
+ | `InvalidCreditStatus` | Wrong credit line state for this action | Check `agether status` for current state |
242
+
243
+ ### CLI Errors
244
+ | Error | Meaning | Fix |
245
+ |-------|---------|-----|
246
+ | `Not initialized` | No config file | `agether init <private-key>` |
247
+ | `No agentId` | Registered but agentId=0 | `agether register --name <name>` |
248
+ | `No AgentAccount` | Account not created | `agether register --name <name>` or `agether wallet-create` |
249
+ | `Credit line not active` | Status is Pending/Frozen/Closed | Wait for admin approval or check status |
250
+ | `WALLET MISMATCH` | PK does not match agent owner | Use correct private key |
251
+ | `Insufficient USDC` | Not enough USDC for operation | Fund wallet with USDC on Base |
252
+ | `Insufficient <TOKEN> balance` | Not enough collateral in EOA | Get more WETH/wstETH/cbETH |
253
+ | `Failed to reach backend` | Backend is down | Check http://95.179.189.214:3001/health |
254
+ | `Unsupported token` | Wrong collateral symbol | Use: WETH, wstETH, or cbETH |
255
+ | `No Morpho debt to repay` | No active Morpho borrow | Nothing to do |
256
+ | `No collateral deposited` | Trying to borrow without collateral | `agether morpho-deposit --amount <n> --token WETH` first |
257
+ | `Already registered` | ERC-8004 identity exists | Use existing agentId, set via `agether init <pk> --agent-id <id>` |
258
+
259
+ ### x402 Errors
260
+ | Error | Meaning | Fix |
261
+ |-------|---------|-----|
262
+ | `Payment rejected (HTTP 4xx/5xx)` | Facilitator rejected the payment | Check USDC balance: wallet needs real USDC |
263
+ | `Could not parse payment requirements` | API response is not valid x402 | API may not be x402-compatible |
264
+ | `Risk check: deny` | Backend risk engine blocked | Payment amount may be too high for credit score |
265
+ | `Request failed` | Network error | Check internet and RPC connectivity |
266
+
267
+ ---
268
+
269
+ ## 6. Credit Line States
270
+
271
+ ```
272
+ None (0) --[apply]--> Pending (1) --[admin approves]--> Active (2)
273
+ |
274
+ [admin freezes] --> Frozen (3)
275
+ [admin unfreezes] --> Active (2)
276
+ [default] --> Defaulted (5)
277
+ [close] --> Closed (4)
278
+ ```
279
+
280
+ | Status | Can Draw | Can Repay | Can Apply |
281
+ |--------|----------|-----------|-----------|
282
+ | None (0) | No | No | Yes |
283
+ | Pending (1) | No | No | No |
284
+ | Active (2) | Yes | Yes | No |
285
+ | Frozen (3) | No | Yes | No |
286
+ | Closed (4) | No | No | Yes (re-apply) |
287
+ | Defaulted (5) | No | No | No |
288
+
289
+ ---
290
+
291
+ ## 7. Decision Trees
292
+
293
+ ### User wants to call a paid API
294
+ ```
295
+ 1. agether balance -- Do I have USDC?
296
+ YES --> agether x402 <url>
297
+ NO --> Get USDC first:
298
+ Option A: Buy USDC on Base, send to EOA wallet
299
+ Option B: Get credit line, then draw:
300
+ agether morpho-deposit --amount 0.05 --token WETH
301
+ agether morpho-borrow --amount 10
302
+ agether wallet-status -- verify USDC arrived
303
+ agether x402 <url>
304
+ ```
305
+
306
+ ### User wants credit fast, no approval needed (120% collateral, Morpho)
307
+ ```
308
+ 1. agether morpho-compare --amount <how-much-usd>
309
+ 2. agether morpho-deposit --amount <needed> --token WETH
310
+ 3. agether morpho-borrow --amount <usd>
311
+ 4. agether wallet-status -- USDC is in AgentAccount
312
+ ```
313
+
314
+ ### User wants credit with less collateral (80% collateral, needs admin approval)
315
+ ```
316
+ 1. agether apply --limit 5000
317
+ 2. Wait for admin approval (KYA + credit) at /test panel
318
+ 3. agether status -- check if Active
319
+ 4. Deposit 80% collateral (e.g. $4000 for $5000 limit)
320
+ 5. agether draw --amount 1000
321
+ 6. agether wallet-status -- USDC is in AgentAccount
322
+ ```
323
+
324
+ ### User wants to check everything
325
+ ```
326
+ agether balance -- EOA ETH + USDC
327
+ agether wallet-status -- AgentAccount ETH + USDC
328
+ agether unified-status -- ALL credit lines, both types
329
+ agether score -- Bayesian score + 5-factor subscores
330
+ ```
331
+
332
+ ### User wants to repay
333
+ ```
334
+ ReputationCredit: agether repay --amount <usd>
335
+ MorphoCredit: agether morpho-repay --amount <usd>
336
+ ```
337
+
338
+ ---
339
+
340
+ ## 8. Backend API Endpoints
341
+
342
+ All at http://95.179.189.214:3001:
343
+
344
+ | Endpoint | Method | Purpose |
345
+ |----------|--------|---------|
346
+ | `/health` | GET | Health check |
347
+ | `/status` | GET | Contract addresses, chainId, version |
348
+ | `/agents/count` | GET | Total registered agents |
349
+ | `/credit/score/<agentId>` | GET | Bayesian score + 5-factor subscores |
350
+ | `/credit/evaluate` | POST | Evaluate credit application. Body: `{"agentId":"123","requestedLimit":"5000000000"}` |
351
+ | `/morpho/balances/<address>` | GET | Token balances + credit capacity |
352
+ | `/morpho/markets` | GET | Supported Morpho markets |
353
+ | `/morpho/estimate/<amountUsd>` | GET | Collateral estimates with oracle prices |
354
+ | `/morpho/compare` | POST | Compare credit options. Body: `{"agentId":"123","amount":"5000000000"}` |
355
+ | `/morpho/unified/agent/<agentId>` | GET | All credit lines for agent |
356
+ | `/kya/verify` | POST | Verify agent identity (KYA) |
357
+ | `/x402/verify` | POST | Risk-check x402 payment |
358
+ | `/x402/settle` | POST | Settle x402 payment |
359
+ | `/risk/evaluate` | POST | Risk evaluation |
360
+
361
+ ---
362
+
363
+ ## 9. Architecture
364
+
365
+ ```
366
+ EOA Wallet (your private key)
367
+ |-- Owns ERC-8004 Identity NFT (agentId)
368
+ +-- Owns AgentAccount (smart wallet)
369
+ |-- Holds USDC (from draws or direct funding)
370
+ |-- drawCredit(provider, amount) -- pull USDC from credit provider
371
+ |-- repayCredit(provider, amount) -- push USDC back to credit provider
372
+ |-- fund(token, amount) -- pull from EOA into account
373
+ +-- payX402(...) -- pay for API call
374
+
375
+ ReputationCredit (undercollat, 80%)
376
+ |-- applyForCredit() --> Pending
377
+ |-- approveCreditLine() --> Active (admin only)
378
+ |-- draw() --> USDC from LP Vault to AgentAccount
379
+ |-- repay() --> USDC from AgentAccount to LP Vault
380
+ +-- declareDefault() --> slashes collateral (admin only)
381
+
382
+ MorphoCredit (overcollat, 120%)
383
+ |-- depositCollateral() --> into Morpho Blue
384
+ |-- drawWithCollateral() --> USDC from Morpho Blue to AgentAccount
385
+ |-- repayWithCollateral() --> USDC from AgentAccount to Morpho Blue
386
+ +-- withdrawCollateral() --> collateral back to EOA
387
+
388
+ LP Vault (ERC-4626)
389
+ |-- LPs deposit USDC --> earn yield from interest
390
+ +-- Funds ReputationCredit draws
391
+ ```
392
+
393
+ ---
394
+
395
+ ## 10. Key Addresses (Base Mainnet)
396
+
397
+ | Contract | Address |
398
+ |----------|---------|
399
+ | AccountFactory | 0xeB72f248Ad9F4bf4024e8D9da75cf7AAD37B58f5 |
400
+ | LPVault | 0x612A80D6c3175F8283e9C7EE71d5177fE9acc338 |
401
+ | ReputationCredit | 0x57B2B4ef3e7B8BE5FC86c6369602125d240F552A |
402
+ | MorphoCredit | 0x7dFfa40E17471F7f26F5662D0F07a31977F47BeB |
403
+ | ValidationRegistry | 0x8842f2383A86134Dd80c3Ecf6Bbae2e38396A5ec |
404
+ | AgentReputation | 0xF1bed094D4E33E47CC8C72E086FFFde09e2211b4 |
405
+ | USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
406
+ | ERC-8004 Identity | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 |
407
+ | WETH | 0x4200000000000000000000000000000000000006 |
408
+ | wstETH | 0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452 |
409
+ | cbETH | 0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22 |
410
+ | Morpho Blue | 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb |
411
+
412
+ ---
413
+
414
+ ## 11. Installation (For New Bot or Team Lead)
415
+
416
+ ### Prerequisites
417
+ - Node.js >= 20
418
+ - A wallet with ~$0.01 ETH on Base (gas) + USDC on Base (for x402 payments)
419
+
420
+ ### Install CLI
421
+ ```bash
422
+ git clone <repo-url>
423
+ cd agent-credit-protocol/sdk
424
+ npm install
425
+ npm run build
426
+ npm link # Makes agether command available globally
427
+ ```
428
+
429
+ ### Quick Test
430
+ ```bash
431
+ # 1. Setup
432
+ agether init <private-key>
433
+ agether register --name "TestBot"
434
+ agether balance
435
+
436
+ # 2. Quick credit via Morpho (if you have WETH)
437
+ agether morpho-compare --amount 5
438
+ agether morpho-deposit --amount 0.003 --token WETH
439
+ agether morpho-borrow --amount 5
440
+ agether wallet-status
441
+
442
+ # 3. Make a paid API call ($0.001)
443
+ agether x402 "https://agent.weatherxm.com/api/forecast?lat=40.71&lon=-74.00"
444
+
445
+ # 4. Repay and clean up
446
+ agether morpho-repay --amount 5
447
+ agether morpho-status
448
+ ```
449
+
450
+ ### OpenClaw Integration
451
+ 1. Copy SKILL.md into OpenClaw's skill folder:
452
+ ```bash
453
+ mkdir -p ~/.openclaw/skills/agether
454
+ cp SKILL.md ~/.openclaw/skills/agether/SKILL.md
455
+ ```
456
+
457
+ 2. Add to `~/.openclaw/openclaw.json` under `skills.entries`:
458
+ ```json
459
+ {
460
+ "agether": {
461
+ "enabled": true,
462
+ "env": {
463
+ "AGETHER_PRIVATE_KEY": "<your-private-key>",
464
+ "AGETHER_RPC_URL": "https://base-rpc.publicnode.com",
465
+ "AGETHER_BACKEND_URL": "http://95.179.189.214:3001"
466
+ }
467
+ }
468
+ }
469
+ ```
470
+
471
+ OpenClaw reads the SKILL.md from `~/.openclaw/skills/agether/` and finds the `agether` binary on PATH (from `npm link`).
package/src/index.ts ADDED
@@ -0,0 +1,757 @@
1
+ /**
2
+ * Agether OpenClaw Plugin
3
+ *
4
+ * Registers on-chain credit tools for AI agents.
5
+ * Uses @agether/sdk clients under the hood.
6
+ */
7
+
8
+ import { ethers } from "ethers";
9
+ import axios from "axios";
10
+ import {
11
+ ACCOUNT_FACTORY_ABI,
12
+ AGENT_ACCOUNT_ABI,
13
+ ERC20_ABI,
14
+ getDefaultConfig,
15
+ } from "@agether/sdk";
16
+
17
+ // ─── Helpers ──────────────────────────────────────────────
18
+
19
+ interface PluginConfig {
20
+ privateKey: string;
21
+ agentId?: string;
22
+ rpcUrl?: string;
23
+ backendUrl?: string;
24
+ }
25
+
26
+ const MORPHO_CREDIT_ABI = [
27
+ "function depositCollateral(address collateralToken, uint256 amount)",
28
+ "function depositCollateralFor(address onBehalf, address collateralToken, uint256 amount)",
29
+ "function withdrawCollateral(address collateralToken, uint256 amount)",
30
+ "function drawWithCollateral(address collateralToken, uint256 amount)",
31
+ "function repayWithCollateral(address collateralToken, uint256 amount)",
32
+ "function getPosition(address account, address collateralToken) view returns (tuple(uint256 collateralAmount, uint256 borrowedAmount, uint256 borrowShares, bool isActive))",
33
+ "function getSupportedCollaterals() view returns (address[])",
34
+ "function asset() view returns (address)",
35
+ "function getCreditInfo(address account) view returns (tuple(uint256 limit, uint256 used, uint256 available, uint256 accruedInterest, uint256 aprBps, bool isActive, bool requiresCollateral))",
36
+ "function getTotalDebt(address account) view returns (uint256)",
37
+ "function maxDrawable(address account) view returns (uint256)",
38
+ ];
39
+
40
+ const ERC8004_ABI = [
41
+ "function register(string agentURI) returns (uint256)",
42
+ "function ownerOf(uint256 tokenId) view returns (address)",
43
+ "function balanceOf(address owner) view returns (uint256)",
44
+ ];
45
+
46
+ const COLLATERAL_TOKENS: Record<string, { address: string; decimals: number }> = {
47
+ WETH: { address: "0x4200000000000000000000000000000000000006", decimals: 18 },
48
+ wstETH: { address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", decimals: 18 },
49
+ cbETH: { address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", decimals: 18 },
50
+ };
51
+
52
+ function getConfig(api: any): PluginConfig {
53
+ const cfg = api.config?.plugins?.entries?.agether?.config;
54
+ if (!cfg?.privateKey) {
55
+ throw new Error("Agether plugin not configured: missing privateKey in plugins.entries.agether.config");
56
+ }
57
+ return {
58
+ privateKey: cfg.privateKey,
59
+ agentId: cfg.agentId,
60
+ rpcUrl: cfg.rpcUrl || "https://base-rpc.publicnode.com",
61
+ backendUrl: cfg.backendUrl || "http://95.179.189.214:3001",
62
+ };
63
+ }
64
+
65
+ function getSigner(cfg: PluginConfig) {
66
+ const provider = new ethers.JsonRpcProvider(cfg.rpcUrl);
67
+ return new ethers.Wallet(cfg.privateKey, provider);
68
+ }
69
+
70
+ async function getBackendStatus(cfg: PluginConfig) {
71
+ const { data } = await axios.get(`${cfg.backendUrl}/status`);
72
+ return data;
73
+ }
74
+
75
+ async function getAccountAddr(signer: ethers.Wallet, factoryAddr: string, agentId: string) {
76
+ const factory = new ethers.Contract(factoryAddr, ACCOUNT_FACTORY_ABI, signer);
77
+ const addr = await factory.getAccount(agentId);
78
+ if (addr === ethers.ZeroAddress) throw new Error("No AgentAccount. Register first.");
79
+ return addr;
80
+ }
81
+
82
+ function ok(text: string) {
83
+ return { content: [{ type: "text" as const, text }] };
84
+ }
85
+
86
+ function fail(err: unknown) {
87
+ const msg = err instanceof Error ? err.message : String(err);
88
+ return { content: [{ type: "text" as const, text: `❌ ${msg}` }], isError: true };
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 signer = getSigner(cfg);
106
+ const status = await getBackendStatus(cfg);
107
+ const address = signer.address;
108
+
109
+ const provider = signer.provider!;
110
+ const ethBal = await provider.getBalance(address);
111
+ const usdc = new ethers.Contract(status.contracts.usdc, ERC20_ABI, provider);
112
+ const usdcBal = await usdc.balanceOf(address);
113
+
114
+ const result: any = {
115
+ address,
116
+ agentId: cfg.agentId || "not set",
117
+ eth: ethers.formatEther(ethBal),
118
+ usdc: ethers.formatUnits(usdcBal, 6),
119
+ };
120
+
121
+ // AgentAccount balances
122
+ if (cfg.agentId && status.contracts.accountFactory) {
123
+ try {
124
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
125
+ const accEth = await provider.getBalance(accountAddr);
126
+ const accUsdc = await usdc.balanceOf(accountAddr);
127
+ result.agentAccount = {
128
+ address: accountAddr,
129
+ eth: ethers.formatEther(accEth),
130
+ usdc: ethers.formatUnits(accUsdc, 6),
131
+ };
132
+ } catch { /* no account yet */ }
133
+ }
134
+
135
+ return ok(JSON.stringify(result, null, 2));
136
+ } catch (e) { return fail(e); }
137
+ },
138
+ });
139
+
140
+ // ═══════════════════════════════════════════════════════
141
+ // TOOL: agether_register
142
+ // ═══════════════════════════════════════════════════════
143
+ api.registerTool({
144
+ name: "agether_register",
145
+ description:
146
+ "Register a new ERC-8004 agent identity on Base and create an AgentAccount. Returns the new agentId. If agentId is already configured, returns the existing one.",
147
+ parameters: {
148
+ type: "object",
149
+ properties: {
150
+ name: { type: "string", description: "Agent display name" },
151
+ },
152
+ required: ["name"],
153
+ },
154
+ async execute(_id: string, params: { name: string }) {
155
+ try {
156
+ const cfg = getConfig(api);
157
+ const signer = getSigner(cfg);
158
+ const status = await getBackendStatus(cfg);
159
+
160
+ const agentRegistry = new ethers.Contract(status.contracts.agentRegistry, ERC8004_ABI, signer);
161
+
162
+ let agentId: bigint;
163
+
164
+ // If agentId already set, verify and use
165
+ if (cfg.agentId && cfg.agentId !== "0") {
166
+ agentId = BigInt(cfg.agentId);
167
+ const owner = await agentRegistry.ownerOf(agentId);
168
+ if (owner.toLowerCase() !== signer.address.toLowerCase()) {
169
+ return fail("agentId in config does not belong to this wallet");
170
+ }
171
+ // Ensure AgentAccount exists
172
+ if (status.contracts.accountFactory) {
173
+ const factory = new ethers.Contract(status.contracts.accountFactory, ACCOUNT_FACTORY_ABI, signer);
174
+ const exists = await factory.accountExists(agentId);
175
+ if (!exists) {
176
+ const tx = await factory.createAccount(agentId);
177
+ await tx.wait();
178
+ }
179
+ }
180
+ return ok(JSON.stringify({
181
+ status: "already_registered",
182
+ agentId: agentId.toString(),
183
+ address: signer.address,
184
+ }));
185
+ }
186
+
187
+ // Mint new ERC-8004 identity
188
+ const registrationFile = JSON.stringify({
189
+ type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
190
+ name: params.name,
191
+ description: "AI agent registered via Agether OpenClaw plugin",
192
+ active: true,
193
+ registrations: [{
194
+ agentId: 0,
195
+ agentRegistry: `eip155:${status.chainId}:${status.contracts.agentRegistry}`,
196
+ }],
197
+ });
198
+ const agentURI = `data:application/json;base64,${Buffer.from(registrationFile).toString("base64")}`;
199
+
200
+ const tx = await agentRegistry["register(string)"](agentURI);
201
+ const receipt = await tx.wait();
202
+
203
+ // Parse agentId from Transfer event
204
+ const transferTopic = ethers.id("Transfer(address,address,uint256)");
205
+ const transferLog = receipt.logs.find((l: any) => l.topics[0] === transferTopic);
206
+ if (transferLog?.topics?.length >= 4) {
207
+ agentId = BigInt(transferLog.topics[3]);
208
+ } else {
209
+ return fail("Could not parse agentId from receipt");
210
+ }
211
+
212
+ // Create AgentAccount
213
+ let accountAddr = "";
214
+ if (status.contracts.accountFactory) {
215
+ const factory = new ethers.Contract(status.contracts.accountFactory, ACCOUNT_FACTORY_ABI, signer);
216
+ const exists = await factory.accountExists(agentId);
217
+ if (!exists) {
218
+ const accTx = await factory.createAccount(agentId);
219
+ await accTx.wait();
220
+ }
221
+ accountAddr = await factory.getAccount(agentId);
222
+ }
223
+
224
+ return ok(JSON.stringify({
225
+ status: "registered",
226
+ agentId: agentId.toString(),
227
+ address: signer.address,
228
+ agentAccount: accountAddr,
229
+ tx: tx.hash,
230
+ important: "Save this agentId in your plugin config: plugins.entries.agether.config.agentId",
231
+ }));
232
+ } catch (e) { return fail(e); }
233
+ },
234
+ });
235
+
236
+ // ═══════════════════════════════════════════════════════
237
+ // TOOL: morpho_status
238
+ // ═══════════════════════════════════════════════════════
239
+ api.registerTool({
240
+ name: "morpho_status",
241
+ description:
242
+ "Show all Morpho credit positions — collateral deposited, USDC borrowed, debt, for each supported collateral token.",
243
+ parameters: { type: "object", properties: {}, required: [] },
244
+ async execute() {
245
+ try {
246
+ const cfg = getConfig(api);
247
+ const signer = getSigner(cfg);
248
+ const status = await getBackendStatus(cfg);
249
+ if (!cfg.agentId) return fail("No agentId configured");
250
+
251
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
252
+ const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
253
+
254
+ const positions: any[] = [];
255
+ for (const [symbol, info] of Object.entries(COLLATERAL_TOKENS)) {
256
+ const pos = await morpho.getPosition(accountAddr, info.address);
257
+ if (pos.isActive || pos.collateralAmount > 0n || pos.borrowedAmount > 0n) {
258
+ positions.push({
259
+ token: symbol,
260
+ collateral: ethers.formatUnits(pos.collateralAmount, info.decimals),
261
+ debt: `$${ethers.formatUnits(pos.borrowedAmount, 6)}`,
262
+ active: pos.isActive,
263
+ });
264
+ }
265
+ }
266
+
267
+ const totalDebt = await morpho.getTotalDebt(accountAddr);
268
+
269
+ return ok(JSON.stringify({
270
+ agentAccount: accountAddr,
271
+ totalDebt: `$${ethers.formatUnits(totalDebt, 6)}`,
272
+ positions: positions.length > 0 ? positions : "No active positions",
273
+ }, null, 2));
274
+ } catch (e) { return fail(e); }
275
+ },
276
+ });
277
+
278
+ // ═══════════════════════════════════════════════════════
279
+ // TOOL: morpho_deposit
280
+ // ═══════════════════════════════════════════════════════
281
+ api.registerTool({
282
+ name: "morpho_deposit",
283
+ description:
284
+ "Deposit collateral (WETH, wstETH, or cbETH) from EOA wallet into Morpho Blue via MorphoCredit. This enables borrowing USDC.",
285
+ parameters: {
286
+ type: "object",
287
+ properties: {
288
+ amount: { type: "string", description: "Amount of collateral tokens (e.g. '0.05')" },
289
+ token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
290
+ },
291
+ required: ["amount", "token"],
292
+ },
293
+ async execute(_id: string, params: { amount: string; token: string }) {
294
+ try {
295
+ const cfg = getConfig(api);
296
+ const signer = getSigner(cfg);
297
+ const status = await getBackendStatus(cfg);
298
+ if (!cfg.agentId) return fail("No agentId configured");
299
+
300
+ const tokenInfo = COLLATERAL_TOKENS[params.token];
301
+ if (!tokenInfo) return fail(`Unsupported token: ${params.token}. Use WETH, wstETH, or cbETH`);
302
+
303
+ const morphoCreditAddr = status.contracts.morphoCredit;
304
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
305
+ const amount = ethers.parseUnits(params.amount, tokenInfo.decimals);
306
+
307
+ // Check balance
308
+ const token = new ethers.Contract(tokenInfo.address, ERC20_ABI, signer);
309
+ const balance = await token.balanceOf(signer.address);
310
+ if (balance < amount) {
311
+ return fail(`Insufficient ${params.token}: have ${ethers.formatUnits(balance, tokenInfo.decimals)}, need ${params.amount}`);
312
+ }
313
+
314
+ // Approve
315
+ const approveTx = await token.approve(morphoCreditAddr, amount);
316
+ await approveTx.wait();
317
+
318
+ // Deposit for AgentAccount
319
+ const morpho = new ethers.Contract(morphoCreditAddr, MORPHO_CREDIT_ABI, signer);
320
+ const depositTx = await morpho.depositCollateralFor(accountAddr, tokenInfo.address, amount);
321
+ await depositTx.wait();
322
+
323
+ // Approve credit provider on AgentAccount
324
+ const account = new ethers.Contract(accountAddr, [
325
+ ...AGENT_ACCOUNT_ABI,
326
+ "function approveCreditProvider(address creditProvider)",
327
+ ], signer);
328
+ try {
329
+ const cpTx = await account.approveCreditProvider(morphoCreditAddr);
330
+ await cpTx.wait();
331
+ } catch { /* already approved */ }
332
+
333
+ const pos = await morpho.getPosition(accountAddr, tokenInfo.address);
334
+
335
+ return ok(JSON.stringify({
336
+ status: "deposited",
337
+ amount: `${params.amount} ${params.token}`,
338
+ totalCollateral: `${ethers.formatUnits(pos.collateralAmount, tokenInfo.decimals)} ${params.token}`,
339
+ agentAccount: accountAddr,
340
+ tx: depositTx.hash,
341
+ }));
342
+ } catch (e) { return fail(e); }
343
+ },
344
+ });
345
+
346
+ // ═══════════════════════════════════════════════════════
347
+ // TOOL: morpho_borrow
348
+ // ═══════════════════════════════════════════════════════
349
+ api.registerTool({
350
+ name: "morpho_borrow",
351
+ description:
352
+ "Borrow USDC against deposited collateral via Morpho Blue. USDC lands in AgentAccount. Requires collateral deposited first.",
353
+ parameters: {
354
+ type: "object",
355
+ properties: {
356
+ amount: { type: "string", description: "Amount in USD to borrow (e.g. '100')" },
357
+ },
358
+ required: ["amount"],
359
+ },
360
+ async execute(_id: string, params: { amount: string }) {
361
+ try {
362
+ const cfg = getConfig(api);
363
+ const signer = getSigner(cfg);
364
+ const status = await getBackendStatus(cfg);
365
+ if (!cfg.agentId) return fail("No agentId configured");
366
+
367
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
368
+ const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
369
+
370
+ // Find active collateral
371
+ let activeToken: string | null = null;
372
+ for (const [symbol, info] of Object.entries(COLLATERAL_TOKENS)) {
373
+ const pos = await morpho.getPosition(accountAddr, info.address);
374
+ if (pos.collateralAmount > 0n) { activeToken = symbol; break; }
375
+ }
376
+ if (!activeToken) return fail("No collateral deposited. Use morpho_deposit first.");
377
+
378
+ const collateralAddr = COLLATERAL_TOKENS[activeToken].address;
379
+ const amountWei = ethers.parseUnits(params.amount, 6);
380
+
381
+ // Borrow via AgentAccount.execute → drawWithCollateral
382
+ const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
383
+ const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
384
+ const calldata = morphoIface.encodeFunctionData("drawWithCollateral", [collateralAddr, amountWei]);
385
+
386
+ const tx = await account.execute(status.contracts.morphoCredit, 0, calldata);
387
+ await tx.wait();
388
+
389
+ const totalDebt = await morpho.getTotalDebt(accountAddr);
390
+
391
+ return ok(JSON.stringify({
392
+ status: "borrowed",
393
+ amount: `$${params.amount}`,
394
+ collateral: activeToken,
395
+ totalDebt: `$${ethers.formatUnits(totalDebt, 6)}`,
396
+ agentAccount: accountAddr,
397
+ tx: tx.hash,
398
+ }));
399
+ } catch (e) { return fail(e); }
400
+ },
401
+ });
402
+
403
+ // ═══════════════════════════════════════════════════════
404
+ // TOOL: morpho_repay
405
+ // ═══════════════════════════════════════════════════════
406
+ api.registerTool({
407
+ name: "morpho_repay",
408
+ description:
409
+ "Repay borrowed USDC back to Morpho Blue from AgentAccount. Reduces debt.",
410
+ parameters: {
411
+ type: "object",
412
+ properties: {
413
+ amount: { type: "string", description: "Amount in USD to repay (e.g. '50')" },
414
+ },
415
+ required: ["amount"],
416
+ },
417
+ async execute(_id: string, params: { amount: string }) {
418
+ try {
419
+ const cfg = getConfig(api);
420
+ const signer = getSigner(cfg);
421
+ const status = await getBackendStatus(cfg);
422
+ if (!cfg.agentId) return fail("No agentId configured");
423
+
424
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
425
+ const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
426
+
427
+ // Find active collateral with debt
428
+ let activeToken: string | null = null;
429
+ for (const [symbol, info] of Object.entries(COLLATERAL_TOKENS)) {
430
+ const pos = await morpho.getPosition(accountAddr, info.address);
431
+ if (pos.borrowedAmount > 0n) { activeToken = symbol; break; }
432
+ }
433
+ if (!activeToken) return fail("No Morpho debt to repay");
434
+
435
+ const collateralAddr = COLLATERAL_TOKENS[activeToken].address;
436
+ const amountWei = ethers.parseUnits(params.amount, 6);
437
+
438
+ // Check USDC in AgentAccount
439
+ const usdc = new ethers.Contract(status.contracts.usdc, ERC20_ABI, signer.provider!);
440
+ const accBalance = await usdc.balanceOf(accountAddr);
441
+ if (accBalance < amountWei) {
442
+ return fail(`Insufficient USDC in AgentAccount: have $${ethers.formatUnits(accBalance, 6)}, need $${params.amount}`);
443
+ }
444
+
445
+ const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
446
+ const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
447
+ const erc20Iface = new ethers.Interface(ERC20_ABI);
448
+
449
+ // Approve USDC for MorphoCredit
450
+ const approveData = erc20Iface.encodeFunctionData("approve", [status.contracts.morphoCredit, amountWei]);
451
+ const approveTx = await account.execute(status.contracts.usdc, 0, approveData);
452
+ await approveTx.wait();
453
+
454
+ // Repay
455
+ const repayData = morphoIface.encodeFunctionData("repayWithCollateral", [collateralAddr, amountWei]);
456
+ const tx = await account.execute(status.contracts.morphoCredit, 0, repayData);
457
+ await tx.wait();
458
+
459
+ const totalDebt = await morpho.getTotalDebt(accountAddr);
460
+
461
+ return ok(JSON.stringify({
462
+ status: "repaid",
463
+ amount: `$${params.amount}`,
464
+ remainingDebt: `$${ethers.formatUnits(totalDebt, 6)}`,
465
+ tx: tx.hash,
466
+ }));
467
+ } catch (e) { return fail(e); }
468
+ },
469
+ });
470
+
471
+ // ═══════════════════════════════════════════════════════
472
+ // TOOL: morpho_withdraw
473
+ // ═══════════════════════════════════════════════════════
474
+ api.registerTool({
475
+ name: "morpho_withdraw",
476
+ description:
477
+ "Withdraw collateral from Morpho Blue back to EOA wallet. Use amount 'all' to withdraw maximum. Must maintain 120% LTV if debt remains.",
478
+ parameters: {
479
+ type: "object",
480
+ properties: {
481
+ amount: { type: "string", description: "Amount to withdraw (e.g. '0.05' or 'all')" },
482
+ token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
483
+ },
484
+ required: ["amount", "token"],
485
+ },
486
+ async execute(_id: string, params: { amount: string; token: string }) {
487
+ try {
488
+ const cfg = getConfig(api);
489
+ const signer = getSigner(cfg);
490
+ const status = await getBackendStatus(cfg);
491
+ if (!cfg.agentId) return fail("No agentId configured");
492
+
493
+ const tokenInfo = COLLATERAL_TOKENS[params.token];
494
+ if (!tokenInfo) return fail(`Unsupported token: ${params.token}`);
495
+
496
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
497
+ const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
498
+ const pos = await morpho.getPosition(accountAddr, tokenInfo.address);
499
+
500
+ if (pos.collateralAmount === 0n) return fail(`No ${params.token} collateral deposited`);
501
+
502
+ const withdrawAmount = params.amount.toLowerCase() === "all" ? pos.collateralAmount : ethers.parseUnits(params.amount, tokenInfo.decimals);
503
+
504
+ if (withdrawAmount > pos.collateralAmount) {
505
+ return fail(`Cannot withdraw more than deposited: max ${ethers.formatUnits(pos.collateralAmount, tokenInfo.decimals)} ${params.token}`);
506
+ }
507
+
508
+ const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
509
+ const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
510
+
511
+ // Step 1: Withdraw from Morpho → AgentAccount
512
+ const withdrawData = morphoIface.encodeFunctionData("withdrawCollateral", [tokenInfo.address, withdrawAmount]);
513
+ const tx1 = await account.execute(status.contracts.morphoCredit, 0, withdrawData);
514
+ await tx1.wait();
515
+
516
+ // Step 2: Move from AgentAccount → EOA
517
+ const tx2 = await account.withdraw(tokenInfo.address, withdrawAmount, signer.address);
518
+ await tx2.wait();
519
+
520
+ const newPos = await morpho.getPosition(accountAddr, tokenInfo.address);
521
+
522
+ return ok(JSON.stringify({
523
+ status: "withdrawn",
524
+ amount: `${ethers.formatUnits(withdrawAmount, tokenInfo.decimals)} ${params.token}`,
525
+ remainingCollateral: `${ethers.formatUnits(newPos.collateralAmount, tokenInfo.decimals)} ${params.token}`,
526
+ remainingDebt: `$${ethers.formatUnits(newPos.borrowedAmount, 6)}`,
527
+ destination: signer.address,
528
+ tx: tx1.hash,
529
+ }));
530
+ } catch (e) { return fail(e); }
531
+ },
532
+ });
533
+
534
+ // ═══════════════════════════════════════════════════════
535
+ // TOOL: morpho_compare
536
+ // ═══════════════════════════════════════════════════════
537
+ api.registerTool({
538
+ name: "morpho_compare",
539
+ description:
540
+ "Compare Morpho credit options — shows how much collateral is needed to borrow a given USD amount, with current oracle prices and wallet balances.",
541
+ parameters: {
542
+ type: "object",
543
+ properties: {
544
+ amount: { type: "string", description: "Amount in USD to compare (e.g. '100')" },
545
+ },
546
+ required: ["amount"],
547
+ },
548
+ async execute(_id: string, params: { amount: string }) {
549
+ try {
550
+ const cfg = getConfig(api);
551
+ const { data } = await axios.get(`${cfg.backendUrl}/morpho/estimate/${params.amount}`);
552
+ const signer = getSigner(cfg);
553
+
554
+ // Get wallet balances
555
+ const balances: Record<string, string> = {};
556
+ for (const [symbol, info] of Object.entries(COLLATERAL_TOKENS)) {
557
+ const token = new ethers.Contract(info.address, ERC20_ABI, signer.provider!);
558
+ const bal = await token.balanceOf(signer.address);
559
+ balances[symbol] = ethers.formatUnits(bal, info.decimals);
560
+ }
561
+
562
+ return ok(JSON.stringify({
563
+ borrowAmount: `$${params.amount}`,
564
+ estimates: data,
565
+ walletBalances: balances,
566
+ }, null, 2));
567
+ } catch (e) { return fail(e); }
568
+ },
569
+ });
570
+
571
+ // ═══════════════════════════════════════════════════════
572
+ // TOOL: morpho_markets
573
+ // ═══════════════════════════════════════════════════════
574
+ api.registerTool({
575
+ name: "morpho_markets",
576
+ description: "List all supported Morpho Blue markets with collateral tokens and parameters.",
577
+ parameters: { type: "object", properties: {}, required: [] },
578
+ async execute() {
579
+ try {
580
+ const cfg = getConfig(api);
581
+ const { data } = await axios.get(`${cfg.backendUrl}/morpho/markets`);
582
+ return ok(JSON.stringify(data, null, 2));
583
+ } catch (e) { return fail(e); }
584
+ },
585
+ });
586
+
587
+ // ═══════════════════════════════════════════════════════
588
+ // TOOL: agether_score
589
+ // ═══════════════════════════════════════════════════════
590
+ api.registerTool({
591
+ name: "agether_score",
592
+ description: "Get the agent's Bayesian credit score with 5-factor subscores.",
593
+ parameters: { type: "object", properties: {}, required: [] },
594
+ async execute() {
595
+ try {
596
+ const cfg = getConfig(api);
597
+ if (!cfg.agentId) return fail("No agentId configured");
598
+ const { data } = await axios.get(`${cfg.backendUrl}/credit/score/${cfg.agentId}`);
599
+ return ok(JSON.stringify(data, null, 2));
600
+ } catch (e) { return fail(e); }
601
+ },
602
+ });
603
+
604
+ // ═══════════════════════════════════════════════════════
605
+ // TOOL: wallet_fund
606
+ // ═══════════════════════════════════════════════════════
607
+ api.registerTool({
608
+ name: "wallet_fund",
609
+ description: "Transfer USDC from EOA wallet into AgentAccount.",
610
+ parameters: {
611
+ type: "object",
612
+ properties: {
613
+ amount: { type: "string", description: "USDC amount (e.g. '50')" },
614
+ },
615
+ required: ["amount"],
616
+ },
617
+ async execute(_id: string, params: { amount: string }) {
618
+ try {
619
+ const cfg = getConfig(api);
620
+ const signer = getSigner(cfg);
621
+ const status = await getBackendStatus(cfg);
622
+ if (!cfg.agentId) return fail("No agentId configured");
623
+
624
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
625
+ const amountWei = ethers.parseUnits(params.amount, 6);
626
+
627
+ const usdc = new ethers.Contract(status.contracts.usdc, ERC20_ABI, signer);
628
+ const balance = await usdc.balanceOf(signer.address);
629
+ if (balance < amountWei) {
630
+ return fail(`Insufficient USDC: have $${ethers.formatUnits(balance, 6)}, need $${params.amount}`);
631
+ }
632
+
633
+ const tx = await usdc.transfer(accountAddr, amountWei);
634
+ await tx.wait();
635
+
636
+ return ok(JSON.stringify({
637
+ status: "funded",
638
+ amount: `$${params.amount}`,
639
+ agentAccount: accountAddr,
640
+ tx: tx.hash,
641
+ }));
642
+ } catch (e) { return fail(e); }
643
+ },
644
+ });
645
+
646
+ // ═══════════════════════════════════════════════════════
647
+ // TOOL: x402_pay
648
+ // ═══════════════════════════════════════════════════════
649
+ api.registerTool({
650
+ name: "x402_pay",
651
+ description:
652
+ "Make a paid API call using x402 protocol. Pays with USDC via EIP-3009 signature. Returns the API response.",
653
+ parameters: {
654
+ type: "object",
655
+ properties: {
656
+ url: { type: "string", description: "API endpoint URL" },
657
+ method: { type: "string", enum: ["GET", "POST"], description: "HTTP method (default: GET)" },
658
+ body: { type: "string", description: "JSON body for POST requests" },
659
+ },
660
+ required: ["url"],
661
+ },
662
+ async execute(_id: string, params: { url: string; method?: string; body?: string }) {
663
+ try {
664
+ const cfg = getConfig(api);
665
+ // Delegate to CLI for now since x402 flow is complex (EIP-3009 signing)
666
+ const { execSync } = await import("child_process");
667
+ const args = [`x402 "${params.url}"`];
668
+ if (params.method) args.push(`--method ${params.method}`);
669
+ if (params.body) args.push(`--body '${params.body}'`);
670
+
671
+ const env = {
672
+ ...process.env,
673
+ AGETHER_PRIVATE_KEY: cfg.privateKey,
674
+ AGETHER_RPC_URL: cfg.rpcUrl,
675
+ AGETHER_BACKEND_URL: cfg.backendUrl,
676
+ };
677
+
678
+ const result = execSync(`agether ${args.join(" ")}`, {
679
+ encoding: "utf-8",
680
+ timeout: 30000,
681
+ env,
682
+ });
683
+
684
+ return ok(result);
685
+ } catch (e: any) {
686
+ return fail(e.stderr || e.stdout || e.message);
687
+ }
688
+ },
689
+ });
690
+
691
+ // ═══════════════════════════════════════════════════════
692
+ // AUTO-REPLY COMMANDS (no AI needed)
693
+ // ═══════════════════════════════════════════════════════
694
+
695
+ api.registerCommand({
696
+ name: "balance",
697
+ description: "Show agent wallet balances (no AI)",
698
+ handler: async () => {
699
+ try {
700
+ const cfg = getConfig(api);
701
+ const signer = getSigner(cfg);
702
+ const provider = signer.provider!;
703
+ const ethBal = await provider.getBalance(signer.address);
704
+ const status = await getBackendStatus(cfg);
705
+ const usdc = new ethers.Contract(status.contracts.usdc, ERC20_ABI, provider);
706
+ const usdcBal = await usdc.balanceOf(signer.address);
707
+
708
+ let text = `💰 Agent #${cfg.agentId || "?"}\n`;
709
+ text += `Address: ${signer.address}\n`;
710
+ text += `ETH: ${parseFloat(ethers.formatEther(ethBal)).toFixed(6)}\n`;
711
+ text += `USDC: $${ethers.formatUnits(usdcBal, 6)}`;
712
+
713
+ if (cfg.agentId && status.contracts.accountFactory) {
714
+ try {
715
+ const accAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
716
+ const accUsdc = await usdc.balanceOf(accAddr);
717
+ text += `\n\n🏦 AgentAccount: ${accAddr}\nUSDC: $${ethers.formatUnits(accUsdc, 6)}`;
718
+ } catch { /* no account */ }
719
+ }
720
+
721
+ return { text };
722
+ } catch (e: any) {
723
+ return { text: `❌ ${e.message}` };
724
+ }
725
+ },
726
+ });
727
+
728
+ api.registerCommand({
729
+ name: "morpho",
730
+ description: "Show Morpho credit positions (no AI)",
731
+ handler: async () => {
732
+ try {
733
+ const cfg = getConfig(api);
734
+ const signer = getSigner(cfg);
735
+ const status = await getBackendStatus(cfg);
736
+ if (!cfg.agentId) return { text: "❌ No agentId configured" };
737
+
738
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
739
+ const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
740
+ const totalDebt = await morpho.getTotalDebt(accountAddr);
741
+
742
+ let text = `📊 Morpho — Agent #${cfg.agentId}\nAccount: ${accountAddr}\nTotal debt: $${ethers.formatUnits(totalDebt, 6)}\n`;
743
+
744
+ for (const [symbol, info] of Object.entries(COLLATERAL_TOKENS)) {
745
+ const pos = await morpho.getPosition(accountAddr, info.address);
746
+ if (pos.collateralAmount > 0n || pos.borrowedAmount > 0n) {
747
+ text += `\n${symbol}: ${ethers.formatUnits(pos.collateralAmount, info.decimals)} collateral, $${ethers.formatUnits(pos.borrowedAmount, 6)} debt`;
748
+ }
749
+ }
750
+
751
+ return { text };
752
+ } catch (e: any) {
753
+ return { text: `❌ ${e.message}` };
754
+ }
755
+ },
756
+ });
757
+ }