agentic-x402 0.3.1 → 0.3.3
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/SKILL.md +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/plugin/cli.ts +103 -37
- package/scripts/plugin/watcher.ts +14 -7
- package/skills/x402/SKILL.md +1 -1
package/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Make x402 payments to access gated APIs and content. Fetch paid res
|
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Requires Node.js 20+, network access to x402 facilitators and EVM chains
|
|
6
6
|
homepage: https://www.npmjs.com/package/agentic-x402
|
|
7
|
-
metadata: {"author": "monemetrics", "version": "0.3.
|
|
7
|
+
metadata: {"author": "monemetrics", "version": "0.3.3", "openclaw": {"requires": {"bins": ["x402"], "env": ["EVM_PRIVATE_KEY"]}, "primaryEnv": "EVM_PRIVATE_KEY", "install": [{"id": "node", "kind": "node", "package": "agentic-x402", "bins": ["x402"], "label": "Install agentic-x402 (npm)"}], "plugin": true}}
|
|
8
8
|
allowed-tools: Bash(x402:*) Bash(npm:*) Read
|
|
9
9
|
---
|
|
10
10
|
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/scripts/plugin/cli.ts
CHANGED
|
@@ -19,38 +19,41 @@ export function registerCliCommands(
|
|
|
19
19
|
.option('--interval <ms>', 'Poll interval in milliseconds', '30000')
|
|
20
20
|
.action(async (opts: unknown) => {
|
|
21
21
|
const { interval: intervalStr } = opts as { interval: string };
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
|
|
23
|
+
// If the watcher is actually running (gateway context), show its status
|
|
24
|
+
if (watcher && watcher.getStatus().running) {
|
|
24
25
|
const status = watcher.getStatus();
|
|
26
|
+
console.log('Watcher is running as a background service.');
|
|
25
27
|
console.log(`Tracking ${status.trackedRouters.length} router(s)`);
|
|
26
28
|
console.log(`Payments detected: ${status.paymentsDetected}`);
|
|
29
|
+
console.log(`Last poll: ${status.lastPollAt ?? 'Never'}`);
|
|
27
30
|
return;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
//
|
|
33
|
+
// CLI context: watcher exists but was never start()-ed, or is null.
|
|
34
|
+
// Start a watcher in foreground for debugging.
|
|
31
35
|
const { PaymentWatcher: WatcherClass } = await import('./watcher.js');
|
|
32
|
-
const { applyConfigBridge } = await import('./config-bridge.js');
|
|
33
|
-
|
|
34
|
-
applyConfigBridge({});
|
|
35
36
|
|
|
36
37
|
const interval = parseInt(intervalStr, 10);
|
|
38
|
+
const address = getWalletAddress();
|
|
37
39
|
|
|
38
|
-
const
|
|
40
|
+
const fgWatcher = new WatcherClass({
|
|
39
41
|
logger,
|
|
40
|
-
gatewayPort: 0,
|
|
42
|
+
gatewayPort: api.gatewayPort || 0,
|
|
41
43
|
pollIntervalMs: interval,
|
|
42
|
-
notifyOnPayment:
|
|
44
|
+
notifyOnPayment: api.gatewayPort > 0,
|
|
43
45
|
});
|
|
44
46
|
|
|
45
|
-
console.log(
|
|
47
|
+
console.log(`Starting payment watcher in foreground for ${address}...`);
|
|
48
|
+
console.log(`Poll interval: ${interval / 1000}s`);
|
|
46
49
|
console.log('Press Ctrl+C to stop.\n');
|
|
47
50
|
|
|
48
51
|
process.on('SIGINT', async () => {
|
|
49
|
-
await
|
|
52
|
+
await fgWatcher.stop();
|
|
50
53
|
process.exit(0);
|
|
51
54
|
});
|
|
52
55
|
|
|
53
|
-
await
|
|
56
|
+
await fgWatcher.start();
|
|
54
57
|
|
|
55
58
|
// Keep alive
|
|
56
59
|
await new Promise(() => {});
|
|
@@ -59,35 +62,98 @@ export function registerCliCommands(
|
|
|
59
62
|
x402
|
|
60
63
|
.command('status')
|
|
61
64
|
.description('Show payment watcher status, tracked routers, and payment count')
|
|
62
|
-
.action(() => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
.action(async () => {
|
|
66
|
+
const address = getWalletAddress();
|
|
67
|
+
|
|
68
|
+
// If watcher is running in gateway context, show its live state
|
|
69
|
+
if (watcher && watcher.getStatus().running) {
|
|
70
|
+
const status = watcher.getStatus();
|
|
71
|
+
|
|
72
|
+
console.log('x402 Payment Watcher Status');
|
|
73
|
+
console.log('===========================\n');
|
|
74
|
+
console.log(`Running: Yes (background service)`);
|
|
75
|
+
console.log(`Poll interval: ${status.pollIntervalMs / 1000}s`);
|
|
76
|
+
console.log(`Payments detected: ${status.paymentsDetected}`);
|
|
77
|
+
console.log(`Last poll: ${status.lastPollAt ?? 'Never'}`);
|
|
78
|
+
console.log(`Wallet address: ${address}`);
|
|
79
|
+
|
|
80
|
+
if (status.trackedRouters.length === 0) {
|
|
81
|
+
console.log('\nNo routers tracked yet.');
|
|
82
|
+
} else {
|
|
83
|
+
console.log(`\nTracked Routers (${status.trackedRouters.length}):`);
|
|
84
|
+
for (const r of status.trackedRouters) {
|
|
85
|
+
console.log(` ${r.name}`);
|
|
86
|
+
console.log(` Address: ${r.address}`);
|
|
87
|
+
console.log(` Balance: ${r.balance} USDC`);
|
|
88
|
+
console.log(` Checked: ${r.lastChecked}`);
|
|
89
|
+
console.log('');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
66
92
|
return;
|
|
67
93
|
}
|
|
68
94
|
|
|
69
|
-
|
|
70
|
-
|
|
95
|
+
// CLI context: do a one-shot poll to show current state
|
|
96
|
+
console.log('x402 Watcher Status (live query)');
|
|
97
|
+
console.log('================================\n');
|
|
98
|
+
console.log(`Wallet: ${address}`);
|
|
71
99
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
const { getConfig, getUsdcAddress } = await import('../core/config.js');
|
|
101
|
+
const { getClient } = await import('../core/client.js');
|
|
102
|
+
const { formatUnits } = await import('viem');
|
|
103
|
+
|
|
104
|
+
const config = getConfig();
|
|
105
|
+
|
|
106
|
+
// Fetch routers from API
|
|
107
|
+
const apiUrl = `${config.x402LinksApiUrl}/api/links/beneficiary/${address}`;
|
|
108
|
+
const response = await fetch(apiUrl);
|
|
109
|
+
const data = await response.json() as {
|
|
110
|
+
success: boolean;
|
|
111
|
+
links?: Array<{
|
|
112
|
+
router_address: string;
|
|
113
|
+
metadata?: { name?: string };
|
|
114
|
+
}>;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (!data.success || !data.links || data.links.length === 0) {
|
|
118
|
+
console.log('No routers found for this wallet.');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(`Found ${data.links.length} router(s):\n`);
|
|
123
|
+
|
|
124
|
+
const client = getClient();
|
|
125
|
+
const usdcAddress = getUsdcAddress(config.chainId);
|
|
126
|
+
|
|
127
|
+
const ERC20_ABI = [{
|
|
128
|
+
name: 'balanceOf' as const,
|
|
129
|
+
type: 'function' as const,
|
|
130
|
+
stateMutability: 'view' as const,
|
|
131
|
+
inputs: [{ name: 'account', type: 'address' }],
|
|
132
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
133
|
+
}] as const;
|
|
134
|
+
|
|
135
|
+
for (const link of data.links) {
|
|
136
|
+
const name = link.metadata?.name ?? 'Unnamed';
|
|
137
|
+
let balance = '?';
|
|
138
|
+
try {
|
|
139
|
+
const bal = await client.publicClient.readContract({
|
|
140
|
+
address: usdcAddress,
|
|
141
|
+
abi: ERC20_ABI,
|
|
142
|
+
functionName: 'balanceOf',
|
|
143
|
+
args: [link.router_address as `0x${string}`],
|
|
144
|
+
});
|
|
145
|
+
balance = `${formatUnits(bal, 6)} USDC`;
|
|
146
|
+
} catch { /* */ }
|
|
147
|
+
|
|
148
|
+
console.log(` ${name}`);
|
|
149
|
+
console.log(` Router: ${link.router_address}`);
|
|
150
|
+
console.log(` Balance: ${balance}`);
|
|
151
|
+
console.log('');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (watcher && !watcher.getStatus().running) {
|
|
155
|
+
console.log('Note: The background watcher is registered but not running.');
|
|
156
|
+
console.log('It starts automatically with the gateway. Use "openclaw x402 watch" for foreground mode.');
|
|
91
157
|
}
|
|
92
158
|
});
|
|
93
159
|
}, { commands: ['x402'] });
|
|
@@ -49,12 +49,12 @@ export class PaymentWatcher implements PluginService {
|
|
|
49
49
|
async start(): Promise<void> {
|
|
50
50
|
this.logger.info(`Starting payment watcher (poll every ${this.pollIntervalMs / 1000}s)`);
|
|
51
51
|
|
|
52
|
-
//
|
|
52
|
+
// Set timer first so the watcher is "running" even if initial poll fails
|
|
53
|
+
this.timer = setInterval(() => this.poll(), this.pollIntervalMs);
|
|
54
|
+
|
|
55
|
+
// Initial poll to seed state (errors are caught inside poll())
|
|
53
56
|
await this.poll();
|
|
54
57
|
this.seeded = true;
|
|
55
|
-
|
|
56
|
-
// Start periodic polling
|
|
57
|
-
this.timer = setInterval(() => this.poll(), this.pollIntervalMs);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
async stop(): Promise<void> {
|
|
@@ -72,7 +72,7 @@ export class PaymentWatcher implements PluginService {
|
|
|
72
72
|
address: r.address,
|
|
73
73
|
name: r.name,
|
|
74
74
|
balance: formatUnitsLocal(r.lastBalance, 6),
|
|
75
|
-
lastChecked: new Date(r.lastChecked).toISOString(),
|
|
75
|
+
lastChecked: r.lastChecked ? new Date(r.lastChecked).toISOString() : 'never',
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -109,6 +109,8 @@ export class PaymentWatcher implements PluginService {
|
|
|
109
109
|
const address = getWalletAddress();
|
|
110
110
|
const apiUrl = `${config.x402LinksApiUrl}/api/links/beneficiary/${address}`;
|
|
111
111
|
|
|
112
|
+
this.logger.debug(`Fetching routers for ${address} from ${apiUrl}`);
|
|
113
|
+
|
|
112
114
|
const response = await fetch(apiUrl);
|
|
113
115
|
if (!response.ok) {
|
|
114
116
|
this.logger.warn(`Failed to fetch routers: ${response.status}`);
|
|
@@ -123,7 +125,10 @@ export class PaymentWatcher implements PluginService {
|
|
|
123
125
|
}>;
|
|
124
126
|
};
|
|
125
127
|
|
|
126
|
-
if (!data.success || !data.links)
|
|
128
|
+
if (!data.success || !data.links) {
|
|
129
|
+
this.logger.debug(`Router API returned success=${data.success}, links=${data.links?.length ?? 0}`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
127
132
|
|
|
128
133
|
// Add any new routers we haven't seen yet
|
|
129
134
|
for (const link of data.links) {
|
|
@@ -138,6 +143,8 @@ export class PaymentWatcher implements PluginService {
|
|
|
138
143
|
this.logger.info(`Tracking router: ${link.metadata?.name ?? 'Unnamed'} (${link.router_address})`);
|
|
139
144
|
}
|
|
140
145
|
}
|
|
146
|
+
|
|
147
|
+
this.logger.debug(`Tracking ${this.trackedRouters.size} router(s)`);
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
private async checkBalances(): Promise<void> {
|
|
@@ -150,7 +157,7 @@ export class PaymentWatcher implements PluginService {
|
|
|
150
157
|
const client = getClient();
|
|
151
158
|
const usdcAddress = getUsdcAddress(config.chainId);
|
|
152
159
|
|
|
153
|
-
for (const [
|
|
160
|
+
for (const [, router] of this.trackedRouters) {
|
|
154
161
|
try {
|
|
155
162
|
const balance = await client.publicClient.readContract({
|
|
156
163
|
address: usdcAddress,
|
package/skills/x402/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Make x402 payments to access gated APIs and content. Fetch paid res
|
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Requires Node.js 20+, network access to x402 facilitators and EVM chains
|
|
6
6
|
homepage: https://www.npmjs.com/package/agentic-x402
|
|
7
|
-
metadata: {"author": "monemetrics", "version": "0.3.
|
|
7
|
+
metadata: {"author": "monemetrics", "version": "0.3.3", "openclaw": {"requires": {"bins": ["x402"], "env": ["EVM_PRIVATE_KEY"]}, "primaryEnv": "EVM_PRIVATE_KEY", "install": [{"id": "node", "kind": "node", "package": "agentic-x402", "bins": ["x402"], "label": "Install agentic-x402 (npm)"}], "plugin": true}}
|
|
8
8
|
allowed-tools: Bash(x402:*) Bash(npm:*) Read
|
|
9
9
|
---
|
|
10
10
|
|