x402-bazaar 1.2.2 → 2.0.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/bin/cli.js CHANGED
@@ -4,6 +4,10 @@ import { Command } from 'commander';
4
4
  import { initCommand } from '../src/commands/init.js';
5
5
  import { configCommand } from '../src/commands/config.js';
6
6
  import { statusCommand } from '../src/commands/status.js';
7
+ import { listCommand } from '../src/commands/list.js';
8
+ import { searchCommand } from '../src/commands/search.js';
9
+ import { callCommand } from '../src/commands/call.js';
10
+ import { walletCommand } from '../src/commands/wallet.js';
7
11
  import chalk from 'chalk';
8
12
 
9
13
  // Global error handler
@@ -21,7 +25,7 @@ const program = new Command();
21
25
  program
22
26
  .name('x402-bazaar')
23
27
  .description(chalk.hex('#FF9900')('x402 Bazaar') + ' — Connect your AI agent to the marketplace in one command')
24
- .version('1.2.2');
28
+ .version('2.0.0');
25
29
 
26
30
  program
27
31
  .command('init')
@@ -49,17 +53,53 @@ program
49
53
  .option('--server-url <url>', 'Server URL to check', 'https://x402-api.onrender.com')
50
54
  .action(statusCommand);
51
55
 
56
+ program
57
+ .command('list')
58
+ .description('List all services on x402 Bazaar')
59
+ .option('--chain <chain>', 'Filter by chain (base or skale)')
60
+ .option('--category <category>', 'Filter by category (ai, data, weather, etc.)')
61
+ .option('--free', 'Show only free services')
62
+ .option('--server-url <url>', 'Server URL', 'https://x402-api.onrender.com')
63
+ .action(listCommand);
64
+
65
+ program
66
+ .command('search <query>')
67
+ .description('Search for services by keyword')
68
+ .option('--server-url <url>', 'Server URL', 'https://x402-api.onrender.com')
69
+ .action(searchCommand);
70
+
71
+ program
72
+ .command('call <endpoint>')
73
+ .description('Call a marketplace endpoint (testing/debugging)')
74
+ .option('--param <key=value>', 'Add parameter (can be used multiple times)', (value, previous) => {
75
+ return previous ? [...previous, value] : [value];
76
+ }, [])
77
+ .option('--server-url <url>', 'Server URL', 'https://x402-api.onrender.com')
78
+ .action(callCommand);
79
+
80
+ program
81
+ .command('wallet')
82
+ .description('Check USDC wallet balance on Base')
83
+ .option('--address <address>', 'Ethereum address to check')
84
+ .action(walletCommand);
85
+
52
86
  // Default: show help if no command given
