@agether/agether 1.0.0 → 1.2.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 CHANGED
@@ -1,4 +1,4 @@
1
- # @agether/openclaw-plugin
1
+ # @agether/agether
2
2
 
3
3
  OpenClaw plugin for **Agether** — on-chain credit protocol for AI agents on Base.
4
4
 
@@ -11,7 +11,7 @@ OpenClaw plugin for **Agether** — on-chain credit protocol for AI agents on Ba
11
11
  ## Install
12
12
 
13
13
  ```bash
14
- openclaw plugins install @agether/openclaw-plugin
14
+ openclaw plugins install @agether/agether
15
15
  ```
16
16
 
17
17
  ## Configure
@@ -61,35 +61,35 @@ Once installed, the following tools are available to your AI agent:
61
61
  |---|---|
62
62
  | `agether_balance` | Check ETH & USDC balances (EOA + AgentAccount) |
63
63
  | `agether_register` | Mint ERC-8004 identity & create AgentAccount |
64
+ | `agether_set_agent` | Set a known agentId (from memory) and save to config |
64
65
  | `agether_score` | Get Bayesian credit score with 5-factor breakdown |
65
66
  | `morpho_status` | Show all Morpho credit positions |
67
+ | `morpho_markets` | List supported Morpho Blue markets |
66
68
  | `morpho_deposit` | Deposit collateral (WETH, wstETH, cbETH) |
67
69
  | `morpho_deposit_and_borrow` | Deposit collateral + borrow USDC in one step |
68
- | `morpho_sponsor` | Human deposits collateral for an agent (by ID or address) |
69
70
  | `morpho_borrow` | Borrow USDC against deposited collateral |
70
71
  | `morpho_repay` | Repay USDC debt |
71
72
  | `morpho_withdraw` | Withdraw collateral back to EOA |
72
- | `morpho_compare` | Compare collateral options with current prices |
73
- | `morpho_markets` | List supported Morpho Blue markets |
73
+ | `morpho_sponsor` | Human deposits collateral for an agent (by ID or address) |
74
74
  | `wallet_fund` | Transfer USDC from EOA into AgentAccount |
75
75
  | `x402_pay` | Make paid API calls via x402 protocol (supports auto-draw) |
76
76
 
77
- ## Commands
77
+ ## Slash Commands
78
78
 
79
- Quick commands that run without AI:
79
+ Quick commands that run without AI reasoning:
80
80
 
81
81
  - `/balance` — wallet balances
82
82
  - `/morpho` — Morpho positions overview
83
83
 
84
84
  ## Quick Start
85
85
 
86
- 1. Install the plugin: `openclaw plugins install @agether/openclaw-plugin`
86
+ 1. Install: `openclaw plugins install @agether/agether`
87
87
  2. Add config to `~/.openclaw/openclaw.json` (see above)
88
88
  3. Restart: `openclaw gateway --force`
89
- 4. Open Telegram and send to your bot: *"What is my balance?"*
90
- 5. Ask: *"Register me as an agent on Agether"*
91
- 6. Fund your wallet with ETH (for gas) and WETH/wstETH/cbETH (for collateral)
92
- 7. Ask: *"Deposit 0.05 WETH and borrow $50 USDC"*
89
+ 4. Open Telegram *"What is my balance?"*
90
+ 5. *"Register me as an agent on Agether"*
91
+ 6. Fund your wallet with ETH (gas) + WETH/wstETH/cbETH (collateral)
92
+ 7. *"Deposit 0.05 WETH and borrow $50 USDC"*
93
93
 
94
94
  ## Getting Collateral on Base
95
95
 
@@ -105,9 +105,11 @@ Quick commands that run without AI:
105
105
  | `rpcUrl` | — | `https://base-rpc.publicnode.com` | Base RPC endpoint |
106
106
  | `backendUrl` | — | `http://95.179.189.214:3001` | Agether backend API |
107
107
  | `autoDraw` | — | `false` | Auto-borrow from credit line when USDC is insufficient for x402 payments |
108
+ | `agentId` | — | — | Pre-set agent ID (auto-saved after registration) |
108
109
 
109
110
  ## Links
110
111
 
112
+ - [Agether Dashboard](http://95.179.189.214) — Web UI
111
113
  - [Agether SDK](https://www.npmjs.com/package/@agether/sdk) — CLI + TypeScript clients
112
114
  - [ERC-8004 Standard](https://eips.ethereum.org/EIPS/eip-8004) — Agent Identity
113
115
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/agether",
3
- "version": "1.0.0",
3
+ "version": "1.2.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.6.5",
12
+ "@agether/sdk": "^1.9.0",
13
13
  "axios": "^1.6.0",
14
14
  "ethers": "^6.9.0"
15
15
  },
@@ -23,8 +23,13 @@
23
23
  "credit",
24
24
  "defi"
25
25
  ],
26
+ "scripts": {
27
+ "test": "vitest run",
28
+ "test:watch": "vitest"
29
+ },
26
30
  "license": "MIT",
27
31
  "devDependencies": {
28
- "typescript": "^5.9.3"
32
+ "typescript": "^5.9.3",
33
+ "vitest": "^4.0.18"
29
34
  }
30
35
  }
