@agether/agether 1.1.0 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/agether",
3
- "version": "1.1.0",
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.8.0",
12
+ "@agether/sdk": "^1.10.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
  }
@@ -230,9 +230,9 @@ Morpho Blue (direct lending, overcollateralized 125%)
230
230
 
231
231
  | Contract | Address |
232
232
  |----------|---------|
233
- | AccountFactory | `0xb5b09213d0718f3FD56F1b111C8A83FDFcBdd5d8` |
234
- | ValidationRegistry | `0x867F4F6f749Dc422aeFEcF4273cD22015d2CBCc2` |
235
- | AgentReputation | `0x352883c396bc7e88891a7D343ba550A7638256c0` |
233
+ | AccountFactory | `0xFab8b924fb292a736BA05AF7b4AEC2c8d05cdcbF` |
234
+ | ValidationRegistry | `0x493f1551cbbC75afb2127CDBD5375ad8e8959f42` |
235
+ | AgentReputation | `0x1fcb28eE3E8F3234E653b0A7a6fb56dE11E3c203` |
236
236
  | ERC-8004 Identity | `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` |
237
237
  | Morpho Blue | `0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb` |
238
238
  | USDC | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
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
+ });