@mcpaid/sdk 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.
Files changed (111) hide show
  1. package/LICENSE +176 -0
  2. package/NPM_RESTRUCTURE.md +86 -0
  3. package/OPERATOR-LICENSE.md +18 -0
  4. package/README.md +380 -0
  5. package/SELF-HOSTING.md +108 -0
  6. package/dist/circuit-breaker/circuit-breaker.d.ts +26 -0
  7. package/dist/circuit-breaker/circuit-breaker.d.ts.map +1 -0
  8. package/dist/circuit-breaker/circuit-breaker.js +128 -0
  9. package/dist/circuit-breaker/circuit-breaker.js.map +1 -0
  10. package/dist/cli.d.ts +21 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +1560 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/contracts/ToolPayRouter.d.ts +288 -0
  15. package/dist/contracts/ToolPayRouter.d.ts.map +1 -0
  16. package/dist/contracts/ToolPayRouter.js +370 -0
  17. package/dist/contracts/ToolPayRouter.js.map +1 -0
  18. package/dist/contracts/router-registry.d.ts +19 -0
  19. package/dist/contracts/router-registry.d.ts.map +1 -0
  20. package/dist/contracts/router-registry.js +28 -0
  21. package/dist/contracts/router-registry.js.map +1 -0
  22. package/dist/fee/fee-calculator.d.ts +35 -0
  23. package/dist/fee/fee-calculator.d.ts.map +1 -0
  24. package/dist/fee/fee-calculator.js +57 -0
  25. package/dist/fee/fee-calculator.js.map +1 -0
  26. package/dist/fee/fee-ledger.d.ts +66 -0
  27. package/dist/fee/fee-ledger.d.ts.map +1 -0
  28. package/dist/fee/fee-ledger.js +181 -0
  29. package/dist/fee/fee-ledger.js.map +1 -0
  30. package/dist/gateway/config-loader.d.ts +38 -0
  31. package/dist/gateway/config-loader.d.ts.map +1 -0
  32. package/dist/gateway/config-loader.js +125 -0
  33. package/dist/gateway/config-loader.js.map +1 -0
  34. package/dist/gateway/server-registry.d.ts +25 -0
  35. package/dist/gateway/server-registry.d.ts.map +1 -0
  36. package/dist/gateway/server-registry.js +59 -0
  37. package/dist/gateway/server-registry.js.map +1 -0
  38. package/dist/gateway/toolpay-gateway.d.ts +57 -0
  39. package/dist/gateway/toolpay-gateway.d.ts.map +1 -0
  40. package/dist/gateway/toolpay-gateway.js +320 -0
  41. package/dist/gateway/toolpay-gateway.js.map +1 -0
  42. package/dist/index.d.ts +36 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +37 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/sdk/client-agent.d.ts +47 -0
  47. package/dist/sdk/client-agent.d.ts.map +1 -0
  48. package/dist/sdk/client-agent.js +158 -0
  49. package/dist/sdk/client-agent.js.map +1 -0
  50. package/dist/sdk/server-sdk.d.ts +51 -0
  51. package/dist/sdk/server-sdk.d.ts.map +1 -0
  52. package/dist/sdk/server-sdk.js +170 -0
  53. package/dist/sdk/server-sdk.js.map +1 -0
  54. package/dist/settlement/base-contract-client.d.ts +156 -0
  55. package/dist/settlement/base-contract-client.d.ts.map +1 -0
  56. package/dist/settlement/base-contract-client.js +156 -0
  57. package/dist/settlement/base-contract-client.js.map +1 -0
  58. package/dist/settlement/nonce-store.d.ts +54 -0
  59. package/dist/settlement/nonce-store.d.ts.map +1 -0
  60. package/dist/settlement/nonce-store.js +125 -0
  61. package/dist/settlement/nonce-store.js.map +1 -0
  62. package/dist/settlement/onchain-verifier.d.ts +41 -0
  63. package/dist/settlement/onchain-verifier.d.ts.map +1 -0
  64. package/dist/settlement/onchain-verifier.js +114 -0
  65. package/dist/settlement/onchain-verifier.js.map +1 -0
  66. package/dist/settlement/payment-orchestrator.d.ts +59 -0
  67. package/dist/settlement/payment-orchestrator.d.ts.map +1 -0
  68. package/dist/settlement/payment-orchestrator.js +282 -0
  69. package/dist/settlement/payment-orchestrator.js.map +1 -0
  70. package/dist/settlement/permit-verifier.d.ts +60 -0
  71. package/dist/settlement/permit-verifier.d.ts.map +1 -0
  72. package/dist/settlement/permit-verifier.js +80 -0
  73. package/dist/settlement/permit-verifier.js.map +1 -0
  74. package/dist/settlement/session-wallet.d.ts +60 -0
  75. package/dist/settlement/session-wallet.d.ts.map +1 -0
  76. package/dist/settlement/session-wallet.js +105 -0
  77. package/dist/settlement/session-wallet.js.map +1 -0
  78. package/dist/transports/sse-transport.d.ts +27 -0
  79. package/dist/transports/sse-transport.d.ts.map +1 -0
  80. package/dist/transports/sse-transport.js +65 -0
  81. package/dist/transports/sse-transport.js.map +1 -0
  82. package/dist/transports/stdio-bridge.d.ts +29 -0
  83. package/dist/transports/stdio-bridge.d.ts.map +1 -0
  84. package/dist/transports/stdio-bridge.js +133 -0
  85. package/dist/transports/stdio-bridge.js.map +1 -0
  86. package/dist/types/circuit-breaker.d.ts +22 -0
  87. package/dist/types/circuit-breaker.d.ts.map +1 -0
  88. package/dist/types/circuit-breaker.js +5 -0
  89. package/dist/types/circuit-breaker.js.map +1 -0
  90. package/dist/types/ledger.d.ts +57 -0
  91. package/dist/types/ledger.d.ts.map +1 -0
  92. package/dist/types/ledger.js +5 -0
  93. package/dist/types/ledger.js.map +1 -0
  94. package/dist/types/mcp.d.ts +67 -0
  95. package/dist/types/mcp.d.ts.map +1 -0
  96. package/dist/types/mcp.js +5 -0
  97. package/dist/types/mcp.js.map +1 -0
  98. package/dist/types/pricing.d.ts +68 -0
  99. package/dist/types/pricing.d.ts.map +1 -0
  100. package/dist/types/pricing.js +5 -0
  101. package/dist/types/pricing.js.map +1 -0
  102. package/dist/types/x402.d.ts +76 -0
  103. package/dist/types/x402.d.ts.map +1 -0
  104. package/dist/types/x402.js +5 -0
  105. package/dist/types/x402.js.map +1 -0
  106. package/dist/utils/units.d.ts +45 -0
  107. package/dist/utils/units.d.ts.map +1 -0
  108. package/dist/utils/units.js +141 -0
  109. package/dist/utils/units.js.map +1 -0
  110. package/package.json +72 -0
  111. package/src/contracts/ToolPayRouter.json +371 -0
