@agether/agether 2.4.0 → 2.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/agether",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "OpenClaw plugin for Agether — onchain credit for AI agents on Ethereum & Base",
5
5
  "main": "src/index.ts",
6
6
  "openclaw": {
@@ -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 mainnet**. You can borrow USDC, earn yield by lending, and pay for APIs autonomously.
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
- ## 🛠 YOUR TOOLS
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 "alerts" array:
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
- 3. Now you have full context to handle any request
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
- - All on Base mainnet, all autonomous
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
- | Chain | Base mainnet (8453) |
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` (free, rate-limited).
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 (Base Mainnet)
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,19 +15,19 @@
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";
27
27
 
28
28
  // ─── Constants ────────────────────────────────────────────
29
29
 
30
- const BACKEND_URL = "https://api.agether.ai";
30
+ const BACKEND_URL = "http://95.179.189.214:3001";
31
31
 
32
32
  // Per-chain defaults
33
33
  const CHAIN_DEFAULTS: Record<number, { rpc: string; explorer: string; chainName: string }> = {
@@ -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 chainId = (cfg.chain as ChainId) || ChainId.Ethereum;
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 account address required for payment
910
+ // x402-gated fresh score via ScoringClient
837
911
  const accountAddress = await client.getAccountAddress();
838
912
  const contracts = getContractAddresses(cfg.chainId);
839
- const x402 = new X402Client({
840
- privateKey: cfg.privateKey,
841
- rpcUrl: cfg.rpcUrl,
842
- agentId,
843
- accountAddress,
844
- // Safe7579 needs validator prefix for ERC-1271 isValidSignature routing
845
- validatorModule: contracts.erc8004ValidationModule,
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 x402.get(`${BACKEND_URL}/score/${agentId}?chain=${cfg.chainId}`);
848
- if (!result.success) return fail(result.error || "Score request failed");
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 { data } = await axios.get(`${BACKEND_URL}/score/${agentId}/current?chain=${cfg.chainId}`);
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: CHAIN_DEFAULTS[cfg.chainId]?.chainName ?? `Chain ${cfg.chainId}`,
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 prefChainId = (pluginCfg.chain as ChainId) || ChainId.Ethereum;
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
- checks.push({ check: "Chain", status: `✅ ${chainName} (${prefChainId})` });
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