@agether/agether 2.3.1 → 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.
@@ -8,6 +8,12 @@
8
8
  "type": "object",
9
9
  "additionalProperties": false,
10
10
  "properties": {
11
+ "chain": {
12
+ "type": "number",
13
+ "description": "Chain ID: 1 = Ethereum (default), 8453 = Base",
14
+ "default": 1,
15
+ "enum": [1, 8453]
16
+ },
11
17
  "agentId": {
12
18
  "type": "string",
13
19
  "description": "ERC-8004 agent ID (set after registration, auto-saved by tools)"
@@ -36,6 +42,7 @@
36
42
  "required": []
37
43
  },
38
44
  "uiHints": {
45
+ "chain": { "label": "Chain", "placeholder": "1 (Ethereum)" },
39
46
  "agentId": { "label": "Agent ID", "placeholder": "17676" },
40
47
  "autoDraw": { "label": "Auto-Draw Credit", "placeholder": "false" },
41
48
  "autoYield": { "label": "Auto-Yield (use yield before borrowing)", "placeholder": "false" },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agether/agether",
3
- "version": "2.3.1",
4
- "description": "OpenClaw plugin for Agether — onchain credit for AI agents",
3
+ "version": "2.5.0",
4
+ "description": "OpenClaw plugin for Agether — onchain credit for AI agents on Ethereum & Base",
5
5
  "main": "src/index.ts",
6
6
  "openclaw": {
7
7
  "extensions": [
@@ -9,7 +9,7 @@
9
9
  ]
10
10
  },
11
11
  "dependencies": {
12
- "@agether/sdk": "^2.6.1",
12
+ "@agether/sdk": "^2.7.0",
13
13
  "axios": "^1.6.0",
14
14
  "ethers": "^6.9.0"
15
15
  },
@@ -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
@@ -4,30 +4,44 @@
4
4
  * Each tool = MorphoClient / X402Client call → format result with txLink.
5
5
  * Market discovery via Morpho GraphQL API (no backend dependency for markets).
6
6
  *
7
+ * Supported chains: Ethereum (1, default), Base (8453).
8
+ *
7
9
  * v2: Secrets-first config
8
10
  * ─────────────────────────
9
11
  * - privateKey → AGETHER_PRIVATE_KEY env var (set via `openclaw secrets configure` → crypto)
10
12
  * - rpcUrl → auto-resolved from ALCHEMY_API_KEY / ANKR_API_KEY / QUICKNODE_URL env vars,
11
- * falls back to https://base-rpc.publicnode.com
12
- * - backendUrl → hardcoded (no config needed)
13
+ * falls back to publicnode.com (chain-aware)
13
14
  * - agentId → optional plugin config field (auto-saved by tools)
14
- *
15
- * The plugin works with an empty `plugins.entries.agether.config: {}`.
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
+ getContractAddresses,
25
+ ChainId,
24
26
  } from "@agether/sdk";
25
27
 
26
28
  // ─── Constants ────────────────────────────────────────────
27
29
 
28
30
  const BACKEND_URL = "http://95.179.189.214:3001";
29
- const DEFAULT_RPC = "https://base-rpc.publicnode.com";
30
- const BASESCAN = "https://basescan.org/tx";
31
+
32
+ // Per-chain defaults
33
+ const CHAIN_DEFAULTS: Record<number, { rpc: string; explorer: string; chainName: string }> = {
34
+ [ChainId.Ethereum]: {
35
+ rpc: "https://ethereum-rpc.publicnode.com",
36
+ explorer: "https://etherscan.io/tx",
37
+ chainName: "Ethereum",
38
+ },
39
+ [ChainId.Base]: {
40
+ rpc: "https://base-rpc.publicnode.com",
41
+ explorer: "https://basescan.org/tx",
42
+ chainName: "Base",
43
+ },
44
+ };
31
45
 
32
46
  // ─── Spending Cache (persists dailySpendLimit tracker across restarts) ────
33
47
 
@@ -73,11 +87,14 @@ interface PluginConfig {
73
87
  privateKey: string;
74
88
  agentId?: string;
75
89
  rpcUrl: string;
76
- backendUrl: string;
90
+ chainId: ChainId;
91
+ chainConfigured: boolean; // true if user explicitly set chain in config
77
92
  }
78
93
 
79
94
  function txLink(hash: string): string {
80
- return hash ? `${BASESCAN}/${hash}` : "";
95
+ if (!hash) return "";
96
+ const explorer = CHAIN_DEFAULTS[activeChainId]?.explorer ?? CHAIN_DEFAULTS[ChainId.Ethereum].explorer;
97
+ return `${explorer}/${hash}`;
81
98
  }
82
99
 
83
100
  function ok(text: string) {
@@ -102,18 +119,25 @@ function fail(err: unknown) {
102
119
  /**
103
120
  * Resolve RPC URL from environment secrets.
104
121
  * Priority: ALCHEMY_API_KEY → ANKR_API_KEY → QUICKNODE_URL → publicnode fallback.
122
+ * Chain-aware: uses the correct endpoint subdomain for the configured chain.
105
123
  */
106
- function resolveRpcUrl(): string {
124
+ function resolveRpcUrl(chainId: ChainId): string {
107
125
  const alchemy = process.env.ALCHEMY_API_KEY;
108
- if (alchemy) return `https://base-mainnet.g.alchemy.com/v2/${alchemy}`;
126
+ if (alchemy) {
127
+ const alchemyChain = chainId === ChainId.Base ? "base-mainnet" : "eth-mainnet";
128
+ return `https://${alchemyChain}.g.alchemy.com/v2/${alchemy}`;
129
+ }
109
130
 
110
131
  const ankr = process.env.ANKR_API_KEY;
111
- if (ankr) return `https://rpc.ankr.com/base/${ankr}`;
132
+ if (ankr) {
133
+ const ankrChain = chainId === ChainId.Base ? "base" : "eth";
134
+ return `https://rpc.ankr.com/${ankrChain}/${ankr}`;
135
+ }
112
136
 
113
137
  const quicknode = process.env.QUICKNODE_URL;
114
138
  if (quicknode) return quicknode;
115
139
 
116
- return DEFAULT_RPC;
140
+ return CHAIN_DEFAULTS[chainId]?.rpc ?? CHAIN_DEFAULTS[ChainId.Ethereum].rpc;
117
141
  }
118
142
 
119
143
  /**
@@ -134,16 +158,22 @@ function resolvePrivateKey(): string {
134
158
 
135
159
  function getConfig(api: any): PluginConfig {
136
160
  const cfg = api.config?.plugins?.entries?.["agether"]?.config ?? {};
161
+ const rawChain = cfg.chain;
162
+ const chainConfigured = rawChain !== undefined && rawChain !== null && rawChain !== "";
163
+ const chainId = (rawChain as ChainId) || ChainId.Ethereum;
164
+ activeChainId = chainId; // Update module-level for txLink
137
165
  return {
138
166
  privateKey: resolvePrivateKey(),
139
167
  agentId: cfg.agentId,
140
- rpcUrl: resolveRpcUrl(),
141
- backendUrl: BACKEND_URL,
168
+ rpcUrl: resolveRpcUrl(chainId),
169
+ chainId,
170
+ chainConfigured,
142
171
  };
143
172
  }
144
173
 
145
174
  // Module-level cache
146
175
  let cachedAgentId: string | undefined;
176
+ let activeChainId: ChainId = ChainId.Ethereum;
147
177
 
148
178
  function createClient(cfg: PluginConfig): MorphoClient {
149
179
  const agentId = cachedAgentId || cfg.agentId;
@@ -151,6 +181,7 @@ function createClient(cfg: PluginConfig): MorphoClient {
151
181
  privateKey: cfg.privateKey,
152
182
  rpcUrl: cfg.rpcUrl,
153
183
  agentId,
184
+ chainId: cfg.chainId,
154
185
  });
155
186
  }
156
187
 
@@ -177,6 +208,29 @@ function persistAgentId(agentId: string): string {
177
208
  }
178
209
  }
179
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
+
180
234
  // ─── Plugin Entry ─────────────────────────────────────────
181
235
 
182
236
  export default function register(api: any) {
@@ -186,7 +240,7 @@ export default function register(api: any) {
186
240
  api.registerTool({
187
241
  name: "agether_balance",
188
242
  description:
189
- "Check ETH and USDC balances for the agent's EOA wallet and AgentAccount on Base.",
243
+ "Check ETH and USDC balances for the agent's EOA wallet and AgentAccount.",
190
244
  parameters: { type: "object", properties: {}, required: [] },
191
245
  async execute() {
192
246
  try {
@@ -204,7 +258,7 @@ export default function register(api: any) {
204
258
  api.registerTool({
205
259
  name: "agether_register",
206
260
  description:
207
- "Register a new ERC-8004 agent identity on Base and create a Safe smart account (via Safe7579). Returns the new agentId.",
261
+ "Register a new ERC-8004 agent identity and create a Safe smart account (via Safe7579). Returns the new agentId.",
208
262
  parameters: {
209
263
  type: "object",
210
264
  properties: {
@@ -271,6 +325,53 @@ export default function register(api: any) {
271
325
  },
272
326
  });
273
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
+
274
375
  // ═══════════════════════════════════════════════════════
275
376
  // TOOL: agether_kya_status
276
377
  // ═══════════════════════════════════════════════════════
@@ -751,7 +852,7 @@ export default function register(api: any) {
751
852
  api.registerTool({
752
853
  name: "morpho_markets",
753
854
  description:
754
- "List available Morpho Blue USDC markets on Base — liquidity, supply/borrow APY, utilization, LLTV. " +
855
+ "List available Morpho Blue USDC markets — liquidity, supply/borrow APY, utilization, LLTV. " +
755
856
  "Optionally filter by collateral token.",
756
857
  parameters: {
757
858
  type: "object",
@@ -806,24 +907,30 @@ export default function register(api: any) {
806
907
  const agentId = client.getAgentId();
807
908
 
808
909
  if (params.refresh) {
809
- // x402-gated fresh score account address required for payment
910
+ // x402-gated fresh score via ScoringClient
810
911
  const accountAddress = await client.getAccountAddress();
811
- const x402 = new X402Client({
812
- privateKey: cfg.privateKey,
813
- rpcUrl: cfg.rpcUrl,
814
- backendUrl: cfg.backendUrl,
815
- agentId,
816
- accountAddress,
817
- // Safe7579 needs validator prefix for ERC-1271 isValidSignature routing
818
- validatorModule: "0xde896C58163b5f6cAC5B16C1b0109843f26106F6",
912
+ const contracts = getContractAddresses(cfg.chainId);
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
+ },
819
923
  });
820
- const result = await x402.get(`${cfg.backendUrl}/score/${agentId}`);
821
- if (!result.success) return fail(result.error || "Score request failed");
822
- return ok(JSON.stringify(result.data, null, 2));
924
+ const result = await scoring.requestScore(agentId);
925
+ return ok(JSON.stringify(result, null, 2));
823
926
  }
824
927
 
825
- // Free: read current onchain score
826
- const { data } = await axios.get(`${cfg.backendUrl}/score/${agentId}/current`);
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);
827
934
  return ok(JSON.stringify(data, null, 2));
828
935
  } catch (e) { return fail(e); }
829
936
  },
@@ -956,10 +1063,10 @@ export default function register(api: any) {
956
1063
 
957
1064
  const autoDrawEnabled = agetherCfg.autoDraw === true;
958
1065
  const autoYieldEnabled = agetherCfg.autoYield === true;
1066
+ const contracts = getContractAddresses(cfg.chainId);
959
1067
  const x402 = new X402Client({
960
1068
  privateKey: cfg.privateKey,
961
1069
  rpcUrl: cfg.rpcUrl,
962
- backendUrl: cfg.backendUrl,
963
1070
  agentId,
964
1071
  accountAddress,
965
1072
  autoDraw: autoDrawEnabled,
@@ -970,7 +1077,7 @@ export default function register(api: any) {
970
1077
  // Persist every spending update to cache file
971
1078
  onSpendingUpdate: (state: any) => saveSpendCache(state),
972
1079
  // Safe7579 needs validator prefix for ERC-1271 isValidSignature routing
973
- validatorModule: "0xde896C58163b5f6cAC5B16C1b0109843f26106F6",
1080
+ validatorModule: contracts.erc8004ValidationModule,
974
1081
  });
975
1082
 
976
1083
  let result;
@@ -1117,6 +1224,11 @@ export default function register(api: any) {
1117
1224
  alerts.push("⛽ Low ETH for gas — may fail on-chain transactions. Send ETH to EOA.");
1118
1225
  }
1119
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
+
1120
1232
  const result = {
1121
1233
  agentId: balances.agentId,
1122
1234
  walletAddress: balances.address,
@@ -1130,6 +1242,9 @@ export default function register(api: any) {
1130
1242
  totalBorrowingHeadroom: `$${(Number(maxBorrow.total) / 1e6).toFixed(2)}`,
1131
1243
  positions: positionHealth,
1132
1244
  alerts: alerts.length > 0 ? alerts : ["✅ All positions healthy"],
1245
+ chain: cfg.chainConfigured
1246
+ ? (CHAIN_DEFAULTS[cfg.chainId]?.chainName ?? `Chain ${cfg.chainId}`)
1247
+ : "?",
1133
1248
  rpcSource: process.env.ALCHEMY_API_KEY ? "Alchemy" :
1134
1249
  process.env.ANKR_API_KEY ? "Ankr" :
1135
1250
  process.env.QUICKNODE_URL ? "QuickNode" : "PublicNode (free)",
@@ -1161,11 +1276,21 @@ export default function register(api: any) {
1161
1276
  checks.push({ check: "Private key (AGETHER_PRIVATE_KEY)", status: "❌ missing", detail: "Set via: openclaw secrets configure → source: env → id: AGETHER_PRIVATE_KEY" });
1162
1277
  }
1163
1278
 
1164
- // 2. RPC URL
1165
- const rpcUrl = resolveRpcUrl();
1279
+ // 2. Chain & RPC URL
1280
+ const pluginCfg = api.config?.plugins?.entries?.["agether"]?.config ?? {};
1281
+ const rawChain = pluginCfg.chain;
1282
+ const chainConfigured = rawChain !== undefined && rawChain !== null && rawChain !== "";
1283
+ const prefChainId = (rawChain as ChainId) || ChainId.Ethereum;
1284
+ const chainName = CHAIN_DEFAULTS[prefChainId]?.chainName ?? "Unknown";
1285
+ const rpcUrl = resolveRpcUrl(prefChainId);
1166
1286
  const rpcSource = process.env.ALCHEMY_API_KEY ? "Alchemy" :
1167
1287
  process.env.ANKR_API_KEY ? "Ankr" :
1168
1288
  process.env.QUICKNODE_URL ? "QuickNode" : "PublicNode (free fallback)";
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
+ }
1169
1294
  checks.push({ check: "RPC endpoint", status: `✅ ${rpcSource}`, detail: rpcUrl.replace(/\/[a-zA-Z0-9_-]{20,}$/, '/***') });
1170
1295
 
1171
1296
  // 3. Agent registration
@@ -1361,8 +1486,9 @@ export default function register(api: any) {
1361
1486
  const balances = await client.getBalances();
1362
1487
  const agentId = balances.agentId ?? "?";
1363
1488
  const safeUsdc = balances.agentAccount?.usdc ?? "0";
1489
+ const chainName = CHAIN_DEFAULTS[cfg.chainId]?.chainName ?? `Chain ${cfg.chainId}`;
1364
1490
  api.logger?.info?.(
1365
- `[agether] Session start — Agent #${agentId}, EOA: ${balances.eth} ETH / $${balances.usdc} USDC, Safe: $${safeUsdc} USDC`,
1491
+ `[agether] Session start — ${chainName}, Agent #${agentId}, EOA: ${balances.eth} ETH / $${balances.usdc} USDC, Safe: $${safeUsdc} USDC`,
1366
1492
  );
1367
1493
  } catch {
1368
1494
  // Silently fail — hook should not block conversations