@agether/openclaw-plugin 1.0.0 → 1.1.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/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @agether/openclaw-plugin
2
+
3
+ OpenClaw plugin for **Agether** — on-chain credit protocol for AI agents on Base.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ openclaw plugin install @agether/openclaw-plugin
9
+ ```
10
+
11
+ ## Configure
12
+
13
+ Add your private key to the plugin config (OpenClaw will prompt on first use, or set it manually):
14
+
15
+ ```jsonc
16
+ // ~/.openclaw/config.json
17
+ {
18
+ "plugins": {
19
+ "entries": {
20
+ "agether": {
21
+ "config": {
22
+ "privateKey": "0x...", // required — your wallet PK
23
+ "agentId": "17676", // optional — set after registration
24
+ "rpcUrl": "https://base-rpc.publicnode.com", // optional
25
+ "backendUrl": "http://95.179.189.214:3001" // optional
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ > ⚠️ **Never share your private key.** The `privateKey` field is marked as sensitive in the plugin manifest.
34
+
35
+ ## Tools
36
+
37
+ Once installed, the following tools are available to your AI agent:
38
+
39
+ | Tool | Description |
40
+ |---|---|
41
+ | `agether_balance` | Check ETH & USDC balances (EOA + AgentAccount) |
42
+ | `agether_register` | Mint ERC-8004 identity & create AgentAccount |
43
+ | `agether_score` | Get Bayesian credit score with 5-factor breakdown |
44
+ | `morpho_status` | Show all Morpho credit positions |
45
+ | `morpho_deposit` | Deposit collateral (WETH, wstETH, cbETH) |
46
+ | `morpho_borrow` | Borrow USDC against deposited collateral |
47
+ | `morpho_repay` | Repay USDC debt |
48
+ | `morpho_withdraw` | Withdraw collateral back to EOA |
49
+ | `morpho_compare` | Compare collateral options with current prices |
50
+ | `morpho_markets` | List supported Morpho Blue markets |
51
+ | `wallet_fund` | Transfer USDC from EOA into AgentAccount |
52
+ | `x402_pay` | Make paid API calls via x402 protocol |
53
+
54
+ ## Commands
55
+
56
+ Quick commands that run without AI:
57
+
58
+ - `/balance` — wallet balances
59
+ - `/morpho` — Morpho positions overview
60
+
61
+ ## Quick Start
62
+
63
+ 1. Install the plugin
64
+ 2. Set your private key in config
65
+ 3. Ask your agent: *"Register me as an agent on Agether"*
66
+ 4. Fund with ETH (for gas) and collateral (WETH/wstETH/cbETH)
67
+ 5. Ask: *"Deposit 0.05 WETH and borrow $50 USDC"*
68
+
69
+ ## Requirements
70
+
71
+ - Wallet with ETH on **Base** (chain 8453) for gas
72
+ - Collateral tokens (WETH, wstETH, or cbETH) for Morpho credit
73
+ - Backend running at the configured `backendUrl`
74
+
75
+ ## Links
76
+
77
+ - [Agether SDK](https://www.npmjs.com/package/@agether/sdk)
78
+ - [ERC-8004 Standard](https://eips.ethereum.org/EIPS/eip-8004)
79
+
80
+ ## License
81
+
82
+ MIT
@@ -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.0.0",
5
+ "version": "1.1.0",
6
6
  "skills": ["skills/agether"],
7
7
  "configSchema": {
8
8
  "type": "object",
@@ -25,6 +25,11 @@
25
25
  "type": "string",
26
26
  "description": "Agether backend URL",
27
27
  "default": "http://95.179.189.214:3001"
28
+ },
29
+ "autoDraw": {
30
+ "type": "boolean",
31
+ "description": "Auto-borrow from Morpho credit line when USDC balance is low for x402 payments",
32
+ "default": false
28
33
  }
29
34
  },
30
35
  "required": ["privateKey"]
@@ -33,6 +38,7 @@
33
38
  "privateKey": { "label": "Private Key", "sensitive": true },
34
39
  "agentId": { "label": "Agent ID", "placeholder": "17676" },
35
40
  "rpcUrl": { "label": "RPC URL", "placeholder": "https://base-rpc.publicnode.com" },
36
- "backendUrl": { "label": "Backend URL", "placeholder": "http://95.179.189.214:3001" }
41
+ "backendUrl": { "label": "Backend URL", "placeholder": "http://95.179.189.214:3001" },
42
+ "autoDraw": { "label": "Auto-Draw Credit", "placeholder": "false" }
37
43
  }
38
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/openclaw-plugin",
3
- "version": "1.0.0",
3
+ "version": "1.1.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.0.0",
12
+ "@agether/sdk": "^1.1.0",
13
13
  "axios": "^1.6.0",
14
14
  "ethers": "^6.9.0"
15
15
  },
package/src/index.ts CHANGED
@@ -10,7 +10,9 @@ import axios from "axios";
10
10
  import {
11
11
  ACCOUNT_FACTORY_ABI,
12
12
  AGENT_ACCOUNT_ABI,
13
+ MORPHO_CREDIT_ABI,
13
14
  ERC20_ABI,
15
+ X402Client,
14
16
  getDefaultConfig,
15
17
  } from "@agether/sdk";
16
18
 
@@ -21,22 +23,9 @@ interface PluginConfig {
21
23
  agentId?: string;
22
24
  rpcUrl?: string;
23
25
  backendUrl?: string;
26
+ autoDraw?: boolean;
24
27
  }
25
28
 
26
- const MORPHO_CREDIT_ABI = [
27
- "function depositCollateral(address collateralToken, uint256 amount)",
28
- "function depositCollateralFor(address onBehalf, address collateralToken, uint256 amount)",
29
- "function withdrawCollateral(address collateralToken, uint256 amount)",
30
- "function drawWithCollateral(address collateralToken, uint256 amount)",
31
- "function repayWithCollateral(address collateralToken, uint256 amount)",
32
- "function getPosition(address account, address collateralToken) view returns (tuple(uint256 collateralAmount, uint256 borrowedAmount, uint256 borrowShares, bool isActive))",
33
- "function getSupportedCollaterals() view returns (address[])",
34
- "function asset() view returns (address)",
35
- "function getCreditInfo(address account) view returns (tuple(uint256 limit, uint256 used, uint256 available, uint256 accruedInterest, uint256 aprBps, bool isActive, bool requiresCollateral))",
36
- "function getTotalDebt(address account) view returns (uint256)",
37
- "function maxDrawable(address account) view returns (uint256)",
38
- ];
39
-
40
29
  const ERC8004_ABI = [
41
30
  "function register(string agentURI) returns (uint256)",
42
31
  "function ownerOf(uint256 tokenId) view returns (address)",
@@ -59,6 +48,7 @@ function getConfig(api: any): PluginConfig {
59
48
  agentId: cfg.agentId,
60
49
  rpcUrl: cfg.rpcUrl || "https://base-rpc.publicnode.com",
61
50
  backendUrl: cfg.backendUrl || "http://95.179.189.214:3001",
51
+ autoDraw: cfg.autoDraw === true || cfg.autoDraw === "true",
62
52
  };
63
53
  }
64
54
 
@@ -321,10 +311,7 @@ export default function register(api: any) {
321
311
  await depositTx.wait();
322
312
 
323
313
  // Approve credit provider on AgentAccount
324
- const account = new ethers.Contract(accountAddr, [
325
- ...AGENT_ACCOUNT_ABI,
326
- "function approveCreditProvider(address creditProvider)",
327
- ], signer);
314
+ const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
328
315
  try {
329
316
  const cpTx = await account.approveCreditProvider(morphoCreditAddr);
330
317
  await cpTx.wait();
@@ -343,6 +330,183 @@ export default function register(api: any) {
343
330
  },
344
331
  });
345
332
 
333
+ // ═══════════════════════════════════════════════════════
334
+ // TOOL: morpho_deposit_and_borrow (Flow 2)
335
+ // ═══════════════════════════════════════════════════════
336
+ api.registerTool({
337
+ name: "morpho_deposit_and_borrow",
338
+ description:
339
+ "Deposit collateral AND borrow USDC in one call. Deposits collateral from EOA into Morpho, then borrows the specified USDC amount. USDC lands in AgentAccount.",
340
+ parameters: {
341
+ type: "object",
342
+ properties: {
343
+ collateralAmount: { type: "string", description: "Amount of collateral (e.g. '0.05')" },
344
+ token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
345
+ borrowAmount: { type: "string", description: "USDC amount to borrow (e.g. '50')" },
346
+ },
347
+ required: ["collateralAmount", "token", "borrowAmount"],
348
+ },
349
+ async execute(_id: string, params: { collateralAmount: string; token: string; borrowAmount: string }) {
350
+ try {
351
+ const cfg = getConfig(api);
352
+ const signer = getSigner(cfg);
353
+ const status = await getBackendStatus(cfg);
354
+ if (!cfg.agentId) return fail("No agentId configured");
355
+
356
+ const tokenInfo = COLLATERAL_TOKENS[params.token];
357
+ if (!tokenInfo) return fail(`Unsupported token: ${params.token}`);
358
+
359
+ const morphoCreditAddr = status.contracts.morphoCredit;
360
+ const accountAddr = await getAccountAddr(signer, status.contracts.accountFactory, cfg.agentId);
361
+ const collateralWei = ethers.parseUnits(params.collateralAmount, tokenInfo.decimals);
362
+ const borrowWei = ethers.parseUnits(params.borrowAmount, 6);
363
+
364
+ // Check collateral balance
365
+ const token = new ethers.Contract(tokenInfo.address, ERC20_ABI, signer);
366
+ const balance = await token.balanceOf(signer.address);
367
+ if (balance < collateralWei) {
368
+ return fail(`Insufficient ${params.token}: have ${ethers.formatUnits(balance, tokenInfo.decimals)}, need ${params.collateralAmount}`);
369
+ }
370
+
371
+ // Step 1: Approve collateral
372
+ const approveTx = await token.approve(morphoCreditAddr, collateralWei);
373
+ await approveTx.wait();
374
+
375
+ // Step 2: Deposit collateral for AgentAccount
376
+ const morpho = new ethers.Contract(morphoCreditAddr, MORPHO_CREDIT_ABI, signer);
377
+ const depositTx = await morpho.depositCollateralFor(accountAddr, tokenInfo.address, collateralWei);
378
+ await depositTx.wait();
379
+
380
+ // Step 3: Approve credit provider on AgentAccount
381
+ const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
382
+ try {
383
+ const cpTx = await account.approveCreditProvider(morphoCreditAddr);
384
+ await cpTx.wait();
385
+ } catch { /* already approved */ }
386
+
387
+ // Step 4: Borrow via AgentAccount.execute → drawWithCollateral
388
+ const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
389
+ const borrowCalldata = morphoIface.encodeFunctionData("drawWithCollateral", [tokenInfo.address, borrowWei]);
390
+ const borrowTx = await account.execute(morphoCreditAddr, 0, borrowCalldata);
391
+ await borrowTx.wait();
392
+
393
+ const pos = await morpho.getPosition(accountAddr, tokenInfo.address);
394
+ const totalDebt = await morpho.getTotalDebt(accountAddr);
395
+
396
+ return ok(JSON.stringify({
397
+ status: "deposited_and_borrowed",
398
+ collateral: `${params.collateralAmount} ${params.token}`,
399
+ borrowed: `$${params.borrowAmount}`,
400
+ totalCollateral: `${ethers.formatUnits(pos.collateralAmount, tokenInfo.decimals)} ${params.token}`,
401
+ totalDebt: `$${ethers.formatUnits(totalDebt, 6)}`,
402
+ agentAccount: accountAddr,
403
+ depositTx: depositTx.hash,
404
+ borrowTx: borrowTx.hash,
405
+ }));
406
+ } catch (e) { return fail(e); }
407
+ },
408
+ });
409
+
410
+ // ═══════════════════════════════════════════════════════
411
+ // TOOL: morpho_sponsor (Flows 4-7: Human sponsors agent)
412
+ // ═══════════════════════════════════════════════════════
413
+ api.registerTool({
414
+ name: "morpho_sponsor",
415
+ description:
416
+ "Deposit collateral for another agent (by agentId or AgentAccount address). The caller pays collateral from their wallet. Optionally borrow USDC for the agent if caller owns the AgentAccount.",
417
+ parameters: {
418
+ type: "object",
419
+ properties: {
420
+ agentId: { type: "string", description: "Target agent's ERC-8004 ID (e.g. '17676')" },
421
+ agentAddress: { type: "string", description: "Target AgentAccount address (alternative to agentId)" },
422
+ amount: { type: "string", description: "Collateral amount (e.g. '0.05')" },
423
+ token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
424
+ borrow: { type: "string", description: "Optional: USDC amount to borrow for the agent (e.g. '50'). Only works if caller owns the AgentAccount." },
425
+ },
426
+ required: ["amount", "token"],
427
+ },
428
+ async execute(_id: string, params: { agentId?: string; agentAddress?: string; amount: string; token: string; borrow?: string }) {
429
+ try {
430
+ const cfg = getConfig(api);
431
+ const signer = getSigner(cfg);
432
+ const status = await getBackendStatus(cfg);
433
+
434
+ if (!params.agentId && !params.agentAddress) {
435
+ return fail("Provide either agentId or agentAddress");
436
+ }
437
+
438
+ const tokenInfo = COLLATERAL_TOKENS[params.token];
439
+ if (!tokenInfo) return fail(`Unsupported token: ${params.token}`);
440
+
441
+ const morphoCreditAddr = status.contracts.morphoCredit;
442
+ let accountAddr: string;
443
+
444
+ // Resolve target AgentAccount
445
+ if (params.agentAddress) {
446
+ accountAddr = params.agentAddress;
447
+ } else {
448
+ const factory = new ethers.Contract(status.contracts.accountFactory, ACCOUNT_FACTORY_ABI, signer);
449
+ accountAddr = await factory.getAccount(params.agentId!);
450
+ if (accountAddr === ethers.ZeroAddress) {
451
+ return fail(`No AgentAccount for agent #${params.agentId}`);
452
+ }
453
+ }
454
+
455
+ const collateralWei = ethers.parseUnits(params.amount, tokenInfo.decimals);
456
+
457
+ // Check sponsor's balance
458
+ const token = new ethers.Contract(tokenInfo.address, ERC20_ABI, signer);
459
+ const balance = await token.balanceOf(signer.address);
460
+ if (balance < collateralWei) {
461
+ return fail(`Insufficient ${params.token}: have ${ethers.formatUnits(balance, tokenInfo.decimals)}, need ${params.amount}`);
462
+ }
463
+
464
+ // Approve + deposit for target
465
+ const approveTx = await token.approve(morphoCreditAddr, collateralWei);
466
+ await approveTx.wait();
467
+
468
+ const morpho = new ethers.Contract(morphoCreditAddr, MORPHO_CREDIT_ABI, signer);
469
+ const depositTx = await morpho.depositCollateralFor(accountAddr, tokenInfo.address, collateralWei);
470
+ await depositTx.wait();
471
+
472
+ const result: any = {
473
+ status: "sponsored",
474
+ target: accountAddr,
475
+ targetAgentId: params.agentId || "by address",
476
+ collateral: `${params.amount} ${params.token}`,
477
+ depositTx: depositTx.hash,
478
+ };
479
+
480
+ // Optional: borrow for the agent (only works if caller owns the AgentAccount)
481
+ if (params.borrow) {
482
+ try {
483
+ const account = new ethers.Contract(accountAddr, AGENT_ACCOUNT_ABI, signer);
484
+ try {
485
+ const cpTx = await account.approveCreditProvider(morphoCreditAddr);
486
+ await cpTx.wait();
487
+ } catch { /* already approved or not owner */ }
488
+
489
+ const borrowWei = ethers.parseUnits(params.borrow, 6);
490
+ const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
491
+ const calldata = morphoIface.encodeFunctionData("drawWithCollateral", [tokenInfo.address, borrowWei]);
492
+ const borrowTx = await account.execute(morphoCreditAddr, 0, calldata);
493
+ await borrowTx.wait();
494
+ result.borrowed = `$${params.borrow}`;
495
+ result.borrowTx = borrowTx.hash;
496
+ } catch (e: any) {
497
+ result.borrowError = `Borrow failed (caller may not own this AgentAccount): ${e.message}`;
498
+ }
499
+ }
500
+
501
+ const pos = await morpho.getPosition(accountAddr, tokenInfo.address);
502
+ result.totalCollateral = `${ethers.formatUnits(pos.collateralAmount, tokenInfo.decimals)} ${params.token}`;
503
+ result.totalDebt = `$${ethers.formatUnits(pos.borrowedAmount, 6)}`;
504
+
505
+ return ok(JSON.stringify(result));
506
+ } catch (e) { return fail(e); }
507
+ },
508
+ });
509
+
346
510
  // ═══════════════════════════════════════════════════════
347
511
  // TOOL: morpho_borrow
348
512
  // ═══════════════════════════════════════════════════════
@@ -644,47 +808,90 @@ export default function register(api: any) {
644
808
  });
645
809
 
646
810
  // ═══════════════════════════════════════════════════════
647
- // TOOL: x402_pay
811
+ // TOOL: x402_pay (Flows 8 & 9 — uses SDK X402Client)
648
812
  // ═══════════════════════════════════════════════════════
649
813
  api.registerTool({
650
814
  name: "x402_pay",
651
815
  description:
652
- "Make a paid API call using x402 protocol. Pays with USDC via EIP-3009 signature. Returns the API response.",
816
+ "Make a paid API call using x402 protocol. Pays with USDC from AgentAccount via EIP-3009 signature. " +
817
+ "If autoDraw is enabled in config (or --auto-draw param is true), automatically borrows USDC from Morpho " +
818
+ "credit line when AgentAccount balance is insufficient.",
653
819
  parameters: {
654
820
  type: "object",
655
821
  properties: {
656
822
  url: { type: "string", description: "API endpoint URL" },
657
823
  method: { type: "string", enum: ["GET", "POST"], description: "HTTP method (default: GET)" },
658
824
  body: { type: "string", description: "JSON body for POST requests" },
825
+ autoDraw: { type: "boolean", description: "Auto-borrow from Morpho if USDC insufficient (default: uses config autoDraw setting)" },
659
826
  },
660
827
  required: ["url"],
661
828
  },
662
- async execute(_id: string, params: { url: string; method?: string; body?: string }) {
829
+ async execute(_id: string, params: { url: string; method?: string; body?: string; autoDraw?: boolean }) {
663
830
  try {
664
831
  const cfg = getConfig(api);
665
- // Delegate to CLI for now since x402 flow is complex (EIP-3009 signing)
666
- const { execSync } = await import("child_process");
667
- const args = [`x402 "${params.url}"`];
668
- if (params.method) args.push(`--method ${params.method}`);
669
- if (params.body) args.push(`--body '${params.body}'`);
670
-
671
- const env = {
672
- ...process.env,
673
- AGETHER_PRIVATE_KEY: cfg.privateKey,
674
- AGETHER_RPC_URL: cfg.rpcUrl,
675
- AGETHER_BACKEND_URL: cfg.backendUrl,
676
- };
832
+ const signer = getSigner(cfg);
833
+ const status = await getBackendStatus(cfg);
677
834
 
678
- const result = execSync(`agether ${args.join(" ")}`, {
679
- encoding: "utf-8",
680
- timeout: 30000,
681
- env,
835
+ // Resolve AgentAccount address for x402 payments
836
+ 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
+ }
842
+
843
+ // Auto-draw: if enabled, check USDC balance and borrow if needed
844
+ const shouldAutoDraw = params.autoDraw ?? cfg.autoDraw;
845
+ if (shouldAutoDraw && accountAddress && cfg.agentId) {
846
+ const usdc = new ethers.Contract(status.contracts.usdc, ERC20_ABI, signer.provider!);
847
+ const accBalance = await usdc.balanceOf(accountAddress);
848
+ // If balance < $1, try to auto-borrow $10 (enough for several x402 calls)
849
+ if (accBalance < ethers.parseUnits("1", 6)) {
850
+ try {
851
+ const morpho = new ethers.Contract(status.contracts.morphoCredit, MORPHO_CREDIT_ABI, signer.provider!);
852
+ let collateralAddr: string | null = null;
853
+ for (const [, info] of Object.entries(COLLATERAL_TOKENS)) {
854
+ const pos = await morpho.getPosition(accountAddress, info.address);
855
+ if (pos.collateralAmount > 0n) { collateralAddr = info.address; break; }
856
+ }
857
+ if (collateralAddr) {
858
+ const account = new ethers.Contract(accountAddress, AGENT_ACCOUNT_ABI, signer);
859
+ const morphoIface = new ethers.Interface(MORPHO_CREDIT_ABI);
860
+ const drawAmount = ethers.parseUnits("10", 6); // $10 auto-draw
861
+ const calldata = morphoIface.encodeFunctionData("drawWithCollateral", [collateralAddr, drawAmount]);
862
+ const tx = await account.execute(status.contracts.morphoCredit, 0, calldata);
863
+ await tx.wait();
864
+ }
865
+ } catch { /* auto-draw failed — proceed anyway, x402 might still work from EOA */ }
866
+ }
867
+ }
868
+
869
+ // Use SDK's X402Client directly (no execSync)
870
+ const x402 = new X402Client({
871
+ privateKey: cfg.privateKey,
872
+ rpcUrl: cfg.rpcUrl!,
873
+ backendUrl: cfg.backendUrl!,
874
+ agentId: cfg.agentId,
875
+ accountAddress,
682
876
  });
683
877
 
684
- return ok(result);
685
- } catch (e: any) {
686
- return fail(e.stderr || e.stdout || e.message);
687
- }
878
+ let result;
879
+ if (params.method?.toUpperCase() === "POST" && params.body) {
880
+ result = await x402.post(params.url, JSON.parse(params.body));
881
+ } else {
882
+ result = await x402.get(params.url);
883
+ }
884
+
885
+ if (!result.success) {
886
+ return fail(result.error || "x402 payment failed");
887
+ }
888
+
889
+ const response: any = { status: "paid", data: result.data };
890
+ if (result.paymentInfo) {
891
+ response.payment = result.paymentInfo;
892
+ }
893
+ return ok(JSON.stringify(response, null, 2));
894
+ } catch (e) { return fail(e); }
688
895
  },
689
896
  });
690
897