package/dist/cli.js ADDED
@@ -0,0 +1,1560 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ToolPay CLI
4
+ * Command-line interface for MCP developers to configure, validate, and launch ToolPay gateways.
5
+ */
6
+ import { writeFileSync, readFileSync, existsSync, mkdirSync, unlinkSync, realpathSync } from 'node:fs';
7
+ import { spawn } from 'node:child_process';
8
+ import { resolve, join } from 'node:path';
9
+ import { homedir } from 'node:os';
10
+ import { createInterface } from 'node:readline';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { createServer } from 'node:http';
13
+ import { loadConfigFile, generateSampleConfigFile } from './gateway/config-loader.js';
14
+ import { ServerRegistry } from './gateway/server-registry.js';
15
+ import { ToolPayGateway } from './gateway/toolpay-gateway.js';
16
+ import { PaymentOrchestrator } from './settlement/payment-orchestrator.js';
17
+ import { NonceStore } from './settlement/nonce-store.js';
18
+ import { PermitVerifier } from './settlement/permit-verifier.js';
19
+ import { OnChainVerifier } from './settlement/onchain-verifier.js';
20
+ import { SessionWalletManager } from './settlement/session-wallet.js';
21
+ import { FeeCalculator } from './fee/fee-calculator.js';
22
+ import { FeeLedger } from './fee/fee-ledger.js';
23
+ import { CircuitBreaker } from './circuit-breaker/circuit-breaker.js';
24
+ import { StdioBridge } from './transports/stdio-bridge.js';
25
+ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
26
+ function loadEnvFile() {
27
+ let dir = process.cwd();
28
+ for (let i = 0; i < 4; i++) {
29
+ const envPath = resolve(dir, '.env');
30
+ if (existsSync(envPath)) {
31
+ const lines = readFileSync(envPath, 'utf-8').split('\n');
32
+ for (const line of lines) {
33
+ const trimmed = line.trim();
34
+ if (!trimmed || trimmed.startsWith('#'))
35
+ continue;
36
+ const eqIdx = trimmed.indexOf('=');
37
+ if (eqIdx !== -1) {
38
+ const key = trimmed.slice(0, eqIdx).trim();
39
+ const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
40
+ if (!process.env[key]) {
41
+ process.env[key] = val;
42
+ }
43
+ }
44
+ }
45
+ break;
46
+ }
47
+ const parent = resolve(dir, '..');
48
+ if (parent === dir)
49
+ break;
50
+ dir = parent;
51
+ }
52
+ }
53
+ loadEnvFile();
54
+ export function getCredentialsPath() {
55
+ const dir = join(homedir(), '.mcpaid');
56
+ if (!existsSync(dir)) {
57
+ mkdirSync(dir, { recursive: true });
58
+ }
59
+ return join(dir, 'credentials.json');
60
+ }
61
+ export function loadCredentials() {
62
+ try {
63
+ const p = getCredentialsPath();
64
+ if (existsSync(p)) {
65
+ const data = JSON.parse(readFileSync(p, 'utf-8'));
66
+ if (data && data.token)
67
+ return data;
68
+ }
69
+ }
70
+ catch { }
71
+ return null;
72
+ }
73
+ export function saveCredentials(creds) {
74
+ const p = getCredentialsPath();
75
+ writeFileSync(p, JSON.stringify(creds, null, 2), 'utf-8');
76
+ }
77
+ export function clearCredentials() {
78
+ try {
79
+ const p = getCredentialsPath();
80
+ if (existsSync(p)) {
81
+ unlinkSync(p);
82
+ }
83
+ }
84
+ catch { }
85
+ }
86
+ export function getFlagValue(flags, name, alias) {
87
+ for (let i = 0; i < flags.length; i++) {
88
+ const f = flags[i];
89
+ if (f === `--${name}` || (alias && f === `-${alias}`)) {
90
+ return flags[i + 1];
91
+ }
92
+ if (f.startsWith(`--${name}=`)) {
93
+ return f.slice(name.length + 3);
94
+ }
95
+ }
96
+ return undefined;
97
+ }
98
+ export function hasFlag(flags, name, alias) {
99
+ return flags.some((f) => f === `--${name}` || (alias && f === `-${alias}`) || f.startsWith(`--${name}=`));
100
+ }
101
+ export function promptQuestion(query) {
102
+ const rl = createInterface({
103
+ input: process.stdin,
104
+ output: process.stdout,
105
+ });
106
+ return new Promise((res) => {
107
+ rl.question(query, (ans) => {
108
+ rl.close();
109
+ res(ans.trim());
110
+ });
111
+ });
112
+ }
113
+ function printHelp() {
114
+ console.log(`
115
+ MCPaid CLI - Zero-Friction MCP Monetization Gateway
116
+
117
+ Commands:
118
+ init Generate a sample mcpaid.config.json in the current directory
119
+ validate [config-path] Validate an mcpaid.config.json or toolpay.config.json file
120
+ start [config-path] Start local MCPaid gateway proxy on port 8080 (or PORT env)
121
+ dev [config-path] Run live zero-config development tunnel with security shield
122
+ bridge --gateway <url> Run stdio bridge piping local MCP desktop calls to MCPaid
123
+ agent-proxy --url <url> Connect local MCP clients / AI agents to remote monetized gateway (alias for bridge)
124
+ publish [config-path] Publish/deploy an MCP server to MCPaid Edge Gateway
125
+ tunnel [port] Launch zero-config Cloudflare tunnel for local MCP (default: 3000)
126
+ server add [config-path] Register an MCP server on MCPaid Edge Gateway
127
+ server list List your registered MCP servers
128
+ server remove <id> Disconnect and remove an MCP server from MCPaid
129
+ login Sign in to your MCPaid developer account
130
+ logout Log out and remove local credentials
131
+ whoami Show currently authenticated developer account
132
+ withdraw [amount] Withdraw available USDC earnings to your Base L2 payout wallet
133
+ withdrawals View payout and withdrawal history and live transfer statuses
134
+ relayer [options] Inspect automated Base L2 settlement status (operator --run gated)
135
+ wallet new Generate a fresh EVM wallet with funding instructions
136
+ --developer, -d Generate developer payout wallet for receiving earnings
137
+ --agent, -a Generate autonomous agent spending wallet for tool calls
138
+ wallet status Inspect configured agent wallet address and network settings
139
+
140
+ Options:
141
+ --port, -p <number> Port to listen on (default: 8080)
142
+ --gateway <url> Gateway URL (default: https://mcpaid.dev)
143
+ --upstream <url> Override upstream URL when publishing (e.g. tunnel URL)
144
+ --help, -h Show this help message
145
+ `);
146
+ }
147
+ async function handleInit() {
148
+ const targetPath = resolve(process.cwd(), 'mcpaid.config.json');
149
+ if (existsSync(targetPath)) {
150
+ console.error(`Error: ${targetPath} already exists.`);
151
+ process.exit(1);
152
+ }
153
+ const sample = generateSampleConfigFile();
154
+ writeFileSync(targetPath, sample, 'utf-8');
155
+ console.log(`✅ Created sample MCPaid configuration at: ${targetPath}`);
156
+ console.log(`\nNext steps:`);
157
+ console.log(`1. Open mcpaid.config.json and set your payoutWallet (Base/EVM address).`);
158
+ console.log(`2. Define your free vs paid tools.`);
159
+ console.log(`3. Run: npx @mcpaid/sdk start mcpaid.config.json\n`);
160
+ }
161
+ async function handleValidate(configPath) {
162
+ try {
163
+ const defaultPath = existsSync(resolve(process.cwd(), 'mcpaid.config.json'))
164
+ ? 'mcpaid.config.json'
165
+ : 'toolpay.config.json';
166
+ const fullPath = resolve(process.cwd(), configPath || defaultPath);
167
+ const { config, upstreamUrl } = loadConfigFile(fullPath);
168
+ console.log(`✅ Configuration valid!`);
169
+ console.log(`- Server ID: ${config.serverId}`);
170
+ console.log(`- Upstream Target: ${upstreamUrl}`);
171
+ console.log(`- Payout Wallet: ${config.payoutWallet}`);
172
+ console.log(`- Tools Configured: ${config.tools.length}`);
173
+ for (const tool of config.tools) {
174
+ const pricingStr = tool.type === 'free' ? 'FREE' : `$${tool.priceUsd} / call`;
175
+ console.log(` • ${tool.toolName.padEnd(25)} -> ${pricingStr}`);
176
+ }
177
+ }
178
+ catch (err) {
179
+ console.error(`❌ Validation failed: ${err.message}`);
180
+ process.exit(1);
181
+ }
182
+ }
183
+ async function handleStart(configPath) {
184
+ const defaultPath = existsSync(resolve(process.cwd(), 'mcpaid.config.json'))
185
+ ? 'mcpaid.config.json'
186
+ : 'toolpay.config.json';
187
+ const fullPath = resolve(process.cwd(), configPath || defaultPath);
188
+ const { config, upstreamUrl } = loadConfigFile(fullPath);
189
+ const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 8080;
190
+ const registry = new ServerRegistry();
191
+ registry.registerServer(config);
192
+ const treasuryWallet = config.platformFee?.platformTreasuryWallet || '0x1001001001001001001001001001001001001001';
193
+ const feeLedger = new FeeLedger(treasuryWallet);
194
+ const feeCalculator = new FeeCalculator(treasuryWallet, config.platformFee?.platformFeeBasisPoints ?? 300);
195
+ const orchestrator = new PaymentOrchestrator({
196
+ nonceStore: new NonceStore(),
197
+ permitVerifier: new PermitVerifier(),
198
+ onChainVerifier: new OnChainVerifier({ getTransaction: async () => null }),
199
+ sessionWalletManager: new SessionWalletManager(),
200
+ feeCalculator,
201
+ feeLedger,
202
+ });
203
+ const circuitBreaker = new CircuitBreaker();
204
+ // Forwarding handler to upstream MCP HTTP endpoint
205
+ const upstreamHandler = async (req) => {
206
+ try {
207
+ const res = await fetch(upstreamUrl, {
208
+ method: 'POST',
209
+ headers: { 'Content-Type': 'application/json' },
210
+ body: JSON.stringify(req),
211
+ });
212
+ return (await res.json());
213
+ }
214
+ catch (err) {
215
+ return {
216
+ jsonrpc: '2.0',
217
+ id: req.id ?? null,
218
+ error: { code: -32000, message: `Upstream error at ${upstreamUrl}: ${err.message}` },
219
+ };
220
+ }
221
+ };
222
+ const gateway = new ToolPayGateway({
223
+ registry,
224
+ orchestrator,
225
+ circuitBreaker,
226
+ upstreamHandler,
227
+ });
228
+ const server = createServer(async (req, res) => {
229
+ if (req.method !== 'POST') {
230
+ res.writeHead(405, { 'Content-Type': 'text/plain' });
231
+ res.end('Method Not Allowed. ToolPay accepts MCP JSON-RPC POST requests.');
232
+ return;
233
+ }
234
+ let bodyRaw = '';
235
+ req.on('data', (chunk) => {
236
+ bodyRaw += chunk;
237
+ });
238
+ req.on('end', async () => {
239
+ try {
240
+ const jsonRpcReq = JSON.parse(bodyRaw);
241
+ // Extract payment authorization headers if present
242
+ let paymentProof;
243
+ const authHeader = req.headers['authorization'] || req.headers['x-payment-proof'];
244
+ if (typeof authHeader === 'string') {
245
+ const raw = authHeader.replace(/^Bearer\s+/i, '').trim();
246
+ if (raw.startsWith('{')) {
247
+ try {
248
+ paymentProof = JSON.parse(raw);
249
+ }
250
+ catch { }
251
+ }
252
+ else {
253
+ try {
254
+ const decoded = Buffer.from(raw, 'base64').toString('utf8');
255
+ if (decoded.startsWith('{')) {
256
+ paymentProof = JSON.parse(decoded);
257
+ }
258
+ }
259
+ catch { }
260
+ }
261
+ }
262
+ const agentWallet = req.headers['x-agent-wallet'] || paymentProof?.signerWallet;
263
+ const idempotencyKey = req.headers['idempotency-key'];
264
+ const gatewayRes = await gateway.handleRequest(jsonRpcReq, {
265
+ serverId: config.serverId,
266
+ agentWallet,
267
+ paymentProof,
268
+ idempotencyKey,
269
+ });
270
+ res.writeHead(gatewayRes.statusCode, gatewayRes.headers);
271
+ res.end(JSON.stringify(gatewayRes.body));
272
+ }
273
+ catch (err) {
274
+ res.writeHead(500, { 'Content-Type': 'application/json' });
275
+ res.end(JSON.stringify({ error: err.message }));
276
+ }
277
+ });
278
+ });
279
+ server.listen(port, () => {
280
+ console.log(`\n======================================================`);
281
+ console.log(`🚀 MCPaid Gateway running on http://localhost:${port}`);
282
+ console.log(`• Monitored Server: ${config.serverId} (${config.serverName})`);
283
+ console.log(`• Upstream Target: ${upstreamUrl}`);
284
+ console.log(`• Payout Wallet: ${config.payoutWallet}`);
285
+ console.log(`• Settlement Rail: ${config.network.toUpperCase()} (${config.currency})`);
286
+ console.log(`• Platform Cut: ${(config.platformFee?.platformFeeBasisPoints ?? 300) / 100}%`);
287
+ console.log(`======================================================\n`);
288
+ });
289
+ }
290
+ async function handleBridge() {
291
+ let gatewayUrl;
292
+ const gwIdx = process.argv.indexOf('--gateway');
293
+ const urlIdx = process.argv.indexOf('--url');
294
+ if (gwIdx !== -1 && process.argv[gwIdx + 1]) {
295
+ gatewayUrl = process.argv[gwIdx + 1];
296
+ }
297
+ else if (urlIdx !== -1 && process.argv[urlIdx + 1]) {
298
+ gatewayUrl = process.argv[urlIdx + 1];
299
+ }
300
+ else if (process.argv[3] && !process.argv[3].startsWith('--')) {
301
+ gatewayUrl = process.argv[3];
302
+ }
303
+ if (!gatewayUrl) {
304
+ console.error('Error: Missing gateway URL. Usage: mcpaid bridge --gateway <url> (or --url <url>)');
305
+ process.exit(1);
306
+ }
307
+ const walletIndex = process.argv.indexOf('--wallet');
308
+ const agentWallet = walletIndex !== -1 ? process.argv[walletIndex + 1] : undefined;
309
+ const noWallet = process.argv.includes('--no-wallet');
310
+ const keyIndex = process.argv.indexOf('--key');
311
+ const privateKey = noWallet
312
+ ? undefined
313
+ : (keyIndex !== -1
314
+ ? process.argv[keyIndex + 1]
315
+ : (process.env.AGENT_PRIVATE_KEY || process.env.DEPLOYER_PRIVATE_KEY));
316
+ const maxPriceIndex = process.argv.indexOf('--max-price');
317
+ const autoApproveMaxUsd = maxPriceIndex !== -1
318
+ ? parseFloat(process.argv[maxPriceIndex + 1])
319
+ : (process.env.AGENT_MAX_PRICE_USD ? parseFloat(process.env.AGENT_MAX_PRICE_USD) : 1.00);
320
+ console.error(`[MCPaid Stdio Bridge] Connected to gateway: ${gatewayUrl}`);
321
+ if (privateKey) {
322
+ console.error(`[MCPaid Stdio Bridge] Autonomous wallet payment enabled (Auto-approve limit: $${autoApproveMaxUsd}/call)`);
323
+ }
324
+ const bridge = new StdioBridge({
325
+ gatewayUrl,
326
+ agentWallet,
327
+ privateKey,
328
+ autoApproveMaxUsd,
329
+ });
330
+ bridge.start();
331
+ }
332
+ async function handleWallet(subcommand) {
333
+ const walletArgs = process.argv.slice(3);
334
+ const isNew = !subcommand || subcommand === 'new' || subcommand === 'create' || subcommand === 'generate' || subcommand === 'developer' || subcommand === 'agent';
335
+ const isDeveloper = walletArgs.some((a) => a === '--developer' || a === '-d' || a === 'developer');
336
+ const isAgent = walletArgs.some((a) => a === '--agent' || a === '-a' || a === 'agent');
337
+ if (isNew && subcommand !== 'status' && subcommand !== 'info') {
338
+ const privateKey = generatePrivateKey();
339
+ const account = privateKeyToAccount(privateKey);
340
+ if (isDeveloper) {
341
+ console.log(`
342
+ ======================================================
343
+ 🔑 New MCPaid Developer Payout Wallet Generated
344
+ ======================================================
345
+ • Payout Address: ${account.address}
346
+ • Private Key: ${privateKey}
347
+ ======================================================
348
+
349
+ 📋 Developer Setup Instructions:
350
+ 1. Payout Wallet Address (Base L2):
351
+ ${account.address}
352
+
353
+ 2. Configure Your Server:
354
+ • In mcpaid.config.json, set:
355
+ "payoutWallet": "${account.address}"
356
+ • Or paste into the MCPaid Developer Dashboard:
357
+ https://mcpaid.dev/dashboard
358
+ • Or publish via CLI:
359
+ npx @mcpaid/sdk publish
360
+
361
+ 3. On-Chain Revenue Settlement:
362
+ • 97% net developer revenue from all paid tool calls settles
363
+ directly to this address on Base L2 in Native USDC.
364
+ • Native USDC Contract: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
365
+
366
+ 4. Accessing & Withdrawing Funds:
367
+ • Import this private key into Coinbase Wallet, MetaMask, or Rabby.
368
+ • View your address on Basescan:
369
+ https://basescan.org/address/${account.address}
370
+ ======================================================
371
+ `);
372
+ return;
373
+ }
374
+ if (isAgent) {
375
+ console.log(`
376
+ ======================================================
377
+ 🔑 New MCPaid Autonomous Agent Wallet Generated
378
+ ======================================================
379
+ • Agent Address: ${account.address}
380
+ • Private Key: ${privateKey}
381
+ ======================================================
382
+
383
+ 📋 Quick Setup:
384
+ 1. Add this key to your agent project's .env file:
385
+ AGENT_PRIVATE_KEY=${privateKey}
386
+
387
+ 2. Funding on Base L2 (USDC):
388
+ • Production (Base Mainnet - Chain ID 8453):
389
+ - Native USDC Contract: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
390
+ - Coinbase Direct: Withdraw USDC directly to ${account.address} selecting the "Base" network (<$0.01 gas fee).
391
+ - Official Base Bridge: Bridge USDC/ETH from Ethereum at https://bridge.base.org.
392
+ - DEX Swap: Swap ETH/tokens to USDC on Aerodrome or Uniswap on Base.
393
+ • Staging / Testnet (Base Sepolia - Chain ID 84532):
394
+ - Free USDC: https://faucet.circle.com (select Base Sepolia, paste ${account.address})
395
+ - Free Gas: https://console.optimism.io/faucet
396
+
397
+ 3. Using Paid MCP Tools:
398
+ • In your MCP client configuration (Claude Desktop, Cursor, OpenCode), configure the bridge:
399
+ npx @mcpaid/sdk bridge --gateway <gateway-url>
400
+ • The bridge automatically signs zero-gas EIP-712 permits using your AGENT_PRIVATE_KEY
401
+ and executes paid tools with zero manual approval friction.
402
+ ======================================================
403
+ `);
404
+ return;
405
+ }
406
+ // Default: Display wallet with sections for both roles
407
+ console.log(`
408
+ ======================================================
409
+ 🔑 New MCPaid EVM Keypair Generated
410
+ ======================================================
411
+ • Public Address: ${account.address}
412
+ • Private Key: ${privateKey}
413
+ ======================================================
414
+
415
+ 💡 Tip: Run with --developer or --agent for role-specific guidance:
416
+ npx @mcpaid/sdk wallet new --developer
417
+ npx @mcpaid/sdk wallet new --agent
418
+
419
+ ──────────────────────────────────────────────────────
420
+ [ROLE 1: DEVELOPER PAYOUT WALLET]
421
+ ──────────────────────────────────────────────────────
422
+ • Set "payoutWallet": "${account.address}" in mcpaid.config.json or in the dashboard (https://mcpaid.dev/dashboard).
423
+ • All 97% developer payouts from agent tool calls settle directly to this Base L2 address in native USDC.
424
+ • Basescan: https://basescan.org/address/${account.address}
425
+
426
+ ──────────────────────────────────────────────────────
427
+ [ROLE 2: AI AGENT SPENDING WALLET]
428
+ ──────────────────────────────────────────────────────
429
+ • Set AGENT_PRIVATE_KEY=${privateKey} in your project's .env file.
430
+ • Fund with small USDC on Base (via Coinbase Direct or https://bridge.base.org).
431
+ • Connect via stdio bridge: npx @mcpaid/sdk bridge --gateway <gateway-url>
432
+ ======================================================
433
+ `);
434
+ return;
435
+ }
436
+ if (subcommand === 'status' || subcommand === 'info') {
437
+ const creds = loadCredentials();
438
+ const key = process.env.AGENT_PRIVATE_KEY || process.env.DEPLOYER_PRIVATE_KEY;
439
+ if (!key && !creds?.payoutWallet) {
440
+ console.log(`
441
+ ❌ No agent or developer wallet configured in environment.
442
+ Run:
443
+ • For Developers: npx @mcpaid/sdk wallet new --developer
444
+ • For AI Agents: npx @mcpaid/sdk wallet new --agent
445
+ `);
446
+ return;
447
+ }
448
+ console.log(`
449
+ ======================================================
450
+ 👛 Configured MCPaid Wallet Status
451
+ ======================================================`);
452
+ if (key) {
453
+ try {
454
+ const account = privateKeyToAccount(key.startsWith('0x') ? key : `0x${key}`);
455
+ console.log(`• Agent / Local Wallet: ${account.address}`);
456
+ console.log(`• Config Source: ${process.env.AGENT_PRIVATE_KEY ? 'AGENT_PRIVATE_KEY' : 'DEPLOYER_PRIVATE_KEY'}`);
457
+ }
458
+ catch (e) {
459
+ console.error(`Invalid private key in environment: ${e.message}`);
460
+ }
461
+ }
462
+ if (creds?.payoutWallet) {
463
+ console.log(`• Account Payout Wallet: ${creds.payoutWallet}`);
464
+ }
465
+ console.log(`• Supported Rails: Base Mainnet (8453) & Base Sepolia (84532)
466
+ • Native USDC Contract: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
467
+ ======================================================
468
+ `);
469
+ return;
470
+ }
471
+ console.log(`
472
+ Usage: npx @mcpaid/sdk wallet <command> [options]
473
+
474
+ Commands:
475
+ new [--developer|--agent] Generate a fresh EVM wallet with tailored instructions
476
+ status Show the public address and settings for configured wallet
477
+ `);
478
+ }
479
+ async function handleLogin() {
480
+ const args = process.argv.slice(3);
481
+ const gatewayUrl = getFlagValue(args, 'gateway') || process.env.MCPAID_GATEWAY_URL || 'https://mcpaid.dev';
482
+ let email = getFlagValue(args, 'email');
483
+ if (!email) {
484
+ email = await promptQuestion('Enter your developer email: ');
485
+ }
486
+ email = email.toLowerCase().trim();
487
+ if (!email || !email.includes('@')) {
488
+ console.error('❌ Error: A valid email address is required.');
489
+ process.exit(1);
490
+ }
491
+ console.log(`\nConnecting to MCPaid Edge Gateway (${gatewayUrl})...`);
492
+ let authPurpose = 'login_2fa';
493
+ let loginRes = await fetch(`${gatewayUrl}/v1/auth/login`, {
494
+ method: 'POST',
495
+ headers: { 'Content-Type': 'application/json' },
496
+ body: JSON.stringify({ email }),
497
+ });
498
+ let loginData = (await loginRes.json().catch(() => ({})));
499
+ if (loginRes.status === 404) {
500
+ authPurpose = 'signup_confirmation';
501
+ console.log(`No existing account found for ${email}. Initiating developer account signup...`);
502
+ const signupRes = await fetch(`${gatewayUrl}/v1/auth/signup`, {
503
+ method: 'POST',
504
+ headers: { 'Content-Type': 'application/json' },
505
+ body: JSON.stringify({ email }),
506
+ });
507
+ loginData = (await signupRes.json().catch(() => ({})));
508
+ if (!signupRes.ok) {
509
+ console.error(`❌ Signup failed: ${loginData.error || JSON.stringify(loginData)}`);
510
+ process.exit(1);
511
+ }
512
+ }
513
+ else if (!loginRes.ok) {
514
+ console.error(`❌ Login request failed: ${loginData.error || JSON.stringify(loginData)}`);
515
+ process.exit(1);
516
+ }
517
+ console.log(`\n📧 Verification code sent to ${email}. Check your inbox.`);
518
+ if (loginData.devCodeHint) {
519
+ console.log(`[Dev Fallback Code]: ${loginData.devCodeHint}`);
520
+ }
521
+ let code = getFlagValue(args, 'code');
522
+ if (!code) {
523
+ code = await promptQuestion('Enter 6-digit verification code: ');
524
+ }
525
+ code = code.trim();
526
+ const verifyRes = await fetch(`${gatewayUrl}/v1/auth/verify`, {
527
+ method: 'POST',
528
+ headers: { 'Content-Type': 'application/json' },
529
+ body: JSON.stringify({ email, code, purpose: authPurpose, rememberMe: true }),
530
+ });
531
+ const verifyData = (await verifyRes.json().catch(() => ({})));
532
+ if (!verifyRes.ok || !verifyData.success) {
533
+ console.error(`❌ Verification failed: ${verifyData.error || 'Invalid or expired code'}`);
534
+ process.exit(1);
535
+ }
536
+ saveCredentials({
537
+ email: verifyData.user.email,
538
+ token: verifyData.token,
539
+ gatewayUrl,
540
+ payoutWallet: verifyData.user.payout_wallet || undefined,
541
+ });
542
+ console.log(`
543
+ ======================================================
544
+ ✅ Successfully Authenticated with MCPaid!
545
+ ======================================================
546
+ • Developer Email: ${verifyData.user.email}
547
+ • Payout Wallet: ${verifyData.user.payout_wallet || 'None configured yet (set on publish or dashboard)'}
548
+ • Gateway Target: ${gatewayUrl}
549
+ • Session Saved: ${getCredentialsPath()}
550
+ ======================================================
551
+ `);
552
+ }
553
+ async function handleLogout() {
554
+ const creds = loadCredentials();
555
+ if (creds) {
556
+ try {
557
+ await fetch(`${creds.gatewayUrl}/v1/auth/logout`, {
558
+ method: 'POST',
559
+ headers: {
560
+ 'Content-Type': 'application/json',
561
+ Authorization: `Bearer ${creds.token}`,
562
+ },
563
+ });
564
+ }
565
+ catch { }
566
+ }
567
+ clearCredentials();
568
+ console.log('✅ Successfully logged out and removed local credentials.');
569
+ }
570
+ async function handleWhoami() {
571
+ const creds = loadCredentials();
572
+ if (!creds) {
573
+ console.log('❌ Not logged in. Run "npx @mcpaid/sdk login" to sign in.');
574
+ return;
575
+ }
576
+ const gatewayUrl = creds.gatewayUrl || 'https://mcpaid.dev';
577
+ try {
578
+ const res = await fetch(`${gatewayUrl}/v1/auth/me`, {
579
+ headers: { Authorization: `Bearer ${creds.token}` },
580
+ });
581
+ const data = (await res.json());
582
+ if (data.authenticated && data.user) {
583
+ console.log(`
584
+ ======================================================
585
+ 👤 MCPaid Authenticated Developer Profile
586
+ ======================================================
587
+ • Email: ${data.user.email}
588
+ • User ID: ${data.user.id}
589
+ • Payout Wallet: ${data.user.payout_wallet || 'Not configured yet'}
590
+ • Verified: ${data.user.email_verified ? 'Yes' : 'No'}
591
+ • Gateway: ${gatewayUrl}
592
+ ======================================================
593
+ `);
594
+ return;
595
+ }
596
+ }
597
+ catch { }
598
+ console.log(`\n⚠️ Session expired or invalid for ${creds.email}. Please run "npx @mcpaid/sdk login".\n`);
599
+ }
600
+ export function isLocalOrPrivateHost(urlStr) {
601
+ try {
602
+ const parsed = new URL(urlStr);
603
+ const host = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, '');
604
+ return (host === 'localhost' ||
605
+ host === '127.0.0.1' ||
606
+ host === '0.0.0.0' ||
607
+ host === '::1' ||
608
+ host === '169.254.169.254' ||
609
+ host.endsWith('.local') ||
610
+ host.endsWith('.internal') ||
611
+ host.startsWith('10.') ||
612
+ host.startsWith('192.168.') ||
613
+ /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(host));
614
+ }
615
+ catch {
616
+ return false;
617
+ }
618
+ }
619
+ async function handleTunnel(portArg) {
620
+ const port = portArg || '3000';
621
+ console.log(`\n⚡ Starting zero-config Cloudflare Tunnel to http://127.0.0.1:${port}...`);
622
+ const child = spawn('cloudflared', ['tunnel', '--url', `http://127.0.0.1:${port}`], {
623
+ stdio: ['ignore', 'pipe', 'pipe'],
624
+ });
625
+ child.on('error', (err) => {
626
+ if (err.code === 'ENOENT') {
627
+ console.error(`\n❌ "cloudflared" is not installed on your system.`);
628
+ console.log(`Install it with:`);
629
+ console.log(` macOS: brew install cloudflared`);
630
+ console.log(` Linux: sudo apt-get install cloudflared`);
631
+ console.log(` Windows: winget install Cloudflare.cloudflared\n`);
632
+ console.log(`Alternatively, you can use ngrok: ngrok http ${port}\n`);
633
+ }
634
+ else {
635
+ console.error(`❌ Failed to start cloudflared: ${err.message}`);
636
+ }
637
+ process.exit(1);
638
+ });
639
+ let detectedUrl = null;
640
+ const onData = (data) => {
641
+ const text = data.toString();
642
+ const match = text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i);
643
+ if (match && !detectedUrl) {
644
+ detectedUrl = match[0];
645
+ console.log(`
646
+ ======================================================
647
+ 🌐 Cloudflare Tunnel Online!
648
+ ======================================================
649
+ • Public Tunnel: ${detectedUrl}
650
+ • Local Forward: http://127.0.0.1:${port}
651
+ • MCP Target: ${detectedUrl}/mcp
652
+
653
+ In another terminal, publish your MCP server to MCPaid Edge:
654
+ npx @mcpaid/sdk publish --upstream ${detectedUrl}/mcp
655
+ ======================================================
656
+ Press Ctrl+C to disconnect tunnel.
657
+ `);
658
+ }
659
+ };
660
+ child.stdout.on('data', onData);
661
+ child.stderr.on('data', onData);
662
+ process.on('SIGINT', () => {
663
+ console.log('\n🛑 Disconnecting tunnel...');
664
+ child.kill();
665
+ process.exit(0);
666
+ });
667
+ }
668
+ async function handleDev(configPathArg) {
669
+ const args = process.argv.slice(3);
670
+ const gatewayUrl = getFlagValue(args, 'gateway') || process.env.MCPAID_GATEWAY_URL || 'https://mcpaid.dev';
671
+ let creds = loadCredentials();
672
+ let effectiveToken = creds?.token || process.env.MCPAID_TOKEN || process.env.TOOLPAY_ADMIN_KEY;
673
+ if (!effectiveToken) {
674
+ console.log(`\n🔒 Authentication required to start a live dev tunnel on MCPaid Edge Gateway.`);
675
+ const doLogin = await promptQuestion('Would you like to sign in now? (Y/n): ');
676
+ if (doLogin.toLowerCase() === 'n') {
677
+ console.log('Dev mode cancelled. Run "npx @mcpaid/sdk login" first.');
678
+ return;
679
+ }
680
+ await handleLogin();
681
+ creds = loadCredentials();
682
+ effectiveToken = creds?.token || process.env.MCPAID_TOKEN || process.env.TOOLPAY_ADMIN_KEY;
683
+ }
684
+ if (!effectiveToken) {
685
+ console.error('❌ Authentication failed. Please login first.');
686
+ process.exit(1);
687
+ }
688
+ const candidatePath = configPathArg && !configPathArg.startsWith('-')
689
+ ? configPathArg
690
+ : (existsSync(resolve(process.cwd(), 'mcpaid.config.json'))
691
+ ? 'mcpaid.config.json'
692
+ : (existsSync(resolve(process.cwd(), 'toolpay.config.json')) ? 'toolpay.config.json' : null));
693
+ if (!candidatePath || !existsSync(resolve(process.cwd(), candidatePath))) {
694
+ console.error('❌ No mcpaid.config.json found in current directory. Run "npx @mcpaid/sdk init" first.');
695
+ process.exit(1);
696
+ }
697
+ const fullPath = resolve(process.cwd(), candidatePath);
698
+ console.log(`\n📄 Loading configuration from: ${candidatePath}`);
699
+ const loaded = loadConfigFile(fullPath);
700
+ const config = loaded.config;
701
+ const localUpstream = loaded.upstreamUrl;
702
+ // Validate payout wallet
703
+ if (!config.payoutWallet ||
704
+ config.payoutWallet.includes('YourBaseEVMWallet') ||
705
+ !/^0x[a-fA-F0-9]{40}$/.test(config.payoutWallet)) {
706
+ if (creds?.payoutWallet && /^0x[a-fA-F0-9]{40}$/.test(creds.payoutWallet)) {
707
+ console.log(`Using your account default payout wallet: ${creds.payoutWallet}`);
708
+ config.payoutWallet = creds.payoutWallet;
709
+ }
710
+ else {
711
+ const wallet = await promptQuestion('Enter your Base EVM payout wallet address (0x...): ');
712
+ if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
713
+ console.error('❌ Invalid Ethereum/Base address format (0x + 40 hex chars).');
714
+ process.exit(1);
715
+ }
716
+ config.payoutWallet = wallet;
717
+ }
718
+ }
719
+ // Parse local upstream target
720
+ let localPort = 3000;
721
+ let localPath = '/mcp';
722
+ try {
723
+ const parsed = new URL(localUpstream);
724
+ if (parsed.port)
725
+ localPort = parseInt(parsed.port, 10);
726
+ if (parsed.pathname && parsed.pathname !== '/')
727
+ localPath = parsed.pathname;
728
+ }
729
+ catch {
730
+ console.error(`❌ Invalid upstreamUrl in config: ${localUpstream}`);
731
+ process.exit(1);
732
+ }
733
+ console.log(`\n🛡️ Initializing local MCPaid Security Shield...`);
734
+ // Start Local Security Shield Proxy
735
+ // The shield verifies incoming requests have 'X-MCPaid-Gateway: true' to prevent direct tunnel bypass!
736
+ const shieldPort = localPort === 8089 ? 8091 : 8089;
737
+ let totalCalls = 0;
738
+ const shieldServer = createServer(async (req, res) => {
739
+ const isGateway = req.headers['x-mcpaid-gateway'] === 'true';
740
+ if (!isGateway) {
741
+ console.log(`\n⛔ [SECURITY SHIELD] Blocked direct unmonetized tunnel access from ${req.socket.remoteAddress || 'remote'}`);
742
+ res.writeHead(403, { 'Content-Type': 'application/json' });
743
+ res.end(JSON.stringify({
744
+ jsonrpc: '2.0',
745
+ error: {
746
+ code: -32001,
747
+ message: `Direct tunnel access prohibited. This MCP server is monetized and protected by MCPaid. Call via ${gatewayUrl}/mcp/${config.serverId}`,
748
+ },
749
+ }));
750
+ return;
751
+ }
752
+ const chunks = [];
753
+ req.on('data', (c) => chunks.push(c));
754
+ req.on('end', async () => {
755
+ const bodyBuffer = Buffer.concat(chunks);
756
+ const startMs = Date.now();
757
+ let toolName = 'mcp-call';
758
+ try {
759
+ const json = JSON.parse(bodyBuffer.toString('utf-8'));
760
+ if (json.method === 'tools/call' && json.params?.name) {
761
+ toolName = json.params.name;
762
+ }
763
+ else if (json.method) {
764
+ toolName = json.method;
765
+ }
766
+ }
767
+ catch { }
768
+ try {
769
+ const targetUrl = `http://127.0.0.1:${localPort}${req.url || localPath}`;
770
+ const upstreamResponse = await fetch(targetUrl, {
771
+ method: req.method || 'POST',
772
+ headers: {
773
+ 'Content-Type': req.headers['content-type'] || 'application/json',
774
+ 'X-Agent-Wallet': req.headers['x-agent-wallet'] || '',
775
+ 'X-MCPaid-Edge': 'true',
776
+ },
777
+ body: bodyBuffer,
778
+ });
779
+ const respBody = await upstreamResponse.arrayBuffer();
780
+ const duration = Date.now() - startMs;
781
+ totalCalls++;
782
+ const agentWallet = req.headers['x-agent-wallet']
783
+ ? String(req.headers['x-agent-wallet']).slice(0, 8) + '...'
784
+ : 'Agent';
785
+ console.log(`⚡ [CALL #${totalCalls}] ${toolName.padEnd(18)} | Agent: ${agentWallet} | Status: ${upstreamResponse.status} (${duration}ms)`);
786
+ res.writeHead(upstreamResponse.status, {
787
+ 'Content-Type': upstreamResponse.headers.get('content-type') || 'application/json',
788
+ });
789
+ res.end(Buffer.from(respBody));
790
+ }
791
+ catch (err) {
792
+ console.error(`❌ [LOCAL UPSTREAM ERROR] Could not connect to http://127.0.0.1:${localPort}: ${err.message}`);
793
+ res.writeHead(502, { 'Content-Type': 'application/json' });
794
+ res.end(JSON.stringify({
795
+ jsonrpc: '2.0',
796
+ error: {
797
+ code: -32000,
798
+ message: `Failed to connect to local MCP server at http://127.0.0.1:${localPort}: ${err.message}. Ensure your local server is running.`,
799
+ },
800
+ }));
801
+ }
802
+ });
803
+ });
804
+ await new Promise((resolvePromise) => {
805
+ shieldServer.listen(shieldPort, '127.0.0.1', () => resolvePromise());
806
+ shieldServer.on('error', (err) => {
807
+ if (err.code === 'EADDRINUSE') {
808
+ shieldServer.listen(0, '127.0.0.1', () => resolvePromise());
809
+ }
810
+ });
811
+ });
812
+ const bound = shieldServer.address();
813
+ const actualShieldPort = typeof bound === 'object' && bound ? bound.port : shieldPort;
814
+ console.log(`🔒 Security Shield Active on 127.0.0.1:${actualShieldPort} (Direct bypasses blocked)`);
815
+ console.log(`⚡ Starting encrypted Cloudflare Tunnel...`);
816
+ const child = spawn('cloudflared', ['tunnel', '--url', `http://127.0.0.1:${actualShieldPort}`], {
817
+ stdio: ['ignore', 'pipe', 'pipe'],
818
+ });
819
+ child.on('error', (err) => {
820
+ if (err.code === 'ENOENT') {
821
+ console.error(`\n❌ "cloudflared" is not installed on your system.`);
822
+ console.log(`Install it with:`);
823
+ console.log(` macOS: brew install cloudflared`);
824
+ console.log(` Linux: sudo apt-get install cloudflared`);
825
+ console.log(` Windows: winget install Cloudflare.cloudflared\n`);
826
+ }
827
+ else {
828
+ console.error(`❌ Failed to start cloudflared: ${err.message}`);
829
+ }
830
+ shieldServer.close();
831
+ process.exit(1);
832
+ });
833
+ let published = false;
834
+ const onData = async (data) => {
835
+ const text = data.toString();
836
+ const match = text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i);
837
+ if (match && !published) {
838
+ published = true;
839
+ const tunnelBaseUrl = match[0];
840
+ const remoteUpstreamUrl = `${tunnelBaseUrl}${localPath}`;
841
+ console.log(`🌐 Cloudflare Tunnel Online: ${tunnelBaseUrl}`);
842
+ console.log(`🚀 Registering tunnel with MCPaid Edge Gateway (${gatewayUrl})...`);
843
+ try {
844
+ const res = await fetch(`${gatewayUrl}/v1/servers`, {
845
+ method: 'POST',
846
+ headers: {
847
+ 'Content-Type': 'application/json',
848
+ Authorization: `Bearer ${effectiveToken}`,
849
+ },
850
+ body: JSON.stringify({ config, upstreamUrl: remoteUpstreamUrl }),
851
+ });
852
+ const pubData = (await res.json().catch(() => ({})));
853
+ if (!res.ok || !pubData.success) {
854
+ console.error(`❌ Failed to register tunnel on edge: ${pubData.error || JSON.stringify(pubData)}`);
855
+ child.kill();
856
+ shieldServer.close();
857
+ process.exit(1);
858
+ }
859
+ console.log(`
860
+ ============================================================
861
+ ⚡ MCPaid Live Development Tunnel (Connected & Protected)
862
+ ============================================================
863
+ • Local MCP Target: http://127.0.0.1:${localPort}${localPath}
864
+ • Edge Gateway URL: ${gatewayUrl}/mcp/${config.serverId}
865
+ • Cloudflare Tunnel: ${tunnelBaseUrl} [TLS 1.3 Encrypted]
866
+ • Security Shield: ACTIVE (Direct tunnel bypasses blocked)
867
+ • Payout Wallet: ${config.payoutWallet} (Base L2)
868
+ • Monitored Tools (${config.tools.length}):
869
+ ${config.tools.map((t) => ` • ${t.toolName.padEnd(20)} -> ${t.type === 'free' ? 'FREE' : `$${t.priceUsd} USDC`}`).join('\n')}
870
+
871
+ 📋 Agent / Client Connection:
872
+ npx @mcpaid/sdk bridge --gateway ${gatewayUrl}/mcp/${config.serverId}
873
+
874
+ Ready! Agents can now invoke monetized tools. Press Ctrl+C to stop.
875
+ ============================================================
876
+ `);
877
+ }
878
+ catch (err) {
879
+ console.error(`❌ Registration error: ${err.message}`);
880
+ child.kill();
881
+ shieldServer.close();
882
+ process.exit(1);
883
+ }
884
+ }
885
+ };
886
+ child.stdout.on('data', onData);
887
+ child.stderr.on('data', onData);
888
+ const cleanup = () => {
889
+ console.log('\n🛑 Disconnecting tunnel and local security shield...');
890
+ child.kill();
891
+ shieldServer.close();
892
+ process.exit(0);
893
+ };
894
+ process.on('SIGINT', cleanup);
895
+ process.on('SIGTERM', cleanup);
896
+ }
897
+ async function handlePublish(configPathArg) {
898
+ const args = process.argv.slice(3);
899
+ const gatewayUrl = getFlagValue(args, 'gateway') || process.env.MCPAID_GATEWAY_URL || 'https://mcpaid.dev';
900
+ let creds = loadCredentials();
901
+ let effectiveToken = creds?.token || process.env.MCPAID_TOKEN || process.env.TOOLPAY_ADMIN_KEY;
902
+ if (!effectiveToken) {
903
+ console.log(`\n🔒 Authentication required to publish servers to MCPaid Edge Gateway.`);
904
+ const doLogin = await promptQuestion('Would you like to sign in now? (Y/n): ');
905
+ if (doLogin.toLowerCase() === 'n') {
906
+ console.log('Publish cancelled. Run "npx @mcpaid/sdk login" when ready.');
907
+ return;
908
+ }
909
+ await handleLogin();
910
+ creds = loadCredentials();
911
+ effectiveToken = creds?.token || process.env.MCPAID_TOKEN || process.env.TOOLPAY_ADMIN_KEY;
912
+ }
913
+ if (!effectiveToken) {
914
+ console.error('❌ Authentication failed. Please login first.');
915
+ process.exit(1);
916
+ }
917
+ let config;
918
+ let upstreamUrl;
919
+ const candidatePath = configPathArg && !configPathArg.startsWith('-')
920
+ ? configPathArg
921
+ : (existsSync(resolve(process.cwd(), 'mcpaid.config.json'))
922
+ ? 'mcpaid.config.json'
923
+ : (existsSync(resolve(process.cwd(), 'toolpay.config.json')) ? 'toolpay.config.json' : null));
924
+ if (candidatePath && existsSync(resolve(process.cwd(), candidatePath))) {
925
+ const fullPath = resolve(process.cwd(), candidatePath);
926
+ console.log(`\n📄 Loading configuration from: ${candidatePath}`);
927
+ const loaded = loadConfigFile(fullPath);
928
+ config = loaded.config;
929
+ upstreamUrl = loaded.upstreamUrl;
930
+ // Check if payout wallet is placeholder
931
+ if (!config.payoutWallet ||
932
+ config.payoutWallet.includes('YourBaseEVMWallet') ||
933
+ !/^0x[a-fA-F0-9]{40}$/.test(config.payoutWallet)) {
934
+ if (creds?.payoutWallet && /^0x[a-fA-F0-9]{40}$/.test(creds.payoutWallet)) {
935
+ console.log(`Using your account default payout wallet: ${creds.payoutWallet}`);
936
+ config.payoutWallet = creds.payoutWallet;
937
+ }
938
+ else {
939
+ const wallet = await promptQuestion('Enter your Base EVM payout wallet address (0x...): ');
940
+ if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
941
+ console.error('❌ Invalid Ethereum/Base address format (must be 0x followed by 40 hex chars).');
942
+ process.exit(1);
943
+ }
944
+ config.payoutWallet = wallet;
945
+ }
946
+ }
947
+ }
948
+ else {
949
+ // Interactive wizard
950
+ console.log(`\n⚡ No mcpaid.config.json found. Let's configure your MCP server interactively:\n`);
951
+ const serverId = await promptQuestion('Server slug ID (e.g. my-awesome-mcp): ');
952
+ if (!serverId) {
953
+ console.error('❌ Server ID slug is required.');
954
+ process.exit(1);
955
+ }
956
+ const serverName = (await promptQuestion('Friendly server name: ')) || serverId;
957
+ upstreamUrl = await promptQuestion('Upstream MCP URL (e.g. https://my-tunnel.trycloudflare.com/mcp or https://api.mycompany.com/mcp): ');
958
+ if (!upstreamUrl) {
959
+ console.error('❌ Upstream URL is required.');
960
+ process.exit(1);
961
+ }
962
+ let payoutWallet = creds?.payoutWallet;
963
+ if (!payoutWallet) {
964
+ payoutWallet = await promptQuestion('Base EVM payout wallet address (0x...): ');
965
+ }
966
+ else {
967
+ const useSaved = await promptQuestion(`Use your account payout wallet ${payoutWallet}? (Y/n): `);
968
+ if (useSaved.toLowerCase() === 'n') {
969
+ payoutWallet = await promptQuestion('Base EVM payout wallet address (0x...): ');
970
+ }
971
+ }
972
+ if (!/^0x[a-fA-F0-9]{40}$/.test(payoutWallet)) {
973
+ console.error('❌ Invalid Base EVM wallet address.');
974
+ process.exit(1);
975
+ }
976
+ const tools = [];
977
+ let addingTools = true;
978
+ console.log('\nConfigure tools:');
979
+ while (addingTools) {
980
+ const tName = await promptQuestion('Tool name (e.g. get_intel): ');
981
+ if (!tName)
982
+ break;
983
+ const tType = (await promptQuestion('Pricing type [fixed/free] (default: fixed): ')).toLowerCase() || 'fixed';
984
+ let tPrice = '0';
985
+ if (tType === 'fixed') {
986
+ tPrice = (await promptQuestion('Price in USD per call (default: 0.01): ')) || '0.01';
987
+ }
988
+ tools.push({
989
+ toolName: tName,
990
+ type: tType === 'free' ? 'free' : 'fixed',
991
+ priceUsd: tPrice,
992
+ });
993
+ const more = await promptQuestion('Add another tool? (y/N): ');
994
+ if (more.toLowerCase() !== 'y') {
995
+ addingTools = false;
996
+ }
997
+ }
998
+ if (tools.length === 0) {
999
+ tools.push({ toolName: 'default_tool', type: 'free', priceUsd: '0' });
1000
+ }
1001
+ config = {
1002
+ serverId,
1003
+ serverName,
1004
+ payoutWallet,
1005
+ network: 'base',
1006
+ currency: 'USDC',
1007
+ tools,
1008
+ };
1009
+ const saveLocal = await promptQuestion('\nSave this configuration to ./mcpaid.config.json? (Y/n): ');
1010
+ if (saveLocal.toLowerCase() !== 'n') {
1011
+ const fileJson = {
1012
+ version: '1.0.0',
1013
+ server: {
1014
+ id: config.serverId,
1015
+ name: config.serverName,
1016
+ upstreamUrl,
1017
+ payoutWallet: config.payoutWallet,
1018
+ network: config.network,
1019
+ currency: config.currency,
1020
+ },
1021
+ tools: config.tools.map((t) => ({
1022
+ name: t.toolName,
1023
+ type: t.type,
1024
+ priceUsd: t.priceUsd,
1025
+ })),
1026
+ };
1027
+ writeFileSync(resolve(process.cwd(), 'mcpaid.config.json'), JSON.stringify(fileJson, null, 2), 'utf-8');
1028
+ console.log('✅ Created mcpaid.config.json in current directory');
1029
+ }
1030
+ }
1031
+ if (hasFlag(args, 'tunnel') || hasFlag(args, 'dev')) {
1032
+ return handleDev(configPathArg);
1033
+ }
1034
+ const flagUpstream = getFlagValue(args, 'upstream') || getFlagValue(args, 'public-url') || getFlagValue(args, 'tunnel-url');
1035
+ if (flagUpstream) {
1036
+ upstreamUrl = flagUpstream;
1037
+ }
1038
+ if (isLocalOrPrivateHost(upstreamUrl)) {
1039
+ console.log(`
1040
+ ⚠️ Local loopback address detected for upstream MCP: "${upstreamUrl}"
1041
+ The MCPaid Edge Gateway (${gatewayUrl}) runs globally on Cloudflare and cannot directly reach "127.0.0.1" on your computer.
1042
+
1043
+ Choose how to proceed:
1044
+ [1] Launch MCPaid Live Dev Tunnel (Recommended: zero-config, encrypted, with security shield)
1045
+ [2] Enter an existing public tunnel or domain URL
1046
+ [3] Cancel
1047
+ `);
1048
+ const opt = await promptQuestion('Select option (1/2/3, default: 1): ');
1049
+ if (opt === '2') {
1050
+ const tunnelInput = await promptQuestion('Enter your public tunnel URL (e.g. https://xxx.trycloudflare.com/mcp): ');
1051
+ if (!tunnelInput || !tunnelInput.trim()) {
1052
+ console.log('\nPublish cancelled.\n');
1053
+ process.exit(0);
1054
+ }
1055
+ let formatted = tunnelInput.trim();
1056
+ if (!formatted.startsWith('http://') && !formatted.startsWith('https://')) {
1057
+ formatted = 'https://' + formatted;
1058
+ }
1059
+ try {
1060
+ const testUrl = new URL(formatted);
1061
+ if (testUrl.pathname === '/' || !testUrl.pathname) {
1062
+ try {
1063
+ const origPath = new URL(upstreamUrl).pathname;
1064
+ if (origPath && origPath !== '/') {
1065
+ testUrl.pathname = origPath;
1066
+ formatted = testUrl.toString();
1067
+ }
1068
+ }
1069
+ catch { }
1070
+ }
1071
+ }
1072
+ catch {
1073
+ console.error('❌ Invalid tunnel URL format.');
1074
+ process.exit(1);
1075
+ }
1076
+ if (isLocalOrPrivateHost(formatted)) {
1077
+ console.error('❌ Address is still a local/private host. A public URL is required for edge publishing.');
1078
+ process.exit(1);
1079
+ }
1080
+ upstreamUrl = formatted;
1081
+ if (candidatePath && existsSync(resolve(process.cwd(), candidatePath))) {
1082
+ const updateLocal = await promptQuestion(`Update upstreamUrl in ${candidatePath} to "${upstreamUrl}"? (Y/n): `);
1083
+ if (updateLocal.toLowerCase() !== 'n') {
1084
+ try {
1085
+ const rawJson = JSON.parse(readFileSync(resolve(process.cwd(), candidatePath), 'utf-8'));
1086
+ if (rawJson.server) {
1087
+ rawJson.server.upstreamUrl = upstreamUrl;
1088
+ }
1089
+ else {
1090
+ rawJson.upstreamUrl = upstreamUrl;
1091
+ }
1092
+ writeFileSync(resolve(process.cwd(), candidatePath), JSON.stringify(rawJson, null, 2), 'utf-8');
1093
+ console.log(`✅ Updated ${candidatePath} with public tunnel URL.`);
1094
+ }
1095
+ catch { }
1096
+ }
1097
+ }
1098
+ }
1099
+ else if (opt === '3') {
1100
+ console.log('\nPublish cancelled.\n');
1101
+ process.exit(0);
1102
+ }
1103
+ else {
1104
+ // Default: Launch MCPaid Live Dev Tunnel
1105
+ return handleDev(configPathArg);
1106
+ }
1107
+ }
1108
+ console.log(`\n🚀 Publishing server "${config.serverId}" to ${gatewayUrl}...`);
1109
+ const res = await fetch(`${gatewayUrl}/v1/servers`, {
1110
+ method: 'POST',
1111
+ headers: {
1112
+ 'Content-Type': 'application/json',
1113
+ Authorization: `Bearer ${effectiveToken}`,
1114
+ },
1115
+ body: JSON.stringify({ config, upstreamUrl }),
1116
+ });
1117
+ const data = (await res.json().catch(() => ({})));
1118
+ if (!res.ok || !data.success) {
1119
+ console.error(`❌ Publish failed: ${data.error || JSON.stringify(data)}`);
1120
+ process.exit(1);
1121
+ }
1122
+ // Update saved credentials payout wallet if not already present
1123
+ if (creds && !creds.payoutWallet && config.payoutWallet) {
1124
+ creds.payoutWallet = config.payoutWallet;
1125
+ saveCredentials(creds);
1126
+ }
1127
+ console.log(`
1128
+ ======================================================
1129
+ 🎉 MCP Server Successfully Published to MCPaid!
1130
+ ======================================================
1131
+ • Server ID: ${config.serverId}
1132
+ • Name: ${config.serverName}
1133
+ • Edge Gateway: ${gatewayUrl}/mcp/${config.serverId}
1134
+ • Upstream: ${upstreamUrl}
1135
+ • Payout: ${config.payoutWallet} (Base L2)
1136
+ • Configured Tools (${config.tools.length}):
1137
+ ${config.tools.map((t) => ` • ${t.toolName.padEnd(20)} -> ${t.type === 'free' ? 'FREE' : `$${t.priceUsd} USDC`}`).join('\n')}
1138
+
1139
+ 📋 Agent / Client Connection:
1140
+ npx @mcpaid/sdk bridge --gateway ${gatewayUrl}/mcp/${config.serverId}
1141
+ ======================================================
1142
+ `);
1143
+ }
1144
+ async function handleServerList() {
1145
+ const args = process.argv.slice(3);
1146
+ const gatewayUrl = getFlagValue(args, 'gateway') || process.env.MCPAID_GATEWAY_URL || 'https://mcpaid.dev';
1147
+ const creds = loadCredentials();
1148
+ const token = creds?.token || process.env.MCPAID_TOKEN;
1149
+ if (!token) {
1150
+ console.log('❌ You must be logged in to view your registered servers.');
1151
+ console.log('Run: npx @mcpaid/sdk login');
1152
+ return;
1153
+ }
1154
+ console.log(`Fetching registered servers from ${gatewayUrl}...`);
1155
+ const res = await fetch(`${gatewayUrl}/v1/developer/dashboard-data`, {
1156
+ headers: { Authorization: `Bearer ${token}` },
1157
+ });
1158
+ const data = (await res.json().catch(() => ({})));
1159
+ if (!res.ok || !data.success) {
1160
+ console.error(`❌ Failed to fetch servers: ${data.error || JSON.stringify(data)}`);
1161
+ return;
1162
+ }
1163
+ const servers = data.servers || [];
1164
+ if (servers.length === 0) {
1165
+ console.log(`\nNo MCP servers registered under this account yet.`);
1166
+ console.log(`Publish your first server using: npx @mcpaid/sdk publish\n`);
1167
+ return;
1168
+ }
1169
+ console.log(`
1170
+ ======================================================
1171
+ 🌐 Registered MCPaid Servers (${servers.length})
1172
+ ======================================================`);
1173
+ for (const s of servers) {
1174
+ console.log(`• ID: ${s.server_id}`);
1175
+ console.log(` Name: ${s.name || s.server_id}`);
1176
+ console.log(` Gateway: ${gatewayUrl}/mcp/${s.server_id}`);
1177
+ console.log(` Upstream: ${s.upstream_url}`);
1178
+ console.log(` Payout: ${s.payout_wallet}`);
1179
+ console.log(` Tools: ${(s.tools || []).map((t) => `${t.tool_name} (${t.pricing_type === 'free' ? 'FREE' : '$' + t.price_usd})`).join(', ') || 'None'}`);
1180
+ console.log(` Bridge: npx @mcpaid/sdk bridge --gateway ${gatewayUrl}/mcp/${s.server_id}`);
1181
+ console.log(`──────────────────────────────────────────────────────`);
1182
+ }
1183
+ }
1184
+ async function handleServerRemove(targetServerId) {
1185
+ const args = process.argv.slice(3);
1186
+ const gatewayUrl = getFlagValue(args, 'gateway') || process.env.MCPAID_GATEWAY_URL || 'https://mcpaid.dev';
1187
+ const isForce = args.includes('--force') || args.includes('-f') || args.includes('-y') || args.includes('--yes');
1188
+ let serverId = targetServerId;
1189
+ if (serverId && serverId.startsWith('-')) {
1190
+ serverId = undefined;
1191
+ }
1192
+ if (!serverId) {
1193
+ const nonFlags = args.filter(a => !a.startsWith('-') && a !== 'remove' && a !== 'rm' && a !== 'delete' && a !== 'disconnect');
1194
+ if (nonFlags.length > 0) {
1195
+ serverId = nonFlags[0];
1196
+ }
1197
+ }
1198
+ const creds = loadCredentials();
1199
+ const token = creds?.token || process.env.MCPAID_TOKEN;
1200
+ if (!token) {
1201
+ console.log('❌ You must be logged in to disconnect or remove an MCP server.');
1202
+ console.log('Run: npx @mcpaid/sdk login');
1203
+ return;
1204
+ }
1205
+ if (!serverId) {
1206
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1207
+ serverId = await new Promise((res) => {
1208
+ rl.question('Enter the ID of the MCP server to disconnect and remove: ', (ans) => {
1209
+ rl.close();
1210
+ res(ans.trim());
1211
+ });
1212
+ });
1213
+ }
1214
+ if (!serverId) {
1215
+ console.error('❌ Error: Server ID is required. Example: npx @mcpaid/sdk server remove my-custom-mcp');
1216
+ return;
1217
+ }
1218
+ if (!isForce) {
1219
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1220
+ const answer = await new Promise((res) => {
1221
+ rl.question(`⚠️ Are you sure you want to disconnect and permanently remove "${serverId}" from the MCPaid network? (y/N): `, (ans) => {
1222
+ rl.close();
1223
+ res(ans.trim().toLowerCase());
1224
+ });
1225
+ });
1226
+ if (answer !== 'y' && answer !== 'yes') {
1227
+ console.log('Operation cancelled.');
1228
+ return;
1229
+ }
1230
+ }
1231
+ console.log(`Disconnecting server "${serverId}" from ${gatewayUrl}...`);
1232
+ const res = await fetch(`${gatewayUrl}/v1/servers/${encodeURIComponent(serverId)}`, {
1233
+ method: 'DELETE',
1234
+ headers: {
1235
+ Authorization: `Bearer ${token}`,
1236
+ 'Content-Type': 'application/json',
1237
+ },
1238
+ });
1239
+ const data = (await res.json().catch(() => ({})));
1240
+ if (!res.ok || !data.success) {
1241
+ console.error(`❌ Failed to disconnect server: ${data.error || JSON.stringify(data)}`);
1242
+ return;
1243
+ }
1244
+ console.log(`
1245
+ ======================================================
1246
+ 🗑️ MCP Server Disconnected & Removed
1247
+ ======================================================
1248
+ • Server ID: ${serverId}
1249
+ • Status: Removed from MCPaid network & account records
1250
+ • Gateway: ${gatewayUrl}/mcp/${serverId} (Deactivated)
1251
+ ======================================================
1252
+ Clean slate achieved. You can republish anytime with:
1253
+ npx @mcpaid/sdk publish
1254
+ `);
1255
+ }
1256
+ async function handleServer() {
1257
+ const sub = process.argv[3];
1258
+ if (sub === 'list' || sub === 'ls') {
1259
+ return handleServerList();
1260
+ }
1261
+ if (sub === 'add' || sub === 'register' || sub === 'publish' || sub === 'deploy') {
1262
+ return handlePublish(process.argv[4]);
1263
+ }
1264
+ if (sub === 'remove' || sub === 'rm' || sub === 'delete' || sub === 'disconnect') {
1265
+ return handleServerRemove(process.argv[4]);
1266
+ }
1267
+ console.log(`
1268
+ Usage: npx @mcpaid/sdk server <command>
1269
+
1270
+ Commands:
1271
+ add [config-path] Publish and monetize an MCP server on MCPaid Edge Gateway
1272
+ list List all MCP servers registered under your account
1273
+ remove <server-id> Disconnect and remove an MCP server from MCPaid network
1274
+ `);
1275
+ }
1276
+ async function handleWithdraw(amountArg) {
1277
+ const flags = process.argv.slice(2);
1278
+ const gatewayUrl = (getFlagValue(flags, '--gateway') || process.env.MCPAID_GATEWAY || 'https://mcpaid.dev').replace(/\/+$/, '');
1279
+ const isYes = flags.includes('-y') || flags.includes('--yes');
1280
+ const creds = loadCredentials();
1281
+ if (!creds || !creds.token) {
1282
+ console.log('\n🔒 Authentication required to withdraw developer earnings.');
1283
+ const shouldLogin = (await promptQuestion('Would you like to sign in now? (Y/n): ')).toLowerCase();
1284
+ if (shouldLogin === 'n' || shouldLogin === 'no') {
1285
+ console.log('Aborted. Sign in anytime with: npx @mcpaid/sdk login');
1286
+ return;
1287
+ }
1288
+ await handleLogin();
1289
+ }
1290
+ const updatedCreds = loadCredentials();
1291
+ if (!updatedCreds || !updatedCreds.token) {
1292
+ console.error('❌ You must be authenticated to request withdrawals.');
1293
+ return;
1294
+ }
1295
+ console.log(`\nConnecting to MCPaid Edge Gateway (${gatewayUrl})...`);
1296
+ const sessionRes = await fetch(`${gatewayUrl}/v1/developer/dashboard-data`, {
1297
+ headers: { Authorization: `Bearer ${updatedCreds.token}` },
1298
+ });
1299
+ const sessionData = (await sessionRes.json().catch(() => ({})));
1300
+ if (!sessionRes.ok || !sessionData.success) {
1301
+ console.error(`❌ Failed to retrieve balance: ${sessionData.error || 'Unauthorized'}`);
1302
+ return;
1303
+ }
1304
+ const user = sessionData.user;
1305
+ const balance = sessionData.balance || { availableBalanceMicro: 0 };
1306
+ const availMicro = Number(balance.availableBalanceMicro || 0);
1307
+ const availUsdc = (availMicro / 1_000_000).toFixed(4);
1308
+ if (!user.payoutWallet) {
1309
+ console.error('❌ No Base L2 payout wallet configured on your account.');
1310
+ console.log('Please configure your payout wallet in the dashboard (https://mcpaid.dev/dashboard).');
1311
+ return;
1312
+ }
1313
+ if (availMicro <= 0) {
1314
+ console.log(`\nℹ️ No available balance to withdraw.`);
1315
+ console.log(`• Available Balance: $${availUsdc} USDC`);
1316
+ console.log(`• Total Gross Volume: $${((balance.totalGrossMicro || 0) / 1_000_000).toFixed(4)} USDC`);
1317
+ console.log(`• Payout Wallet: ${user.payoutWallet}`);
1318
+ console.log(`\nInvocations from remote AI agents will credit your balance automatically.\n`);
1319
+ return;
1320
+ }
1321
+ let toWithdrawMicro = availMicro;
1322
+ if (amountArg && !amountArg.startsWith('-')) {
1323
+ const parsedUsd = parseFloat(amountArg);
1324
+ if (isNaN(parsedUsd) || parsedUsd <= 0) {
1325
+ console.error(`❌ Invalid withdrawal amount: "${amountArg}". Example: npx @mcpaid/sdk withdraw 10.00`);
1326
+ return;
1327
+ }
1328
+ toWithdrawMicro = Math.round(parsedUsd * 1_000_000);
1329
+ if (toWithdrawMicro > availMicro) {
1330
+ console.error(`❌ Requested withdrawal ($${parsedUsd.toFixed(4)}) exceeds available balance ($${availUsdc}).`);
1331
+ return;
1332
+ }
1333
+ }
1334
+ const withdrawAmountUsdc = (toWithdrawMicro / 1_000_000).toFixed(4);
1335
+ console.log(`
1336
+ ======================================================
1337
+ 💸 MCPaid Payout Withdrawal
1338
+ ======================================================
1339
+ • Available Balance: $${availUsdc} USDC
1340
+ • Amount to Withdraw: $${withdrawAmountUsdc} USDC
1341
+ • Network: Base Mainnet (Chain ID 8453)
1342
+ • Destination Wallet: ${user.payoutWallet}
1343
+ • Protocol Fee: 0.00% (Free)
1344
+ ======================================================
1345
+ `);
1346
+ if (!isYes) {
1347
+ const confirmAns = (await promptQuestion(`Proceed with withdrawal of $${withdrawAmountUsdc} USDC to Base L2? (Y/n): `)).toLowerCase();
1348
+ if (confirmAns === 'n' || confirmAns === 'no') {
1349
+ console.log('Withdrawal cancelled.');
1350
+ return;
1351
+ }
1352
+ }
1353
+ console.log(`Submitting withdrawal request to Base L2 settlement pipeline...`);
1354
+ const res = await fetch(`${gatewayUrl}/v1/developer/withdraw`, {
1355
+ method: 'POST',
1356
+ headers: {
1357
+ Authorization: `Bearer ${updatedCreds.token}`,
1358
+ 'Content-Type': 'application/json',
1359
+ },
1360
+ body: JSON.stringify({ amountMicro: toWithdrawMicro }),
1361
+ });
1362
+ const data = (await res.json().catch(() => ({})));
1363
+ if (!res.ok || !data.success) {
1364
+ console.error(`❌ Withdrawal failed: ${data.error || JSON.stringify(data)}`);
1365
+ return;
1366
+ }
1367
+ const w = data.withdrawal;
1368
+ console.log(`
1369
+ ======================================================
1370
+ ✅ Withdrawal Successfully Dispatched!
1371
+ ======================================================
1372
+ • Withdrawal ID: ${w.id}
1373
+ • Amount: $${(w.amountMicro / 1_000_000).toFixed(4)} USDC
1374
+ • Status: ${w.status.toUpperCase()} (Queued for Settlement Batch)
1375
+ • Destination: ${w.developerWallet}
1376
+ • BaseScan: https://basescan.org/address/${w.developerWallet}
1377
+ ======================================================
1378
+ Your available balance has been updated. View past withdrawals with:
1379
+ npx @mcpaid/sdk withdrawals
1380
+ `);
1381
+ }
1382
+ async function handleWithdrawals() {
1383
+ const flags = process.argv.slice(2);
1384
+ const gatewayUrl = (getFlagValue(flags, '--gateway') || process.env.MCPAID_GATEWAY || 'https://mcpaid.dev').replace(/\/+$/, '');
1385
+ const creds = loadCredentials();
1386
+ if (!creds || !creds.token) {
1387
+ console.error('❌ You must be authenticated to view withdrawals. Run: npx @mcpaid/sdk login');
1388
+ return;
1389
+ }
1390
+ const res = await fetch(`${gatewayUrl}/v1/developer/withdrawals`, {
1391
+ headers: { Authorization: `Bearer ${creds.token}` },
1392
+ });
1393
+ const data = (await res.json().catch(() => ({})));
1394
+ if (!res.ok || !data.success) {
1395
+ console.error(`❌ Failed to retrieve withdrawals: ${data.error || 'Error'}`);
1396
+ return;
1397
+ }
1398
+ const list = data.withdrawals || [];
1399
+ console.log(`
1400
+ ======================================================
1401
+ 📋 Base L2 Payout & Withdrawal History (${list.length})
1402
+ ======================================================`);
1403
+ if (list.length === 0) {
1404
+ console.log('No withdrawal requests recorded yet. Withdraw anytime with:');
1405
+ console.log('npx @mcpaid/sdk withdraw');
1406
+ console.log('======================================================\n');
1407
+ return;
1408
+ }
1409
+ for (const w of list) {
1410
+ const date = new Date(w.createdAt || w.created_at).toLocaleString();
1411
+ const amount = `$${(w.amountMicro / 1_000_000).toFixed(4)} USDC`;
1412
+ const status = (w.status || 'processing').toUpperCase();
1413
+ console.log(`• [${status}] ${amount} -> ${w.developerWallet}`);
1414
+ console.log(` ID: ${w.id} | Date: ${date}`);
1415
+ if (w.txHash) {
1416
+ console.log(` TxHash: https://basescan.org/tx/${w.txHash}`);
1417
+ }
1418
+ else {
1419
+ console.log(` Address: https://basescan.org/address/${w.developerWallet}`);
1420
+ }
1421
+ console.log('');
1422
+ }
1423
+ console.log('======================================================\n');
1424
+ }
1425
+ async function handleRelayer() {
1426
+ const flags = process.argv.slice(2);
1427
+ const isRunLocal = flags.includes('--run') || flags.includes('-r');
1428
+ if (isRunLocal && process.env.MCPAID_OPERATOR !== '1') {
1429
+ console.log(`
1430
+ ======================================================
1431
+ ⛔ Operator-only command
1432
+ ======================================================
1433
+ Manual settlement runs disburse real on-chain funds and are
1434
+ reserved for gateway operators under OPERATOR-LICENSE.md.
1435
+
1436
+ The hosted mcpaid.dev relayer settles automatically every
1437
+ 60 seconds — developers do not need this command. If you
1438
+ operate your own gateway under an operator agreement, re-run
1439
+ with MCPAID_OPERATOR=1.
1440
+ ======================================================
1441
+ `);
1442
+ process.exit(1);
1443
+ }
1444
+ if (!isRunLocal) {
1445
+ console.log(`
1446
+ ======================================================
1447
+ ⏱️ MCPaid Automated Base L2 Settlement Relayer
1448
+ ======================================================
1449
+ • Schedule: Runs automatically EVERY 1 MINUTE (* * * * *)
1450
+ • Execution: Isolated Cloudflare Edge Cron Trigger
1451
+ • Security: HTTP triggers are disabled. Only internal cron
1452
+ triggers or verified local operator scripts can
1453
+ disburse on-chain funds.
1454
+ • Operator Mode: To run a manual settlement cycle directly using
1455
+ your local relayer credentials:
1456
+ npx @mcpaid/sdk relayer --run
1457
+ ======================================================
1458
+ `);
1459
+ return;
1460
+ }
1461
+ console.log(`\nExecuting local Base L2 settlement relayer...`);
1462
+ const scriptPath = resolve(fileURLToPath(import.meta.url), '../../scripts/settle-withdrawals.ts');
1463
+ if (existsSync(scriptPath)) {
1464
+ const child = spawn('npx', ['tsx', scriptPath], { stdio: 'inherit' });
1465
+ child.on('exit', (code) => {
1466
+ if (code !== 0)
1467
+ process.exit(code || 1);
1468
+ });
1469
+ }
1470
+ else {
1471
+ console.log(`Error: Settlement script not found at ${scriptPath}`);
1472
+ }
1473
+ }
1474
+ export async function runCli() {
1475
+ const command = process.argv[2];
1476
+ switch (command) {
1477
+ case 'init':
1478
+ await handleInit();
1479
+ break;
1480
+ case 'validate':
1481
+ await handleValidate(process.argv[3]);
1482
+ break;
1483
+ case 'start':
1484
+ await handleStart(process.argv[3]);
1485
+ break;
1486
+ case 'dev':
1487
+ await handleDev(process.argv[3]);
1488
+ break;
1489
+ case 'agent-proxy':
1490
+ case 'bridge':
1491
+ await handleBridge();
1492
+ break;
1493
+ case 'wallet':
1494
+ await handleWallet(process.argv[3]);
1495
+ break;
1496
+ case 'login':
1497
+ case 'auth':
1498
+ case 'signin':
1499
+ await handleLogin();
1500
+ break;
1501
+ case 'logout':
1502
+ case 'signout':
1503
+ await handleLogout();
1504
+ break;
1505
+ case 'whoami':
1506
+ await handleWhoami();
1507
+ break;
1508
+ case 'publish':
1509
+ case 'deploy':
1510
+ await handlePublish(process.argv[3]);
1511
+ break;
1512
+ case 'tunnel':
1513
+ await handleTunnel(process.argv[3]);
1514
+ break;
1515
+ case 'remove':
1516
+ case 'disconnect':
1517
+ case 'rm':
1518
+ await handleServerRemove(process.argv[3]);
1519
+ break;
1520
+ case 'server':
1521
+ case 'servers':
1522
+ await handleServer();
1523
+ break;
1524
+ case 'withdraw':
1525
+ case 'payout':
1526
+ await handleWithdraw(process.argv[3]);
1527
+ break;
1528
+ case 'withdrawals':
1529
+ case 'payouts':
1530
+ await handleWithdrawals();
1531
+ break;
1532
+ case 'relayer':
1533
+ case 'settle':
1534
+ await handleRelayer();
1535
+ break;
1536
+ default:
1537
+ printHelp();
1538
+ break;
1539
+ }
1540
+ }
1541
+ if (process.argv[1]) {
1542
+ try {
1543
+ const realArgv1 = existsSync(process.argv[1]) ? realpathSync(process.argv[1]) : resolve(process.argv[1]);
1544
+ if (realArgv1 === fileURLToPath(import.meta.url)) {
1545
+ runCli().catch((err) => {
1546
+ console.error(`Fatal error: ${err.message}`);
1547
+ process.exit(1);
1548
+ });
1549
+ }
1550
+ }
1551
+ catch {
1552
+ if (resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
1553
+ runCli().catch((err) => {
1554
+ console.error(`Fatal error: ${err.message}`);
1555
+ process.exit(1);
1556
+ });
1557
+ }
1558
+ }
1559
+ }
1560
+ //# sourceMappingURL=cli.js.map