@agether/agether 2.4.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/agether/SKILL.md +32 -9
- package/src/index.ts +112 -19
package/package.json
CHANGED
package/skills/agether/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: On-chain credit protocol for AI agents on Base. Morpho credit & len
|
|
|
5
5
|
|
|
6
6
|
# Agether — On-Chain Credit for AI Agents
|
|
7
7
|
|
|
8
|
-
You are an AI agent with on-chain financial capabilities on **Base
|
|
8
|
+
You are an AI agent with on-chain financial capabilities on **Ethereum** and **Base** mainnets. You can borrow USDC, earn yield by lending, and pay for APIs autonomously.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -62,7 +62,24 @@ Both `agether_set_agent` and `agether_register` save the agentId to config perma
|
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
|
-
##
|
|
65
|
+
## � CHAIN RESOLUTION (CRITICAL)
|
|
66
|
+
|
|
67
|
+
If `agether_health` returns `chain: "?"`, the chain is NOT configured. You MUST resolve this before proceeding with any on-chain action:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
1. Ask user: "Which blockchain would you like to use? **Ethereum** or **Base**?"
|
|
71
|
+
2. User chooses a chain
|
|
72
|
+
3. Call `agether_set_chain(chain: "<ethereum or base>")`
|
|
73
|
+
4. After set → call `agether_health` to confirm everything is working on that chain
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`agether_set_chain` saves the chain to config permanently (survives restarts). Accepts: `ethereum`, `eth`, `1`, `base`, `8453`.
|
|
77
|
+
|
|
78
|
+
**IMPORTANT:** Resolve chain BEFORE resolving agentId — the agent registration happens on a specific chain.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## �🛠 YOUR TOOLS
|
|
66
83
|
|
|
67
84
|
### Health & Diagnostics
|
|
68
85
|
| Tool | What it does |
|
|
@@ -76,6 +93,7 @@ Both `agether_set_agent` and `agether_register` save the agentId to config perma
|
|
|
76
93
|
| `agether_balance` | Show ETH + USDC + all collateral token balances for both EOA wallet and AgentAccount (Safe). Tokens auto-discovered from markets. |
|
|
77
94
|
| `agether_register` | Register on-chain: mints ERC-8004 identity NFT + creates Safe account (via Safe7579). Auto-saves agentId to config. |
|
|
78
95
|
| `agether_set_agent` | Set a known agentId (e.g. from memory) and save to config. Use when agentId is "?" but you remember it. |
|
|
96
|
+
| `agether_set_chain` | Set the active blockchain (Ethereum or Base) and save to config. Use when chain is "?" or user wants to switch chains. Persists across restarts. |
|
|
79
97
|
| `agether_score` | Get credit score. `refresh: false` = free read. `refresh: true` = pays x402, computes fresh score on-chain. **Always call this when user asks about score.** |
|
|
80
98
|
| `agether_kya_status` | Check if KYA code verification is required for this deployment. |
|
|
81
99
|
| `agether_set_kya` | Enable or disable KYA verification gate (owner only). |
|
|
@@ -129,6 +147,7 @@ These settings are in `openclaw.json` under `plugins.entries.agether.config`:
|
|
|
129
147
|
| Setting | Type | Default | Description |
|
|
130
148
|
|---------|------|---------|-------------|
|
|
131
149
|
| `agentId` | string | — | ERC-8004 agent ID (auto-saved by register/set_agent tools) |
|
|
150
|
+
| `chain` | number | — | Chain ID: `1` (Ethereum) or `8453` (Base). Auto-saved by `agether_set_chain`. If not set, bot asks user on first interaction. |
|
|
132
151
|
| `autoDraw` | boolean | `false` | Auto-borrow from Morpho when USDC is low for x402 payments |
|
|
133
152
|
| `autoYield` | boolean | `false` | Auto-withdraw earned yield before borrowing. Waterfall: balance → yield → borrow |
|
|
134
153
|
| `dailySpendLimitUsdc` | number | `0` | Daily spending cap for x402 auto-draw (0 = unlimited). Persisted to cache file. |
|
|
@@ -156,11 +175,13 @@ When `autoYield` and/or `autoDraw` are enabled, `x402_pay` automatically sources
|
|
|
156
175
|
### Session start (first message or new conversation)
|
|
157
176
|
```
|
|
158
177
|
1. agether_health ← ONE call for full context
|
|
159
|
-
2. Check the "
|
|
178
|
+
2. Check the "chain" field:
|
|
179
|
+
→ If chain is "?" → go to CHAIN RESOLUTION (must resolve before anything else)
|
|
180
|
+
3. Check the "alerts" array:
|
|
160
181
|
→ If any 🔴 alerts → WARN user immediately (liquidation risk!)
|
|
161
182
|
→ If any 🟡 alerts → mention the risk casually
|
|
162
183
|
→ If "agentId: ?" → go to AGENT ID RESOLUTION
|
|
163
|
-
|
|
184
|
+
4. Now you have full context to handle any request
|
|
164
185
|
```
|
|
165
186
|
|
|
166
187
|
### User asks to call a paid API (x402)
|
|
@@ -278,7 +299,7 @@ For supply positions (lent USDC):
|
|
|
278
299
|
|
|
279
300
|
### User asks "what can I do?" or "what is agether?"
|
|
280
301
|
```
|
|
281
|
-
Explain: You're an AI agent with on-chain credit on Base.
|
|
302
|
+
Explain: You're an AI agent with on-chain credit on Ethereum and Base.
|
|
282
303
|
You can:
|
|
283
304
|
- Deposit collateral and borrow USDC instantly (125% collateral ratio)
|
|
284
305
|
- Earn yield by lending USDC to Morpho markets
|
|
@@ -287,7 +308,7 @@ You can:
|
|
|
287
308
|
- Sponsor other agents with collateral
|
|
288
309
|
- Monitor position health and avoid liquidation
|
|
289
310
|
- Move funds freely between AgentAccount and EOA wallet
|
|
290
|
-
-
|
|
311
|
+
- Operate on Ethereum or Base mainnet, all autonomous
|
|
291
312
|
```
|
|
292
313
|
|
|
293
314
|
---
|
|
@@ -338,7 +359,7 @@ If something **fails**:
|
|
|
338
359
|
|
|
339
360
|
| Parameter | Value |
|
|
340
361
|
|-----------|-------|
|
|
341
|
-
|
|
|
362
|
+
| Chains | Ethereum mainnet (1) or Base mainnet (8453) — set via `agether_set_chain` |
|
|
342
363
|
| Currency | USDC (6 decimals) |
|
|
343
364
|
| Max LTV | 80% (= 125% collateral ratio) |
|
|
344
365
|
| Collateral tokens | Dynamically discovered from Morpho markets (call `morpho_markets`) |
|
|
@@ -373,7 +394,7 @@ The plugin reads secrets from environment variables via OpenClaw's secrets syste
|
|
|
373
394
|
| Ankr RPC key | `ANKR_API_KEY` | No | Alternative premium RPC |
|
|
374
395
|
| QuickNode RPC URL | `QUICKNODE_URL` | No | Full URL including API key |
|
|
375
396
|
|
|
376
|
-
If no RPC key is set, the plugin falls back to `https://base-rpc.publicnode.com`
|
|
397
|
+
If no RPC key is set, the plugin falls back to free public RPCs (`https://ethereum-rpc.publicnode.com` or `https://base-rpc.publicnode.com` depending on chain — rate-limited).
|
|
377
398
|
|
|
378
399
|
---
|
|
379
400
|
|
|
@@ -407,7 +428,9 @@ Wallet transfers:
|
|
|
407
428
|
|
|
408
429
|
---
|
|
409
430
|
|
|
410
|
-
## 📍 CONTRACT ADDRESSES
|
|
431
|
+
## 📍 CONTRACT ADDRESSES
|
|
432
|
+
|
|
433
|
+
Contracts are deployed on both **Ethereum** and **Base** mainnets. The SDK auto-resolves the correct addresses based on the configured chain. Key shared addresses:
|
|
411
434
|
|
|
412
435
|
| Contract | Address |
|
|
413
436
|
|----------|---------|
|
package/src/index.ts
CHANGED
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
* - chain → plugin config field (default: 1 = Ethereum)
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import axios from "axios";
|
|
19
18
|
import * as fs from "fs";
|
|
20
19
|
import * as path from "path";
|
|
21
20
|
import {
|
|
22
21
|
MorphoClient,
|
|
23
22
|
X402Client,
|
|
23
|
+
ScoringClient,
|
|
24
24
|
getContractAddresses,
|
|
25
25
|
ChainId,
|
|
26
26
|
} from "@agether/sdk";
|
|
@@ -88,6 +88,7 @@ interface PluginConfig {
|
|
|
88
88
|
agentId?: string;
|
|
89
89
|
rpcUrl: string;
|
|
90
90
|
chainId: ChainId;
|
|
91
|
+
chainConfigured: boolean; // true if user explicitly set chain in config
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
function txLink(hash: string): string {
|
|
@@ -157,13 +158,16 @@ function resolvePrivateKey(): string {
|
|
|
157
158
|
|
|
158
159
|
function getConfig(api: any): PluginConfig {
|
|
159
160
|
const cfg = api.config?.plugins?.entries?.["agether"]?.config ?? {};
|
|
160
|
-
const
|
|
161
|
+
const rawChain = cfg.chain;
|
|
162
|
+
const chainConfigured = rawChain !== undefined && rawChain !== null && rawChain !== "";
|
|
163
|
+
const chainId = (rawChain as ChainId) || ChainId.Ethereum;
|
|
161
164
|
activeChainId = chainId; // Update module-level for txLink
|
|
162
165
|
return {
|
|
163
166
|
privateKey: resolvePrivateKey(),
|
|
164
167
|
agentId: cfg.agentId,
|
|
165
168
|
rpcUrl: resolveRpcUrl(chainId),
|
|
166
169
|
chainId,
|
|
170
|
+
chainConfigured,
|
|
167
171
|
};
|
|
168
172
|
}
|
|
169
173
|
|
|
@@ -204,6 +208,29 @@ function persistAgentId(agentId: string): string {
|
|
|
204
208
|
}
|
|
205
209
|
}
|
|
206
210
|
|
|
211
|
+
/** Persist chain to openclaw.json so it survives restarts. */
|
|
212
|
+
function persistChainId(chainId: ChainId): string {
|
|
213
|
+
activeChainId = chainId;
|
|
214
|
+
try {
|
|
215
|
+
const home = process.env.HOME || process.env.USERPROFILE || "/root";
|
|
216
|
+
const cfgPath = path.join(home, ".openclaw", "openclaw.json");
|
|
217
|
+
const raw = fs.readFileSync(cfgPath, "utf-8");
|
|
218
|
+
const json = JSON.parse(raw);
|
|
219
|
+
|
|
220
|
+
// Ensure config path exists
|
|
221
|
+
if (!json.plugins) json.plugins = {};
|
|
222
|
+
if (!json.plugins.entries) json.plugins.entries = {};
|
|
223
|
+
if (!json.plugins.entries.agether) json.plugins.entries.agether = {};
|
|
224
|
+
if (!json.plugins.entries.agether.config) json.plugins.entries.agether.config = {};
|
|
225
|
+
|
|
226
|
+
json.plugins.entries.agether.config.chain = chainId;
|
|
227
|
+
fs.writeFileSync(cfgPath, JSON.stringify(json, null, 2));
|
|
228
|
+
return "saved";
|
|
229
|
+
} catch (e) {
|
|
230
|
+
return `write failed: ${e instanceof Error ? e.message : String(e)}`;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
207
234
|
// ─── Plugin Entry ─────────────────────────────────────────
|
|
208
235
|
|
|
209
236
|
export default function register(api: any) {
|
|
@@ -298,6 +325,53 @@ export default function register(api: any) {
|
|
|
298
325
|
},
|
|
299
326
|
});
|
|
300
327
|
|
|
328
|
+
// ═══════════════════════════════════════════════════════
|
|
329
|
+
// TOOL: agether_set_chain
|
|
330
|
+
// ═══════════════════════════════════════════════════════
|
|
331
|
+
api.registerTool({
|
|
332
|
+
name: "agether_set_chain",
|
|
333
|
+
description:
|
|
334
|
+
"Set the active blockchain network and save it to config. " +
|
|
335
|
+
"Use this when the user chooses a chain (Ethereum or Base). " +
|
|
336
|
+
"Accepts chain name ('ethereum', 'base') or chain ID (1, 8453). " +
|
|
337
|
+
"This persists across restarts.",
|
|
338
|
+
parameters: {
|
|
339
|
+
type: "object",
|
|
340
|
+
properties: {
|
|
341
|
+
chain: {
|
|
342
|
+
type: "string",
|
|
343
|
+
description: "Chain name ('ethereum', 'base') or chain ID ('1', '8453')",
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
required: ["chain"],
|
|
347
|
+
},
|
|
348
|
+
async execute(_id: string, params: { chain: string }) {
|
|
349
|
+
try {
|
|
350
|
+
// Resolve chain name / ID to ChainId
|
|
351
|
+
const input = params.chain.toLowerCase().trim();
|
|
352
|
+
let chainId: ChainId;
|
|
353
|
+
if (input === "ethereum" || input === "eth" || input === "1") {
|
|
354
|
+
chainId = ChainId.Ethereum;
|
|
355
|
+
} else if (input === "base" || input === "8453") {
|
|
356
|
+
chainId = ChainId.Base;
|
|
357
|
+
} else {
|
|
358
|
+
return fail(`Unknown chain "${params.chain}". Supported: ethereum (1), base (8453)`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const persistStatus = persistChainId(chainId);
|
|
362
|
+
const chainInfo = CHAIN_DEFAULTS[chainId];
|
|
363
|
+
return ok(JSON.stringify({
|
|
364
|
+
status: "chain_set",
|
|
365
|
+
chainId,
|
|
366
|
+
chainName: chainInfo.chainName,
|
|
367
|
+
explorer: chainInfo.explorer,
|
|
368
|
+
rpc: resolveRpcUrl(chainId).replace(/\/[a-zA-Z0-9_-]{20,}$/, '/***'),
|
|
369
|
+
configSaved: persistStatus,
|
|
370
|
+
}));
|
|
371
|
+
} catch (e) { return fail(e); }
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
|
|
301
375
|
// ═══════════════════════════════════════════════════════
|
|
302
376
|
// TOOL: agether_kya_status
|
|
303
377
|
// ═══════════════════════════════════════════════════════
|
|
@@ -833,24 +907,30 @@ export default function register(api: any) {
|
|
|
833
907
|
const agentId = client.getAgentId();
|
|
834
908
|
|
|
835
909
|
if (params.refresh) {
|
|
836
|
-
// x402-gated fresh score
|
|
910
|
+
// x402-gated fresh score via ScoringClient
|
|
837
911
|
const accountAddress = await client.getAccountAddress();
|
|
838
912
|
const contracts = getContractAddresses(cfg.chainId);
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
913
|
+
const scoring = new ScoringClient({
|
|
914
|
+
endpoint: BACKEND_URL,
|
|
915
|
+
chainId: cfg.chainId,
|
|
916
|
+
x402: {
|
|
917
|
+
privateKey: cfg.privateKey,
|
|
918
|
+
rpcUrl: cfg.rpcUrl,
|
|
919
|
+
agentId,
|
|
920
|
+
accountAddress,
|
|
921
|
+
validatorModule: contracts.erc8004ValidationModule,
|
|
922
|
+
},
|
|
846
923
|
});
|
|
847
|
-
const result = await
|
|
848
|
-
|
|
849
|
-
return ok(JSON.stringify(result.data, null, 2));
|
|
924
|
+
const result = await scoring.requestScore(agentId);
|
|
925
|
+
return ok(JSON.stringify(result, null, 2));
|
|
850
926
|
}
|
|
851
927
|
|
|
852
|
-
// Free: read current onchain score
|
|
853
|
-
const
|
|
928
|
+
// Free: read current onchain score via ScoringClient
|
|
929
|
+
const scoring = new ScoringClient({
|
|
930
|
+
endpoint: BACKEND_URL,
|
|
931
|
+
chainId: cfg.chainId,
|
|
932
|
+
});
|
|
933
|
+
const data = await scoring.getCurrentScore(agentId);
|
|
854
934
|
return ok(JSON.stringify(data, null, 2));
|
|
855
935
|
} catch (e) { return fail(e); }
|
|
856
936
|
},
|
|
@@ -1144,6 +1224,11 @@ export default function register(api: any) {
|
|
|
1144
1224
|
alerts.push("⛽ Low ETH for gas — may fail on-chain transactions. Send ETH to EOA.");
|
|
1145
1225
|
}
|
|
1146
1226
|
|
|
1227
|
+
// Check if chain is explicitly configured
|
|
1228
|
+
if (!cfg.chainConfigured) {
|
|
1229
|
+
alerts.push("⚠️ Chain not configured — currently defaulting to Ethereum. Ask user which chain to use and call agether_set_chain.");
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1147
1232
|
const result = {
|
|
1148
1233
|
agentId: balances.agentId,
|
|
1149
1234
|
walletAddress: balances.address,
|
|
@@ -1157,7 +1242,9 @@ export default function register(api: any) {
|
|
|
1157
1242
|
totalBorrowingHeadroom: `$${(Number(maxBorrow.total) / 1e6).toFixed(2)}`,
|
|
1158
1243
|
positions: positionHealth,
|
|
1159
1244
|
alerts: alerts.length > 0 ? alerts : ["✅ All positions healthy"],
|
|
1160
|
-
chain:
|
|
1245
|
+
chain: cfg.chainConfigured
|
|
1246
|
+
? (CHAIN_DEFAULTS[cfg.chainId]?.chainName ?? `Chain ${cfg.chainId}`)
|
|
1247
|
+
: "?",
|
|
1161
1248
|
rpcSource: process.env.ALCHEMY_API_KEY ? "Alchemy" :
|
|
1162
1249
|
process.env.ANKR_API_KEY ? "Ankr" :
|
|
1163
1250
|
process.env.QUICKNODE_URL ? "QuickNode" : "PublicNode (free)",
|
|
@@ -1189,15 +1276,21 @@ export default function register(api: any) {
|
|
|
1189
1276
|
checks.push({ check: "Private key (AGETHER_PRIVATE_KEY)", status: "❌ missing", detail: "Set via: openclaw secrets configure → source: env → id: AGETHER_PRIVATE_KEY" });
|
|
1190
1277
|
}
|
|
1191
1278
|
|
|
1192
|
-
// 2. RPC URL
|
|
1279
|
+
// 2. Chain & RPC URL
|
|
1193
1280
|
const pluginCfg = api.config?.plugins?.entries?.["agether"]?.config ?? {};
|
|
1194
|
-
const
|
|
1281
|
+
const rawChain = pluginCfg.chain;
|
|
1282
|
+
const chainConfigured = rawChain !== undefined && rawChain !== null && rawChain !== "";
|
|
1283
|
+
const prefChainId = (rawChain as ChainId) || ChainId.Ethereum;
|
|
1195
1284
|
const chainName = CHAIN_DEFAULTS[prefChainId]?.chainName ?? "Unknown";
|
|
1196
1285
|
const rpcUrl = resolveRpcUrl(prefChainId);
|
|
1197
1286
|
const rpcSource = process.env.ALCHEMY_API_KEY ? "Alchemy" :
|
|
1198
1287
|
process.env.ANKR_API_KEY ? "Ankr" :
|
|
1199
1288
|
process.env.QUICKNODE_URL ? "QuickNode" : "PublicNode (free fallback)";
|
|
1200
|
-
|
|
1289
|
+
if (chainConfigured) {
|
|
1290
|
+
checks.push({ check: "Chain", status: `✅ ${chainName} (${prefChainId})` });
|
|
1291
|
+
} else {
|
|
1292
|
+
checks.push({ check: "Chain", status: "⚠️ not configured (defaulting to Ethereum)", detail: "Ask user which chain to use and call agether_set_chain" });
|
|
1293
|
+
}
|
|
1201
1294
|
checks.push({ check: "RPC endpoint", status: `✅ ${rpcSource}`, detail: rpcUrl.replace(/\/[a-zA-Z0-9_-]{20,}$/, '/***') });
|
|
1202
1295
|
|
|
1203
1296
|
// 3. Agent registration
|