agentic-x402 0.3.1 → 0.3.2

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": "agentic-x402",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Agent skill for x402 payments - pay for and sell gated content",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- if (watcher) {
23
- console.log('Watcher is already running as a background service.');
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
- // Start a standalone watcher for debugging
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 debugWatcher = new WatcherClass({
40
+ const fgWatcher = new WatcherClass({
39
41
  logger,
40
- gatewayPort: 0,
42
+ gatewayPort: api.gatewayPort || 0,
41
43
  pollIntervalMs: interval,
42
- notifyOnPayment: false,
44
+ notifyOnPayment: api.gatewayPort > 0,
43
45
  });
44
46
 
45
- console.log('Starting payment watcher in foreground...');
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 debugWatcher.stop();
52
+ await fgWatcher.stop();
50
53
  process.exit(0);
51
54
  });
52
55
 
53
- await debugWatcher.start();
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
- if (!watcher) {
64
- console.log('Payment watcher is not running.');
65
- console.log('Enable it in your OpenClaw plugin config or start it with: openclaw x402 watch');
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
- const status = watcher.getStatus();
70
- const address = getWalletAddress();
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
- console.log('x402 Payment Watcher Status');
73
- console.log('===========================\n');
74
- console.log(`Running: ${status.running ? 'Yes' : 'No'}`);
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
- }
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
- // Initial poll to seed state
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) return;
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 [key, router] of this.trackedRouters) {
160
+ for (const [, router] of this.trackedRouters) {
154
161
  try {
155
162
  const balance = await client.publicClient.readContract({
156
163
  address: usdcAddress,