package/src/index.ts CHANGED
@@ -129,11 +129,16 @@ export default function register(api: any) {
129
129
  const client = createClient(cfg);
130
130
  const result = await client.register(params.name);
131
131
  const persistStatus = persistAgentId(result.agentId);
132
+ const kyaMessage = result.kyaRequired
133
+ ? "⚠️ KYA required: submit your agent code hash for review before using credit"
134
+ : "✅ KYA gate disabled — agent can use credit immediately";
132
135
  return ok(JSON.stringify({
133
136
  status: result.alreadyRegistered ? "already_registered" : "registered",
134
137
  agentId: result.agentId,
135
138
  address: result.address,
136
139
  agentAccount: result.agentAccount,
140
+ kyaRequired: result.kyaRequired,
141
+ kyaMessage,
137
142
  tx: result.tx ? txLink(result.tx) : undefined,
138
143
  configSaved: persistStatus,
139
144
  }));
@@ -177,6 +182,86 @@ export default function register(api: any) {
177
182
  },
178
183
  });
179
184
 
185
+ // ═══════════════════════════════════════════════════════
186
+ // TOOL: agether_kya_status
187
+ // ═══════════════════════════════════════════════════════
188
+ api.registerTool({
189
+ name: "agether_kya_status",
190
+ description:
191
+ "Check if KYA code verification is required for this deployment.",
192
+ parameters: { type: "object", properties: {}, required: [] },
193
+ async execute() {
194
+ try {
195
+ const cfg = getConfig(api);
196
+ const client = createClient(cfg);
197
+ const required = await client.isKyaRequired();
198
+ return ok(
199
+ required
200
+ ? "KYA verification is ENABLED — agents must have code approved before using credit"
201
+ : "KYA verification is DISABLED — agents can use credit immediately after registration",
202
+ );
203
+ } catch (e) { return fail(e); }
204
+ },
205
+ });
206
+
207
+ // ═══════════════════════════════════════════════════════
208
+ // TOOL: agether_set_kya
209
+ // ═══════════════════════════════════════════════════════
210
+ api.registerTool({
211
+ name: "agether_set_kya",
212
+ description:
213
+ "Enable or disable KYA verification gate (owner only). " +
214
+ "Pass the ValidationRegistry address to enable, or '0x0000000000000000000000000000000000000000' to disable. " +
215
+ "Note: the actual transaction must be executed by the owner (TimelockController).",
216
+ parameters: {
217
+ type: "object",
218
+ properties: {
219
+ registryAddress: {
220
+ type: "string",
221
+ description:
222
+ "Address of ValidationRegistry, or '0x0000000000000000000000000000000000000000' to disable",
223
+ },
224
+ },
225
+ required: ["registryAddress"],
226
+ },
227
+ async execute(_id: string, params: { registryAddress: string }) {
228
+ try {
229
+ const cfg = getConfig(api);
230
+ const client = createClient(cfg);
231
+
232
+ // Build the calldata for setValidationRegistry — the owner (Timelock) must execute it
233
+ const { ethers } = await import("ethers");
234
+ const iface = new ethers.Interface([
235
+ "function setValidationRegistry(address newRegistry)",
236
+ ]);
237
+ const calldata = iface.encodeFunctionData("setValidationRegistry", [
238
+ params.registryAddress,
239
+ ]);
240
+
241
+ const isDisabling =
242
+ params.registryAddress === ethers.ZeroAddress;
243
+ const action = isDisabling ? "DISABLE" : "ENABLE";
244
+
245
+ return ok(
246
+ JSON.stringify(
247
+ {
248
+ action: `${action} KYA gate`,
249
+ registryAddress: params.registryAddress,
250
+ calldata,
251
+ note:
252
+ "This calldata must be submitted to the TimelockController by the owner. " +
253
+ "Schedule via timelock.schedule(accountFactoryAddr, 0, calldata, predecessor, salt, delay).",
254
+ },
255
+ null,
256
+ 2,
257
+ ),
258
+ );
259
+ } catch (e) {
260
+ return fail(e);
261
+ }
262
+ },
263
+ });
264
+
180
265
  // ═══════════════════════════════════════════════════════
181
266
  // TOOL: morpho_status
182
267
  // ═══════════════════════════════════════════════════════
