@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 +8 -3
- package/skills/agether/SKILL.md +3 -3
- package/src/index.ts +85 -0
- package/test/kya.test.ts +157 -0
- package/vitest.config.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agether/agether",
|
|
3
|
-
"version": "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.
|
|
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
|
}
|
package/skills/agether/SKILL.md
CHANGED
|
@@ -230,9 +230,9 @@ Morpho Blue (direct lending, overcollateralized 125%)
|
|
|
230
230
|
|
|
231
231
|
| Contract | Address |
|
|
232
232
|
|----------|---------|
|
|
233
|
-
| AccountFactory | `
|
|
234
|
-
| ValidationRegistry | `
|
|
235
|
-
| AgentReputation | `
|
|
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
|
// ═══════════════════════════════════════════════════════
|
package/test/kya.test.ts
ADDED
|
@@ -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
|
+
});
|