@agether/openclaw-plugin 1.1.1 → 1.3.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.
@@ -2,7 +2,7 @@
2
2
  "id": "agether",
3
3
  "name": "Agether Credit",
4
4
  "description": "On-chain credit protocol for AI agents — Morpho-backed overcollateralized credit, ERC-8004 identity, x402 payments",
5
- "version": "1.1.0",
5
+ "version": "1.2.0",
6
6
  "skills": ["skills/agether"],
7
7
  "configSchema": {
8
8
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/openclaw-plugin",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "OpenClaw plugin for Agether — on-chain credit for AI agents",
5
5
  "main": "src/index.ts",
6
6
  "openclaw": {
@@ -9,7 +9,7 @@
9
9
  ]
10
10
  },
11
11
  "dependencies": {
12
- "@agether/sdk": "^1.1.0",
12
+ "@agether/sdk": "^1.3.0",
13
13
  "axios": "^1.6.0",
14
14
  "ethers": "^6.9.0"
15
15
  },
package/src/index.ts CHANGED
@@ -30,6 +30,7 @@ const ERC8004_ABI = [
30
30
  "function register(string agentURI) returns (uint256)",
31
31
  "function ownerOf(uint256 tokenId) view returns (address)",
32
32
  "function balanceOf(address owner) view returns (uint256)",
33
+ "function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)",
33
34
  ];
34
35
 
35
36
  const COLLATERAL_TOKENS: Record<string, { address: string; decimals: number }> = {
@@ -69,6 +70,23 @@ async function getAccountAddr(signer: ethers.Wallet, factoryAddr: string, agentI
69
70
  return addr;
70
71
  }
71
72
 
73
+ // ─── Auto-resolve agentId from chain ──────────────────────
74
+ let _cachedAgentId: string | null = null;
75
+
76
+ async function resolveAgentId(cfg: PluginConfig, signer: ethers.Wallet, status: any): Promise<string> {
77
+ // 1. Config takes priority
78
+ if (cfg.agentId && cfg.agentId !== "0") return cfg.agentId;
79
+ // 2. Session cache
80
+ if (_cachedAgentId) return _cachedAgentId;
81
+ // 3. Look up on-chain: does this wallet own any ERC-8004 token?
82
+ const agentRegistry = new ethers.Contract(status.contracts.agentRegistry, ERC8004_ABI, signer.provider!);
83
+ const balance: bigint = await agentRegistry.balanceOf(signer.address);
84
+ if (balance === 0n) throw new Error("No agent registered. Use agether_register first.");
85
+ const tokenId: bigint = await agentRegistry.tokenOfOwnerByIndex(signer.address, 0);
86
+ _cachedAgentId = tokenId.toString();
87
+ return _cachedAgentId;
88
+ }
89
+
72
90
  function ok(text: string) {
73
91
  return { content: [{ type: "text" as const, text }] };
74
92
  }
@@ -103,15 +121,17 @@ export default function register(api: any) {
103
121
 
104
122
  const result: any = {
105
123
  address,
106
- agentId: cfg.agentId || "not set",
124
+ agentId: cfg.agentId || _cachedAgentId || "not registered",
107
125
  eth: ethers.formatEther(ethBal),
108
126
  usdc: ethers.formatUnits(usdcBal, 6),
109
127
  };
110
128
 
111
- // AgentAccount balances
112
- if (cfg.agentId && status.contracts.accountFactory) {
113
- try {
114
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
129
+ // AgentAccount balances — try to auto-resolve agentId
130
+ try {
131
+ const agentId = await resolveAgentId(cfg, signer, status);
132
+ result.agentId = agentId;
133
+ if (status.contracts.accountFactory) {
134
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
115
135
  const accEth = await provider.getBalance(accountAddr);
116
136
  const accUsdc = await usdc.balanceOf(accountAddr);
117
137
  result.agentAccount = {
@@ -119,8 +139,8 @@ export default function register(api: any) {
119
139
  eth: ethers.formatEther(accEth),
120
140
  usdc: ethers.formatUnits(accUsdc, 6),
121
141
  };
122
- } catch { /* no account yet */ }
123
- }
142
+ }
143
+ } catch { /* no agent registered yet */ }
124
144
 
125
145
  return ok(JSON.stringify(result, null, 2));
126
146
  } catch (e) { return fail(e); }
@@ -211,13 +231,15 @@ export default function register(api: any) {
211
231
  accountAddr = await factory.getAccount(agentId);
212
232
  }
213
233
 
234
+ // Cache agentId for the session so all subsequent tools work
235
+ _cachedAgentId = agentId.toString();
236
+
214
237
  return ok(JSON.stringify({
215
238
  status: "registered",
216
239
  agentId: agentId.toString(),
217
240
  address: signer.address,
218
241
  agentAccount: accountAddr,
219
242
  tx: tx.hash,
220
- important: "Save this agentId in your plugin config: plugins.entries.agether.config.agentId",
221
243
  }));
222
244
  } catch (e) { return fail(e); }
223
245
  },