@@ -0,0 +1,157 @@
1
+ /**
2
+ * KYA gate feature — unit tests for plugin tools.
3
+ *
4
+ * Mocks MorphoClient so no blockchain connection is needed.
5
+ */
6
+
7
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
8
+
9
+ // ── Mock state ──
10
+
11
+ const mockIsKyaRequired = vi.fn<() => Promise<boolean>>();
12
+ const mockRegister = vi.fn();
13
+ const mockGetBalances = vi.fn();
14
+ const mockGetAgentId = vi.fn().mockReturnValue('12345');
15
+ const mockGetAccountAddress = vi.fn().mockResolvedValue('0xAGENTACCOUNT');
16
+
17
+ vi.mock('@agether/sdk', () => {
18
+ // MorphoClient must work with `new` — use a class
19
+ class MockMorphoClient {
20
+ isKyaRequired = mockIsKyaRequired;
21
+ register = mockRegister;
22
+ getBalances = mockGetBalances;
23
+ getAgentId = mockGetAgentId;
24
+ getAccountAddress = mockGetAccountAddress;
25
+ }
26
+ class MockX402Client {}
27
+ return {
28
+ MorphoClient: MockMorphoClient,
29
+ X402Client: MockX402Client,
30
+ };
31
+ });
32
+
33
+ // Suppress fs.readFileSync for persistAgentId
34
+ vi.mock('fs', async (importOriginal) => {
35
+ const orig = await importOriginal<typeof import('fs')>();
36
+ return {
37
+ ...orig,
38
+ default: {
39
+ ...orig,
40
+ readFileSync: vi.fn().mockReturnValue('{}'),
41
+ writeFileSync: vi.fn(),
42
+ },
43
+ readFileSync: vi.fn().mockReturnValue('{}'),
44
+ writeFileSync: vi.fn(),
45
+ };
46
+ });
47
+
48
+ import registerPlugin from '../src/index';
49
+
50
+ // ── Helpers ──
51
+
52
+ /** Minimal mock for the OpenClaw plugin API */
53
+ function createMockApi(config?: Record<string, any>) {
54
+ const tools = new Map<string, { execute: Function }>();
55
+ return {
56
+ config: {
57
+ plugins: {
58
+ entries: {
59
+ agether: {
60
+ config: {
61
+ privateKey: '0x' + 'ab'.repeat(32),
62
+ agentId: '12345',
63
+ rpcUrl: 'http://127.0.0.1:8545',
64
+ backendUrl: 'http://localhost:3001',
65
+ ...config,
66
+ },
67
+ },
68
+ },
69
+ },
70
+ },
71
+ registerTool(def: { name: string; execute: Function }) {
72
+ tools.set(def.name, def);
73
+ },
74
+ registerCommand() { /* noop */ },
75
+ getTool(name: string) {
76
+ return tools.get(name);
77
+ },
78
+ };
79
+ }
80
+
81
+ // ── Tests ──
82
+
83
+ describe('agether_kya_status tool', () => {
84
+ let api: ReturnType<typeof createMockApi>;
85
+
86
+ beforeEach(() => {
87
+ vi.clearAllMocks();
88
+ api = createMockApi();
89
+ registerPlugin(api);
90
+ });
91
+
92
+ it('returns DISABLED message when isKyaRequired = false', async () => {
93
+ mockIsKyaRequired.mockResolvedValue(false);
94
+
95
+ const tool = api.getTool('agether_kya_status')!;
96
+ const result = await tool.execute();
97
+
98
+ expect(result.content[0].text).toContain('DISABLED');
99
+ expect(result.isError).toBeUndefined();
100
+ });
101
+
102
+ it('returns ENABLED message when isKyaRequired = true', async () => {
103
+ mockIsKyaRequired.mockResolvedValue(true);
104
+
105
+ const tool = api.getTool('agether_kya_status')!;
106
+ const result = await tool.execute();
107
+
108
+ expect(result.content[0].text).toContain('ENABLED');
109
+ expect(result.isError).toBeUndefined();
110
+ });
111
+ });
112
+
113
+ describe('agether_register kyaMessage', () => {
114
+ let api: ReturnType<typeof createMockApi>;
115
+
116
+ beforeEach(() => {
117
+ vi.clearAllMocks();
118
+ api = createMockApi();
119
+ registerPlugin(api);
120
+ });
121
+
122
+ it('includes "gate disabled" kyaMessage when kyaRequired = false', async () => {
123
+ mockRegister.mockResolvedValue({
124
+ agentId: '99999',
125
+ address: '0xWALLET',
126
+ agentAccount: '0xACCOUNT',
127
+ alreadyRegistered: false,
128
+ kyaRequired: false,
129
+ tx: '0xTXHASH',
130
+ });
131
+
132
+ const tool = api.getTool('agether_register')!;
133
+ const result = await tool.execute('test-id', { name: 'TestAgent' });
134
+ const data = JSON.parse(result.content[0].text);
135
+
136
+ expect(data.kyaRequired).toBe(false);
137
+ expect(data.kyaMessage).toContain('gate disabled');
138
+ });
139
+
140
+ it('includes "KYA required" kyaMessage when kyaRequired = true', async () => {
141
+ mockRegister.mockResolvedValue({
142
+ agentId: '99999',
143
+ address: '0xWALLET',
144
+ agentAccount: '0xACCOUNT',
145
+ alreadyRegistered: false,
146
+ kyaRequired: true,
147
+ tx: '0xTXHASH',
148
+ });
149
+
150
+ const tool = api.getTool('agether_register')!;
151
+ const result = await tool.execute('test-id', { name: 'TestAgent' });
152
+ const data = JSON.parse(result.content[0].text);
153
+
154
+ expect(data.kyaRequired).toBe(true);
155
+ expect(data.kyaMessage).toContain('KYA required');
156
+ });
157
+ });
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ testTimeout: 10_000,
7
+ },
8
+ });