53
87
  if (process.argv.length <= 2) {
54
88
  console.log('');
55
- console.log(chalk.hex('#FF9900').bold(' x402 Bazaar') + chalk.dim(' — AI Agent Marketplace CLI'));
89
+ console.log(chalk.hex('#FF9900').bold(' x402 Bazaar') + chalk.dim(' — AI Agent Marketplace CLI v2'));
90
+ console.log('');
91
+ console.log(' Setup commands:');
92
+ console.log(chalk.cyan(' npx x402-bazaar init') + chalk.dim(' Full interactive setup'));
93
+ console.log(chalk.cyan(' npx x402-bazaar config') + chalk.dim(' Generate MCP config'));
94
+ console.log(chalk.cyan(' npx x402-bazaar status') + chalk.dim(' Check server connection'));
56
95
  console.log('');
57
- console.log(' Quick start:');
58
- console.log(chalk.cyan(' npx x402-bazaar init') + chalk.dim(' Full interactive setup'));
59
- console.log(chalk.cyan(' npx x402-bazaar status') + chalk.dim(' Check server connection'));
60
- console.log(chalk.cyan(' npx x402-bazaar config') + chalk.dim(' Generate MCP config'));
96
+ console.log(' Marketplace commands:');
97
+ console.log(chalk.cyan(' npx x402-bazaar list') + chalk.dim(' Browse all services'));
98
+ console.log(chalk.cyan(' npx x402-bazaar search <query>') + chalk.dim(' Find services by keyword'));
99
+ console.log(chalk.cyan(' npx x402-bazaar call <endpoint>') + chalk.dim(' Test an API endpoint'));
100
+ console.log(chalk.cyan(' npx x402-bazaar wallet') + chalk.dim(' Check wallet balance'));
61
101
  console.log('');
62
- console.log(chalk.dim(' Run with --help for all options'));
102
+ console.log(chalk.dim(' Run any command with --help for detailed options'));
63
103
  console.log('');
64
104
  process.exit(0);
65
105
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "x402-bazaar",
3
- "version": "1.2.2",
4
- "description": "CLI to set up x402 Bazaar MCP server for AI agents. One command to connect your agent to the marketplace.",
3
+ "version": "2.0.0",
4
+ "description": "CLI to set up x402 Bazaar MCP server for AI agents. Browse, search, and call marketplace APIs. One command to connect your agent.",
5
5
  "type": "module",
6
6
  "main": "bin/cli.js",
7
7
  "bin": {
@@ -0,0 +1,197 @@
1
+ import ora from 'ora';
2
+ import chalk from 'chalk';
3
+ import { log } from '../utils/logger.js';
4
+
5
+ export async function callCommand(endpoint, options) {
6
+ if (!endpoint || endpoint.trim().length === 0) {
7
+ log.error('Endpoint is required');
8
+ log.dim(' Usage: x402-bazaar call <endpoint> [--param key=value...]');
9
+ log.dim(' Example: x402-bazaar call /api/weather --param city=Paris');
10
+ log.dim(' Example: x402-bazaar call /api/search --param q="AI agents"');
11
+ console.log('');
12
+ process.exit(1);
13
+ }
14
+
15
+ const serverUrl = options.serverUrl || 'https://x402-api.onrender.com';
16
+
17
+ // Normalize endpoint (add leading slash if missing)
18
+ const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
19
+ const fullUrl = `${serverUrl}${normalizedEndpoint}`;
20
+
21
+ log.banner();
22
+ log.info(`Calling endpoint: ${chalk.bold(normalizedEndpoint)}`);
23
+ console.log('');
24
+
25
+ // Parse params
26
+ const params = {};
27
+ if (options.param) {
28
+ const paramArray = Array.isArray(options.param) ? options.param : [options.param];
29
+
30
+ for (const p of paramArray) {
31
+ const [key, ...valueParts] = p.split('=');
32
+ if (!key || valueParts.length === 0) {
33
+ log.warn(`Invalid param format: ${p} (expected key=value)`);
34
+ continue;
35
+ }
36
+ let value = valueParts.join('='); // rejoin in case value contains '='
37
+ // Strip quotes if present
38
+ if ((value.startsWith('"') && value.endsWith('"')) ||
39
+ (value.startsWith("'") && value.endsWith("'"))) {
40
+ value = value.slice(1, -1);
41
+ }
42
+ params[key.trim()] = value;
43
+ }
44
+ }
45
+
46
+ if (Object.keys(params).length > 0) {
47
+ log.info('Parameters:');
48
+ for (const [k, v] of Object.entries(params)) {
49
+ log.dim(` ${k}: ${v}`);
50
+ }
51
+ console.log('');
52
+ }
53
+
54
+ // Build URL with query params (always use GET for marketplace APIs)
55
+ let finalUrl = fullUrl;
56
+ if (Object.keys(params).length > 0) {
57
+ const queryString = new URLSearchParams(params).toString();
58
+ finalUrl = `${fullUrl}?${queryString}`;
59
+ }
60
+
61
+ const spinner = ora(`GET ${finalUrl}...`).start();
62
+
63
+ try {
64
+ const fetchOptions = {
65
+ method: 'GET',
66
+ headers: {
67
+ 'Content-Type': 'application/json',
68
+ },
69
+ signal: AbortSignal.timeout(30000),
70
+ };
71
+
72
+ const res = await fetch(finalUrl, fetchOptions);
73
+
74
+ spinner.stop();
75
+
76
+ // Handle 402 Payment Required
77
+ if (res.status === 402) {
78
+ console.log('');
79
+ log.warn(chalk.bold('Payment Required (HTTP 402)'));
80
+ console.log('');
81
+
82
+ try {
83
+ const paymentInfo = await res.json();
84
+
85
+ if (paymentInfo.price) {
86
+ log.info(`Price: ${chalk.cyan(`${paymentInfo.price} USDC`)}`);
87
+ }
88
+ if (paymentInfo.paymentAddress) {
89
+ log.info(`Payment address: ${chalk.hex('#34D399')(paymentInfo.paymentAddress)}`);
90
+ }
91
+ if (paymentInfo.message) {
92
+ log.dim(` ${paymentInfo.message}`);
93
+ }
94
+
95
+ console.log('');
96
+ log.separator();
97
+ console.log('');
98
+ log.info('To pay automatically, use the MCP server via Claude/Cursor.');
99
+ log.dim(' The MCP server handles x402 payments transparently.');
100
+ log.dim(' Install: npx x402-bazaar init');
101
+ console.log('');
102
+
103
+ } catch {
104
+ log.dim(' This endpoint requires payment.');
105
+ log.dim(' Use the MCP server for automatic payment handling.');
106
+ console.log('');
107
+ }
108
+ return;
109
+ }
110
+
111
+ // Handle other errors
112
+ if (!res.ok) {
113
+ console.log('');
114
+ log.error(`HTTP ${res.status}: ${res.statusText}`);
115
+
116
+ try {
117
+ const errorBody = await res.text();
118
+ if (errorBody) {
119
+ console.log('');
120
+ log.dim('Response:');
121
+ console.log(chalk.red(errorBody));
122
+ }
123
+ } catch {
124
+ // Ignore
125
+ }
126
+
127
+ console.log('');
128
+ process.exit(1);
129
+ }
130
+
131
+ // Success
132
+ console.log('');
133
+ log.success(`${chalk.bold(res.status)} ${res.statusText}`);
134
+ console.log('');
135
+
136
+ const contentType = res.headers.get('content-type') || '';
137
+
138
+ if (contentType.includes('application/json')) {
139
+ const responseData = await res.json();
140
+
141
+ log.separator();
142
+ console.log('');
143
+ log.info('Response (JSON):');
144
+ console.log('');
145
+
146
+ // Pretty-print JSON with syntax highlighting
147
+ const jsonString = JSON.stringify(responseData, null, 2);
148
+ const highlighted = highlightJson(jsonString);
149
+ console.log(highlighted);
150
+ console.log('');
151
+ log.separator();
152
+
153
+ } else {
154
+ const responseText = await res.text();
155
+
156
+ log.separator();
157
+ console.log('');
158
+ log.info('Response:');
159
+ console.log('');
160
+ console.log(chalk.white(responseText));
161
+ console.log('');
162
+ log.separator();
163
+ }
164
+
165
+ console.log('');
166
+
167
+ } catch (err) {
168
+ spinner.fail('Request failed');
169
+ console.log('');
170
+
171
+ if (err.name === 'AbortError') {
172
+ log.error('Request timeout (30s) — server may be slow or endpoint not responding');
173
+ log.dim(' Try again or check status: npx x402-bazaar status');
174
+ } else if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') {
175
+ log.error('Cannot connect to server');
176
+ log.dim(` Server URL: ${serverUrl}`);
177
+ log.dim(' Check your internet connection or try: npx x402-bazaar status');
178
+ } else {
179
+ log.error(err.message);
180
+ }
181
+
182
+ console.log('');
183
+ process.exit(1);
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Simple JSON syntax highlighting for terminal
189
+ */
190
+ function highlightJson(jsonString) {
191
+ return jsonString
192
+ .replace(/"([^"]+)":/g, chalk.hex('#60A5FA')('"$1"') + ':')
193
+ .replace(/: "([^"]+)"/g, ': ' + chalk.hex('#34D399')('"$1"'))
194
+ .replace(/: (\d+\.?\d*)/g, ': ' + chalk.hex('#FBBF24')('$1'))
195
+ .replace(/: (true|false)/g, ': ' + chalk.hex('#9333EA')('$1'))
196
+ .replace(/: null/g, ': ' + chalk.hex('#6B7280')('null'));
197
+ }
@@ -0,0 +1,125 @@
1
+ import ora from 'ora';
2
+ import chalk from 'chalk';
3
+ import { log } from '../utils/logger.js';
4
+
5
+ const CATEGORIES = [
6
+ 'ai', 'data', 'automation', 'blockchain', 'weather', 'finance',
7
+ 'social', 'image', 'video', 'audio', 'search', 'translation', 'other'
8
+ ];
9
+
10
+ export async function listCommand(options) {
11
+ const serverUrl = options.serverUrl || 'https://x402-api.onrender.com';
12
+
13
+ log.banner();
14
+ log.info(`Fetching services from ${chalk.bold('x402 Bazaar')}`);
15
+ console.log('');
16
+
17
+ // Build query params
18
+ const params = new URLSearchParams();
19
+ if (options.chain) params.append('chain', options.chain);
20
+ if (options.category) params.append('category', options.category);
21
+ if (options.free) params.append('free', 'true');
22
+
23
+ const url = `${serverUrl}/api/services${params.toString() ? '?' + params.toString() : ''}`;
24
+
25
+ const spinner = ora('Loading services...').start();
26
+
27
+ try {
28
+ const res = await fetch(url, { signal: AbortSignal.timeout(15000) });
29
+
30
+ if (!res.ok) {
31
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
32
+ }
33
+
34
+ const data = await res.json();
35
+ const services = data.services || data || [];
36
+
37
+ spinner.succeed(`Found ${chalk.bold(services.length)} service${services.length !== 1 ? 's' : ''}`);
38
+ console.log('');
39
+
40
+ if (services.length === 0) {
41
+ log.warn('No services found with these filters.');
42
+ log.dim(' Try: x402-bazaar list --category ai');
43
+ console.log('');
44
+ return;
45
+ }
46
+
47
+ // Display services table
48
+ log.separator();
49
+ console.log('');
50
+
51
+ services.forEach((service, idx) => {
52
+ const name = service.name || service.title || 'Unnamed Service';
53
+ const price = parseFloat(service.price || 0);
54
+ const priceLabel = price === 0
55
+ ? chalk.hex('#34D399').bold('FREE')
56
+ : chalk.cyan(`$${price.toFixed(3)} USDC`);
57
+
58
+ // Extract category from tags
59
+ let category = 'other';
60
+ if (service.tags && Array.isArray(service.tags)) {
61
+ const realCategories = service.tags.filter(t => CATEGORIES.includes(t.toLowerCase()));
62
+ if (realCategories.length > 0) {
63
+ category = realCategories[0];
64
+ }
65
+ }
66
+
67
+ const chain = service.chain || 'base';
68
+ const chainLabel = chain === 'skale'
69
+ ? chalk.hex('#9333EA')('SKALE')
70
+ : chalk.hex('#0052FF')('Base');
71
+
72
+ console.log(
73
+ chalk.hex('#FF9900').bold(`${(idx + 1).toString().padStart(2, ' ')}. `) +
74
+ chalk.white.bold(name)
75
+ );
76
+ console.log(` ${priceLabel} ${chalk.dim('|')} ${chalk.hex('#60A5FA')(category)} ${chalk.dim('|')} ${chainLabel}`);
77
+
78
+ if (service.description) {
79
+ const desc = service.description.length > 80
80
+ ? service.description.substring(0, 77) + '...'
81
+ : service.description;
82
+ console.log(` ${chalk.hex('#6B7280')(desc)}`);
83
+ }
84
+
85
+ if (service.endpoint) {
86
+ console.log(` ${chalk.dim('Endpoint:')} ${chalk.hex('#FBBF24')(service.endpoint)}`);
87
+ }
88
+
89
+ console.log('');
90
+ });
91
+
92
+ log.separator();
93
+ console.log('');
94
+ log.info(`Total: ${chalk.bold(services.length)} service${services.length !== 1 ? 's' : ''}`);
95
+
96
+ // Show filter info
97
+ if (options.chain || options.category || options.free) {
98
+ console.log('');
99
+ log.dim(' Active filters:');
100
+ if (options.chain) log.dim(` Chain: ${options.chain}`);
101
+ if (options.category) log.dim(` Category: ${options.category}`);
102
+ if (options.free) log.dim(' Price: free only');
103
+ log.dim(' Run without options to see all services');
104
+ }
105
+
106
+ console.log('');
107
+
108
+ } catch (err) {
109
+ spinner.fail('Failed to fetch services');
110
+
111
+ if (err.name === 'AbortError') {
112
+ log.error('Request timeout — server may be sleeping (Render free tier)');
113
+ log.dim(' Try again in 30 seconds or check status: npx x402-bazaar status');
114
+ } else if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') {
115
+ log.error('Cannot connect to server');
116
+ log.dim(` Server URL: ${serverUrl}`);
117
+ log.dim(' Check your internet connection or try: npx x402-bazaar status');
118
+ } else {
119
+ log.error(err.message);
120
+ }
121
+
122
+ console.log('');
123
+ process.exit(1);
124
+ }
125
+ }
@@ -0,0 +1,120 @@
1
+ import ora from 'ora';
2
+ import chalk from 'chalk';
3
+ import { log } from '../utils/logger.js';
4
+
5
+ const CATEGORIES = [
6
+ 'ai', 'data', 'automation', 'blockchain', 'weather', 'finance',
7
+ 'social', 'image', 'video', 'audio', 'search', 'translation', 'other'
8
+ ];
9
+
10
+ export async function searchCommand(query, options) {
11
+ if (!query || query.trim().length === 0) {
12
+ log.error('Search query is required');
13
+ log.dim(' Usage: x402-bazaar search <query>');
14
+ log.dim(' Example: x402-bazaar search "weather API"');
15
+ console.log('');
16
+ process.exit(1);
17
+ }
18
+
19
+ const serverUrl = options.serverUrl || 'https://x402-api.onrender.com';
20
+
21
+ log.banner();
22
+ log.info(`Searching for: ${chalk.bold(query)}`);
23
+ console.log('');
24
+
25
+ const url = `${serverUrl}/api/services?search=${encodeURIComponent(query)}`;
26
+ const spinner = ora('Searching...').start();
27
+
28
+ try {
29
+ const res = await fetch(url, { signal: AbortSignal.timeout(15000) });
30
+
31
+ if (!res.ok) {
32
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
33
+ }
34
+
35
+ const data = await res.json();
36
+ const services = data.services || data || [];
37
+
38
+ if (services.length === 0) {
39
+ spinner.fail('No results found');
40
+ console.log('');
41
+ log.warn(`No services match "${query}"`);
42
+ log.dim(' Tips:');
43
+ log.dim(' • Check your spelling');
44
+ log.dim(' • Try broader keywords (e.g., "AI" instead of "GPT-4")');
45
+ log.dim(' • Browse all services: x402-bazaar list');
46
+ console.log('');
47
+ return;
48
+ }
49
+
50
+ spinner.succeed(`Found ${chalk.bold(services.length)} result${services.length !== 1 ? 's' : ''}`);
51
+ console.log('');
52
+
53
+ // Display results
54
+ log.separator();
55
+ console.log('');
56
+
57
+ services.forEach((service, idx) => {
58
+ const name = service.name || service.title || 'Unnamed Service';
59
+ const price = parseFloat(service.price || 0);
60
+ const priceLabel = price === 0
61
+ ? chalk.hex('#34D399').bold('FREE')
62
+ : chalk.cyan(`$${price.toFixed(3)} USDC`);
63
+
64
+ // Extract category from tags
65
+ let category = 'other';
66
+ if (service.tags && Array.isArray(service.tags)) {
67
+ const realCategories = service.tags.filter(t => CATEGORIES.includes(t.toLowerCase()));
68
+ if (realCategories.length > 0) {
69
+ category = realCategories[0];
70
+ }
71
+ }
72
+
73
+ const chain = service.chain || 'base';
74
+ const chainLabel = chain === 'skale'
75
+ ? chalk.hex('#9333EA')('SKALE')
76
+ : chalk.hex('#0052FF')('Base');
77
+
78
+ console.log(
79
+ chalk.hex('#FF9900').bold(`${(idx + 1).toString().padStart(2, ' ')}. `) +
80
+ chalk.white.bold(name)
81
+ );
82
+ console.log(` ${priceLabel} ${chalk.dim('|')} ${chalk.hex('#60A5FA')(category)} ${chalk.dim('|')} ${chainLabel}`);
83
+
84
+ if (service.description) {
85
+ const desc = service.description.length > 80
86
+ ? service.description.substring(0, 77) + '...'
87
+ : service.description;
88
+ console.log(` ${chalk.hex('#6B7280')(desc)}`);
89
+ }
90
+
91
+ if (service.endpoint) {
92
+ console.log(` ${chalk.dim('Endpoint:')} ${chalk.hex('#FBBF24')(service.endpoint)}`);
93
+ }
94
+
95
+ console.log('');
96
+ });
97
+
98
+ log.separator();
99
+ console.log('');
100
+ log.info(`Total: ${chalk.bold(services.length)} result${services.length !== 1 ? 's' : ''}`);
101
+ console.log('');
102
+
103
+ } catch (err) {
104
+ spinner.fail('Search failed');
105
+
106
+ if (err.name === 'AbortError') {
107
+ log.error('Request timeout — server may be sleeping (Render free tier)');
108
+ log.dim(' Try again in 30 seconds or check status: npx x402-bazaar status');
109
+ } else if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') {
110
+ log.error('Cannot connect to server');
111
+ log.dim(` Server URL: ${serverUrl}`);
112
+ log.dim(' Check your internet connection or try: npx x402-bazaar status');
113
+ } else {
114
+ log.error(err.message);
115
+ }
116
+
117
+ console.log('');
118
+ process.exit(1);
119
+ }
120
+ }
@@ -0,0 +1,156 @@
1
+ import ora from 'ora';
2
+ import chalk from 'chalk';
3
+ import { log } from '../utils/logger.js';
4
+
5
+ const BASE_RPC_URL = 'https://mainnet.base.org';
6
+ const USDC_CONTRACT_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
7
+ const BALANCE_OF_SELECTOR = '0x70a08231';
8
+
9
+ export async function walletCommand(options) {
10
+ log.banner();
11
+
12
+ if (!options.address) {
13
+ log.info('Check USDC balance for any wallet address on Base.');
14
+ console.log('');
15
+ log.dim(' Usage:');
16
+ log.dim(' x402-bazaar wallet --address 0xYourAddress');
17
+ console.log('');
18
+ log.dim(' Example:');
19
+ log.dim(' x402-bazaar wallet --address 0xA986540F0AaDFB5Ba5ceb2b1d81d90DBE479084b');
20
+ console.log('');
21
+ log.dim(' To find your agent wallet address:');
22
+ log.dim(' 1. Run: npx x402-bazaar init');
23
+ log.dim(' 2. Or check your .env file: AGENT_PRIVATE_KEY');
24
+ log.dim(' 3. Or ask your AI agent: "What is my wallet address?"');
25
+ console.log('');
26
+ return;
27
+ }
28
+
29
+ const address = options.address.trim();
30
+
31
+ // Validate address format
32
+ if (!/^0x[a-fA-F0-9]{40}$/.test(address)) {
33
+ log.error('Invalid Ethereum address format');
34
+ log.dim(' Expected: 0x followed by 40 hexadecimal characters');
35
+ log.dim(` Got: ${address}`);
36
+ console.log('');
37
+ process.exit(1);
38
+ }
39
+
40
+ log.info(`Checking wallet: ${chalk.bold(maskAddress(address))}`);
41
+ console.log('');
42
+
43
+ const spinner = ora('Fetching USDC balance from Base...').start();
44
+
45
+ try {
46
+ // Encode balanceOf(address) call
47
+ // balanceOf selector: 0x70a08231
48
+ // Param: address (32 bytes, left-padded)
49
+ const paddedAddress = address.slice(2).padStart(64, '0');
50
+ const data = BALANCE_OF_SELECTOR + paddedAddress;
51
+
52
+ // Make RPC call
53
+ const rpcPayload = {
54
+ jsonrpc: '2.0',
55
+ id: 1,
56
+ method: 'eth_call',
57
+ params: [
58
+ {
59
+ to: USDC_CONTRACT_BASE,
60
+ data: data,
61
+ },
62
+ 'latest',
63
+ ],
64
+ };
65
+
66
+ const res = await fetch(BASE_RPC_URL, {
67
+ method: 'POST',
68
+ headers: { 'Content-Type': 'application/json' },
69
+ body: JSON.stringify(rpcPayload),
70
+ signal: AbortSignal.timeout(15000),
71
+ });
72
+
73
+ if (!res.ok) {
74
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
75
+ }
76
+
77
+ const rpcResponse = await res.json();
78
+
79
+ if (rpcResponse.error) {
80
+ throw new Error(rpcResponse.error.message || 'RPC error');
81
+ }
82
+
83
+ if (!rpcResponse.result) {
84
+ throw new Error('No result from RPC');
85
+ }
86
+
87
+ // Parse balance (USDC has 6 decimals)
88
+ const balanceHex = rpcResponse.result;
89
+ const balanceRaw = BigInt(balanceHex);
90
+ const balanceUsdc = Number(balanceRaw) / 1_000_000;
91
+
92
+ spinner.succeed('Balance fetched');
93
+ console.log('');
94
+
95
+ log.separator();
96
+ console.log('');
97
+
98
+ log.info(`Address: ${chalk.hex('#34D399')(maskAddress(address))}`);
99
+ log.info(`Network: ${chalk.hex('#0052FF').bold('Base Mainnet')} (Chain ID: 8453)`);
100
+ log.info(`Balance: ${chalk.cyan.bold(balanceUsdc.toFixed(6))} ${chalk.dim('USDC')}`);
101
+
102
+ console.log('');
103
+ log.separator();
104
+ console.log('');
105
+
106
+ // Show explorer link
107
+ log.dim(` Explorer: https://basescan.org/address/${address}`);
108
+
109
+ // Helpful tips based on balance
110
+ if (balanceUsdc === 0) {
111
+ console.log('');
112
+ log.warn('This wallet has no USDC.');
113
+ log.dim(' To fund it:');
114
+ log.dim(' 1. Open MetaMask and switch to Base network');
115
+ log.dim(' 2. Send USDC to this address');
116
+ log.dim(' 3. Send a tiny amount of ETH for gas (~$0.01)');
117
+ log.dim(' Get USDC: bridge from Ethereum or buy on Base DEX');
118
+ } else if (balanceUsdc < 0.1) {
119
+ console.log('');
120
+ log.warn('Low balance — consider adding more USDC.');
121
+ log.dim(' Most x402 Bazaar APIs cost $0.005-$0.05 per call.');
122
+ } else {
123
+ console.log('');
124
+ log.success('Wallet is funded and ready!');
125
+ const estimatedCalls = Math.floor(balanceUsdc / 0.02);
126
+ log.dim(` Estimated API calls: ~${estimatedCalls} (at avg $0.02/call)`);
127
+ }
128
+
129
+ console.log('');
130
+
131
+ } catch (err) {
132
+ spinner.fail('Failed to fetch balance');
133
+ console.log('');
134
+
135
+ if (err.name === 'AbortError') {
136
+ log.error('Request timeout — Base RPC may be slow');
137
+ log.dim(' Try again in a few seconds');
138
+ } else if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') {
139
+ log.error('Cannot connect to Base RPC');
140
+ log.dim(' Check your internet connection');
141
+ } else {
142
+ log.error(err.message);
143
+ }
144
+
145
+ console.log('');
146
+ process.exit(1);
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Mask address for display: 0xabcd...1234
152
+ */
153
+ function maskAddress(address) {
154
+ if (address.length < 12) return address;
155
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
156
+ }