@agether/agether 2.3.1 → 2.4.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.4.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
  },
package/src/index.ts CHANGED
@@ -4,15 +4,15 @@
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
18
  import axios from "axios";
@@ -21,13 +21,27 @@ import * as path from "path";
21
21
  import {
22
22
  MorphoClient,
23
23
  X402Client,
24
+ getContractAddresses,
25
+ ChainId,
24
26
  } from "@agether/sdk";
25
27
 
26
28
  // ─── Constants ────────────────────────────────────────────
27
29
 
28
- 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";
30
+ const BACKEND_URL = "https://api.agether.ai";
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,13 @@ interface PluginConfig {
73
87
  privateKey: string;
74
88
  agentId?: string;
75
89
  rpcUrl: string;
76
- backendUrl: string;
90
+ chainId: ChainId;
77
91
  }
78
92
 
79
93
  function txLink(hash: string): string {
80
- return hash ? `${BASESCAN}/${hash}` : "";
94
+ if (!hash) return "";
95
+ const explorer = CHAIN_DEFAULTS[activeChainId]?.explorer ?? CHAIN_DEFAULTS[ChainId.Ethereum].explorer;
96
+ return `${explorer}/${hash}`;
81
97
  }
82
98
 
83
99
  function ok(text: string) {
@@ -102,18 +118,25 @@ function fail(err: unknown) {
102
118
  /**
103
119
  * Resolve RPC URL from environment secrets.
104
120
  * Priority: ALCHEMY_API_KEY → ANKR_API_KEY → QUICKNODE_URL → publicnode fallback.
121
+ * Chain-aware: uses the correct endpoint subdomain for the configured chain.
105
122
  */
106
- function resolveRpcUrl(): string {
123
+ function resolveRpcUrl(chainId: ChainId): string {
107
124
  const alchemy = process.env.ALCHEMY_API_KEY;
108
- if (alchemy) return `https://base-mainnet.g.alchemy.com/v2/${alchemy}`;
125
+ if (alchemy) {
126
+ const alchemyChain = chainId === ChainId.Base ? "base-mainnet" : "eth-mainnet";
127
+ return `https://${alchemyChain}.g.alchemy.com/v2/${alchemy}`;
128
+ }
109
129
 
110
130
  const ankr = process.env.ANKR_API_KEY;
111
- if (ankr) return `https://rpc.ankr.com/base/${ankr}`;
131
+ if (ankr) {
132
+ const ankrChain = chainId === ChainId.Base ? "base" : "eth";
133
+ return `https://rpc.ankr.com/${ankrChain}/${ankr}`;
134
+ }
112
135
 
113
136
  const quicknode = process.env.QUICKNODE_URL;
114
137
  if (quicknode) return quicknode;
115
138
 
116
- return DEFAULT_RPC;
139
+ return CHAIN_DEFAULTS[chainId]?.rpc ?? CHAIN_DEFAULTS[ChainId.Ethereum].rpc;
117
140
  }
118
141
 
119
142
  /**
@@ -134,16 +157,19 @@ function resolvePrivateKey(): string {
134
157
 
135
158
  function getConfig(api: any): PluginConfig {
136
159
  const cfg = api.config?.plugins?.entries?.["agether"]?.config ?? {};
160
+ const chainId = (cfg.chain as ChainId) || ChainId.Ethereum;
161
+ activeChainId = chainId; // Update module-level for txLink
137
162
  return {
138
163
  privateKey: resolvePrivateKey(),
139
164
  agentId: cfg.agentId,
140
- rpcUrl: resolveRpcUrl(),
141
- backendUrl: BACKEND_URL,
165
+ rpcUrl: resolveRpcUrl(chainId),
166
+ chainId,
142
167
  };
143
168
  }
144
169
 
145
170
  // Module-level cache
146
171
  let cachedAgentId: string | undefined;
172
+ let activeChainId: ChainId = ChainId.Ethereum;
147
173
 
148
174
  function createClient(cfg: PluginConfig): MorphoClient {
149
175
  const agentId = cachedAgentId || cfg.agentId;
@@ -151,6 +177,7 @@ function createClient(cfg: PluginConfig): MorphoClient {
151
177
  privateKey: cfg.privateKey,
152
178
  rpcUrl: cfg.rpcUrl,
153
179
  agentId,
180
+ chainId: cfg.chainId,
154
181
  });
155
182
  }
156
183
 
@@ -186,7 +213,7 @@ export default function register(api: any) {
186
213
  api.registerTool({
187
214
  name: "agether_balance",
188
215
  description:
189
- "Check ETH and USDC balances for the agent's EOA wallet and AgentAccount on Base.",
216
+ "Check ETH and USDC balances for the agent's EOA wallet and AgentAccount.",
190
217
  parameters: { type: "object", properties: {}, required: [] },
191
218
  async execute() {
192
219
  try {
@@ -204,7 +231,7 @@ export default function register(api: any) {
204
231
  api.registerTool({
205
232
  name: "agether_register",
206
233
  description:
207
- "Register a new ERC-8004 agent identity on Base and create a Safe smart account (via Safe7579). Returns the new agentId.",
234
+ "Register a new ERC-8004 agent identity and create a Safe smart account (via Safe7579). Returns the new agentId.",
208
235
  parameters: {
209
236
  type: "object",
210
237
  properties: {
@@ -751,7 +778,7 @@ export default function register(api: any) {
751
778
  api.registerTool({
752
779
  name: "morpho_markets",
753
780
  description:
754
- "List available Morpho Blue USDC markets on Base — liquidity, supply/borrow APY, utilization, LLTV. " +
781
+ "List available Morpho Blue USDC markets — liquidity, supply/borrow APY, utilization, LLTV. " +
755
782
  "Optionally filter by collateral token.",
756
783
  parameters: {
757
784
  type: "object",
@@ -808,22 +835,22 @@ export default function register(api: any) {
808
835
  if (params.refresh) {
809
836
  // x402-gated fresh score — account address required for payment
810
837
  const accountAddress = await client.getAccountAddress();
838
+ const contracts = getContractAddresses(cfg.chainId);
811
839
  const x402 = new X402Client({
812
840
  privateKey: cfg.privateKey,
813
841
  rpcUrl: cfg.rpcUrl,
814
- backendUrl: cfg.backendUrl,
815
842
  agentId,
816
843
  accountAddress,
817
844
  // Safe7579 needs validator prefix for ERC-1271 isValidSignature routing
818
- validatorModule: "0xde896C58163b5f6cAC5B16C1b0109843f26106F6",
845
+ validatorModule: contracts.erc8004ValidationModule,
819
846
  });
820
- const result = await x402.get(`${cfg.backendUrl}/score/${agentId}`);
847
+ const result = await x402.get(`${BACKEND_URL}/score/${agentId}?chain=${cfg.chainId}`);
821
848
  if (!result.success) return fail(result.error || "Score request failed");
822
849
  return ok(JSON.stringify(result.data, null, 2));
823
850
  }
824
851
 
825
852
  // Free: read current onchain score
826
- const { data } = await axios.get(`${cfg.backendUrl}/score/${agentId}/current`);
853
+ const { data } = await axios.get(`${BACKEND_URL}/score/${agentId}/current?chain=${cfg.chainId}`);
827
854
  return ok(JSON.stringify(data, null, 2));
828
855
  } catch (e) { return fail(e); }
829
856
  },
@@ -956,10 +983,10 @@ export default function register(api: any) {
956
983
 
957
984
  const autoDrawEnabled = agetherCfg.autoDraw === true;
958
985
  const autoYieldEnabled = agetherCfg.autoYield === true;
986
+ const contracts = getContractAddresses(cfg.chainId);
959
987
  const x402 = new X402Client({
960
988
  privateKey: cfg.privateKey,
961
989
  rpcUrl: cfg.rpcUrl,
962
- backendUrl: cfg.backendUrl,
963
990
  agentId,
964
991
  accountAddress,
965
992
  autoDraw: autoDrawEnabled,
@@ -970,7 +997,7 @@ export default function register(api: any) {
970
997
  // Persist every spending update to cache file
971
998
  onSpendingUpdate: (state: any) => saveSpendCache(state),
972
999
  // Safe7579 needs validator prefix for ERC-1271 isValidSignature routing
973
- validatorModule: "0xde896C58163b5f6cAC5B16C1b0109843f26106F6",
1000
+ validatorModule: contracts.erc8004ValidationModule,
974
1001
  });
975
1002
 
976
1003
  let result;
@@ -1130,6 +1157,7 @@ export default function register(api: any) {
1130
1157
  totalBorrowingHeadroom: `$${(Number(maxBorrow.total) / 1e6).toFixed(2)}`,
1131
1158
  positions: positionHealth,
1132
1159
  alerts: alerts.length > 0 ? alerts : ["✅ All positions healthy"],
1160
+ chain: CHAIN_DEFAULTS[cfg.chainId]?.chainName ?? `Chain ${cfg.chainId}`,
1133
1161
  rpcSource: process.env.ALCHEMY_API_KEY ? "Alchemy" :
1134
1162
  process.env.ANKR_API_KEY ? "Ankr" :
1135
1163
  process.env.QUICKNODE_URL ? "QuickNode" : "PublicNode (free)",
@@ -1162,10 +1190,14 @@ export default function register(api: any) {
1162
1190
  }
1163
1191
 
1164
1192
  // 2. RPC URL
1165
- const rpcUrl = resolveRpcUrl();
1193
+ const pluginCfg = api.config?.plugins?.entries?.["agether"]?.config ?? {};
1194
+ const prefChainId = (pluginCfg.chain as ChainId) || ChainId.Ethereum;
1195
+ const chainName = CHAIN_DEFAULTS[prefChainId]?.chainName ?? "Unknown";
1196
+ const rpcUrl = resolveRpcUrl(prefChainId);
1166
1197
  const rpcSource = process.env.ALCHEMY_API_KEY ? "Alchemy" :
1167
1198
  process.env.ANKR_API_KEY ? "Ankr" :
1168
1199
  process.env.QUICKNODE_URL ? "QuickNode" : "PublicNode (free fallback)";
1200
+ checks.push({ check: "Chain", status: `✅ ${chainName} (${prefChainId})` });
1169
1201
  checks.push({ check: "RPC endpoint", status: `✅ ${rpcSource}`, detail: rpcUrl.replace(/\/[a-zA-Z0-9_-]{20,}$/, '/***') });
1170
1202
 
1171
1203
  // 3. Agent registration
@@ -1361,8 +1393,9 @@ export default function register(api: any) {
1361
1393
  const balances = await client.getBalances();
1362
1394
  const agentId = balances.agentId ?? "?";
1363
1395
  const safeUsdc = balances.agentAccount?.usdc ?? "0";
1396
+ const chainName = CHAIN_DEFAULTS[cfg.chainId]?.chainName ?? `Chain ${cfg.chainId}`;
1364
1397
  api.logger?.info?.(
1365
- `[agether] Session start — Agent #${agentId}, EOA: ${balances.eth} ETH / $${balances.usdc} USDC, Safe: $${safeUsdc} USDC`,
1398
+ `[agether] Session start — ${chainName}, Agent #${agentId}, EOA: ${balances.eth} ETH / $${balances.usdc} USDC, Safe: $${safeUsdc} USDC`,
1366
1399
  );
1367
1400
  } catch {
1368
1401
  // Silently fail — hook should not block conversations