agentic-x402 0.2.33 → 0.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/README.md +143 -2
- package/SKILL.md +123 -4
- package/bin/cli.ts +12 -0
- package/openclaw.plugin.json +73 -0
- package/package.json +6 -1
- package/scripts/commands/distribute.ts +201 -0
- package/scripts/commands/fetch-paid.ts +3 -1
- package/scripts/commands/pay.ts +8 -2
- package/scripts/commands/routers.ts +183 -0
- package/scripts/plugin/cli.ts +91 -0
- package/scripts/plugin/config-bridge.ts +31 -0
- package/scripts/plugin/index.ts +49 -0
- package/scripts/plugin/tools.ts +453 -0
- package/scripts/plugin/types.ts +107 -0
- package/scripts/plugin/watcher.ts +235 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
// 8 Agent Tools for the OpenClaw plugin
|
|
2
|
+
// Each wraps existing command logic with dynamic imports to avoid loading viem at registration
|
|
3
|
+
|
|
4
|
+
import type { PluginTool } from './types.js';
|
|
5
|
+
import type { PaymentWatcher } from './watcher.js';
|
|
6
|
+
|
|
7
|
+
export function createTools(watcher: PaymentWatcher | null): PluginTool[] {
|
|
8
|
+
return [
|
|
9
|
+
// 1. x402_balance
|
|
10
|
+
{
|
|
11
|
+
name: 'x402_balance',
|
|
12
|
+
description: 'Check wallet USDC and ETH balances on Base',
|
|
13
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
14
|
+
async execute() {
|
|
15
|
+
const { getClient, getWalletAddress, getUsdcBalance, getEthBalance } = await import('../core/client.js');
|
|
16
|
+
const client = getClient();
|
|
17
|
+
const address = getWalletAddress();
|
|
18
|
+
const [usdc, eth] = await Promise.all([getUsdcBalance(), getEthBalance()]);
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
address,
|
|
22
|
+
network: client.config.network,
|
|
23
|
+
chainId: client.config.chainId,
|
|
24
|
+
balances: {
|
|
25
|
+
usdc: { raw: usdc.raw.toString(), formatted: usdc.formatted },
|
|
26
|
+
eth: { raw: eth.raw.toString(), formatted: eth.formatted },
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// 2. x402_pay
|
|
33
|
+
{
|
|
34
|
+
name: 'x402_pay',
|
|
35
|
+
description: 'Pay for an x402-gated resource. Returns the response body after payment.',
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
url: { type: 'string', description: 'URL of the x402-gated resource' },
|
|
40
|
+
method: { type: 'string', description: 'HTTP method (default: GET)' },
|
|
41
|
+
body: { type: 'string', description: 'Request body for POST/PUT' },
|
|
42
|
+
maxPaymentUsd: { type: 'number', description: 'Maximum payment in USD' },
|
|
43
|
+
dryRun: { type: 'boolean', description: 'If true, show payment details without paying' },
|
|
44
|
+
},
|
|
45
|
+
required: ['url'],
|
|
46
|
+
},
|
|
47
|
+
async execute(params) {
|
|
48
|
+
const url = params.url as string;
|
|
49
|
+
const method = (params.method as string) || 'GET';
|
|
50
|
+
const body = params.body as string | undefined;
|
|
51
|
+
const maxPaymentUsd = params.maxPaymentUsd as number | undefined;
|
|
52
|
+
const dryRun = params.dryRun as boolean | undefined;
|
|
53
|
+
|
|
54
|
+
const { getClient } = await import('../core/client.js');
|
|
55
|
+
const client = getClient();
|
|
56
|
+
|
|
57
|
+
const headers: Record<string, string> = { Accept: 'application/json' };
|
|
58
|
+
if (body) headers['Content-Type'] = 'application/json';
|
|
59
|
+
|
|
60
|
+
// Probe for 402
|
|
61
|
+
const probe = await fetch(url, { method, body, headers });
|
|
62
|
+
|
|
63
|
+
if (probe.status !== 402) {
|
|
64
|
+
const contentType = probe.headers.get('content-type');
|
|
65
|
+
const responseBody = contentType?.includes('json')
|
|
66
|
+
? await probe.json()
|
|
67
|
+
: await probe.text();
|
|
68
|
+
return { paid: false, status: probe.status, response: responseBody };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Parse payment info
|
|
72
|
+
let paymentInfo: Record<string, unknown> | null = null;
|
|
73
|
+
const xPayment = probe.headers.get('x-payment');
|
|
74
|
+
if (xPayment) {
|
|
75
|
+
try { paymentInfo = JSON.parse(Buffer.from(xPayment, 'base64').toString()); } catch { /* */ }
|
|
76
|
+
}
|
|
77
|
+
if (!paymentInfo) {
|
|
78
|
+
try { paymentInfo = await probe.json() as Record<string, unknown>; } catch { /* */ }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const accepts = (paymentInfo as Record<string, unknown>)?.accepts as Array<Record<string, unknown>> | undefined;
|
|
82
|
+
const priceStr = String(accepts?.[0]?.price ?? accepts?.[0]?.maxAmountRequired ?? '0');
|
|
83
|
+
const priceNum = parseFloat(priceStr.replace(/[$,]/g, ''));
|
|
84
|
+
const effectiveMax = maxPaymentUsd ?? client.config.maxPaymentUsd;
|
|
85
|
+
|
|
86
|
+
if (priceNum > effectiveMax) {
|
|
87
|
+
return { paid: false, error: `Price $${priceNum} exceeds max $${effectiveMax}`, paymentInfo };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (dryRun) {
|
|
91
|
+
return { paid: false, dryRun: true, price: priceNum, paymentInfo };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Execute payment
|
|
95
|
+
const response = await client.fetchWithPayment(url, { method, body, headers });
|
|
96
|
+
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
return { paid: false, error: `${response.status} ${response.statusText}` };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const contentType = response.headers.get('content-type');
|
|
102
|
+
const responseBody = contentType?.includes('json')
|
|
103
|
+
? await response.json()
|
|
104
|
+
: await response.text();
|
|
105
|
+
|
|
106
|
+
let txHash: string | undefined;
|
|
107
|
+
const paymentResponse = response.headers.get('x-payment-response');
|
|
108
|
+
if (paymentResponse) {
|
|
109
|
+
try {
|
|
110
|
+
const receipt = JSON.parse(Buffer.from(paymentResponse, 'base64').toString());
|
|
111
|
+
txHash = receipt.transactionHash ?? receipt.txHash;
|
|
112
|
+
} catch { /* */ }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { paid: true, price: priceNum, transactionHash: txHash, response: responseBody };
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
// 3. x402_fetch
|
|
120
|
+
{
|
|
121
|
+
name: 'x402_fetch',
|
|
122
|
+
description: 'Fetch a URL with automatic x402 payment handling. Simpler than x402_pay — just returns the content.',
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: {
|
|
126
|
+
url: { type: 'string', description: 'URL to fetch' },
|
|
127
|
+
method: { type: 'string', description: 'HTTP method (default: GET)' },
|
|
128
|
+
body: { type: 'string', description: 'Request body for POST/PUT' },
|
|
129
|
+
},
|
|
130
|
+
required: ['url'],
|
|
131
|
+
},
|
|
132
|
+
async execute(params) {
|
|
133
|
+
const url = params.url as string;
|
|
134
|
+
const method = (params.method as string) || 'GET';
|
|
135
|
+
const body = params.body as string | undefined;
|
|
136
|
+
|
|
137
|
+
const { getClient } = await import('../core/client.js');
|
|
138
|
+
const client = getClient();
|
|
139
|
+
|
|
140
|
+
const headers: Record<string, string> = { Accept: 'application/json' };
|
|
141
|
+
if (body) headers['Content-Type'] = 'application/json';
|
|
142
|
+
|
|
143
|
+
const response = await client.fetchWithPayment(url, { method, body, headers });
|
|
144
|
+
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
return { success: false, status: response.status, error: response.statusText };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const contentType = response.headers.get('content-type');
|
|
150
|
+
const responseBody = contentType?.includes('json')
|
|
151
|
+
? await response.json()
|
|
152
|
+
: await response.text();
|
|
153
|
+
|
|
154
|
+
return { success: true, status: response.status, response: responseBody };
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
// 4. x402_create_link
|
|
159
|
+
{
|
|
160
|
+
name: 'x402_create_link',
|
|
161
|
+
description: 'Create a payment link via 21.cash to sell gated content',
|
|
162
|
+
inputSchema: {
|
|
163
|
+
type: 'object',
|
|
164
|
+
properties: {
|
|
165
|
+
name: { type: 'string', description: 'Name of the payment link' },
|
|
166
|
+
price: { type: 'string', description: 'Price in USD (e.g., "5.00")' },
|
|
167
|
+
url: { type: 'string', description: 'URL to gate behind payment' },
|
|
168
|
+
text: { type: 'string', description: 'Text content to gate behind payment' },
|
|
169
|
+
description: { type: 'string', description: 'Description of the link' },
|
|
170
|
+
webhookUrl: { type: 'string', description: 'Webhook URL for payment notifications' },
|
|
171
|
+
},
|
|
172
|
+
required: ['name', 'price'],
|
|
173
|
+
},
|
|
174
|
+
async execute(params) {
|
|
175
|
+
const name = params.name as string;
|
|
176
|
+
const price = params.price as string;
|
|
177
|
+
const gatedUrl = params.url as string | undefined;
|
|
178
|
+
const gatedText = params.text as string | undefined;
|
|
179
|
+
const description = params.description as string | undefined;
|
|
180
|
+
const webhookUrl = params.webhookUrl as string | undefined;
|
|
181
|
+
|
|
182
|
+
if (!gatedUrl && !gatedText) {
|
|
183
|
+
return { success: false, error: 'Either url or text is required' };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const { getClient, getWalletAddress } = await import('../core/client.js');
|
|
187
|
+
const { getConfig } = await import('../core/config.js');
|
|
188
|
+
|
|
189
|
+
const config = getConfig();
|
|
190
|
+
const client = getClient();
|
|
191
|
+
const creatorAddress = getWalletAddress();
|
|
192
|
+
|
|
193
|
+
const requestBody: Record<string, unknown> = {
|
|
194
|
+
name,
|
|
195
|
+
price,
|
|
196
|
+
creatorAddress,
|
|
197
|
+
chainId: config.chainId,
|
|
198
|
+
};
|
|
199
|
+
if (description) requestBody.description = description;
|
|
200
|
+
if (gatedUrl) requestBody.gatedUrl = gatedUrl;
|
|
201
|
+
if (gatedText) requestBody.gatedText = gatedText;
|
|
202
|
+
if (webhookUrl) requestBody.webhookUrl = webhookUrl;
|
|
203
|
+
|
|
204
|
+
const apiUrl = `${config.x402LinksApiUrl}/api/links/programmatic`;
|
|
205
|
+
const response = await client.fetchWithPayment(apiUrl, {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: { 'Content-Type': 'application/json' },
|
|
208
|
+
body: JSON.stringify(requestBody),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const data = await response.json();
|
|
212
|
+
if (!response.ok || !(data as Record<string, unknown>).success) {
|
|
213
|
+
return { success: false, error: (data as Record<string, unknown>).error ?? 'Unknown error' };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return data;
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
// 5. x402_link_info
|
|
221
|
+
{
|
|
222
|
+
name: 'x402_link_info',
|
|
223
|
+
description: 'Get details about a payment link by router address',
|
|
224
|
+
inputSchema: {
|
|
225
|
+
type: 'object',
|
|
226
|
+
properties: {
|
|
227
|
+
routerAddress: { type: 'string', description: 'Router contract address or payment URL' },
|
|
228
|
+
},
|
|
229
|
+
required: ['routerAddress'],
|
|
230
|
+
},
|
|
231
|
+
async execute(params) {
|
|
232
|
+
let routerAddress = params.routerAddress as string;
|
|
233
|
+
|
|
234
|
+
// Extract address from URL if needed
|
|
235
|
+
if (routerAddress.startsWith('http')) {
|
|
236
|
+
const url = new URL(routerAddress);
|
|
237
|
+
const parts = url.pathname.split('/');
|
|
238
|
+
routerAddress = parts[parts.length - 1];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!routerAddress.startsWith('0x') || routerAddress.length !== 42) {
|
|
242
|
+
return { success: false, error: 'Invalid router address' };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const { getConfig } = await import('../core/config.js');
|
|
246
|
+
const config = getConfig();
|
|
247
|
+
|
|
248
|
+
const apiUrl = `${config.x402LinksApiUrl}/api/links/${routerAddress}/details`;
|
|
249
|
+
const response = await fetch(apiUrl);
|
|
250
|
+
const data = await response.json();
|
|
251
|
+
|
|
252
|
+
if (!response.ok || !(data as Record<string, unknown>).success) {
|
|
253
|
+
return { success: false, error: (data as Record<string, unknown>).error ?? 'Link not found' };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return data;
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
// 6. x402_routers
|
|
261
|
+
{
|
|
262
|
+
name: 'x402_routers',
|
|
263
|
+
description: 'List payment routers where your wallet is a beneficiary',
|
|
264
|
+
inputSchema: {
|
|
265
|
+
type: 'object',
|
|
266
|
+
properties: {
|
|
267
|
+
withBalances: { type: 'boolean', description: 'Fetch on-chain USDC balance for each router' },
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
async execute(params) {
|
|
271
|
+
const withBalances = params.withBalances as boolean | undefined;
|
|
272
|
+
|
|
273
|
+
const { getWalletAddress } = await import('../core/client.js');
|
|
274
|
+
const { getConfig, getUsdcAddress } = await import('../core/config.js');
|
|
275
|
+
|
|
276
|
+
const config = getConfig();
|
|
277
|
+
const address = getWalletAddress();
|
|
278
|
+
|
|
279
|
+
const apiUrl = `${config.x402LinksApiUrl}/api/links/beneficiary/${address}`;
|
|
280
|
+
const response = await fetch(apiUrl);
|
|
281
|
+
const data = await response.json() as { success: boolean; links?: Array<Record<string, unknown>>; error?: string };
|
|
282
|
+
|
|
283
|
+
if (!response.ok || !data.success) {
|
|
284
|
+
return { success: false, error: data.error ?? 'Failed to fetch routers' };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const links = data.links ?? [];
|
|
288
|
+
|
|
289
|
+
if (!withBalances) {
|
|
290
|
+
return {
|
|
291
|
+
success: true,
|
|
292
|
+
address,
|
|
293
|
+
routers: links.map((l) => ({
|
|
294
|
+
routerAddress: l.router_address,
|
|
295
|
+
name: (l.metadata as Record<string, unknown>)?.name ?? 'Unnamed',
|
|
296
|
+
chainId: l.chain_id,
|
|
297
|
+
sharePercent: l.beneficiary_percentage,
|
|
298
|
+
createdAt: l.created_at,
|
|
299
|
+
})),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Fetch balances
|
|
304
|
+
const { getClient } = await import('../core/client.js');
|
|
305
|
+
const { formatUnits } = await import('viem');
|
|
306
|
+
const client = getClient();
|
|
307
|
+
const usdcAddress = getUsdcAddress(config.chainId);
|
|
308
|
+
|
|
309
|
+
const ERC20_ABI = [{
|
|
310
|
+
name: 'balanceOf' as const,
|
|
311
|
+
type: 'function' as const,
|
|
312
|
+
stateMutability: 'view' as const,
|
|
313
|
+
inputs: [{ name: 'account', type: 'address' }],
|
|
314
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
315
|
+
}] as const;
|
|
316
|
+
|
|
317
|
+
const routers = await Promise.all(links.map(async (l) => {
|
|
318
|
+
const routerAddr = l.router_address as `0x${string}`;
|
|
319
|
+
const share = (l.beneficiary_percentage as number) / 100;
|
|
320
|
+
let balance = '0';
|
|
321
|
+
try {
|
|
322
|
+
const bal = await client.publicClient.readContract({
|
|
323
|
+
address: usdcAddress,
|
|
324
|
+
abi: ERC20_ABI,
|
|
325
|
+
functionName: 'balanceOf',
|
|
326
|
+
args: [routerAddr],
|
|
327
|
+
});
|
|
328
|
+
balance = formatUnits(bal, 6);
|
|
329
|
+
} catch { /* */ }
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
routerAddress: l.router_address,
|
|
333
|
+
name: (l.metadata as Record<string, unknown>)?.name ?? 'Unnamed',
|
|
334
|
+
chainId: l.chain_id,
|
|
335
|
+
sharePercent: l.beneficiary_percentage,
|
|
336
|
+
balance,
|
|
337
|
+
estimatedWithdrawal: (parseFloat(balance) * share).toFixed(6),
|
|
338
|
+
createdAt: l.created_at,
|
|
339
|
+
};
|
|
340
|
+
}));
|
|
341
|
+
|
|
342
|
+
return { success: true, address, routers };
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
// 7. x402_distribute
|
|
347
|
+
{
|
|
348
|
+
name: 'x402_distribute',
|
|
349
|
+
description: 'Distribute (withdraw) USDC from a PaymentRouter contract',
|
|
350
|
+
inputSchema: {
|
|
351
|
+
type: 'object',
|
|
352
|
+
properties: {
|
|
353
|
+
routerAddress: { type: 'string', description: 'PaymentRouter contract address' },
|
|
354
|
+
amount: { type: 'string', description: 'USDC amount to distribute (defaults to full balance)' },
|
|
355
|
+
},
|
|
356
|
+
required: ['routerAddress'],
|
|
357
|
+
},
|
|
358
|
+
async execute(params) {
|
|
359
|
+
const routerAddress = params.routerAddress as string;
|
|
360
|
+
const specifiedAmount = params.amount as string | undefined;
|
|
361
|
+
|
|
362
|
+
if (!routerAddress.startsWith('0x') || routerAddress.length !== 42) {
|
|
363
|
+
return { success: false, error: 'Invalid router address' };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const { getClient } = await import('../core/client.js');
|
|
367
|
+
const { getConfig, getUsdcAddress } = await import('../core/config.js');
|
|
368
|
+
const { formatUnits, parseUnits } = await import('viem');
|
|
369
|
+
|
|
370
|
+
const config = getConfig();
|
|
371
|
+
const client = getClient();
|
|
372
|
+
const usdcAddress = getUsdcAddress(config.chainId);
|
|
373
|
+
const routerAddr = routerAddress as `0x${string}`;
|
|
374
|
+
|
|
375
|
+
const ERC20_ABI = [{
|
|
376
|
+
name: 'balanceOf' as const,
|
|
377
|
+
type: 'function' as const,
|
|
378
|
+
stateMutability: 'view' as const,
|
|
379
|
+
inputs: [{ name: 'account', type: 'address' }],
|
|
380
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
381
|
+
}] as const;
|
|
382
|
+
|
|
383
|
+
const ROUTER_ABI = [{
|
|
384
|
+
name: 'distribute' as const,
|
|
385
|
+
type: 'function' as const,
|
|
386
|
+
stateMutability: 'nonpayable' as const,
|
|
387
|
+
inputs: [
|
|
388
|
+
{ name: 'token', type: 'address' },
|
|
389
|
+
{ name: 'amount', type: 'uint256' },
|
|
390
|
+
],
|
|
391
|
+
outputs: [],
|
|
392
|
+
}] as const;
|
|
393
|
+
|
|
394
|
+
const routerBalance = await client.publicClient.readContract({
|
|
395
|
+
address: usdcAddress,
|
|
396
|
+
abi: ERC20_ABI,
|
|
397
|
+
functionName: 'balanceOf',
|
|
398
|
+
args: [routerAddr],
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
if (routerBalance === 0n) {
|
|
402
|
+
return { success: false, error: 'Router has no USDC balance to distribute' };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
let distributeAmount: bigint;
|
|
406
|
+
if (specifiedAmount) {
|
|
407
|
+
distributeAmount = parseUnits(specifiedAmount, 6);
|
|
408
|
+
if (distributeAmount > routerBalance) {
|
|
409
|
+
return {
|
|
410
|
+
success: false,
|
|
411
|
+
error: `Requested ${specifiedAmount} USDC exceeds balance ${formatUnits(routerBalance, 6)} USDC`,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
distributeAmount = routerBalance;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
419
|
+
const txHash = await (client.walletClient as any).writeContract({
|
|
420
|
+
address: routerAddr,
|
|
421
|
+
abi: ROUTER_ABI,
|
|
422
|
+
functionName: 'distribute',
|
|
423
|
+
args: [usdcAddress, distributeAmount],
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const receipt = await client.publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
success: receipt.status === 'success',
|
|
430
|
+
routerAddress,
|
|
431
|
+
amount: formatUnits(distributeAmount, 6),
|
|
432
|
+
amountRaw: distributeAmount.toString(),
|
|
433
|
+
transactionHash: txHash,
|
|
434
|
+
blockNumber: receipt.blockNumber.toString(),
|
|
435
|
+
status: receipt.status,
|
|
436
|
+
};
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
|
|
440
|
+
// 8. x402_watcher_status
|
|
441
|
+
{
|
|
442
|
+
name: 'x402_watcher_status',
|
|
443
|
+
description: 'Get the status of the background payment watcher (tracked routers, payments detected)',
|
|
444
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
445
|
+
async execute() {
|
|
446
|
+
if (!watcher) {
|
|
447
|
+
return { running: false, error: 'Watcher is not enabled' };
|
|
448
|
+
}
|
|
449
|
+
return watcher.getStatus();
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
];
|
|
453
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// OpenClaw Plugin API types for agentic-x402
|
|
2
|
+
|
|
3
|
+
/** Logger provided by OpenClaw to plugins */
|
|
4
|
+
export interface PluginLogger {
|
|
5
|
+
info(message: string): void;
|
|
6
|
+
warn(message: string): void;
|
|
7
|
+
error(message: string): void;
|
|
8
|
+
debug(message: string): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** A background service registered by a plugin */
|
|
12
|
+
export interface PluginService {
|
|
13
|
+
id: string;
|
|
14
|
+
start(): Promise<void>;
|
|
15
|
+
stop(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** JSON Schema for tool input parameters */
|
|
19
|
+
export interface ToolInputSchema {
|
|
20
|
+
type: 'object';
|
|
21
|
+
properties: Record<string, {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
enum?: string[];
|
|
25
|
+
default?: unknown;
|
|
26
|
+
}>;
|
|
27
|
+
required?: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** An agent tool registered by a plugin */
|
|
31
|
+
export interface PluginTool {
|
|
32
|
+
name: string;
|
|
33
|
+
description: string;
|
|
34
|
+
inputSchema: ToolInputSchema;
|
|
35
|
+
execute(params: Record<string, unknown>): Promise<unknown>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Commander.js-style program for CLI registration */
|
|
39
|
+
export interface CliProgram {
|
|
40
|
+
command(name: string): CliCommand;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CliCommand {
|
|
44
|
+
command(name: string): CliCommand;
|
|
45
|
+
description(desc: string): CliCommand;
|
|
46
|
+
argument(name: string, desc?: string): CliCommand;
|
|
47
|
+
option(flags: string, desc?: string, defaultValue?: unknown): CliCommand;
|
|
48
|
+
action(fn: (...args: unknown[]) => void | Promise<void>): CliCommand;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The API that OpenClaw passes to plugin register() */
|
|
52
|
+
export interface OpenClawPluginApi {
|
|
53
|
+
logger: PluginLogger;
|
|
54
|
+
config: Record<string, unknown>;
|
|
55
|
+
gatewayPort: number;
|
|
56
|
+
registerService(service: PluginService): void;
|
|
57
|
+
registerTool(tool: PluginTool): void;
|
|
58
|
+
registerCli(
|
|
59
|
+
setup: (ctx: { program: CliProgram }) => void,
|
|
60
|
+
opts: { commands: string[] },
|
|
61
|
+
): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Plugin config schema matching openclaw.plugin.json */
|
|
65
|
+
export interface X402PluginConfig {
|
|
66
|
+
evmPrivateKey?: string;
|
|
67
|
+
network?: 'mainnet' | 'testnet';
|
|
68
|
+
maxPaymentUsd?: number;
|
|
69
|
+
x402LinksApiUrl?: string;
|
|
70
|
+
watcher?: {
|
|
71
|
+
enabled?: boolean;
|
|
72
|
+
pollIntervalMs?: number;
|
|
73
|
+
notifyOnPayment?: boolean;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Tracked router state in the watcher */
|
|
78
|
+
export interface TrackedRouter {
|
|
79
|
+
address: string;
|
|
80
|
+
name: string;
|
|
81
|
+
lastBalance: bigint;
|
|
82
|
+
lastChecked: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Payment detection event */
|
|
86
|
+
export interface PaymentEvent {
|
|
87
|
+
routerAddress: string;
|
|
88
|
+
routerName: string;
|
|
89
|
+
previousBalance: string;
|
|
90
|
+
newBalance: string;
|
|
91
|
+
increase: string;
|
|
92
|
+
detectedAt: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Watcher status for introspection */
|
|
96
|
+
export interface WatcherStatus {
|
|
97
|
+
running: boolean;
|
|
98
|
+
pollIntervalMs: number;
|
|
99
|
+
trackedRouters: Array<{
|
|
100
|
+
address: string;
|
|
101
|
+
name: string;
|
|
102
|
+
balance: string;
|
|
103
|
+
lastChecked: string;
|
|
104
|
+
}>;
|
|
105
|
+
paymentsDetected: number;
|
|
106
|
+
lastPollAt: string | null;
|
|
107
|
+
}
|