@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.
- package/openclaw.plugin.json +38 -0
- package/package.json +30 -0
- package/skills/agether/SKILL.md +471 -0
- package/src/index.ts +757 -0
|
@@ -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
|
+
}
|