@@ -236,9 +258,9 @@ export default function register(api: any) {
236
258
  const cfg = getConfig(api);
237
259
  const signer = getSigner(cfg);
238
260
  const status = await getBackendStatus(cfg);
239
- if (!cfg.agentId) return fail("No agentId configured");
261
+ const agentId = await resolveAgentId(cfg, signer, status);
240
262
 
241
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
263
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
242
264
  const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
243
265
 
244
266
  const positions: any[] = [];
@@ -285,13 +307,13 @@ export default function register(api: any) {
285
307
  const cfg = getConfig(api);
286
308
  const signer = getSigner(cfg);
287
309
  const status = await getBackendStatus(cfg);
288
- if (!cfg.agentId) return fail("No agentId configured");
310
+ const agentId = await resolveAgentId(cfg, signer, status);
289
311
 
290
312
  const tokenInfo = COLLATERAL_TOKENS[params.token];
291
313
  if (!tokenInfo) return fail(`Unsupported token: ${params.token}. Use WETH, wstETH, or cbETH`);
292
314
 
293
315
  const morphoCreditAddr = status.contracts.morphoCredit;
294
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
316
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
295
317
  const amount = ethers.parseUnits(params.amount, tokenInfo.decimals);
296
318
 
297
319
  // Check balance
@@ -351,13 +373,13 @@ export default function register(api: any) {
351
373
  const cfg = getConfig(api);
352
374
  const signer = getSigner(cfg);
353
375
  const status = await getBackendStatus(cfg);
354
- if (!cfg.agentId) return fail("No agentId configured");
376
+ const agentId = await resolveAgentId(cfg, signer, status);
355
377
 
356
378
  const tokenInfo = COLLATERAL_TOKENS[params.token];
357
379
  if (!tokenInfo) return fail(`Unsupported token: ${params.token}`);
358
380
 
359
381
  const morphoCreditAddr = status.contracts.morphoCredit;
360
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
382
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
361
383
  const collateralWei = ethers.parseUnits(params.collateralAmount, tokenInfo.decimals);
362
384
  const borrowWei = ethers.parseUnits(params.borrowAmount, 6);
363
385
 
@@ -526,9 +548,9 @@ export default function register(api: any) {
526
548
  const cfg = getConfig(api);
527
549
  const signer = getSigner(cfg);
528
550
  const status = await getBackendStatus(cfg);
529
- if (!cfg.agentId) return fail("No agentId configured");
551
+ const agentId = await resolveAgentId(cfg, signer, status);
530
552
 
531
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
553
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
532
554
  const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
533
555
 
534
556
  // Find active collateral
@@ -544,6 +566,10 @@ export default function register(api: any) {
544
566
 
545
567
  // Borrow via AgentAccount.execute → drawWithCollateral
546
568
  const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
569
+
570
+ // Ensure credit provider approved (one-time, idempotent)
571
+ try { const cpTx = await account.approveCreditProvider(status.contracts.morphoCredit); await cpTx.wait(); } catch { /* already approved */ }
572
+
547
573
  const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
548
574
  const calldata = morphoIface.encodeFunctionData("drawWithCollateral", [collateralAddr, amountWei]);
549
575
 
@@ -583,9 +609,9 @@ export default function register(api: any) {
583
609
  const cfg = getConfig(api);
584
610
  const signer = getSigner(cfg);
585
611
  const status = await getBackendStatus(cfg);
586
- if (!cfg.agentId) return fail("No agentId configured");
612
+ const agentId = await resolveAgentId(cfg, signer, status);
587
613
 
588
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
614
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
589
615
  const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
590
616
 
591
617
  // Find active collateral with debt
@@ -610,14 +636,15 @@ export default function register(api: any) {
610
636
  const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
611
637
  const erc20Iface = new ethers.Interface(ERC20_ABI);
612
638
 
613
- // Approve USDC for MorphoCredit
614
- const approveData = erc20Iface.encodeFunctionData("approve", [status.contracts.morphoCredit, amountWei]);
615
- const approveTx = await account.execute(status.contracts.usdc, 0, approveData);
616
- await approveTx.wait();
617
-
618
- // Repay
619
- const repayData = morphoIface.encodeFunctionData("repayWithCollateral", [collateralAddr, amountWei]);
620
- const tx = await account.execute(status.contracts.morphoCredit, 0, repayData);
639
+ // Single tx via executeBatch: approve USDC + repayWithCollateral
640
+ const tx = await account.executeBatch(
641
+ [status.contracts.usdc, status.contracts.morphoCredit],
642
+ [0, 0],
643
+ [
644
+ erc20Iface.encodeFunctionData("approve", [status.contracts.morphoCredit, amountWei]),
645
+ morphoIface.encodeFunctionData("repayWithCollateral", [collateralAddr, amountWei]),
646
+ ],
647
+ );
621
648
  await tx.wait();
622
649
 
623
650
  const totalDebt = await morpho.getTotalDebt(accountAddr);
@@ -652,12 +679,12 @@ export default function register(api: any) {
652
679
  const cfg = getConfig(api);
653
680
  const signer = getSigner(cfg);
654
681
  const status = await getBackendStatus(cfg);
655
- if (!cfg.agentId) return fail("No agentId configured");
682
+ const agentId = await resolveAgentId(cfg, signer, status);
656
683
 
657
684
  const tokenInfo = COLLATERAL_TOKENS[params.token];
658
685
  if (!tokenInfo) return fail(`Unsupported token: ${params.token}`);
659
686
 
660
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
687
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
661
688
  const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
662
689
  const pos = await morpho.getPosition(accountAddr, tokenInfo.address);
663
690
 
@@ -671,15 +698,18 @@ export default function register(api: any) {
671
698
 
672
699
  const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
673
700
  const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
701
+ const erc20Iface = new ethers.Interface(ERC20_ABI);
674
702
 
675
- // Step 1: Withdraw from Morpho AgentAccount
676
- const withdrawData = morphoIface.encodeFunctionData("withdrawCollateral", [tokenInfo.address, withdrawAmount]);
677
- const tx1 = await account.execute(status.contracts.morphoCredit, 0, withdrawData);
678
- await tx1.wait();
679
-
680
- // Step 2: Move from AgentAccount → EOA
681
- const tx2 = await account.withdraw(tokenInfo.address, withdrawAmount, signer.address);
682
- await tx2.wait();
703
+ // Single tx via executeBatch: withdrawCollateral + transfer to EOA
704
+ const tx = await account.executeBatch(
705
+ [status.contracts.morphoCredit, tokenInfo.address],
706
+ [0, 0],
707
+ [
708
+ morphoIface.encodeFunctionData("withdrawCollateral", [tokenInfo.address, withdrawAmount]),
709
+ erc20Iface.encodeFunctionData("transfer", [signer.address, withdrawAmount]),
710
+ ],
711
+ );
712
+ await tx.wait();
683
713
 
684
714
  const newPos = await morpho.getPosition(accountAddr, tokenInfo.address);
685
715
 
@@ -689,7 +719,7 @@ export default function register(api: any) {
689
719
  remainingCollateral: `${ethers.formatUnits(newPos.collateralAmount, tokenInfo.decimals)} ${params.token}`,
690
720
  remainingDebt: `$${ethers.formatUnits(newPos.borrowedAmount, 6)}`,
691
721
  destination: signer.address,
692
- tx: tx1.hash,
722
+ tx: tx.hash,
693
723
  }));
694
724
  } catch (e) { return fail(e); }
695
725
  },
@@ -758,8 +788,10 @@ export default function register(api: any) {
758
788
  async execute() {
759
789
  try {
760
790
  const cfg = getConfig(api);
761
- if (!cfg.agentId) return fail("No agentId configured");
762
- const { data } = await axios.get(`${cfg.backendUrl}/credit/score/${cfg.agentId}`);
791
+ const signer = getSigner(cfg);
792
+ const status = await getBackendStatus(cfg);
793
+ const agentId = await resolveAgentId(cfg, signer, status);
794
+ const { data } = await axios.get(`${cfg.backendUrl}/credit/score/${agentId}`);
763
795
  return ok(JSON.stringify(data, null, 2));
764
796
  } catch (e) { return fail(e); }
765
797
  },
@@ -783,9 +815,9 @@ export default function register(api: any) {
783
815
  const cfg = getConfig(api);
784
816
  const signer = getSigner(cfg);
785
817
  const status = await getBackendStatus(cfg);
786
- if (!cfg.agentId) return fail("No agentId configured");
818
+ const agentId = await resolveAgentId(cfg, signer, status);
787
819
 
788
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
820
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
789
821
  const amountWei = ethers.parseUnits(params.amount, 6);
790
822
 
791
823
  const usdc = new ethers.Contract(status.contracts.usdc, ERC20_ABI, signer);
@@ -834,15 +866,15 @@ export default function register(api: any) {
834
866
 
835
867
  // Resolve AgentAccount address for x402 payments
836
868
  let accountAddress: string | undefined;
837
- if (cfg.agentId && status.contracts.accountFactory) {
838
- try {
839
- accountAddress = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
840
- } catch { /* no account, pay from EOA */ }
841
- }
869
+ let agentId: string | undefined;
870
+ try {
871
+ agentId = await resolveAgentId(cfg, signer, status);
872
+ accountAddress = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
873
+ } catch { /* no account, pay from EOA */ }
842
874
 
843
875
  // Auto-draw: if enabled, check USDC balance and borrow if needed
844
876
  const shouldAutoDraw = params.autoDraw ?? cfg.autoDraw;
845
- if (shouldAutoDraw && accountAddress && cfg.agentId) {
877
+ if (shouldAutoDraw && accountAddress && agentId) {
846
878
  const usdc = new ethers.Contract(status.contracts.usdc, ERC20_ABI, signer.provider!);
847
879
  const accBalance = await usdc.balanceOf(accountAddress);
848
880
  // If balance < $1, try to auto-borrow $10 (enough for several x402 calls)
@@ -871,7 +903,7 @@ export default function register(api: any) {
871
903
  privateKey: cfg.privateKey,
872
904
  rpcUrl: cfg.rpcUrl!,
873
905
  backendUrl: cfg.backendUrl!,
874
- agentId: cfg.agentId,
906
+ agentId: agentId,
875
907
  accountAddress,
876
908
  });
877
909
 
@@ -912,14 +944,17 @@ export default function register(api: any) {
912
944
  const usdc = new ethers.Contract(status.contracts.usdc, ERC20_ABI, provider);
913
945
  const usdcBal = await usdc.balanceOf(signer.address);
914
946
 
915
- let text = `💰 Agent #${cfg.agentId || "?"}\n`;
947
+ let agentId: string | undefined;
948
+ try { agentId = await resolveAgentId(cfg, signer, status); } catch {}
949
+
950
+ let text = `💰 Agent #${agentId || "?"}\n`;
916
951
  text += `Address: ${signer.address}\n`;
917
952
  text += `ETH: ${parseFloat(ethers.formatEther(ethBal)).toFixed(6)}\n`;
918
953
  text += `USDC: $${ethers.formatUnits(usdcBal, 6)}`;
919
954
 
920
- if (cfg.agentId && status.contracts.accountFactory) {
955
+ if (agentId && status.contracts.accountFactory) {
921
956
  try {
922
- const accAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
957
+ const accAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
923
958
  const accUsdc = await usdc.balanceOf(accAddr);
924
959
  text += `\n\n🏦 AgentAccount: ${accAddr}\nUSDC: $${ethers.formatUnits(accUsdc, 6)}`;
925
960
  } catch { /* no account */ }
@@ -940,13 +975,13 @@ export default function register(api: any) {
940
975
  const cfg = getConfig(api);
941
976
  const signer = getSigner(cfg);
942
977
  const status = await getBackendStatus(cfg);
943
- if (!cfg.agentId) return { text: "❌ No agentId configured" };
978
+ const agentId = await resolveAgentId(cfg, signer, status);
944
979
 
945
- const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
980
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, agentId);
946
981
  const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
947
982
  const totalDebt = await morpho.getTotalDebt(accountAddr);
948
983
 
949
- let text = `📊 Morpho — Agent #${cfg.agentId}\nAccount: ${accountAddr}\nTotal debt: $${ethers.formatUnits(totalDebt, 6)}\n`;
984
+ let text = `📊 Morpho — Agent #${agentId}\nAccount: ${accountAddr}\nTotal debt: $${ethers.formatUnits(totalDebt, 6)}\n`;
950
985
 
951
986
  for (const [symbol, info] of Object.entries(COLLATERAL_TOKENS)) {
952
987
  const pos = await morpho.getPosition(accountAddr, info.address);