@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 +14 -12
- package/package.json +8 -3
- package/src/index.ts +85 -0
- package/test/kya.test.ts +157 -0
- package/vitest.config.ts +8 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @agether/
|
|
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/
|
|
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
|
-
| `
|
|
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
|
|
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
|
|
90
|
-
5.
|
|
91
|
-
6. Fund your wallet with ETH (
|
|
92
|
-
7.
|
|
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.
|
|
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.
|
|
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
|
// ═══════════════════════════════════════════════════════
|
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
|
+
});
|