moltspay 1.3.0 → 1.4.1

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 (58) hide show
  1. package/.env.example +14 -0
  2. package/README.md +319 -89
  3. package/dist/cdp/index.d.mts +4 -4
  4. package/dist/cdp/index.d.ts +4 -4
  5. package/dist/cdp/index.js +57 -0
  6. package/dist/cdp/index.js.map +1 -1
  7. package/dist/cdp/index.mjs +57 -0
  8. package/dist/cdp/index.mjs.map +1 -1
  9. package/dist/chains/index.d.mts +9 -8
  10. package/dist/chains/index.d.ts +9 -8
  11. package/dist/chains/index.js +57 -0
  12. package/dist/chains/index.js.map +1 -1
  13. package/dist/chains/index.mjs +57 -0
  14. package/dist/chains/index.mjs.map +1 -1
  15. package/dist/cli/index.js +2021 -285
  16. package/dist/cli/index.js.map +1 -1
  17. package/dist/cli/index.mjs +2023 -277
  18. package/dist/cli/index.mjs.map +1 -1
  19. package/dist/client/index.d.mts +39 -3
  20. package/dist/client/index.d.ts +39 -3
  21. package/dist/client/index.js +563 -37
  22. package/dist/client/index.js.map +1 -1
  23. package/dist/client/index.mjs +571 -35
  24. package/dist/client/index.mjs.map +1 -1
  25. package/dist/facilitators/index.d.mts +220 -1
  26. package/dist/facilitators/index.d.ts +220 -1
  27. package/dist/facilitators/index.js +664 -1
  28. package/dist/facilitators/index.js.map +1 -1
  29. package/dist/facilitators/index.mjs +670 -1
  30. package/dist/facilitators/index.mjs.map +1 -1
  31. package/dist/{index-On9ZaGDW.d.mts → index-D_2FkLwV.d.mts} +6 -2
  32. package/dist/{index-On9ZaGDW.d.ts → index-D_2FkLwV.d.ts} +6 -2
  33. package/dist/index.d.mts +2 -1
  34. package/dist/index.d.ts +2 -1
  35. package/dist/index.js +1440 -153
  36. package/dist/index.js.map +1 -1
  37. package/dist/index.mjs +1448 -151
  38. package/dist/index.mjs.map +1 -1
  39. package/dist/server/index.d.mts +13 -3
  40. package/dist/server/index.d.ts +13 -3
  41. package/dist/server/index.js +909 -54
  42. package/dist/server/index.js.map +1 -1
  43. package/dist/server/index.mjs +919 -54
  44. package/dist/server/index.mjs.map +1 -1
  45. package/dist/verify/index.d.mts +1 -1
  46. package/dist/verify/index.d.ts +1 -1
  47. package/dist/verify/index.js +57 -0
  48. package/dist/verify/index.js.map +1 -1
  49. package/dist/verify/index.mjs +57 -0
  50. package/dist/verify/index.mjs.map +1 -1
  51. package/dist/wallet/index.d.mts +3 -3
  52. package/dist/wallet/index.d.ts +3 -3
  53. package/dist/wallet/index.js +57 -0
  54. package/dist/wallet/index.js.map +1 -1
  55. package/dist/wallet/index.mjs +57 -0
  56. package/dist/wallet/index.mjs.map +1 -1
  57. package/package.json +5 -2
  58. package/schemas/moltspay.services.schema.json +27 -132
package/dist/cli/index.js CHANGED
@@ -37,16 +37,17 @@ var init_cjs_shims = __esm({
37
37
  init_cjs_shims();
38
38
  var import_crypto = require("crypto");
39
39
  var import_commander = require("commander");
40
- var import_os2 = require("os");
41
- var import_path2 = require("path");
42
- var import_fs4 = require("fs");
40
+ var import_os3 = require("os");
41
+ var import_path3 = require("path");
42
+ var import_fs5 = require("fs");
43
43
  var import_child_process = require("child_process");
44
+ var import_ethers2 = require("ethers");
44
45
 
45
46
  // src/client/index.ts
46
47
  init_cjs_shims();
47
- var import_fs = require("fs");
48
- var import_os = require("os");
49
- var import_path = require("path");
48
+ var import_fs2 = require("fs");
49
+ var import_os2 = require("os");
50
+ var import_path2 = require("path");
50
51
  var import_ethers = require("ethers");
51
52
 
52
53
  // src/chains/index.ts
@@ -157,6 +158,63 @@ var CHAINS = {
157
158
  explorerTx: "https://explore.testnet.tempo.xyz/tx/",
158
159
  avgBlockTime: 0.5
159
160
  // ~500ms finality
161
+ },
162
+ // ============ BNB Chain Testnet ============
163
+ bnb_testnet: {
164
+ name: "BNB Testnet",
165
+ chainId: 97,
166
+ rpc: "https://data-seed-prebsc-1-s1.binance.org:8545",
167
+ tokens: {
168
+ // Note: BNB uses 18 decimals for stablecoins (unlike Base/Polygon which use 6)
169
+ // Using official Binance-Peg testnet tokens
170
+ USDC: {
171
+ address: "0x64544969ed7EBf5f083679233325356EbE738930",
172
+ // Testnet USDC
173
+ decimals: 18,
174
+ symbol: "USDC",
175
+ eip712Name: "USD Coin"
176
+ },
177
+ USDT: {
178
+ address: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd",
179
+ // Testnet USDT
180
+ decimals: 18,
181
+ symbol: "USDT",
182
+ eip712Name: "Tether USD"
183
+ }
184
+ },
185
+ usdc: "0x64544969ed7EBf5f083679233325356EbE738930",
186
+ explorer: "https://testnet.bscscan.com/address/",
187
+ explorerTx: "https://testnet.bscscan.com/tx/",
188
+ avgBlockTime: 3,
189
+ // BNB-specific: requires approval for pay-for-success flow
190
+ requiresApproval: true
191
+ },
192
+ // ============ BNB Chain Mainnet ============
193
+ bnb: {
194
+ name: "BNB Smart Chain",
195
+ chainId: 56,
196
+ rpc: "https://bsc-dataseed.binance.org",
197
+ tokens: {
198
+ // Note: BNB uses 18 decimals for stablecoins
199
+ USDC: {
200
+ address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
201
+ decimals: 18,
202
+ symbol: "USDC",
203
+ eip712Name: "USD Coin"
204
+ },
205
+ USDT: {
206
+ address: "0x55d398326f99059fF775485246999027B3197955",
207
+ decimals: 18,
208
+ symbol: "USDT",
209
+ eip712Name: "Tether USD"
210
+ }
211
+ },
212
+ usdc: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
213
+ explorer: "https://bscscan.com/address/",
214
+ explorerTx: "https://bscscan.com/tx/",
215
+ avgBlockTime: 3,
216
+ // BNB-specific: requires approval for pay-for-success flow
217
+ requiresApproval: true
160
218
  }
161
219
  };
162
220
  function getChain(name) {
@@ -167,6 +225,362 @@ function getChain(name) {
167
225
  return config;
168
226
  }
169
227
 
228
+ // src/wallet/solana.ts
229
+ init_cjs_shims();
230
+ var import_web32 = require("@solana/web3.js");
231
+ var import_spl_token = require("@solana/spl-token");
232
+ var import_fs = require("fs");
233
+ var import_path = require("path");
234
+ var import_os = require("os");
235
+ var import_bs58 = __toESM(require("bs58"));
236
+
237
+ // src/chains/solana.ts
238
+ init_cjs_shims();
239
+ var import_web3 = require("@solana/web3.js");
240
+ var SOLANA_CHAINS = {
241
+ solana: {
242
+ name: "Solana Mainnet",
243
+ cluster: "mainnet-beta",
244
+ rpc: "https://api.mainnet-beta.solana.com",
245
+ explorer: "https://solscan.io/account/",
246
+ explorerTx: "https://solscan.io/tx/",
247
+ tokens: {
248
+ USDC: {
249
+ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
250
+ // Circle official USDC
251
+ decimals: 6
252
+ }
253
+ }
254
+ },
255
+ solana_devnet: {
256
+ name: "Solana Devnet",
257
+ cluster: "devnet",
258
+ rpc: "https://api.devnet.solana.com",
259
+ explorer: "https://solscan.io/account/",
260
+ explorerTx: "https://solscan.io/tx/",
261
+ tokens: {
262
+ USDC: {
263
+ // Circle's devnet USDC (if not available, we'll deploy our own test token)
264
+ mint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
265
+ decimals: 6
266
+ }
267
+ }
268
+ }
269
+ };
270
+ function getSolanaConnection(chain) {
271
+ const config = SOLANA_CHAINS[chain];
272
+ return new import_web3.Connection(config.rpc, "confirmed");
273
+ }
274
+ function getUSDCMint(chain) {
275
+ return new import_web3.PublicKey(SOLANA_CHAINS[chain].tokens.USDC.mint);
276
+ }
277
+
278
+ // src/wallet/solana.ts
279
+ var DEFAULT_CONFIG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".moltspay");
280
+ var SOLANA_WALLET_FILE = "wallet-solana.json";
281
+ function getSolanaWalletPath(configDir = DEFAULT_CONFIG_DIR) {
282
+ return (0, import_path.join)(configDir, SOLANA_WALLET_FILE);
283
+ }
284
+ function solanaWalletExists(configDir = DEFAULT_CONFIG_DIR) {
285
+ return (0, import_fs.existsSync)(getSolanaWalletPath(configDir));
286
+ }
287
+ function loadSolanaWallet(configDir = DEFAULT_CONFIG_DIR) {
288
+ const walletPath = getSolanaWalletPath(configDir);
289
+ if (!(0, import_fs.existsSync)(walletPath)) {
290
+ return null;
291
+ }
292
+ try {
293
+ const data = JSON.parse((0, import_fs.readFileSync)(walletPath, "utf-8"));
294
+ const secretKey = import_bs58.default.decode(data.secretKey);
295
+ return import_web32.Keypair.fromSecretKey(secretKey);
296
+ } catch (error) {
297
+ console.error("Failed to load Solana wallet:", error);
298
+ return null;
299
+ }
300
+ }
301
+ function createSolanaWallet(configDir = DEFAULT_CONFIG_DIR) {
302
+ if (!(0, import_fs.existsSync)(configDir)) {
303
+ (0, import_fs.mkdirSync)(configDir, { recursive: true });
304
+ }
305
+ const keypair = import_web32.Keypair.generate();
306
+ const data = {
307
+ publicKey: keypair.publicKey.toBase58(),
308
+ secretKey: import_bs58.default.encode(keypair.secretKey),
309
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
310
+ };
311
+ const walletPath = getSolanaWalletPath(configDir);
312
+ (0, import_fs.writeFileSync)(walletPath, JSON.stringify(data, null, 2));
313
+ return keypair;
314
+ }
315
+ function getSolanaAddress(configDir = DEFAULT_CONFIG_DIR) {
316
+ const wallet = loadSolanaWallet(configDir);
317
+ return wallet?.publicKey.toBase58() || null;
318
+ }
319
+ async function getSolanaBalance(address, chain) {
320
+ const connection = getSolanaConnection(chain);
321
+ const pubkey = new import_web32.PublicKey(address);
322
+ const balance = await connection.getBalance(pubkey);
323
+ return balance / import_web32.LAMPORTS_PER_SOL;
324
+ }
325
+ async function getSolanaUSDCBalance(address, chain) {
326
+ const connection = getSolanaConnection(chain);
327
+ const owner = new import_web32.PublicKey(address);
328
+ const mint = getUSDCMint(chain);
329
+ try {
330
+ const ata = await (0, import_spl_token.getAssociatedTokenAddress)(mint, owner);
331
+ const account = await (0, import_spl_token.getAccount)(connection, ata);
332
+ return Number(account.amount) / 1e6;
333
+ } catch (error) {
334
+ if (error.name === "TokenAccountNotFoundError" || error.message?.includes("could not find account")) {
335
+ return 0;
336
+ }
337
+ throw error;
338
+ }
339
+ }
340
+ async function getSolanaBalances(address, chain) {
341
+ const [sol, usdc] = await Promise.all([
342
+ getSolanaBalance(address, chain),
343
+ getSolanaUSDCBalance(address, chain)
344
+ ]);
345
+ return { sol, usdc };
346
+ }
347
+ function isValidSolanaAddress(address) {
348
+ try {
349
+ new import_web32.PublicKey(address);
350
+ return true;
351
+ } catch {
352
+ return false;
353
+ }
354
+ }
355
+
356
+ // src/facilitators/solana.ts
357
+ init_cjs_shims();
358
+ var import_web33 = require("@solana/web3.js");
359
+ var import_spl_token2 = require("@solana/spl-token");
360
+
361
+ // src/facilitators/interface.ts
362
+ init_cjs_shims();
363
+ var BaseFacilitator = class {
364
+ supportsNetwork(network) {
365
+ return this.supportedNetworks.includes(network);
366
+ }
367
+ };
368
+
369
+ // src/facilitators/solana.ts
370
+ var SolanaFacilitator = class extends BaseFacilitator {
371
+ name = "solana";
372
+ displayName = "Solana Direct";
373
+ supportedNetworks = ["solana:mainnet", "solana:devnet"];
374
+ connections = /* @__PURE__ */ new Map();
375
+ feePayerKeypair;
376
+ constructor(config) {
377
+ super();
378
+ this.feePayerKeypair = config?.feePayerKeypair;
379
+ for (const [chain, config2] of Object.entries(SOLANA_CHAINS)) {
380
+ this.connections.set(
381
+ chain,
382
+ new import_web33.Connection(config2.rpc, "confirmed")
383
+ );
384
+ }
385
+ if (this.feePayerKeypair) {
386
+ console.log(`[SolanaFacilitator] Gasless mode enabled. Fee payer: ${this.feePayerKeypair.publicKey.toBase58()}`);
387
+ }
388
+ }
389
+ /**
390
+ * Get fee payer public key (for gasless transactions)
391
+ */
392
+ getFeePayerPubkey() {
393
+ return this.feePayerKeypair?.publicKey.toBase58() || null;
394
+ }
395
+ getConnection(chain) {
396
+ const conn = this.connections.get(chain);
397
+ if (!conn) {
398
+ throw new Error(`No connection for chain: ${chain}`);
399
+ }
400
+ return conn;
401
+ }
402
+ /**
403
+ * Convert our chain name to network identifier
404
+ */
405
+ static chainToNetwork(chain) {
406
+ return chain === "solana" ? "solana:mainnet" : "solana:devnet";
407
+ }
408
+ /**
409
+ * Convert network identifier to chain name
410
+ */
411
+ static networkToChain(network) {
412
+ if (network === "solana:mainnet") return "solana";
413
+ if (network === "solana:devnet") return "solana_devnet";
414
+ return null;
415
+ }
416
+ async healthCheck() {
417
+ const start = Date.now();
418
+ try {
419
+ const conn = this.getConnection("solana_devnet");
420
+ await conn.getSlot();
421
+ return {
422
+ healthy: true,
423
+ latencyMs: Date.now() - start
424
+ };
425
+ } catch (error) {
426
+ return {
427
+ healthy: false,
428
+ error: error.message
429
+ };
430
+ }
431
+ }
432
+ /**
433
+ * Verify a Solana payment
434
+ *
435
+ * Checks:
436
+ * 1. Transaction is valid and properly signed
437
+ * 2. Transfer instruction matches expected amount and recipient
438
+ */
439
+ async verify(paymentPayload, requirements) {
440
+ try {
441
+ const solanaPayload = paymentPayload.payload;
442
+ if (!solanaPayload || !solanaPayload.signedTransaction) {
443
+ return { valid: false, error: "Missing signed transaction" };
444
+ }
445
+ const chain = solanaPayload.chain || "solana_devnet";
446
+ const chainConfig = SOLANA_CHAINS[chain];
447
+ if (!chainConfig) {
448
+ return { valid: false, error: `Invalid chain: ${chain}` };
449
+ }
450
+ const txBuffer = Buffer.from(solanaPayload.signedTransaction, "base64");
451
+ let tx;
452
+ try {
453
+ tx = import_web33.Transaction.from(txBuffer);
454
+ } catch {
455
+ tx = import_web33.VersionedTransaction.deserialize(txBuffer);
456
+ }
457
+ if (tx instanceof import_web33.Transaction) {
458
+ const hasAnySignature = tx.signatures.some(
459
+ (sig) => sig.signature && !sig.signature.every((b) => b === 0)
460
+ );
461
+ if (!hasAnySignature) {
462
+ return { valid: false, error: "Transaction not signed" };
463
+ }
464
+ }
465
+ const expectedAmount = BigInt(requirements.amount);
466
+ const expectedRecipient = new import_web33.PublicKey(requirements.payTo);
467
+ return {
468
+ valid: true,
469
+ details: {
470
+ chain,
471
+ sender: solanaPayload.sender,
472
+ recipient: requirements.payTo,
473
+ amount: requirements.amount
474
+ }
475
+ };
476
+ } catch (error) {
477
+ return { valid: false, error: error.message };
478
+ }
479
+ }
480
+ /**
481
+ * Settle a Solana payment
482
+ *
483
+ * Submits the signed transaction to the network.
484
+ * In gasless mode, adds fee payer signature before submitting.
485
+ */
486
+ async settle(paymentPayload, requirements) {
487
+ try {
488
+ const solanaPayload = paymentPayload.payload;
489
+ if (!solanaPayload || !solanaPayload.signedTransaction) {
490
+ return { success: false, error: "Missing signed transaction" };
491
+ }
492
+ const chain = solanaPayload.chain || "solana_devnet";
493
+ const connection = this.getConnection(chain);
494
+ const txBuffer = Buffer.from(solanaPayload.signedTransaction, "base64");
495
+ let txToSend;
496
+ try {
497
+ const tx = import_web33.Transaction.from(txBuffer);
498
+ if (this.feePayerKeypair && tx.feePayer) {
499
+ const feePayerPubkey = this.feePayerKeypair.publicKey.toBase58();
500
+ const txFeePayer = tx.feePayer.toBase58();
501
+ if (txFeePayer === feePayerPubkey) {
502
+ console.log(`[SolanaFacilitator] Gasless mode: adding fee payer signature`);
503
+ tx.partialSign(this.feePayerKeypair);
504
+ }
505
+ }
506
+ txToSend = tx.serialize();
507
+ } catch (e) {
508
+ txToSend = txBuffer;
509
+ }
510
+ const signature = await connection.sendRawTransaction(txToSend, {
511
+ skipPreflight: false,
512
+ preflightCommitment: "confirmed"
513
+ });
514
+ const confirmation = await connection.confirmTransaction(signature, "confirmed");
515
+ if (confirmation.value.err) {
516
+ return {
517
+ success: false,
518
+ error: `Transaction failed: ${JSON.stringify(confirmation.value.err)}`,
519
+ transaction: signature
520
+ };
521
+ }
522
+ return {
523
+ success: true,
524
+ transaction: signature,
525
+ status: "confirmed"
526
+ };
527
+ } catch (error) {
528
+ return { success: false, error: error.message };
529
+ }
530
+ }
531
+ supportsNetwork(network) {
532
+ return this.supportedNetworks.includes(network);
533
+ }
534
+ };
535
+ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
536
+ const chainConfig = SOLANA_CHAINS[chain];
537
+ const connection = new import_web33.Connection(chainConfig.rpc, "confirmed");
538
+ const mint = new import_web33.PublicKey(chainConfig.tokens.USDC.mint);
539
+ const actualFeePayer = feePayerPubkey || senderPubkey;
540
+ const senderATA = await (0, import_spl_token2.getAssociatedTokenAddress)(mint, senderPubkey);
541
+ const recipientATA = await (0, import_spl_token2.getAssociatedTokenAddress)(mint, recipientPubkey);
542
+ const transaction = new import_web33.Transaction();
543
+ try {
544
+ await (0, import_spl_token2.getAccount)(connection, recipientATA);
545
+ } catch {
546
+ transaction.add(
547
+ (0, import_spl_token2.createAssociatedTokenAccountInstruction)(
548
+ actualFeePayer,
549
+ // payer (fee payer in gasless mode)
550
+ recipientATA,
551
+ // ata to create
552
+ recipientPubkey,
553
+ // owner
554
+ mint
555
+ // mint
556
+ )
557
+ );
558
+ }
559
+ transaction.add(
560
+ (0, import_spl_token2.createTransferCheckedInstruction)(
561
+ senderATA,
562
+ // source
563
+ mint,
564
+ // mint
565
+ recipientATA,
566
+ // destination
567
+ senderPubkey,
568
+ // owner (sender still authorizes the transfer)
569
+ amount,
570
+ // amount
571
+ chainConfig.tokens.USDC.decimals
572
+ // decimals
573
+ )
574
+ );
575
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
576
+ transaction.recentBlockhash = blockhash;
577
+ transaction.feePayer = actualFeePayer;
578
+ return transaction;
579
+ }
580
+
581
+ // src/client/index.ts
582
+ var import_web34 = require("@solana/web3.js");
583
+
170
584
  // src/client/types.ts
171
585
  init_cjs_shims();
172
586
 
@@ -189,7 +603,7 @@ var MoltsPayClient = class {
189
603
  todaySpending = 0;
190
604
  lastSpendingReset = 0;
191
605
  constructor(options = {}) {
192
- this.configDir = options.configDir || (0, import_path.join)((0, import_os.homedir)(), ".moltspay");
606
+ this.configDir = options.configDir || (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
193
607
  this.config = this.loadConfig();
194
608
  this.walletData = this.loadWallet();
195
609
  this.loadSpending();
@@ -209,6 +623,12 @@ var MoltsPayClient = class {
209
623
  get address() {
210
624
  return this.wallet?.address || null;
211
625
  }
626
+ /**
627
+ * Get wallet instance (for direct operations like approvals)
628
+ */
629
+ getWallet() {
630
+ return this.wallet;
631
+ }
212
632
  /**
213
633
  * Get current config
214
634
  */
@@ -262,11 +682,26 @@ var MoltsPayClient = class {
262
682
  throw new Error("Client not initialized. Run: npx moltspay init");
263
683
  }
264
684
  console.log(`[MoltsPay] Requesting service: ${service}`);
265
- const requestBody = { service, params };
685
+ let executeUrl = `${serverUrl}/execute`;
686
+ try {
687
+ const services = await this.getServices(serverUrl);
688
+ const svc = services.services?.find((s) => s.id === service);
689
+ if (svc?.endpoint) {
690
+ executeUrl = `${serverUrl}${svc.endpoint}`;
691
+ console.log(`[MoltsPay] Using service endpoint: ${svc.endpoint}`);
692
+ }
693
+ } catch {
694
+ }
695
+ let requestBody;
696
+ if (options.rawData) {
697
+ requestBody = { service, ...params };
698
+ } else {
699
+ requestBody = { service, params };
700
+ }
266
701
  if (options.chain) {
267
702
  requestBody.chain = options.chain;
268
703
  }
269
- const initialRes = await fetch(`${serverUrl}/execute`, {
704
+ const initialRes = await fetch(executeUrl, {
270
705
  method: "POST",
271
706
  headers: { "Content-Type": "application/json" },
272
707
  body: JSON.stringify(requestBody)
@@ -278,9 +713,14 @@ var MoltsPayClient = class {
278
713
  }
279
714
  throw new Error(data.error || "Unexpected response");
280
715
  }
716
+ const wwwAuthHeader = initialRes.headers.get("www-authenticate");
281
717
  const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER);
718
+ if (wwwAuthHeader && wwwAuthHeader.toLowerCase().includes("payment")) {
719
+ console.log("[MoltsPay] Detected MPP protocol, using Tempo flow...");
720
+ return await this.handleMPPPayment(executeUrl, service, params, wwwAuthHeader, options);
721
+ }
282
722
  if (!paymentRequiredHeader) {
283
- throw new Error("Missing x-payment-required header");
723
+ throw new Error("Missing payment header (x-payment-required or www-authenticate)");
284
724
  }
285
725
  let requirements;
286
726
  try {
@@ -297,17 +737,22 @@ var MoltsPayClient = class {
297
737
  throw new Error("Invalid x-payment-required header");
298
738
  }
299
739
  const networkToChainName = (network2) => {
740
+ if (network2 === "solana:mainnet") return "solana";
741
+ if (network2 === "solana:devnet") return "solana_devnet";
300
742
  const match = network2.match(/^eip155:(\d+)$/);
301
743
  if (!match) return null;
302
744
  const chainId = parseInt(match[1]);
303
745
  if (chainId === 8453) return "base";
304
746
  if (chainId === 137) return "polygon";
305
747
  if (chainId === 84532) return "base_sepolia";
748
+ if (chainId === 42431) return "tempo_moderato";
749
+ if (chainId === 56) return "bnb";
750
+ if (chainId === 97) return "bnb_testnet";
306
751
  return null;
307
752
  };
308
753
  const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
309
- let chainName;
310
754
  const userSpecifiedChain = options.chain;
755
+ let selectedChain;
311
756
  if (userSpecifiedChain) {
312
757
  if (!serverChains.includes(userSpecifiedChain)) {
313
758
  throw new Error(
@@ -315,17 +760,27 @@ var MoltsPayClient = class {
315
760
  Server accepts: ${serverChains.join(", ")}`
316
761
  );
317
762
  }
318
- chainName = userSpecifiedChain;
763
+ selectedChain = userSpecifiedChain;
319
764
  } else {
320
765
  if (serverChains.length === 1 && serverChains[0] === "base") {
321
- chainName = "base";
766
+ selectedChain = "base";
322
767
  } else {
323
768
  throw new Error(
324
769
  `Server accepts: ${serverChains.join(", ")}
325
- Please specify: --chain base, --chain polygon, or --chain base_sepolia`
770
+ Please specify: --chain <chain_name>`
326
771
  );
327
772
  }
328
773
  }
774
+ if (selectedChain === "solana" || selectedChain === "solana_devnet") {
775
+ const solanaChain = selectedChain;
776
+ const network2 = solanaChain === "solana" ? "solana:mainnet" : "solana:devnet";
777
+ const req2 = requirements.find((r) => r.network === network2);
778
+ if (!req2) {
779
+ throw new Error(`Failed to find payment requirement for ${selectedChain}`);
780
+ }
781
+ return await this.handleSolanaPayment(executeUrl, service, params, req2, solanaChain, options);
782
+ }
783
+ const chainName = selectedChain;
329
784
  const chain = getChain(chainName);
330
785
  const network = `eip155:${chain.chainId}`;
331
786
  const req = requirements.find((r) => r.scheme === "exact" && r.network === network);
@@ -360,6 +815,25 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
360
815
  } else {
361
816
  console.log(`[MoltsPay] Signing payment: $${amount} ${token} (gasless)`);
362
817
  }
818
+ if (chainName === "bnb" || chainName === "bnb_testnet") {
819
+ console.log(`[MoltsPay] Using BNB intent-based payment flow...`);
820
+ const payTo2 = req.payTo || req.resource;
821
+ if (!payTo2) {
822
+ throw new Error("Missing payTo address in payment requirements");
823
+ }
824
+ const bnbSpender = req.extra?.bnbSpender;
825
+ if (!bnbSpender) {
826
+ throw new Error("Server did not provide bnbSpender address. Server may not support BNB payments.");
827
+ }
828
+ return await this.handleBNBPayment(executeUrl, service, params, {
829
+ to: payTo2,
830
+ amount,
831
+ token,
832
+ chainName,
833
+ chain,
834
+ spender: bnbSpender
835
+ }, options);
836
+ }
363
837
  const payTo = req.payTo || req.resource;
364
838
  if (!payTo) {
365
839
  throw new Error("Missing payTo address in payment requirements");
@@ -389,11 +863,11 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
389
863
  };
390
864
  const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
391
865
  console.log(`[MoltsPay] Sending request with payment...`);
392
- const paidRequestBody = { service, params };
866
+ const paidRequestBody = options.rawData ? { service, ...params } : { service, params };
393
867
  if (options.chain) {
394
868
  paidRequestBody.chain = options.chain;
395
869
  }
396
- const paidRes = await fetch(`${serverUrl}/execute`, {
870
+ const paidRes = await fetch(executeUrl, {
397
871
  method: "POST",
398
872
  headers: {
399
873
  "Content-Type": "application/json",
@@ -407,7 +881,304 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
407
881
  }
408
882
  this.recordSpending(amount);
409
883
  console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
410
- return result.result;
884
+ return result.result || result;
885
+ }
886
+ /**
887
+ * Handle MPP (Machine Payments Protocol) payment flow
888
+ * Called when pay() detects WWW-Authenticate header in 402 response
889
+ */
890
+ async handleMPPPayment(executeUrl, service, params, wwwAuthHeader, options = {}) {
891
+ const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
892
+ const { createWalletClient, createPublicClient, http } = await import("viem");
893
+ const { tempoModerato } = await import("viem/chains");
894
+ const { Actions } = await import("viem/tempo");
895
+ const privateKey = this.walletData.privateKey;
896
+ const account = privateKeyToAccount2(privateKey);
897
+ console.log(`[MoltsPay] Using MPP protocol on Tempo`);
898
+ console.log(`[MoltsPay] Account: ${account.address}`);
899
+ const parseAuthParam = (header, key) => {
900
+ const match = header.match(new RegExp(`${key}="([^"]+)"`, "i"));
901
+ return match ? match[1] : null;
902
+ };
903
+ const challengeId = parseAuthParam(wwwAuthHeader, "id");
904
+ const method = parseAuthParam(wwwAuthHeader, "method");
905
+ const realm = parseAuthParam(wwwAuthHeader, "realm");
906
+ const requestB64 = parseAuthParam(wwwAuthHeader, "request");
907
+ if (method !== "tempo") {
908
+ throw new Error(`Unsupported payment method: ${method}`);
909
+ }
910
+ if (!requestB64) {
911
+ throw new Error("Missing request in WWW-Authenticate");
912
+ }
913
+ const requestJson = Buffer.from(requestB64, "base64").toString("utf-8");
914
+ const paymentRequest = JSON.parse(requestJson);
915
+ const { amount, currency, recipient, methodDetails } = paymentRequest;
916
+ const chainId = methodDetails?.chainId || 42431;
917
+ const amountDisplay = Number(amount) / 1e6;
918
+ console.log(`[MoltsPay] Payment: $${amountDisplay} to ${recipient}`);
919
+ this.checkLimits(amountDisplay);
920
+ console.log(`[MoltsPay] Sending transaction on Tempo...`);
921
+ const tempoChain = { ...tempoModerato, feeToken: currency };
922
+ const publicClient = createPublicClient({
923
+ chain: tempoChain,
924
+ transport: http("https://rpc.moderato.tempo.xyz")
925
+ });
926
+ const walletClient = createWalletClient({
927
+ account,
928
+ chain: tempoChain,
929
+ transport: http("https://rpc.moderato.tempo.xyz")
930
+ });
931
+ const txHash = await Actions.token.transfer(walletClient, {
932
+ to: recipient,
933
+ amount: BigInt(amount),
934
+ token: currency
935
+ });
936
+ console.log(`[MoltsPay] Transaction: ${txHash}`);
937
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
938
+ console.log(`[MoltsPay] Confirmed! Retrying with credential...`);
939
+ const credential = {
940
+ challenge: {
941
+ id: challengeId,
942
+ realm,
943
+ method: "tempo",
944
+ intent: "charge",
945
+ request: paymentRequest
946
+ },
947
+ payload: { hash: txHash, type: "hash" },
948
+ source: `did:pkh:eip155:${chainId}:${account.address}`
949
+ };
950
+ const credentialB64 = Buffer.from(JSON.stringify(credential)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
951
+ const retryBody = options.rawData ? { service, ...params, chain: "tempo_moderato" } : { service, params, chain: "tempo_moderato" };
952
+ const paidRes = await fetch(executeUrl, {
953
+ method: "POST",
954
+ headers: {
955
+ "Content-Type": "application/json",
956
+ "Authorization": `Payment ${credentialB64}`
957
+ },
958
+ body: JSON.stringify(retryBody)
959
+ });
960
+ const result = await paidRes.json();
961
+ if (!paidRes.ok) {
962
+ throw new Error(result.error || "Payment verification failed");
963
+ }
964
+ this.recordSpending(amountDisplay);
965
+ console.log(`[MoltsPay] Success!`);
966
+ return result.result || result;
967
+ }
968
+ /**
969
+ * Handle BNB Chain payment flow (pre-approval + intent signature)
970
+ *
971
+ * Flow:
972
+ * 1. Check client has approved server wallet (done via `moltspay init`)
973
+ * 2. Sign EIP-712 payment intent (no gas, just signature)
974
+ * 3. Send intent to server
975
+ * 4. Server executes service
976
+ * 5. Server calls transferFrom if successful (pay-for-success)
977
+ */
978
+ async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
979
+ const { to, amount, token, chainName, chain, spender } = paymentDetails;
980
+ const tokenConfig = chain.tokens[token];
981
+ const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
982
+ const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
983
+ const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
984
+ if (allowance < amountWeiCheck) {
985
+ const nativeBalance = await provider.getBalance(this.wallet.address);
986
+ const minGasBalance = import_ethers.ethers.parseEther("0.0005");
987
+ if (nativeBalance < minGasBalance) {
988
+ const nativeBNB = parseFloat(import_ethers.ethers.formatEther(nativeBalance)).toFixed(4);
989
+ const isTestnet = chainName === "bnb_testnet";
990
+ if (isTestnet) {
991
+ throw new Error(
992
+ `\u274C Insufficient tBNB for approval transaction
993
+
994
+ Current tBNB: ${nativeBNB}
995
+ Required: ~0.001 tBNB
996
+
997
+ Get testnet tokens: npx moltspay faucet --chain bnb_testnet
998
+ (Gives USDC + tBNB for gas)`
999
+ );
1000
+ } else {
1001
+ throw new Error(
1002
+ `\u274C Insufficient BNB for approval transaction
1003
+
1004
+ Current BNB: ${nativeBNB}
1005
+ Required: ~0.001 BNB (~$0.60)
1006
+
1007
+ To get BNB:
1008
+ \u2022 Withdraw from Binance/exchange to your wallet
1009
+ \u2022 Most exchanges include BNB dust with withdrawals
1010
+
1011
+ After funding, run:
1012
+ npx moltspay approve --chain ${chainName} --spender ${spender}`
1013
+ );
1014
+ }
1015
+ }
1016
+ throw new Error(
1017
+ `Insufficient allowance for ${spender.slice(0, 10)}...
1018
+ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1019
+ );
1020
+ }
1021
+ const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
1022
+ const intent = {
1023
+ from: this.wallet.address,
1024
+ to,
1025
+ amount: amountWei,
1026
+ token: tokenConfig.address,
1027
+ service,
1028
+ nonce: Date.now(),
1029
+ // Use timestamp as nonce for simplicity
1030
+ deadline: Date.now() + 36e5
1031
+ // 1 hour
1032
+ };
1033
+ const domain = {
1034
+ name: "MoltsPay",
1035
+ version: "1",
1036
+ chainId: chain.chainId
1037
+ };
1038
+ const types = {
1039
+ PaymentIntent: [
1040
+ { name: "from", type: "address" },
1041
+ { name: "to", type: "address" },
1042
+ { name: "amount", type: "uint256" },
1043
+ { name: "token", type: "address" },
1044
+ { name: "service", type: "string" },
1045
+ { name: "nonce", type: "uint256" },
1046
+ { name: "deadline", type: "uint256" }
1047
+ ]
1048
+ };
1049
+ console.log(`[MoltsPay] Signing BNB payment intent...`);
1050
+ const signature = await this.wallet.signTypedData(domain, types, intent);
1051
+ const network = `eip155:${chain.chainId}`;
1052
+ const payload = {
1053
+ x402Version: 2,
1054
+ scheme: "exact",
1055
+ network,
1056
+ payload: {
1057
+ intent: {
1058
+ ...intent,
1059
+ signature
1060
+ },
1061
+ chainId: chain.chainId
1062
+ },
1063
+ accepted: {
1064
+ scheme: "exact",
1065
+ network,
1066
+ asset: tokenConfig.address,
1067
+ amount: amountWei,
1068
+ payTo: to,
1069
+ maxTimeoutSeconds: 300
1070
+ }
1071
+ };
1072
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
1073
+ console.log(`[MoltsPay] Sending BNB payment request...`);
1074
+ const bnbRequestBody = options.rawData ? { service, ...params, chain: chainName } : { service, params, chain: chainName };
1075
+ const paidRes = await fetch(executeUrl, {
1076
+ method: "POST",
1077
+ headers: {
1078
+ "Content-Type": "application/json",
1079
+ "X-Payment": paymentHeader
1080
+ },
1081
+ body: JSON.stringify(bnbRequestBody)
1082
+ });
1083
+ const result = await paidRes.json();
1084
+ if (!paidRes.ok) {
1085
+ throw new Error(result.error || "BNB payment failed");
1086
+ }
1087
+ this.recordSpending(amount);
1088
+ console.log(`[MoltsPay] Success! BNB payment settled.`);
1089
+ return result.result || result;
1090
+ }
1091
+ /**
1092
+ * Handle Solana payment flow
1093
+ *
1094
+ * Solana uses SPL token transfers with pay-for-success model:
1095
+ * 1. Client creates and signs a transfer transaction
1096
+ * 2. Server submits the transaction after service completes
1097
+ */
1098
+ async handleSolanaPayment(executeUrl, service, params, requirements, chain, options = {}) {
1099
+ const solanaWallet = loadSolanaWallet(this.configDir);
1100
+ if (!solanaWallet) {
1101
+ throw new Error("No Solana wallet found. Run: npx moltspay init --chain solana_devnet");
1102
+ }
1103
+ const amount = Number(requirements.amount);
1104
+ const amountUSDC = amount / 1e6;
1105
+ this.checkLimits(amountUSDC);
1106
+ console.log(`[MoltsPay] Creating Solana payment: $${amountUSDC} USDC`);
1107
+ if (!requirements.payTo) {
1108
+ throw new Error("Missing payTo address in payment requirements");
1109
+ }
1110
+ const solanaFeePayer = requirements.extra?.solanaFeePayer;
1111
+ const feePayerPubkey = solanaFeePayer ? new import_web34.PublicKey(solanaFeePayer) : void 0;
1112
+ if (feePayerPubkey) {
1113
+ console.log(`[MoltsPay] Gasless mode: server pays fees`);
1114
+ }
1115
+ const recipientPubkey = new import_web34.PublicKey(requirements.payTo);
1116
+ const transaction = await createSolanaPaymentTransaction(
1117
+ solanaWallet.publicKey,
1118
+ recipientPubkey,
1119
+ BigInt(amount),
1120
+ chain,
1121
+ feePayerPubkey
1122
+ // Optional fee payer for gasless mode
1123
+ );
1124
+ if (feePayerPubkey) {
1125
+ transaction.partialSign(solanaWallet);
1126
+ } else {
1127
+ transaction.sign(solanaWallet);
1128
+ }
1129
+ const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
1130
+ console.log(`[MoltsPay] Transaction signed, sending to server...`);
1131
+ const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
1132
+ const payload = {
1133
+ x402Version: 2,
1134
+ scheme: "exact",
1135
+ network,
1136
+ payload: {
1137
+ signedTransaction: signedTx,
1138
+ sender: solanaWallet.publicKey.toBase58(),
1139
+ chain
1140
+ },
1141
+ accepted: {
1142
+ scheme: "exact",
1143
+ network,
1144
+ asset: requirements.asset,
1145
+ amount: requirements.amount,
1146
+ payTo: requirements.payTo,
1147
+ maxTimeoutSeconds: 300
1148
+ }
1149
+ };
1150
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
1151
+ const solanaRequestBody = options.rawData ? { service, ...params, chain } : { service, params, chain };
1152
+ const paidRes = await fetch(executeUrl, {
1153
+ method: "POST",
1154
+ headers: {
1155
+ "Content-Type": "application/json",
1156
+ "X-Payment": paymentHeader
1157
+ },
1158
+ body: JSON.stringify(solanaRequestBody)
1159
+ });
1160
+ const result = await paidRes.json();
1161
+ if (!paidRes.ok) {
1162
+ throw new Error(result.error || "Solana payment failed");
1163
+ }
1164
+ this.recordSpending(amountUSDC);
1165
+ console.log(`[MoltsPay] Success! Solana payment settled.`);
1166
+ if (result.payment?.transaction) {
1167
+ const explorerUrl = chain === "solana" ? `https://solscan.io/tx/${result.payment.transaction}` : `https://solscan.io/tx/${result.payment.transaction}?cluster=devnet`;
1168
+ console.log(`[MoltsPay] Transaction: ${explorerUrl}`);
1169
+ }
1170
+ return result.result || result;
1171
+ }
1172
+ /**
1173
+ * Check ERC20 allowance for a spender
1174
+ */
1175
+ async checkAllowance(tokenAddress, spender, provider) {
1176
+ const contract = new import_ethers.ethers.Contract(
1177
+ tokenAddress,
1178
+ ["function allowance(address owner, address spender) view returns (uint256)"],
1179
+ provider
1180
+ );
1181
+ return await contract.allowance(this.wallet.address, spender);
411
1182
  }
412
1183
  /**
413
1184
  * Sign EIP-3009 transferWithAuthorization (GASLESS)
@@ -479,26 +1250,26 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
479
1250
  }
480
1251
  // --- Config & Wallet Management ---
481
1252
  loadConfig() {
482
- const configPath = (0, import_path.join)(this.configDir, "config.json");
483
- if ((0, import_fs.existsSync)(configPath)) {
484
- const content = (0, import_fs.readFileSync)(configPath, "utf-8");
1253
+ const configPath = (0, import_path2.join)(this.configDir, "config.json");
1254
+ if ((0, import_fs2.existsSync)(configPath)) {
1255
+ const content = (0, import_fs2.readFileSync)(configPath, "utf-8");
485
1256
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
486
1257
  }
487
1258
  return { ...DEFAULT_CONFIG };
488
1259
  }
489
1260
  saveConfig() {
490
- (0, import_fs.mkdirSync)(this.configDir, { recursive: true });
491
- const configPath = (0, import_path.join)(this.configDir, "config.json");
492
- (0, import_fs.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
1261
+ (0, import_fs2.mkdirSync)(this.configDir, { recursive: true });
1262
+ const configPath = (0, import_path2.join)(this.configDir, "config.json");
1263
+ (0, import_fs2.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
493
1264
  }
494
1265
  /**
495
1266
  * Load spending data from disk
496
1267
  */
497
1268
  loadSpending() {
498
- const spendingPath = (0, import_path.join)(this.configDir, "spending.json");
499
- if ((0, import_fs.existsSync)(spendingPath)) {
1269
+ const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
1270
+ if ((0, import_fs2.existsSync)(spendingPath)) {
500
1271
  try {
501
- const data = JSON.parse((0, import_fs.readFileSync)(spendingPath, "utf-8"));
1272
+ const data = JSON.parse((0, import_fs2.readFileSync)(spendingPath, "utf-8"));
502
1273
  const today = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
503
1274
  if (data.date && data.date === today) {
504
1275
  this.todaySpending = data.amount || 0;
@@ -517,29 +1288,29 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
517
1288
  * Save spending data to disk
518
1289
  */
519
1290
  saveSpending() {
520
- (0, import_fs.mkdirSync)(this.configDir, { recursive: true });
521
- const spendingPath = (0, import_path.join)(this.configDir, "spending.json");
1291
+ (0, import_fs2.mkdirSync)(this.configDir, { recursive: true });
1292
+ const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
522
1293
  const data = {
523
1294
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
524
1295
  amount: this.todaySpending,
525
1296
  updatedAt: Date.now()
526
1297
  };
527
- (0, import_fs.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
1298
+ (0, import_fs2.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
528
1299
  }
529
1300
  loadWallet() {
530
- const walletPath = (0, import_path.join)(this.configDir, "wallet.json");
531
- if ((0, import_fs.existsSync)(walletPath)) {
1301
+ const walletPath = (0, import_path2.join)(this.configDir, "wallet.json");
1302
+ if ((0, import_fs2.existsSync)(walletPath)) {
532
1303
  try {
533
- const stats = (0, import_fs.statSync)(walletPath);
1304
+ const stats = (0, import_fs2.statSync)(walletPath);
534
1305
  const mode = stats.mode & 511;
535
1306
  if (mode !== 384) {
536
1307
  console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
537
1308
  console.warn(`[MoltsPay] Fixing permissions to 0600...`);
538
- (0, import_fs.chmodSync)(walletPath, 384);
1309
+ (0, import_fs2.chmodSync)(walletPath, 384);
539
1310
  }
540
1311
  } catch (err) {
541
1312
  }
542
- const content = (0, import_fs.readFileSync)(walletPath, "utf-8");
1313
+ const content = (0, import_fs2.readFileSync)(walletPath, "utf-8");
543
1314
  return JSON.parse(content);
544
1315
  }
545
1316
  return null;
@@ -548,15 +1319,15 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
548
1319
  * Initialize a new wallet (called by CLI)
549
1320
  */
550
1321
  static init(configDir, options) {
551
- (0, import_fs.mkdirSync)(configDir, { recursive: true });
1322
+ (0, import_fs2.mkdirSync)(configDir, { recursive: true });
552
1323
  const wallet = import_ethers.Wallet.createRandom();
553
1324
  const walletData = {
554
1325
  address: wallet.address,
555
1326
  privateKey: wallet.privateKey,
556
1327
  createdAt: Date.now()
557
1328
  };
558
- const walletPath = (0, import_path.join)(configDir, "wallet.json");
559
- (0, import_fs.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
1329
+ const walletPath = (0, import_path2.join)(configDir, "wallet.json");
1330
+ (0, import_fs2.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
560
1331
  const config = {
561
1332
  chain: options.chain,
562
1333
  limits: {
@@ -564,8 +1335,8 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
564
1335
  maxPerDay: options.maxPerDay
565
1336
  }
566
1337
  };
567
- const configPath = (0, import_path.join)(configDir, "config.json");
568
- (0, import_fs.writeFileSync)(configPath, JSON.stringify(config, null, 2));
1338
+ const configPath = (0, import_path2.join)(configDir, "config.json");
1339
+ (0, import_fs2.writeFileSync)(configPath, JSON.stringify(config, null, 2));
569
1340
  return { address: wallet.address, configDir };
570
1341
  }
571
1342
  /**
@@ -601,7 +1372,7 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
601
1372
  if (!this.wallet) {
602
1373
  throw new Error("Client not initialized");
603
1374
  }
604
- const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
1375
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet"];
605
1376
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
606
1377
  const results = {};
607
1378
  const tempoTokens = {
@@ -672,12 +1443,12 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
672
1443
  if (!this.wallet || !this.walletData) {
673
1444
  throw new Error("Client not initialized. Run: npx moltspay init");
674
1445
  }
675
- const { privateKeyToAccount } = await import("viem/accounts");
1446
+ const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
676
1447
  const { createWalletClient, createPublicClient, http } = await import("viem");
677
1448
  const { tempoModerato } = await import("viem/chains");
678
1449
  const { Actions } = await import("viem/tempo");
679
1450
  const privateKey = this.walletData.privateKey;
680
- const account = privateKeyToAccount(privateKey);
1451
+ const account = privateKeyToAccount2(privateKey);
681
1452
  console.log(`[MoltsPay] Making MPP request to: ${url}`);
682
1453
  console.log(`[MoltsPay] Using account: ${account.address}`);
683
1454
  const initResponse = await fetch(url, {
@@ -774,24 +1545,16 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
774
1545
 
775
1546
  // src/server/index.ts
776
1547
  init_cjs_shims();
777
- var import_fs3 = require("fs");
1548
+ var import_fs4 = require("fs");
778
1549
  var import_http = require("http");
779
1550
  var path2 = __toESM(require("path"));
780
1551
 
781
1552
  // src/facilitators/index.ts
782
1553
  init_cjs_shims();
783
1554
 
784
- // src/facilitators/interface.ts
785
- init_cjs_shims();
786
- var BaseFacilitator = class {
787
- supportsNetwork(network) {
788
- return this.supportedNetworks.includes(network);
789
- }
790
- };
791
-
792
1555
  // src/facilitators/cdp.ts
793
1556
  init_cjs_shims();
794
- var import_fs2 = require("fs");
1557
+ var import_fs3 = require("fs");
795
1558
  var path = __toESM(require("path"));
796
1559
  var X402_VERSION2 = 2;
797
1560
  var CDP_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
@@ -802,9 +1565,9 @@ function loadEnvFile() {
802
1565
  path.join(process.env.HOME || "", ".moltspay", ".env")
803
1566
  ];
804
1567
  for (const envPath of envPaths) {
805
- if ((0, import_fs2.existsSync)(envPath)) {
1568
+ if ((0, import_fs3.existsSync)(envPath)) {
806
1569
  try {
807
- const content = (0, import_fs2.readFileSync)(envPath, "utf-8");
1570
+ const content = (0, import_fs3.readFileSync)(envPath, "utf-8");
808
1571
  for (const line of content.split("\n")) {
809
1572
  const trimmed = line.trim();
810
1573
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -1040,18 +1803,280 @@ var TempoFacilitator = class extends BaseFacilitator {
1040
1803
  if (chainId !== 42431) {
1041
1804
  return { healthy: false, error: `Wrong chainId: ${chainId}` };
1042
1805
  }
1043
- return { healthy: true, latencyMs: Date.now() - start };
1806
+ return { healthy: true, latencyMs: Date.now() - start };
1807
+ } catch (error) {
1808
+ return { healthy: false, error: String(error) };
1809
+ }
1810
+ }
1811
+ async verify(paymentPayload, requirements) {
1812
+ try {
1813
+ const tempoPayload = paymentPayload.payload;
1814
+ if (!tempoPayload?.txHash) {
1815
+ return { valid: false, error: "Missing txHash in payment payload" };
1816
+ }
1817
+ const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
1818
+ if (!receipt) {
1819
+ return { valid: false, error: "Transaction not found" };
1820
+ }
1821
+ if (receipt.status !== "0x1") {
1822
+ return { valid: false, error: "Transaction failed" };
1823
+ }
1824
+ const transferLog = receipt.logs.find(
1825
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC
1826
+ );
1827
+ if (!transferLog) {
1828
+ return { valid: false, error: "No Transfer event found" };
1829
+ }
1830
+ const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
1831
+ const expectedTo = requirements.payTo.toLowerCase();
1832
+ if (toAddress !== expectedTo) {
1833
+ return {
1834
+ valid: false,
1835
+ error: `Wrong recipient: ${toAddress}, expected ${expectedTo}`
1836
+ };
1837
+ }
1838
+ const amount = BigInt(transferLog.data);
1839
+ const expectedAmount = BigInt(requirements.amount);
1840
+ if (amount < expectedAmount) {
1841
+ return {
1842
+ valid: false,
1843
+ error: `Insufficient amount: ${amount}, expected ${expectedAmount}`
1844
+ };
1845
+ }
1846
+ const tokenAddress = transferLog.address.toLowerCase();
1847
+ const expectedToken = requirements.asset.toLowerCase();
1848
+ if (tokenAddress !== expectedToken) {
1849
+ return {
1850
+ valid: false,
1851
+ error: `Wrong token: ${tokenAddress}, expected ${expectedToken}`
1852
+ };
1853
+ }
1854
+ return {
1855
+ valid: true,
1856
+ details: {
1857
+ txHash: tempoPayload.txHash,
1858
+ from: "0x" + transferLog.topics[1].slice(26),
1859
+ to: toAddress,
1860
+ amount: amount.toString(),
1861
+ token: tokenAddress
1862
+ }
1863
+ };
1864
+ } catch (error) {
1865
+ return { valid: false, error: `Verification failed: ${error}` };
1866
+ }
1867
+ }
1868
+ async settle(paymentPayload, requirements) {
1869
+ const verifyResult = await this.verify(paymentPayload, requirements);
1870
+ if (!verifyResult.valid) {
1871
+ return { success: false, error: verifyResult.error };
1872
+ }
1873
+ const tempoPayload = paymentPayload.payload;
1874
+ return {
1875
+ success: true,
1876
+ transaction: tempoPayload.txHash,
1877
+ status: "settled"
1878
+ };
1879
+ }
1880
+ async getTransactionReceipt(txHash) {
1881
+ const response = await fetch(this.rpcUrl, {
1882
+ method: "POST",
1883
+ headers: { "Content-Type": "application/json" },
1884
+ body: JSON.stringify({
1885
+ jsonrpc: "2.0",
1886
+ method: "eth_getTransactionReceipt",
1887
+ params: [txHash],
1888
+ id: 1
1889
+ })
1890
+ });
1891
+ const data = await response.json();
1892
+ return data.result;
1893
+ }
1894
+ };
1895
+
1896
+ // src/facilitators/bnb.ts
1897
+ init_cjs_shims();
1898
+ var import_accounts = require("viem/accounts");
1899
+ var TRANSFER_EVENT_TOPIC2 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
1900
+ var EIP712_DOMAIN = {
1901
+ name: "MoltsPay",
1902
+ version: "1"
1903
+ };
1904
+ var INTENT_TYPES = {
1905
+ PaymentIntent: [
1906
+ { name: "from", type: "address" },
1907
+ { name: "to", type: "address" },
1908
+ { name: "amount", type: "uint256" },
1909
+ { name: "token", type: "address" },
1910
+ { name: "service", type: "string" },
1911
+ { name: "nonce", type: "uint256" },
1912
+ { name: "deadline", type: "uint256" }
1913
+ ]
1914
+ };
1915
+ var BNBFacilitator = class extends BaseFacilitator {
1916
+ name = "bnb";
1917
+ displayName = "BNB Smart Chain";
1918
+ supportedNetworks = ["eip155:56", "eip155:97"];
1919
+ // Mainnet + Testnet
1920
+ serverPrivateKey;
1921
+ spenderAddress = null;
1922
+ chainConfigs;
1923
+ constructor(serverPrivateKey) {
1924
+ super();
1925
+ this.serverPrivateKey = serverPrivateKey || process.env.BNB_SERVER_PRIVATE_KEY || "";
1926
+ if (this.serverPrivateKey) {
1927
+ const key = this.serverPrivateKey.startsWith("0x") ? this.serverPrivateKey : `0x${this.serverPrivateKey}`;
1928
+ const account = (0, import_accounts.privateKeyToAccount)(key);
1929
+ this.spenderAddress = account.address;
1930
+ }
1931
+ this.chainConfigs = {
1932
+ 56: { rpc: CHAINS.bnb.rpc, chain: CHAINS.bnb },
1933
+ 97: { rpc: CHAINS.bnb_testnet.rpc, chain: CHAINS.bnb_testnet }
1934
+ };
1935
+ }
1936
+ async healthCheck() {
1937
+ const start = Date.now();
1938
+ try {
1939
+ const response = await fetch(this.chainConfigs[56].rpc, {
1940
+ method: "POST",
1941
+ headers: { "Content-Type": "application/json" },
1942
+ body: JSON.stringify({
1943
+ jsonrpc: "2.0",
1944
+ method: "eth_chainId",
1945
+ params: [],
1946
+ id: 1
1947
+ })
1948
+ });
1949
+ const data = await response.json();
1950
+ const chainId = parseInt(data.result, 16);
1951
+ if (chainId !== 56) {
1952
+ return { healthy: false, error: `Wrong chainId: ${chainId}` };
1953
+ }
1954
+ return { healthy: true, latencyMs: Date.now() - start };
1955
+ } catch (error) {
1956
+ return { healthy: false, error: String(error) };
1957
+ }
1958
+ }
1959
+ /**
1960
+ * Verify a payment intent signature (before service execution)
1961
+ *
1962
+ * This verifies:
1963
+ * 1. Signature is valid for the intent
1964
+ * 2. Client has approved server wallet
1965
+ * 3. Client has sufficient balance
1966
+ * 4. Intent hasn't expired
1967
+ */
1968
+ async verify(paymentPayload, requirements) {
1969
+ try {
1970
+ const bnbPayload = paymentPayload.payload;
1971
+ if (!bnbPayload?.intent) {
1972
+ return { valid: false, error: "Missing intent in payment payload" };
1973
+ }
1974
+ const { intent, chainId } = bnbPayload;
1975
+ const config = this.chainConfigs[chainId];
1976
+ if (!config) {
1977
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
1978
+ }
1979
+ if (intent.deadline < Date.now()) {
1980
+ return { valid: false, error: "Intent expired" };
1981
+ }
1982
+ const recoveredAddress = await this.recoverIntentSigner(intent, chainId);
1983
+ if (recoveredAddress.toLowerCase() !== intent.from.toLowerCase()) {
1984
+ return { valid: false, error: "Invalid signature" };
1985
+ }
1986
+ if (intent.to.toLowerCase() !== requirements.payTo.toLowerCase()) {
1987
+ return { valid: false, error: `Wrong recipient: ${intent.to}` };
1988
+ }
1989
+ if (BigInt(intent.amount) < BigInt(requirements.amount)) {
1990
+ return { valid: false, error: `Insufficient amount: ${intent.amount}` };
1991
+ }
1992
+ if (intent.token.toLowerCase() !== requirements.asset.toLowerCase()) {
1993
+ return { valid: false, error: `Wrong token: ${intent.token}` };
1994
+ }
1995
+ const serverAddress = await this.getServerAddress();
1996
+ const allowance = await this.getAllowance(intent.from, serverAddress, intent.token, config.rpc);
1997
+ if (BigInt(allowance) < BigInt(intent.amount)) {
1998
+ return { valid: false, error: "Insufficient allowance. Run: npx moltspay init --chain bnb" };
1999
+ }
2000
+ const balance = await this.getBalance(intent.from, intent.token, config.rpc);
2001
+ if (BigInt(balance) < BigInt(intent.amount)) {
2002
+ return { valid: false, error: "Insufficient balance" };
2003
+ }
2004
+ return {
2005
+ valid: true,
2006
+ details: {
2007
+ from: intent.from,
2008
+ to: intent.to,
2009
+ amount: intent.amount,
2010
+ token: intent.token,
2011
+ service: intent.service,
2012
+ nonce: intent.nonce,
2013
+ deadline: intent.deadline
2014
+ }
2015
+ };
2016
+ } catch (error) {
2017
+ return { valid: false, error: `Verification failed: ${error}` };
2018
+ }
2019
+ }
2020
+ /**
2021
+ * Settle a payment by executing transferFrom
2022
+ *
2023
+ * This is called AFTER the service has been successfully delivered.
2024
+ * Server pays gas, transfers tokens from client to provider.
2025
+ */
2026
+ async settle(paymentPayload, requirements) {
2027
+ if (!this.serverPrivateKey) {
2028
+ return { success: false, error: "Server wallet not configured (BNB_SERVER_PRIVATE_KEY)" };
2029
+ }
2030
+ try {
2031
+ const verifyResult = await this.verify(paymentPayload, requirements);
2032
+ if (!verifyResult.valid) {
2033
+ return { success: false, error: verifyResult.error };
2034
+ }
2035
+ const bnbPayload = paymentPayload.payload;
2036
+ const { intent, chainId } = bnbPayload;
2037
+ const config = this.chainConfigs[chainId];
2038
+ const txHash = await this.executeTransferFrom(
2039
+ intent.from,
2040
+ intent.to,
2041
+ intent.amount,
2042
+ intent.token,
2043
+ config.rpc
2044
+ );
2045
+ return {
2046
+ success: true,
2047
+ transaction: txHash,
2048
+ status: "settled"
2049
+ };
1044
2050
  } catch (error) {
1045
- return { healthy: false, error: String(error) };
2051
+ return { success: false, error: `Settlement failed: ${error}` };
1046
2052
  }
1047
2053
  }
1048
- async verify(paymentPayload, requirements) {
2054
+ /**
2055
+ * Check if client has approved the server wallet
2056
+ */
2057
+ async checkApproval(clientAddress, token, chainId) {
2058
+ const config = this.chainConfigs[chainId];
2059
+ if (!config) {
2060
+ throw new Error(`Unsupported chainId: ${chainId}`);
2061
+ }
2062
+ const serverAddress = await this.getServerAddress();
2063
+ const allowance = await this.getAllowance(clientAddress, serverAddress, token, config.rpc);
2064
+ const minAllowance = BigInt("1000000000000000000000");
2065
+ return {
2066
+ approved: BigInt(allowance) >= minAllowance,
2067
+ allowance
2068
+ };
2069
+ }
2070
+ /**
2071
+ * Verify a completed transaction (for checking past payments)
2072
+ */
2073
+ async verifyTransaction(txHash, expected, chainId) {
2074
+ const config = this.chainConfigs[chainId];
2075
+ if (!config) {
2076
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
2077
+ }
1049
2078
  try {
1050
- const tempoPayload = paymentPayload.payload;
1051
- if (!tempoPayload?.txHash) {
1052
- return { valid: false, error: "Missing txHash in payment payload" };
1053
- }
1054
- const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
2079
+ const receipt = await this.getTransactionReceipt(txHash, config.rpc);
1055
2080
  if (!receipt) {
1056
2081
  return { valid: false, error: "Transaction not found" };
1057
2082
  }
@@ -1059,63 +2084,117 @@ var TempoFacilitator = class extends BaseFacilitator {
1059
2084
  return { valid: false, error: "Transaction failed" };
1060
2085
  }
1061
2086
  const transferLog = receipt.logs.find(
1062
- (log) => log.topics[0] === TRANSFER_EVENT_TOPIC
2087
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC2 && log.address.toLowerCase() === expected.token.toLowerCase()
1063
2088
  );
1064
2089
  if (!transferLog) {
1065
2090
  return { valid: false, error: "No Transfer event found" };
1066
2091
  }
1067
2092
  const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
1068
- const expectedTo = requirements.payTo.toLowerCase();
1069
- if (toAddress !== expectedTo) {
1070
- return {
1071
- valid: false,
1072
- error: `Wrong recipient: ${toAddress}, expected ${expectedTo}`
1073
- };
2093
+ if (toAddress !== expected.to.toLowerCase()) {
2094
+ return { valid: false, error: `Wrong recipient: ${toAddress}` };
1074
2095
  }
1075
2096
  const amount = BigInt(transferLog.data);
1076
- const expectedAmount = BigInt(requirements.amount);
1077
- if (amount < expectedAmount) {
1078
- return {
1079
- valid: false,
1080
- error: `Insufficient amount: ${amount}, expected ${expectedAmount}`
1081
- };
1082
- }
1083
- const tokenAddress = transferLog.address.toLowerCase();
1084
- const expectedToken = requirements.asset.toLowerCase();
1085
- if (tokenAddress !== expectedToken) {
1086
- return {
1087
- valid: false,
1088
- error: `Wrong token: ${tokenAddress}, expected ${expectedToken}`
1089
- };
2097
+ if (amount < BigInt(expected.amount)) {
2098
+ return { valid: false, error: `Insufficient amount: ${amount}` };
1090
2099
  }
1091
2100
  return {
1092
2101
  valid: true,
1093
2102
  details: {
1094
- txHash: tempoPayload.txHash,
2103
+ txHash,
1095
2104
  from: "0x" + transferLog.topics[1].slice(26),
1096
2105
  to: toAddress,
1097
2106
  amount: amount.toString(),
1098
- token: tokenAddress
2107
+ token: transferLog.address
1099
2108
  }
1100
2109
  };
1101
2110
  } catch (error) {
1102
2111
  return { valid: false, error: `Verification failed: ${error}` };
1103
2112
  }
1104
2113
  }
1105
- async settle(paymentPayload, requirements) {
1106
- const verifyResult = await this.verify(paymentPayload, requirements);
1107
- if (!verifyResult.valid) {
1108
- return { success: false, error: verifyResult.error };
1109
- }
1110
- const tempoPayload = paymentPayload.payload;
1111
- return {
1112
- success: true,
1113
- transaction: tempoPayload.txHash,
1114
- status: "settled"
2114
+ // ==================== Private Methods ====================
2115
+ /**
2116
+ * Get the server's spender address (public, for 402 responses)
2117
+ * Returns cached value computed at construction time.
2118
+ */
2119
+ getSpenderAddress() {
2120
+ return this.spenderAddress;
2121
+ }
2122
+ async getServerAddress() {
2123
+ const { ethers: ethers3 } = await import("ethers");
2124
+ const wallet = new ethers3.Wallet(this.serverPrivateKey);
2125
+ return wallet.address;
2126
+ }
2127
+ async recoverIntentSigner(intent, chainId) {
2128
+ const { ethers: ethers3 } = await import("ethers");
2129
+ const domain = {
2130
+ ...EIP712_DOMAIN,
2131
+ chainId
2132
+ };
2133
+ const message = {
2134
+ from: intent.from,
2135
+ to: intent.to,
2136
+ amount: intent.amount,
2137
+ token: intent.token,
2138
+ service: intent.service,
2139
+ nonce: intent.nonce,
2140
+ deadline: intent.deadline
1115
2141
  };
2142
+ const recoveredAddress = ethers3.verifyTypedData(
2143
+ domain,
2144
+ INTENT_TYPES,
2145
+ message,
2146
+ intent.signature
2147
+ );
2148
+ return recoveredAddress;
1116
2149
  }
1117
- async getTransactionReceipt(txHash) {
1118
- const response = await fetch(this.rpcUrl, {
2150
+ async getAllowance(owner, spender, token, rpcUrl) {
2151
+ const selector = "0xdd62ed3e";
2152
+ const ownerPadded = owner.toLowerCase().replace("0x", "").padStart(64, "0");
2153
+ const spenderPadded = spender.toLowerCase().replace("0x", "").padStart(64, "0");
2154
+ const data = selector + ownerPadded + spenderPadded;
2155
+ const response = await fetch(rpcUrl, {
2156
+ method: "POST",
2157
+ headers: { "Content-Type": "application/json" },
2158
+ body: JSON.stringify({
2159
+ jsonrpc: "2.0",
2160
+ method: "eth_call",
2161
+ params: [{ to: token, data }, "latest"],
2162
+ id: 1
2163
+ })
2164
+ });
2165
+ const result = await response.json();
2166
+ return result.result || "0x0";
2167
+ }
2168
+ async getBalance(account, token, rpcUrl) {
2169
+ const selector = "0x70a08231";
2170
+ const accountPadded = account.toLowerCase().replace("0x", "").padStart(64, "0");
2171
+ const data = selector + accountPadded;
2172
+ const response = await fetch(rpcUrl, {
2173
+ method: "POST",
2174
+ headers: { "Content-Type": "application/json" },
2175
+ body: JSON.stringify({
2176
+ jsonrpc: "2.0",
2177
+ method: "eth_call",
2178
+ params: [{ to: token, data }, "latest"],
2179
+ id: 1
2180
+ })
2181
+ });
2182
+ const result = await response.json();
2183
+ return result.result || "0x0";
2184
+ }
2185
+ async executeTransferFrom(from, to, amount, token, rpcUrl) {
2186
+ const { ethers: ethers3 } = await import("ethers");
2187
+ const provider = new ethers3.JsonRpcProvider(rpcUrl);
2188
+ const wallet = new ethers3.Wallet(this.serverPrivateKey, provider);
2189
+ const tokenContract = new ethers3.Contract(token, [
2190
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)"
2191
+ ], wallet);
2192
+ const tx = await tokenContract.transferFrom(from, to, amount);
2193
+ const receipt = await tx.wait();
2194
+ return receipt.hash;
2195
+ }
2196
+ async getTransactionReceipt(txHash, rpcUrl) {
2197
+ const response = await fetch(rpcUrl, {
1119
2198
  method: "POST",
1120
2199
  headers: { "Content-Type": "application/json" },
1121
2200
  body: JSON.stringify({
@@ -1132,6 +2211,8 @@ var TempoFacilitator = class extends BaseFacilitator {
1132
2211
 
1133
2212
  // src/facilitators/registry.ts
1134
2213
  init_cjs_shims();
2214
+ var import_web35 = require("@solana/web3.js");
2215
+ var import_bs582 = __toESM(require("bs58"));
1135
2216
  var FacilitatorRegistry = class {
1136
2217
  factories = /* @__PURE__ */ new Map();
1137
2218
  instances = /* @__PURE__ */ new Map();
@@ -1140,7 +2221,20 @@ var FacilitatorRegistry = class {
1140
2221
  constructor(selection) {
1141
2222
  this.registerFactory("cdp", (config) => new CDPFacilitator(config));
1142
2223
  this.registerFactory("tempo", () => new TempoFacilitator());
1143
- this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
2224
+ this.registerFactory("bnb", (config) => new BNBFacilitator(config?.serverPrivateKey));
2225
+ this.registerFactory("solana", (config) => {
2226
+ let feePayerKeypair;
2227
+ const feePayerKey = config?.feePayerPrivateKey || process.env.SOLANA_FEE_PAYER_KEY;
2228
+ if (feePayerKey) {
2229
+ try {
2230
+ feePayerKeypair = import_web35.Keypair.fromSecretKey(import_bs582.default.decode(feePayerKey));
2231
+ } catch (e) {
2232
+ console.warn(`[SolanaFacilitator] Invalid fee payer key: ${e.message}`);
2233
+ }
2234
+ }
2235
+ return new SolanaFacilitator({ feePayerKeypair });
2236
+ });
2237
+ this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
1144
2238
  }
1145
2239
  /**
1146
2240
  * Register a new facilitator factory
@@ -1387,14 +2481,40 @@ var TOKEN_ADDRESSES = {
1387
2481
  // pathUSD
1388
2482
  USDT: "0x20c0000000000000000000000000000000000001"
1389
2483
  // alphaUSD
2484
+ },
2485
+ // BNB Smart Chain mainnet
2486
+ "eip155:56": {
2487
+ USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
2488
+ USDT: "0x55d398326f99059fF775485246999027B3197955"
2489
+ },
2490
+ // BNB Smart Chain testnet
2491
+ "eip155:97": {
2492
+ USDC: "0x64544969ed7EBf5f083679233325356EbE738930",
2493
+ USDT: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd"
2494
+ },
2495
+ // Solana networks use mint addresses (SPL tokens)
2496
+ "solana:mainnet": {
2497
+ USDC: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
2498
+ // Circle USDC
2499
+ },
2500
+ "solana:devnet": {
2501
+ USDC: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
2502
+ // Devnet USDC
1390
2503
  }
1391
2504
  };
1392
2505
  var CHAIN_TO_NETWORK = {
1393
2506
  "base": "eip155:8453",
1394
2507
  "base_sepolia": "eip155:84532",
1395
2508
  "polygon": "eip155:137",
1396
- "tempo_moderato": "eip155:42431"
2509
+ "tempo_moderato": "eip155:42431",
2510
+ "bnb": "eip155:56",
2511
+ "bnb_testnet": "eip155:97",
2512
+ "solana": "solana:mainnet",
2513
+ "solana_devnet": "solana:devnet"
1397
2514
  };
2515
+ function isSolanaNetwork(network) {
2516
+ return network.startsWith("solana:");
2517
+ }
1398
2518
  var TOKEN_DOMAINS = {
1399
2519
  // Base mainnet
1400
2520
  "eip155:8453": {
@@ -1416,6 +2536,16 @@ var TOKEN_DOMAINS = {
1416
2536
  "eip155:42431": {
1417
2537
  USDC: { name: "pathUSD", version: "1" },
1418
2538
  USDT: { name: "alphaUSD", version: "1" }
2539
+ },
2540
+ // BNB Smart Chain mainnet
2541
+ "eip155:56": {
2542
+ USDC: { name: "USD Coin", version: "1" },
2543
+ USDT: { name: "Tether USD", version: "1" }
2544
+ },
2545
+ // BNB Smart Chain testnet
2546
+ "eip155:97": {
2547
+ USDC: { name: "USD Coin", version: "1" },
2548
+ USDT: { name: "Tether USD", version: "1" }
1419
2549
  }
1420
2550
  };
1421
2551
  function getTokenDomain(network, token) {
@@ -1431,9 +2561,9 @@ function loadEnvFile2() {
1431
2561
  path2.join(process.env.HOME || "", ".moltspay", ".env")
1432
2562
  ];
1433
2563
  for (const envPath of envPaths) {
1434
- if ((0, import_fs3.existsSync)(envPath)) {
2564
+ if ((0, import_fs4.existsSync)(envPath)) {
1435
2565
  try {
1436
- const content = (0, import_fs3.readFileSync)(envPath, "utf-8");
2566
+ const content = (0, import_fs4.readFileSync)(envPath, "utf-8");
1437
2567
  for (const line of content.split("\n")) {
1438
2568
  const trimmed = line.trim();
1439
2569
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -1464,7 +2594,7 @@ var MoltsPayServer = class {
1464
2594
  useMainnet;
1465
2595
  constructor(servicesPath, options = {}) {
1466
2596
  loadEnvFile2();
1467
- const content = (0, import_fs3.readFileSync)(servicesPath, "utf-8");
2597
+ const content = (0, import_fs4.readFileSync)(servicesPath, "utf-8");
1468
2598
  this.manifest = JSON.parse(content);
1469
2599
  this.options = {
1470
2600
  port: options.port || 3e3,
@@ -1473,7 +2603,7 @@ var MoltsPayServer = class {
1473
2603
  };
1474
2604
  this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
1475
2605
  this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
1476
- const defaultFallback = ["tempo"];
2606
+ const defaultFallback = ["tempo", "bnb", "solana"];
1477
2607
  const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
1478
2608
  const facilitatorConfig = options.facilitators || {
1479
2609
  primary: process.env.FACILITATOR_PRIMARY || "cdp",
@@ -1516,12 +2646,20 @@ var MoltsPayServer = class {
1516
2646
  */
1517
2647
  getProviderChains() {
1518
2648
  const provider = this.manifest.provider;
2649
+ const getWalletForChain = (chainName, explicitWallet) => {
2650
+ if (explicitWallet) return explicitWallet;
2651
+ if ((chainName === "solana" || chainName === "solana_devnet") && provider.solana_wallet) {
2652
+ return provider.solana_wallet;
2653
+ }
2654
+ return provider.wallet;
2655
+ };
1519
2656
  if (provider.chains && provider.chains.length > 0) {
1520
2657
  return provider.chains.map((c) => {
1521
2658
  const chainName = typeof c === "string" ? c : c.chain;
2659
+ const explicitWallet = typeof c === "object" ? c.wallet : null;
1522
2660
  return {
1523
2661
  network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
1524
- wallet: (typeof c === "object" ? c.wallet : null) || provider.wallet,
2662
+ wallet: getWalletForChain(chainName, explicitWallet || void 0),
1525
2663
  tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
1526
2664
  };
1527
2665
  });
@@ -1530,7 +2668,7 @@ var MoltsPayServer = class {
1530
2668
  const network = CHAIN_TO_NETWORK[chain] || this.networkId;
1531
2669
  return [{
1532
2670
  network,
1533
- wallet: provider.wallet,
2671
+ wallet: getWalletForChain(chain),
1534
2672
  tokens: ["USDC"]
1535
2673
  }];
1536
2674
  }
@@ -1601,7 +2739,8 @@ var MoltsPayServer = class {
1601
2739
  }
1602
2740
  const body = await this.readBody(req);
1603
2741
  const paymentHeader = req.headers[PAYMENT_HEADER2];
1604
- return await this.handleProxy(body, paymentHeader, res);
2742
+ const authHeader = req.headers[MPP_AUTH_HEADER];
2743
+ return await this.handleProxy(body, paymentHeader, authHeader, res);
1605
2744
  }
1606
2745
  const servicePath = url.pathname.replace(/^\//, "");
1607
2746
  const skill = this.skills.get(servicePath);
@@ -1638,7 +2777,9 @@ var MoltsPayServer = class {
1638
2777
  name: this.manifest.provider.name,
1639
2778
  description: this.manifest.provider.description,
1640
2779
  wallet: this.manifest.provider.wallet,
1641
- chain: this.manifest.provider.chain || "base"
2780
+ chain: this.manifest.provider.chain || "base",
2781
+ solana_wallet: this.manifest.provider.solana_wallet,
2782
+ chains: this.manifest.provider.chains
1642
2783
  },
1643
2784
  services,
1644
2785
  endpoints: {
@@ -1751,6 +2892,21 @@ var MoltsPayServer = class {
1751
2892
  });
1752
2893
  }
1753
2894
  console.log(`[MoltsPay] Verified by ${verifyResult.facilitator}`);
2895
+ const isSolana = isSolanaNetwork(paymentNetwork);
2896
+ let settlement = null;
2897
+ if (isSolana) {
2898
+ console.log(`[MoltsPay] Solana detected - settling payment FIRST (blockhash expiry protection)`);
2899
+ try {
2900
+ settlement = await this.registry.settle(payment, requirements);
2901
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2902
+ } catch (err) {
2903
+ console.error("[MoltsPay] Solana settlement failed:", err.message);
2904
+ return this.sendJson(res, 402, {
2905
+ error: "Payment settlement failed",
2906
+ message: err.message
2907
+ });
2908
+ }
2909
+ }
1754
2910
  const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
1755
2911
  console.log(`[MoltsPay] Executing skill: ${service} (timeout: ${timeoutSeconds}s)`);
1756
2912
  let result;
@@ -1765,16 +2921,19 @@ var MoltsPayServer = class {
1765
2921
  console.error("[MoltsPay] Skill execution failed:", err.message);
1766
2922
  return this.sendJson(res, 500, {
1767
2923
  error: "Service execution failed",
1768
- message: err.message
2924
+ message: err.message,
2925
+ paymentSettled: isSolana ? true : false,
2926
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
1769
2927
  });
1770
2928
  }
1771
- console.log(`[MoltsPay] Skill succeeded, settling payment...`);
1772
- let settlement = null;
1773
- try {
1774
- settlement = await this.registry.settle(payment, requirements);
1775
- console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1776
- } catch (err) {
1777
- console.error("[MoltsPay] Settlement failed:", err.message);
2929
+ if (!isSolana) {
2930
+ console.log(`[MoltsPay] Skill succeeded, settling payment...`);
2931
+ try {
2932
+ settlement = await this.registry.settle(payment, requirements);
2933
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2934
+ } catch (err) {
2935
+ console.error("[MoltsPay] Settlement failed:", err.message);
2936
+ }
1778
2937
  }
1779
2938
  const responseHeaders = {};
1780
2939
  if (settlement?.success) {
@@ -2050,7 +3209,7 @@ var MoltsPayServer = class {
2050
3209
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
2051
3210
  const tokenAddress = tokenAddresses[selectedToken];
2052
3211
  const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
2053
- return {
3212
+ const requirements = {
2054
3213
  scheme: "exact",
2055
3214
  network: selectedNetwork,
2056
3215
  asset: tokenAddress,
@@ -2059,6 +3218,27 @@ var MoltsPayServer = class {
2059
3218
  maxTimeoutSeconds: 300,
2060
3219
  extra: tokenDomain
2061
3220
  };
3221
+ if (selectedNetwork === "solana:mainnet" || selectedNetwork === "solana:devnet") {
3222
+ const solanaFacilitator = this.registry.get("solana");
3223
+ const feePayerPubkey = solanaFacilitator?.getFeePayerPubkey?.();
3224
+ if (feePayerPubkey) {
3225
+ requirements.extra = {
3226
+ ...requirements.extra || {},
3227
+ solanaFeePayer: feePayerPubkey
3228
+ };
3229
+ }
3230
+ }
3231
+ if (selectedNetwork === "eip155:56" || selectedNetwork === "eip155:97") {
3232
+ const bnbFacilitator = this.registry.get("bnb");
3233
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
3234
+ if (spenderAddress) {
3235
+ requirements.extra = {
3236
+ ...requirements.extra || {},
3237
+ bnbSpender: spenderAddress
3238
+ };
3239
+ }
3240
+ }
3241
+ return requirements;
2062
3242
  }
2063
3243
  /**
2064
3244
  * Detect which token is being used in the payment
@@ -2111,8 +3291,10 @@ var MoltsPayServer = class {
2111
3291
  isProxyAllowed(clientIP) {
2112
3292
  const allowedIPs = process.env.PROXY_ALLOWED_IPS?.split(",").map((ip) => ip.trim()) || [];
2113
3293
  if (allowedIPs.length === 0) {
2114
- console.log(`[MoltsPay] /proxy denied: no PROXY_ALLOWED_IPS configured`);
2115
- return false;
3294
+ return true;
3295
+ }
3296
+ if (allowedIPs.includes("*")) {
3297
+ return true;
2116
3298
  }
2117
3299
  const normalizedIP = clientIP === "::1" ? "127.0.0.1" : clientIP.replace("::ffff:", "");
2118
3300
  const allowed = allowedIPs.includes(normalizedIP) || allowedIPs.includes(clientIP);
@@ -2124,31 +3306,42 @@ var MoltsPayServer = class {
2124
3306
  /**
2125
3307
  * POST /proxy - Handle payment for external services (moltspay-creators)
2126
3308
  *
2127
- * This endpoint allows other services to delegate x402 payment handling.
3309
+ * This endpoint allows other services to delegate x402/MPP payment handling.
2128
3310
  * It does NOT execute any skill - just handles payment verification/settlement.
2129
3311
  *
2130
3312
  * Request body:
2131
3313
  * { wallet, amount, currency, chain, memo, serviceId, description }
2132
3314
  *
2133
- * Without X-Payment header: returns 402 with payment requirements
2134
- * With X-Payment header: verifies payment and returns result
3315
+ * For x402 (base, polygon, base_sepolia):
3316
+ * Without X-Payment header: returns 402 with X-Payment-Required
3317
+ * With X-Payment header: verifies payment via CDP
3318
+ *
3319
+ * For MPP (tempo_moderato):
3320
+ * Without Authorization header: returns 402 with WWW-Authenticate
3321
+ * With Authorization: Payment header: verifies tx on Tempo chain
2135
3322
  */
2136
- async handleProxy(body, paymentHeader, res) {
3323
+ async handleProxy(body, paymentHeader, authHeader, res) {
2137
3324
  const { wallet, amount, currency, chain, memo, serviceId, description } = body;
2138
3325
  if (!wallet || !amount) {
2139
3326
  return this.sendJson(res, 400, { error: "Missing required fields: wallet, amount" });
2140
3327
  }
2141
- if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
2142
- return this.sendJson(res, 400, { error: "Invalid wallet address format" });
3328
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet", "solana", "solana_devnet"];
3329
+ if (chain && !supportedChains.includes(chain)) {
3330
+ return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
3331
+ }
3332
+ const isSolanaChain = chain === "solana" || chain === "solana_devnet";
3333
+ const isValidEvmAddress = /^0x[a-fA-F0-9]{40}$/.test(wallet);
3334
+ const isValidSolanaAddress2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(wallet);
3335
+ if (isSolanaChain && !isValidSolanaAddress2) {
3336
+ return this.sendJson(res, 400, { error: "Invalid Solana wallet address format" });
3337
+ }
3338
+ if (!isSolanaChain && !isValidEvmAddress) {
3339
+ return this.sendJson(res, 400, { error: "Invalid EVM wallet address format" });
2143
3340
  }
2144
3341
  const amountNum = parseFloat(amount);
2145
3342
  if (isNaN(amountNum) || amountNum <= 0) {
2146
3343
  return this.sendJson(res, 400, { error: "Invalid amount" });
2147
3344
  }
2148
- const supportedChains = ["base", "polygon", "base_sepolia"];
2149
- if (chain && !supportedChains.includes(chain)) {
2150
- return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
2151
- }
2152
3345
  const proxyConfig = {
2153
3346
  id: serviceId || "proxy",
2154
3347
  name: description || "Proxy Payment",
@@ -2160,6 +3353,9 @@ var MoltsPayServer = class {
2160
3353
  input: {},
2161
3354
  output: {}
2162
3355
  };
3356
+ if (chain === "tempo_moderato") {
3357
+ return await this.handleProxyMPP(body, proxyConfig, authHeader, res);
3358
+ }
2163
3359
  const requirements = this.buildProxyPaymentRequirements(proxyConfig, wallet, currency, chain);
2164
3360
  if (!paymentHeader) {
2165
3361
  return this.sendProxyPaymentRequired(proxyConfig, wallet, memo, chain, res);
@@ -2171,37 +3367,225 @@ var MoltsPayServer = class {
2171
3367
  } catch {
2172
3368
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
2173
3369
  }
2174
- if (payment.x402Version !== X402_VERSION3) {
2175
- return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
3370
+ if (payment.x402Version !== X402_VERSION3) {
3371
+ return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
3372
+ }
3373
+ const scheme = payment.accepted?.scheme || payment.scheme;
3374
+ const network = payment.accepted?.network || payment.network;
3375
+ if (scheme !== "exact") {
3376
+ return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
3377
+ }
3378
+ const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
3379
+ if (network !== expectedNetwork) {
3380
+ return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
3381
+ }
3382
+ console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
3383
+ const verifyResult = await this.registry.verify(payment, requirements);
3384
+ if (!verifyResult.valid) {
3385
+ return this.sendJson(res, 402, {
3386
+ success: false,
3387
+ error: `Payment verification failed: ${verifyResult.error}`,
3388
+ facilitator: verifyResult.facilitator
3389
+ });
3390
+ }
3391
+ console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
3392
+ const { execute, service, params } = body;
3393
+ if (execute && service) {
3394
+ const skill = this.skills.get(service);
3395
+ if (!skill) {
3396
+ console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
3397
+ return this.sendJson(res, 404, {
3398
+ success: false,
3399
+ paymentSettled: false,
3400
+ error: `Service not found: ${service}`
3401
+ });
3402
+ }
3403
+ const isSolana = isSolanaNetwork(network);
3404
+ let settlement2 = null;
3405
+ if (isSolana) {
3406
+ console.log(`[MoltsPay] /proxy: Solana detected - settling payment FIRST`);
3407
+ try {
3408
+ settlement2 = await this.registry.settle(payment, requirements);
3409
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
3410
+ if (!settlement2.success) {
3411
+ console.error(`[MoltsPay] /proxy: Solana settlement failed: ${settlement2.error}`);
3412
+ return this.sendJson(res, 402, {
3413
+ success: false,
3414
+ paymentSettled: false,
3415
+ error: `Payment settlement failed: ${settlement2.error || "Unknown error"}`
3416
+ });
3417
+ }
3418
+ } catch (err) {
3419
+ console.error("[MoltsPay] /proxy: Solana settlement failed:", err.message);
3420
+ return this.sendJson(res, 402, {
3421
+ success: false,
3422
+ paymentSettled: false,
3423
+ error: `Payment settlement failed: ${err.message}`
3424
+ });
3425
+ }
3426
+ } else {
3427
+ console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
3428
+ }
3429
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
3430
+ let result;
3431
+ try {
3432
+ result = await Promise.race([
3433
+ skill.handler(params || {}),
3434
+ new Promise(
3435
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
3436
+ )
3437
+ ]);
3438
+ console.log(`[MoltsPay] /proxy: Skill succeeded`);
3439
+ } catch (err) {
3440
+ console.error(`[MoltsPay] /proxy: Skill failed: ${err.message}`);
3441
+ return this.sendJson(res, 500, {
3442
+ success: false,
3443
+ paymentSettled: isSolana ? true : false,
3444
+ error: `Service execution failed: ${err.message}`,
3445
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
3446
+ });
3447
+ }
3448
+ if (!isSolana) {
3449
+ console.log(`[MoltsPay] /proxy: Settling payment...`);
3450
+ try {
3451
+ settlement2 = await this.registry.settle(payment, requirements);
3452
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
3453
+ } catch (err) {
3454
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
3455
+ return this.sendJson(res, 200, {
3456
+ success: true,
3457
+ verified: true,
3458
+ settled: false,
3459
+ settlementError: err.message,
3460
+ from: payment.payload?.authorization?.from,
3461
+ paidTo: wallet,
3462
+ amount: amountNum,
3463
+ currency: currency || "USDC",
3464
+ memo,
3465
+ result
3466
+ });
3467
+ }
3468
+ }
3469
+ return this.sendJson(res, 200, {
3470
+ success: true,
3471
+ verified: true,
3472
+ settled: settlement2?.success || false,
3473
+ txHash: settlement2?.transaction,
3474
+ from: payment.payload?.authorization?.from,
3475
+ paidTo: wallet,
3476
+ amount: amountNum,
3477
+ currency: currency || "USDC",
3478
+ facilitator: settlement2?.facilitator,
3479
+ memo,
3480
+ result
3481
+ });
3482
+ }
3483
+ console.log(`[MoltsPay] /proxy: Settling payment (no execution)...`);
3484
+ let settlement = null;
3485
+ try {
3486
+ settlement = await this.registry.settle(payment, requirements);
3487
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
3488
+ } catch (err) {
3489
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
3490
+ return this.sendJson(res, 500, {
3491
+ success: false,
3492
+ error: `Settlement failed: ${err.message}`
3493
+ });
3494
+ }
3495
+ this.sendJson(res, 200, {
3496
+ success: true,
3497
+ verified: true,
3498
+ settled: settlement?.success || false,
3499
+ txHash: settlement?.transaction,
3500
+ from: payment.payload?.authorization?.from,
3501
+ // Buyer's wallet address
3502
+ paidTo: wallet,
3503
+ amount: amountNum,
3504
+ currency: currency || "USDC",
3505
+ facilitator: settlement?.facilitator,
3506
+ memo
3507
+ });
3508
+ }
3509
+ /**
3510
+ * Handle MPP payment flow for /proxy endpoint (tempo_moderato chain)
3511
+ */
3512
+ async handleProxyMPP(body, config, authHeader, res) {
3513
+ const { wallet, amount, memo, serviceId } = body;
3514
+ const amountNum = parseFloat(amount);
3515
+ const amountInUnits = Math.floor(amountNum * 1e6).toString();
3516
+ if (!authHeader || !authHeader.toLowerCase().startsWith("payment ")) {
3517
+ const challengeId = this.generateChallengeId();
3518
+ const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
3519
+ const mppRequest = {
3520
+ amount: amountInUnits,
3521
+ currency: tokenAddress,
3522
+ methodDetails: {
3523
+ chainId: 42431,
3524
+ feePayer: true
3525
+ },
3526
+ recipient: wallet
3527
+ };
3528
+ const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
3529
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
3530
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
3531
+ res.writeHead(402, {
3532
+ "Content-Type": "application/problem+json",
3533
+ [MPP_WWW_AUTH_HEADER]: wwwAuth
3534
+ });
3535
+ res.end(JSON.stringify({
3536
+ type: "https://paymentauth.org/problems/payment-required",
3537
+ title: "Payment Required",
3538
+ status: 402,
3539
+ detail: `Payment is required (${config.name}).`,
3540
+ service: serviceId || "proxy",
3541
+ price: amountNum,
3542
+ currency: "USDC"
3543
+ }, null, 2));
3544
+ return;
3545
+ }
3546
+ const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
3547
+ if (!credentialMatch) {
3548
+ return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
2176
3549
  }
2177
- const scheme = payment.accepted?.scheme || payment.scheme;
2178
- const network = payment.accepted?.network || payment.network;
2179
- if (scheme !== "exact") {
2180
- return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
3550
+ let mppCredential;
3551
+ try {
3552
+ const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
3553
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
3554
+ mppCredential = JSON.parse(decoded);
3555
+ } catch (err) {
3556
+ console.error("[MoltsPay] /proxy MPP: Failed to parse credential:", err);
3557
+ return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
2181
3558
  }
2182
- const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
2183
- if (network !== expectedNetwork) {
2184
- return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
3559
+ let txHash;
3560
+ if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
3561
+ txHash = mppCredential.payload.hash;
3562
+ } else {
3563
+ return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
2185
3564
  }
2186
- console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
2187
- const verifyResult = await this.registry.verify(payment, requirements);
2188
- if (!verifyResult.valid) {
3565
+ console.log(`[MoltsPay] /proxy MPP: Verifying tx ${txHash} on Tempo...`);
3566
+ const requirements = this.buildPaymentRequirements(config, "eip155:42431", wallet, "USDC");
3567
+ const paymentPayload = {
3568
+ x402Version: X402_VERSION3,
3569
+ scheme: "exact",
3570
+ network: "eip155:42431",
3571
+ payload: { txHash, chainId: 42431 }
3572
+ };
3573
+ const verification = await this.registry.verify(paymentPayload, requirements);
3574
+ if (!verification.valid) {
2189
3575
  return this.sendJson(res, 402, {
2190
- success: false,
2191
- error: `Payment verification failed: ${verifyResult.error}`,
2192
- facilitator: verifyResult.facilitator
3576
+ error: `Payment verification failed: ${verification.error}`
2193
3577
  });
2194
3578
  }
2195
- console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
3579
+ console.log(`[MoltsPay] /proxy MPP: Payment verified by ${verification.facilitator}`);
2196
3580
  const { execute, service, params } = body;
2197
3581
  if (execute && service) {
2198
- console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
3582
+ console.log(`[MoltsPay] /proxy MPP: Executing skill: ${service}`);
2199
3583
  const skill = this.skills.get(service);
2200
3584
  if (!skill) {
2201
- console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
2202
3585
  return this.sendJson(res, 404, {
2203
3586
  success: false,
2204
- paymentSettled: false,
3587
+ paymentSettled: true,
3588
+ // Payment already happened on Tempo
2205
3589
  error: `Service not found: ${service}`
2206
3590
  });
2207
3591
  }
@@ -2214,73 +3598,36 @@ var MoltsPayServer = class {
2214
3598
  (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
2215
3599
  )
2216
3600
  ]);
2217
- console.log(`[MoltsPay] /proxy: Skill succeeded, now settling payment...`);
2218
3601
  } catch (err) {
2219
- console.error(`[MoltsPay] /proxy: Skill failed: ${err.message} - NOT settling`);
3602
+ console.error(`[MoltsPay] /proxy MPP: Skill failed: ${err.message}`);
2220
3603
  return this.sendJson(res, 500, {
2221
3604
  success: false,
2222
- paymentSettled: false,
3605
+ paymentSettled: true,
2223
3606
  error: `Service execution failed: ${err.message}`
2224
3607
  });
2225
3608
  }
2226
- let settlement2 = null;
2227
- try {
2228
- settlement2 = await this.registry.settle(payment, requirements);
2229
- console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
2230
- } catch (err) {
2231
- console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
2232
- return this.sendJson(res, 200, {
2233
- success: true,
2234
- verified: true,
2235
- settled: false,
2236
- settlementError: err.message,
2237
- from: payment.payload?.authorization?.from,
2238
- // Buyer's wallet address
2239
- paidTo: wallet,
2240
- amount: amountNum,
2241
- currency: currency || "USDC",
2242
- memo,
2243
- result
2244
- });
2245
- }
2246
3609
  return this.sendJson(res, 200, {
2247
3610
  success: true,
2248
3611
  verified: true,
2249
- settled: settlement2?.success || false,
2250
- txHash: settlement2?.transaction,
2251
- from: payment.payload?.authorization?.from,
2252
- // Buyer's wallet address
3612
+ txHash,
3613
+ chain: "tempo_moderato",
2253
3614
  paidTo: wallet,
2254
3615
  amount: amountNum,
2255
- currency: currency || "USDC",
2256
- facilitator: settlement2?.facilitator,
3616
+ currency: "USDC",
3617
+ facilitator: verification.facilitator,
2257
3618
  memo,
2258
3619
  result
2259
3620
  });
2260
3621
  }
2261
- console.log(`[MoltsPay] /proxy: Settling payment (no execution)...`);
2262
- let settlement = null;
2263
- try {
2264
- settlement = await this.registry.settle(payment, requirements);
2265
- console.log(`[MoltsPay] /proxy: Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2266
- } catch (err) {
2267
- console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
2268
- return this.sendJson(res, 500, {
2269
- success: false,
2270
- error: `Settlement failed: ${err.message}`
2271
- });
2272
- }
2273
3622
  this.sendJson(res, 200, {
2274
3623
  success: true,
2275
3624
  verified: true,
2276
- settled: settlement?.success || false,
2277
- txHash: settlement?.transaction,
2278
- from: payment.payload?.authorization?.from,
2279
- // Buyer's wallet address
3625
+ txHash,
3626
+ chain: "tempo_moderato",
2280
3627
  paidTo: wallet,
2281
3628
  amount: amountNum,
2282
- currency: currency || "USDC",
2283
- facilitator: settlement?.facilitator,
3629
+ currency: "USDC",
3630
+ facilitator: verification.facilitator,
2284
3631
  memo
2285
3632
  });
2286
3633
  }
@@ -2295,7 +3642,7 @@ var MoltsPayServer = class {
2295
3642
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
2296
3643
  const tokenAddress = tokenAddresses[selectedToken];
2297
3644
  const tokenDomain = getTokenDomain(networkId, selectedToken);
2298
- return {
3645
+ const requirements = {
2299
3646
  scheme: "exact",
2300
3647
  network: networkId,
2301
3648
  asset: tokenAddress,
@@ -2305,6 +3652,17 @@ var MoltsPayServer = class {
2305
3652
  maxTimeoutSeconds: 300,
2306
3653
  extra: tokenDomain
2307
3654
  };
3655
+ if (networkId === "eip155:56" || networkId === "eip155:97") {
3656
+ const bnbFacilitator = this.registry.get("bnb");
3657
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
3658
+ if (spenderAddress) {
3659
+ requirements.extra = {
3660
+ ...requirements.extra || {},
3661
+ bnbSpender: spenderAddress
3662
+ };
3663
+ }
3664
+ }
3665
+ return requirements;
2308
3666
  }
2309
3667
  /**
2310
3668
  * Return 402 with x402 payment requirements for proxy endpoint
@@ -2354,14 +3712,14 @@ if (!globalThis.crypto) {
2354
3712
  }
2355
3713
  function getVersion() {
2356
3714
  const locations = [
2357
- (0, import_path2.join)(__dirname, "../../package.json"),
2358
- (0, import_path2.join)(__dirname, "../package.json"),
2359
- (0, import_path2.join)(process.cwd(), "node_modules/moltspay/package.json")
3715
+ (0, import_path3.join)(__dirname, "../../package.json"),
3716
+ (0, import_path3.join)(__dirname, "../package.json"),
3717
+ (0, import_path3.join)(process.cwd(), "node_modules/moltspay/package.json")
2360
3718
  ];
2361
3719
  for (const loc of locations) {
2362
3720
  try {
2363
- if ((0, import_fs4.existsSync)(loc)) {
2364
- const pkg = JSON.parse((0, import_fs4.readFileSync)(loc, "utf-8"));
3721
+ if ((0, import_fs5.existsSync)(loc)) {
3722
+ const pkg = JSON.parse((0, import_fs5.readFileSync)(loc, "utf-8"));
2365
3723
  if (pkg.name === "moltspay") return pkg.version;
2366
3724
  }
2367
3725
  } catch {
@@ -2369,11 +3727,94 @@ function getVersion() {
2369
3727
  }
2370
3728
  return "0.0.0";
2371
3729
  }
3730
+ var BNB_SPONSOR_KEY = process.env.MOLTSPAY_BNB_SPONSOR_KEY;
3731
+ var BNB_SPENDER_ADDRESS = process.env.MOLTSPAY_BNB_SPENDER || "0xEBB45208D806A0c73F9673E0c5713FF720DD6b79";
3732
+ var ERC20_APPROVE_ABI = [
3733
+ "function approve(address spender, uint256 amount) returns (bool)",
3734
+ "function allowance(address owner, address spender) view returns (uint256)"
3735
+ ];
3736
+ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = false) {
3737
+ const chainConfig = CHAINS[chain];
3738
+ const provider = new import_ethers2.ethers.JsonRpcProvider(chainConfig.rpc);
3739
+ const wallet = client.getWallet();
3740
+ if (!wallet) {
3741
+ console.log(" \u274C No wallet found");
3742
+ return;
3743
+ }
3744
+ const signer = wallet.connect(provider);
3745
+ console.log(` Spender: ${spenderAddress}`);
3746
+ let bnbBalance = await provider.getBalance(wallet.address);
3747
+ const minGasRequired = import_ethers2.ethers.parseEther("0.0005");
3748
+ if (bnbBalance < minGasRequired) {
3749
+ if (sponsorGas && BNB_SPONSOR_KEY) {
3750
+ console.log(" \u23F3 Sponsoring BNB gas for approvals...");
3751
+ try {
3752
+ const sponsorWallet = new import_ethers2.ethers.Wallet(BNB_SPONSOR_KEY, provider);
3753
+ const tx = await sponsorWallet.sendTransaction({
3754
+ to: wallet.address,
3755
+ value: import_ethers2.ethers.parseEther("0.001")
3756
+ });
3757
+ await tx.wait();
3758
+ console.log(` \u2705 Sponsored 0.001 BNB (tx: ${tx.hash.slice(0, 10)}...)`);
3759
+ bnbBalance = await provider.getBalance(wallet.address);
3760
+ } catch (err) {
3761
+ console.log(` \u26A0\uFE0F Gas sponsorship failed: ${err.message}`);
3762
+ console.log(` \u{1F4A1} Get testnet BNB: https://testnet.bnbchain.org/faucet-smart`);
3763
+ return;
3764
+ }
3765
+ } else {
3766
+ console.log(` \u26A0\uFE0F Need BNB for gas (~0.0005 BNB)`);
3767
+ console.log(` \u{1F4A1} Run: npx moltspay faucet --chain bnb_testnet`);
3768
+ console.log(` Then run: npx moltspay approve --chain ${chain} --spender ${spenderAddress}`);
3769
+ return;
3770
+ }
3771
+ }
3772
+ for (const tokenSymbol of ["USDT", "USDC"]) {
3773
+ const tokenConfig = chainConfig.tokens[tokenSymbol];
3774
+ const tokenContract = new import_ethers2.ethers.Contract(tokenConfig.address, ERC20_APPROVE_ABI, signer);
3775
+ const allowance = await tokenContract.allowance(wallet.address, spenderAddress);
3776
+ if (allowance > 0n) {
3777
+ console.log(` \u2705 ${tokenSymbol}: already approved for ${spenderAddress.slice(0, 10)}...`);
3778
+ continue;
3779
+ }
3780
+ console.log(` \u23F3 Approving ${tokenSymbol}...`);
3781
+ try {
3782
+ const tx = await tokenContract.approve(spenderAddress, import_ethers2.ethers.MaxUint256);
3783
+ await tx.wait();
3784
+ console.log(` \u2705 ${tokenSymbol}: approved (tx: ${tx.hash.slice(0, 10)}...)`);
3785
+ } catch (err) {
3786
+ console.log(` \u274C ${tokenSymbol}: approval failed - ${err.message}`);
3787
+ }
3788
+ }
3789
+ console.log("");
3790
+ }
3791
+ async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2) {
3792
+ const chainConfig = CHAINS[chain];
3793
+ const provider = new import_ethers2.ethers.JsonRpcProvider(chainConfig.rpc);
3794
+ let spenderAddress = null;
3795
+ try {
3796
+ const walletPath = (0, import_path3.join)(configDir, "wallet.json");
3797
+ const walletData = JSON.parse((0, import_fs5.readFileSync)(walletPath, "utf-8"));
3798
+ spenderAddress = walletData.approvals?.[chain] || null;
3799
+ } catch {
3800
+ }
3801
+ const result = { usdt: false, usdc: false, spender: spenderAddress };
3802
+ if (!spenderAddress) {
3803
+ return result;
3804
+ }
3805
+ for (const tokenSymbol of ["USDT", "USDC"]) {
3806
+ const tokenConfig = chainConfig.tokens[tokenSymbol];
3807
+ const tokenContract = new import_ethers2.ethers.Contract(tokenConfig.address, ERC20_APPROVE_ABI, provider);
3808
+ const allowance = await tokenContract.allowance(address, spenderAddress);
3809
+ result[tokenSymbol.toLowerCase()] = allowance > 0n;
3810
+ }
3811
+ return result;
3812
+ }
2372
3813
  var program = new import_commander.Command();
2373
- var DEFAULT_CONFIG_DIR = (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
2374
- var PID_FILE = (0, import_path2.join)(DEFAULT_CONFIG_DIR, "server.pid");
2375
- if (!(0, import_fs4.existsSync)(DEFAULT_CONFIG_DIR)) {
2376
- (0, import_fs4.mkdirSync)(DEFAULT_CONFIG_DIR, { recursive: true });
3814
+ var DEFAULT_CONFIG_DIR2 = (0, import_path3.join)((0, import_os3.homedir)(), ".moltspay");
3815
+ var PID_FILE = (0, import_path3.join)(DEFAULT_CONFIG_DIR2, "server.pid");
3816
+ if (!(0, import_fs5.existsSync)(DEFAULT_CONFIG_DIR2)) {
3817
+ (0, import_fs5.mkdirSync)(DEFAULT_CONFIG_DIR2, { recursive: true });
2377
3818
  }
2378
3819
  function prompt(question) {
2379
3820
  const rl = readline.createInterface({
@@ -2388,19 +3829,49 @@ function prompt(question) {
2388
3829
  });
2389
3830
  }
2390
3831
  program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version(getVersion());
2391
- program.command("init").description("Initialize MoltsPay client (create wallet, set limits)").option("--chain <chain>", "Blockchain to use", "base").option("--max-per-tx <amount>", "Max amount per transaction").option("--max-per-day <amount>", "Max amount per day").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
2392
- console.log("\n\u{1F510} MoltsPay Client Setup\n");
2393
- if ((0, import_fs4.existsSync)((0, import_path2.join)(options.configDir, "wallet.json"))) {
2394
- console.log('\u26A0\uFE0F Already initialized. Use "moltspay config" to update settings.');
2395
- console.log(` Config dir: ${options.configDir}`);
2396
- return;
2397
- }
3832
+ program.command("init").description("Initialize MoltsPay client (create wallet, set limits)").option("--chain <chain>", "Blockchain to use", "base").option("--max-per-tx <amount>", "Max amount per transaction").option("--max-per-day <amount>", "Max amount per day").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR2).action(async (options) => {
2398
3833
  let chain = options.chain;
2399
- const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
3834
+ const supportedEVMChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet"];
3835
+ const supportedSolanaChains = ["solana", "solana_devnet"];
3836
+ const supportedChains = [...supportedEVMChains, ...supportedSolanaChains];
2400
3837
  if (!supportedChains.includes(chain)) {
2401
3838
  console.error(`\u274C Unknown chain: ${chain}. Supported: ${supportedChains.join(", ")}`);
2402
3839
  process.exit(1);
2403
3840
  }
3841
+ if (supportedSolanaChains.includes(chain)) {
3842
+ console.log("\n\u{1F7E3} Solana Wallet Setup\n");
3843
+ if (solanaWalletExists(options.configDir)) {
3844
+ const existingAddress = getSolanaAddress(options.configDir);
3845
+ console.log(`\u26A0\uFE0F Solana wallet already exists: ${existingAddress}`);
3846
+ console.log(` Config dir: ${options.configDir}`);
3847
+ return;
3848
+ }
3849
+ console.log("Creating Solana wallet...");
3850
+ const keypair = createSolanaWallet(options.configDir);
3851
+ const address = keypair.publicKey.toBase58();
3852
+ console.log(`
3853
+ \u2705 Solana wallet created: ${address}`);
3854
+ console.log(`
3855
+ \u{1F4C1} Config saved to: ${(0, import_path3.join)(options.configDir, "wallet-solana.json")}`);
3856
+ console.log(`
3857
+ \u26A0\uFE0F IMPORTANT: Back up your wallet file!`);
3858
+ console.log(` This file contains your private key!
3859
+ `);
3860
+ if (chain === "solana_devnet") {
3861
+ console.log("\u{1F4A1} Get testnet tokens:");
3862
+ console.log(" npx moltspay faucet --chain solana_devnet\n");
3863
+ } else {
3864
+ console.log(`\u{1F4B0} Fund your wallet with USDC on Solana to start (gasless - no SOL needed).
3865
+ `);
3866
+ }
3867
+ return;
3868
+ }
3869
+ console.log("\n\u{1F510} MoltsPay Client Setup\n");
3870
+ if ((0, import_fs5.existsSync)((0, import_path3.join)(options.configDir, "wallet.json"))) {
3871
+ console.log('\u26A0\uFE0F EVM wallet already initialized. Use "moltspay config" to update settings.');
3872
+ console.log(` Config dir: ${options.configDir}`);
3873
+ return;
3874
+ }
2404
3875
  let maxPerTx = options.maxPerTx ? parseFloat(options.maxPerTx) : null;
2405
3876
  let maxPerDay = options.maxPerDay ? parseFloat(options.maxPerDay) : null;
2406
3877
  if (!maxPerTx) {
@@ -2422,13 +3893,21 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
2422
3893
  console.log(`
2423
3894
  \u{1F4C1} Config saved to: ${result.configDir}`);
2424
3895
  console.log(`
2425
- \u26A0\uFE0F IMPORTANT: Back up ${(0, import_path2.join)(result.configDir, "wallet.json")}`);
3896
+ \u26A0\uFE0F IMPORTANT: Back up ${(0, import_path3.join)(result.configDir, "wallet.json")}`);
2426
3897
  console.log(` This file contains your private key!
2427
3898
  `);
3899
+ if (chain === "bnb" || chain === "bnb_testnet") {
3900
+ console.log("\u{1F4CB} Setting up BNB chain approvals...\n");
3901
+ console.log(" \u2139\uFE0F Using default spender. For other services, run:");
3902
+ console.log(` npx moltspay approve --chain ${chain} --spender <address>
3903
+ `);
3904
+ const client = new MoltsPayClient({ configDir: options.configDir });
3905
+ await setupBNBApprovals(client, chain, BNB_SPENDER_ADDRESS, true);
3906
+ }
2428
3907
  console.log(`\u{1F4B0} Fund your wallet with USDC on ${chain} to start using services.
2429
3908
  `);
2430
3909
  });
2431
- program.command("config").description("Update MoltsPay settings").option("--max-per-tx <amount>", "Max amount per transaction").option("--max-per-day <amount>", "Max amount per day").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
3910
+ program.command("config").description("Update MoltsPay settings").option("--max-per-tx <amount>", "Max amount per transaction").option("--max-per-day <amount>", "Max amount per day").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR2).action(async (options) => {
2432
3911
  const client = new MoltsPayClient({ configDir: options.configDir });
2433
3912
  if (!client.isInitialized) {
2434
3913
  console.log("\u274C Not initialized. Run: npx moltspay init");
@@ -2463,25 +3942,40 @@ program.command("config").description("Update MoltsPay settings").option("--max-
2463
3942
  }
2464
3943
  }
2465
3944
  });
2466
- program.command("fund <amount>").description("Fund wallet with USDC via Coinbase (US debit card / Apple Pay)").option("--chain <chain>", "Chain to fund (base, polygon, or base_sepolia)", "base").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (amountStr, options) => {
3945
+ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase (US debit card / Apple Pay)").option("--chain <chain>", "Chain to fund (base, polygon, solana, base_sepolia, bnb, or bnb_testnet)", "base").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR2).action(async (amountStr, options) => {
2467
3946
  const client = new MoltsPayClient({ configDir: options.configDir });
2468
- if (!client.isInitialized) {
2469
- console.log("\u274C Not initialized. Run: npx moltspay init");
2470
- return;
2471
- }
2472
3947
  const amount = parseFloat(amountStr);
2473
3948
  if (isNaN(amount) || amount < 5) {
2474
3949
  console.log("\u274C Minimum $5.");
2475
3950
  return;
2476
3951
  }
2477
3952
  const chain = options.chain?.toLowerCase() || "base";
2478
- if (!["base", "polygon", "base_sepolia"].includes(chain)) {
2479
- console.log("\u274C Invalid chain. Use: base, polygon, or base_sepolia");
3953
+ if (!["base", "polygon", "base_sepolia", "solana", "bnb", "bnb_testnet"].includes(chain)) {
3954
+ console.log("\u274C Invalid chain. Use: base, polygon, solana, base_sepolia, bnb, or bnb_testnet");
2480
3955
  return;
2481
3956
  }
3957
+ let walletAddress;
3958
+ if (chain === "solana") {
3959
+ const solanaWallet = loadSolanaWallet(options.configDir || DEFAULT_CONFIG_DIR2);
3960
+ if (!solanaWallet) {
3961
+ console.log("\u274C No Solana wallet found. Run: npx moltspay init --chain solana");
3962
+ return;
3963
+ }
3964
+ walletAddress = getSolanaAddress(options.configDir || DEFAULT_CONFIG_DIR2) || "";
3965
+ if (!walletAddress) {
3966
+ console.log("\u274C Could not get Solana wallet address.");
3967
+ return;
3968
+ }
3969
+ } else {
3970
+ if (!client.isInitialized) {
3971
+ console.log("\u274C Not initialized. Run: npx moltspay init");
3972
+ return;
3973
+ }
3974
+ walletAddress = client.address;
3975
+ }
2482
3976
  if (chain === "base_sepolia") {
2483
3977
  console.log("\n\u{1F9EA} Testnet Funding\n");
2484
- console.log(` Wallet: ${client.address}`);
3978
+ console.log(` Wallet: ${walletAddress}`);
2485
3979
  console.log(` Chain: Base Sepolia (testnet)
2486
3980
  `);
2487
3981
  console.log("\u{1F4A1} Use the MoltsPay faucet to get free testnet USDC:\n");
@@ -2489,9 +3983,36 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
2489
3983
  console.log(" Or get from Circle Faucet: https://faucet.circle.com/\n");
2490
3984
  return;
2491
3985
  }
3986
+ if (chain === "bnb_testnet") {
3987
+ console.log("\n\u{1F9EA} BNB Testnet Funding\n");
3988
+ console.log(` Wallet: ${walletAddress}`);
3989
+ console.log(` Chain: BNB Testnet
3990
+ `);
3991
+ console.log("\u{1F4A1} Use the MoltsPay faucet to get testnet USDC + tBNB:\n");
3992
+ console.log(" npx moltspay faucet --chain bnb_testnet\n");
3993
+ console.log(" This gives you:\n");
3994
+ console.log(" \u2022 1 USDC (testnet) for payments");
3995
+ console.log(" \u2022 0.001 tBNB for gas (first approval tx)\n");
3996
+ return;
3997
+ }
3998
+ if (chain === "bnb") {
3999
+ console.log("\n\u{1F4CB} BNB Chain Funding\n");
4000
+ console.log(` Wallet: ${walletAddress}
4001
+ `);
4002
+ console.log(" To use MoltsPay on BNB Chain, you need:\n");
4003
+ console.log(" 1. USDC for payments");
4004
+ console.log(" \u2192 Withdraw from Binance/exchange to your wallet address\n");
4005
+ console.log(" 2. Small amount of BNB for gas (~0.001 BNB / ~$0.60)");
4006
+ console.log(" \u2192 First approval transaction requires gas");
4007
+ console.log(" \u2192 After approval, all payments are gasless\n");
4008
+ console.log(" \u{1F4A1} Tip: Most exchanges include BNB dust when you withdraw to BNB Chain\n");
4009
+ console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
4010
+ console.log(" After funding, check status: npx moltspay status\n");
4011
+ return;
4012
+ }
2492
4013
  console.log("\n\u{1F4B3} Fund your agent wallet\n");
2493
- console.log(` Wallet: ${client.address}`);
2494
- console.log(` Chain: ${chain}`);
4014
+ console.log(` Wallet: ${walletAddress}`);
4015
+ console.log(` Chain: ${chain === "solana" ? "Solana" : chain}`);
2495
4016
  console.log(` Amount: $${amount.toFixed(2)}
2496
4017
  `);
2497
4018
  try {
@@ -2500,7 +4021,7 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
2500
4021
  method: "POST",
2501
4022
  headers: { "Content-Type": "application/json" },
2502
4023
  body: JSON.stringify({
2503
- address: client.address,
4024
+ address: walletAddress,
2504
4025
  amount,
2505
4026
  chain
2506
4027
  })
@@ -2518,11 +4039,92 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
2518
4039
  console.log(`\u274C ${error.message}`);
2519
4040
  }
2520
4041
  });
2521
- program.command("faucet").description("Request testnet tokens from faucet (Base Sepolia or Tempo Moderato)").option("--chain <chain>", "Chain to get tokens on (base_sepolia or tempo_moderato)", "base_sepolia").option("--address <address>", "Wallet address (defaults to your wallet)").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
4042
+ program.command("approve").description("Approve a spender address for BNB chain payments").requiredOption("--spender <address>", "Spender address to approve (from server 402 response)").option("--chain <chain>", "BNB chain (bnb or bnb_testnet)", "bnb_testnet").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR2).action(async (options) => {
4043
+ const chain = options.chain;
4044
+ if (chain !== "bnb" && chain !== "bnb_testnet") {
4045
+ console.log("\u274C approve command is only for BNB chains (bnb or bnb_testnet)");
4046
+ return;
4047
+ }
4048
+ if (!options.spender.match(/^0x[a-fA-F0-9]{40}$/)) {
4049
+ console.log("\u274C Invalid spender address format");
4050
+ return;
4051
+ }
4052
+ const client = new MoltsPayClient({ configDir: options.configDir });
4053
+ if (!client.isInitialized) {
4054
+ console.log("\u274C Wallet not initialized. Run: npx moltspay init --chain " + chain);
4055
+ return;
4056
+ }
4057
+ console.log(`
4058
+ \u{1F510} Approving spender for ${chain}...
4059
+ `);
4060
+ await setupBNBApprovals(client, chain, options.spender, false);
4061
+ const walletPath = (0, import_path3.join)(options.configDir || DEFAULT_CONFIG_DIR2, "wallet.json");
4062
+ try {
4063
+ const walletData = JSON.parse((0, import_fs5.readFileSync)(walletPath, "utf-8"));
4064
+ walletData.approvals = walletData.approvals || {};
4065
+ walletData.approvals[chain] = options.spender;
4066
+ (0, import_fs5.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2));
4067
+ console.log(`\u2705 Approval complete! Spender saved for ${chain}.
4068
+ `);
4069
+ } catch (err) {
4070
+ console.log("\u2705 Approval complete!\n");
4071
+ console.log("\u26A0\uFE0F Could not save spender to wallet config");
4072
+ }
4073
+ });
4074
+ program.command("faucet").description("Request testnet tokens from faucet (Base Sepolia, Tempo Moderato, BNB Testnet, or Solana Devnet)").option("--chain <chain>", "Chain to get tokens on (base_sepolia, tempo_moderato, bnb_testnet, or solana_devnet)", "base_sepolia").option("--address <address>", "Wallet address (defaults to your wallet)").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR2).action(async (options) => {
2522
4075
  let address = options.address;
2523
4076
  const chain = options.chain?.toLowerCase() || "base_sepolia";
2524
- if (!["base_sepolia", "tempo_moderato"].includes(chain)) {
2525
- console.log("\u274C Invalid chain. Use: base_sepolia or tempo_moderato");
4077
+ if (!["base_sepolia", "tempo_moderato", "bnb_testnet", "solana_devnet"].includes(chain)) {
4078
+ console.log("\u274C Invalid chain. Use: base_sepolia, tempo_moderato, bnb_testnet, or solana_devnet");
4079
+ return;
4080
+ }
4081
+ if (chain === "solana_devnet") {
4082
+ if (!address) {
4083
+ address = getSolanaAddress(options.configDir);
4084
+ if (!address) {
4085
+ console.log("\u274C No Solana wallet found. Run: npx moltspay init --chain solana_devnet");
4086
+ return;
4087
+ }
4088
+ }
4089
+ if (!isValidSolanaAddress(address)) {
4090
+ console.log("\u274C Invalid Solana address");
4091
+ return;
4092
+ }
4093
+ console.log("\n\u{1F6B0} Solana Devnet Faucet (Gasless Mode)\n");
4094
+ console.log(` Address: ${address}
4095
+ `);
4096
+ let usdcSuccess = false;
4097
+ try {
4098
+ console.log(" \u23F3 Requesting 1 USDC from faucet...");
4099
+ const FAUCET_API = process.env.MOLTSPAY_FAUCET_API || "https://moltspay.com/api/v1/faucet";
4100
+ const response = await fetch(FAUCET_API, {
4101
+ method: "POST",
4102
+ headers: { "Content-Type": "application/json" },
4103
+ body: JSON.stringify({ address, chain: "solana_devnet" })
4104
+ });
4105
+ const result = await response.json();
4106
+ if (!response.ok) {
4107
+ console.log(` \u26A0\uFE0F USDC faucet: ${result.error || "Request failed"}`);
4108
+ if (result.hint) console.log(` ${result.hint}`);
4109
+ if (result.retry_after) console.log(` Retry after: ${result.retry_after}`);
4110
+ } else {
4111
+ console.log(` \u2705 Received ${result.amount} USDC!`);
4112
+ console.log(` Transaction: ${result.explorer}`);
4113
+ if (result.faucet_balance) {
4114
+ console.log(` Faucet balance: ${result.faucet_balance} USDC remaining`);
4115
+ }
4116
+ usdcSuccess = true;
4117
+ }
4118
+ } catch (error) {
4119
+ console.log(` \u26A0\uFE0F USDC faucet error: ${error.message}`);
4120
+ }
4121
+ console.log("");
4122
+ if (usdcSuccess) {
4123
+ console.log("\u{1F4A1} Check your balance:");
4124
+ console.log(" npx moltspay status\n");
4125
+ } else {
4126
+ console.log("\u274C Faucet request failed. Try again in a few minutes.\n");
4127
+ }
2526
4128
  return;
2527
4129
  }
2528
4130
  if (!address) {
@@ -2570,6 +4172,46 @@ program.command("faucet").description("Request testnet tokens from faucet (Base
2570
4172
  console.log(`\u274C ${error.message}`);
2571
4173
  console.log("\n Try Tempo Wallet instead: https://wallet.tempo.xyz\n");
2572
4174
  }
4175
+ } else if (chain === "bnb_testnet") {
4176
+ console.log(` Requesting 1 USDC on BNB Testnet...`);
4177
+ console.log(` Address: ${address}
4178
+ `);
4179
+ try {
4180
+ const FAUCET_API = process.env.MOLTSPAY_FAUCET_API || "https://moltspay.com/api/v1/faucet";
4181
+ const response = await fetch(FAUCET_API, {
4182
+ method: "POST",
4183
+ headers: { "Content-Type": "application/json" },
4184
+ body: JSON.stringify({ address, chain: "bnb_testnet" })
4185
+ });
4186
+ const result = await response.json();
4187
+ if (!response.ok) {
4188
+ console.log(`\u274C ${result.error || "Request failed"}`);
4189
+ if (result.hint) console.log(` ${result.hint}`);
4190
+ if (result.retry_after) console.log(` Retry after: ${result.retry_after}`);
4191
+ console.log("\n\u{1F4A1} Alternatively, get tokens manually:");
4192
+ console.log(` 1. Get test BNB: https://www.bnbchain.org/en/testnet-faucet`);
4193
+ console.log(` 2. Select "Peggy Tokens" -> USDC`);
4194
+ console.log(` 3. Enter: ${address}
4195
+ `);
4196
+ return;
4197
+ }
4198
+ console.log(`\u2705 Received ${result.amount} ${result.token || "USDC"} on ${result.chain_name || "BNB Testnet"}!
4199
+ `);
4200
+ console.log(` Transaction: ${result.explorer || `https://testnet.bscscan.com/tx/${result.transaction}`}`);
4201
+ if (result.faucet_balance) {
4202
+ console.log(` Faucet balance: ${result.faucet_balance} USDC`);
4203
+ }
4204
+ console.log("\n\u{1F4A1} Now you can test BNB payments:");
4205
+ console.log(` npx moltspay pay <service-url> <service-id> --chain bnb_testnet
4206
+ `);
4207
+ } catch (error) {
4208
+ console.log(`\u274C ${error.message}`);
4209
+ console.log("\n\u{1F4A1} Get tokens manually:");
4210
+ console.log(` 1. Get test BNB: https://www.bnbchain.org/en/testnet-faucet`);
4211
+ console.log(` 2. Select "Peggy Tokens" -> USDC`);
4212
+ console.log(` 3. Enter: ${address}
4213
+ `);
4214
+ }
2573
4215
  } else {
2574
4216
  console.log(` Requesting 1 USDC on Base Sepolia...`);
2575
4217
  console.log(` Address: ${address}
@@ -2579,7 +4221,7 @@ program.command("faucet").description("Request testnet tokens from faucet (Base
2579
4221
  const response = await fetch(FAUCET_API, {
2580
4222
  method: "POST",
2581
4223
  headers: { "Content-Type": "application/json" },
2582
- body: JSON.stringify({ address })
4224
+ body: JSON.stringify({ address, chain: "base_sepolia" })
2583
4225
  });
2584
4226
  const result = await response.json();
2585
4227
  if (!response.ok) {
@@ -2602,7 +4244,7 @@ program.command("faucet").description("Request testnet tokens from faucet (Base
2602
4244
  }
2603
4245
  }
2604
4246
  });
2605
- program.command("status").description("Show wallet status and balance").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).option("--json", "Output as JSON").action(async (options) => {
4247
+ program.command("status").description("Show wallet status and balance").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR2).option("--json", "Output as JSON").action(async (options) => {
2606
4248
  const client = new MoltsPayClient({ configDir: options.configDir });
2607
4249
  if (!client.isInitialized) {
2608
4250
  if (options.json) {
@@ -2619,12 +4261,31 @@ program.command("status").description("Show wallet status and balance").option("
2619
4261
  } catch (err) {
2620
4262
  console.error("Warning: Could not fetch balances:", err.message);
2621
4263
  }
4264
+ const solanaAddress = getSolanaAddress(options.configDir);
4265
+ let solanaBalances = {};
4266
+ if (solanaAddress) {
4267
+ try {
4268
+ solanaBalances.devnet = await getSolanaBalances(solanaAddress, "solana_devnet");
4269
+ } catch {
4270
+ }
4271
+ try {
4272
+ solanaBalances.mainnet = await getSolanaBalances(solanaAddress, "solana");
4273
+ } catch {
4274
+ }
4275
+ }
2622
4276
  if (options.json) {
2623
- console.log(JSON.stringify({
4277
+ const output = {
2624
4278
  address: client.address,
2625
4279
  balances: allBalances,
2626
4280
  limits: config.limits
2627
- }, null, 2));
4281
+ };
4282
+ if (solanaAddress) {
4283
+ output.solana = {
4284
+ address: solanaAddress,
4285
+ balances: solanaBalances
4286
+ };
4287
+ }
4288
+ console.log(JSON.stringify(output, null, 2));
2628
4289
  } else {
2629
4290
  console.log("\n\u{1F4CA} MoltsPay Wallet Status\n");
2630
4291
  console.log(` Address: ${client.address}`);
@@ -2648,18 +4309,90 @@ program.command("status").description("Show wallet status and balance").option("
2648
4309
  console.log(` alphaUSD: ${tempo.alphaUSD.toFixed(2)}`);
2649
4310
  console.log(` betaUSD: ${tempo.betaUSD.toFixed(2)}`);
2650
4311
  console.log(` thetaUSD: ${tempo.thetaUSD.toFixed(2)}`);
4312
+ } else if (chainName === "bnb" || chainName === "bnb_testnet") {
4313
+ const bnbBalance = balance.native;
4314
+ const bnbWarning = bnbBalance < 5e-4 ? " \u26A0\uFE0F Low gas" : "";
4315
+ console.log(` ${chainLabel.padEnd(14)} ${balance.usdc.toFixed(2)} USDC | ${balance.usdt.toFixed(2)} USDT | ${bnbBalance.toFixed(4)} BNB${bnbWarning}`);
2651
4316
  } else {
2652
4317
  console.log(` ${chainLabel.padEnd(14)} ${balance.usdc.toFixed(2)} USDC | ${balance.usdt.toFixed(2)} USDT`);
2653
4318
  }
2654
4319
  }
4320
+ const address = client.address;
4321
+ let bnbApprovalStatus = null;
4322
+ let bnbTestnetApprovalStatus = null;
4323
+ try {
4324
+ if (allBalances["bnb"]) {
4325
+ bnbApprovalStatus = await checkBNBApprovals(address, "bnb", options.configDir);
4326
+ }
4327
+ if (allBalances["bnb_testnet"]) {
4328
+ bnbTestnetApprovalStatus = await checkBNBApprovals(address, "bnb_testnet", options.configDir);
4329
+ }
4330
+ } catch {
4331
+ }
4332
+ if (bnbApprovalStatus || bnbTestnetApprovalStatus) {
4333
+ console.log("");
4334
+ console.log(" BNB Approvals (pay-for-success):");
4335
+ if (bnbApprovalStatus) {
4336
+ if (!bnbApprovalStatus.spender) {
4337
+ console.log(" BNB: \u26A0\uFE0F No spender configured");
4338
+ console.log(" \u2514\u2500 Run a payment first, or: npx moltspay approve --chain bnb --spender <address>");
4339
+ } else {
4340
+ const status = bnbApprovalStatus.usdt && bnbApprovalStatus.usdc ? "\u2705" : "\u26A0\uFE0F";
4341
+ const tokens = [
4342
+ bnbApprovalStatus.usdt ? "USDT\u2713" : "USDT\u2717",
4343
+ bnbApprovalStatus.usdc ? "USDC\u2713" : "USDC\u2717"
4344
+ ].join(", ");
4345
+ console.log(` BNB: ${status} ${tokens}`);
4346
+ const bnbNative = allBalances["bnb"]?.native || 0;
4347
+ if (!bnbApprovalStatus.usdc && !bnbApprovalStatus.usdt && bnbNative < 5e-4) {
4348
+ console.log(" \u26A0\uFE0F Need ~0.001 BNB for first approval tx. Get from exchange.");
4349
+ }
4350
+ }
4351
+ }
4352
+ if (bnbTestnetApprovalStatus) {
4353
+ if (!bnbTestnetApprovalStatus.spender) {
4354
+ console.log(" BNB Testnet: \u26A0\uFE0F No spender configured");
4355
+ console.log(" \u2514\u2500 Run a payment first, or: npx moltspay approve --chain bnb_testnet --spender <address>");
4356
+ } else {
4357
+ const status = bnbTestnetApprovalStatus.usdt && bnbTestnetApprovalStatus.usdc ? "\u2705" : "\u26A0\uFE0F";
4358
+ const tokens = [
4359
+ bnbTestnetApprovalStatus.usdt ? "USDT\u2713" : "USDT\u2717",
4360
+ bnbTestnetApprovalStatus.usdc ? "USDC\u2713" : "USDC\u2717"
4361
+ ].join(", ");
4362
+ console.log(` BNB Testnet: ${status} ${tokens}`);
4363
+ const tbnbNative = allBalances["bnb_testnet"]?.native || 0;
4364
+ if (!bnbTestnetApprovalStatus.usdc && !bnbTestnetApprovalStatus.usdt && tbnbNative < 5e-4) {
4365
+ console.log(" \u26A0\uFE0F Need tBNB for approval. Run: npx moltspay faucet --chain bnb_testnet");
4366
+ }
4367
+ }
4368
+ }
4369
+ }
2655
4370
  console.log("");
2656
4371
  console.log(" Spending Limits:");
2657
4372
  console.log(` Per Transaction: $${config.limits.maxPerTx}`);
2658
4373
  console.log(` Daily: $${config.limits.maxPerDay}`);
4374
+ const solanaAddress2 = getSolanaAddress(options.configDir);
4375
+ if (solanaAddress2) {
4376
+ console.log("");
4377
+ console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
4378
+ console.log(` \u{1F7E3} Solana: ${solanaAddress2}`);
4379
+ try {
4380
+ const devnetBalances = await getSolanaBalances(solanaAddress2, "solana_devnet");
4381
+ console.log(` Devnet: ${devnetBalances.sol.toFixed(4)} SOL | ${devnetBalances.usdc.toFixed(2)} USDC`);
4382
+ } catch (err) {
4383
+ console.log(` Devnet: (unable to fetch)`);
4384
+ }
4385
+ try {
4386
+ const mainnetBalances = await getSolanaBalances(solanaAddress2, "solana");
4387
+ console.log(` Mainnet: ${mainnetBalances.sol.toFixed(4)} SOL | ${mainnetBalances.usdc.toFixed(2)} USDC`);
4388
+ } catch (err) {
4389
+ console.log(` Mainnet: (unable to fetch)`);
4390
+ }
4391
+ }
2659
4392
  console.log("");
2660
4393
  }
2661
4394
  });
2662
- program.command("list").description("List recent transactions").option("--days <n>", "Number of days to look back", "7").option("--chain <chain>", "Chain to query (base, polygon, base_sepolia, or all)", "all").option("--limit <n>", "Max transactions to show", "20").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
4395
+ program.command("list").description("List recent transactions").option("--days <n>", "Number of days to look back", "7").option("--chain <chain>", "Chain to query (base, polygon, base_sepolia, or all)", "all").option("--limit <n>", "Max transactions to show", "20").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR2).action(async (options) => {
2663
4396
  const client = new MoltsPayClient({ configDir: options.configDir });
2664
4397
  if (!client.isInitialized) {
2665
4398
  console.log("\u274C Not initialized. Run: npx moltspay init");
@@ -2962,18 +4695,18 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
2962
4695
  const handlers = /* @__PURE__ */ new Map();
2963
4696
  let provider = null;
2964
4697
  for (const inputPath of allPaths) {
2965
- const resolvedPath = (0, import_path2.resolve)(inputPath);
4698
+ const resolvedPath = (0, import_path3.resolve)(inputPath);
2966
4699
  let manifestPath;
2967
4700
  let skillDir;
2968
4701
  let isSkillDir = false;
2969
- if ((0, import_fs4.existsSync)((0, import_path2.join)(resolvedPath, "moltspay.services.json"))) {
2970
- manifestPath = (0, import_path2.join)(resolvedPath, "moltspay.services.json");
4702
+ if ((0, import_fs5.existsSync)((0, import_path3.join)(resolvedPath, "moltspay.services.json"))) {
4703
+ manifestPath = (0, import_path3.join)(resolvedPath, "moltspay.services.json");
2971
4704
  skillDir = resolvedPath;
2972
4705
  isSkillDir = true;
2973
- } else if ((0, import_fs4.existsSync)(resolvedPath) && resolvedPath.endsWith(".json")) {
4706
+ } else if ((0, import_fs5.existsSync)(resolvedPath) && resolvedPath.endsWith(".json")) {
2974
4707
  manifestPath = resolvedPath;
2975
- skillDir = (0, import_path2.dirname)(resolvedPath);
2976
- } else if ((0, import_fs4.existsSync)(resolvedPath)) {
4708
+ skillDir = (0, import_path3.dirname)(resolvedPath);
4709
+ } else if ((0, import_fs5.existsSync)(resolvedPath)) {
2977
4710
  console.error(`\u274C No moltspay.services.json found in: ${resolvedPath}`);
2978
4711
  continue;
2979
4712
  } else {
@@ -2982,25 +4715,25 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
2982
4715
  }
2983
4716
  console.log(`\u{1F4E6} Loading: ${manifestPath}`);
2984
4717
  try {
2985
- const manifestContent = JSON.parse((0, import_fs4.readFileSync)(manifestPath, "utf-8"));
4718
+ const manifestContent = JSON.parse((0, import_fs5.readFileSync)(manifestPath, "utf-8"));
2986
4719
  if (!provider) {
2987
4720
  provider = manifestContent.provider;
2988
4721
  }
2989
4722
  let skillModule = null;
2990
4723
  if (isSkillDir) {
2991
4724
  let entryPoint = "index.js";
2992
- const pkgJsonPath = (0, import_path2.join)(skillDir, "package.json");
2993
- if ((0, import_fs4.existsSync)(pkgJsonPath)) {
4725
+ const pkgJsonPath = (0, import_path3.join)(skillDir, "package.json");
4726
+ if ((0, import_fs5.existsSync)(pkgJsonPath)) {
2994
4727
  try {
2995
- const pkgJson = JSON.parse((0, import_fs4.readFileSync)(pkgJsonPath, "utf-8"));
4728
+ const pkgJson = JSON.parse((0, import_fs5.readFileSync)(pkgJsonPath, "utf-8"));
2996
4729
  if (pkgJson.main) {
2997
4730
  entryPoint = pkgJson.main;
2998
4731
  }
2999
4732
  } catch {
3000
4733
  }
3001
4734
  }
3002
- const modulePath = (0, import_path2.join)(skillDir, entryPoint);
3003
- if ((0, import_fs4.existsSync)(modulePath)) {
4735
+ const modulePath = (0, import_path3.join)(skillDir, entryPoint);
4736
+ if ((0, import_fs5.existsSync)(modulePath)) {
3004
4737
  try {
3005
4738
  skillModule = await import(modulePath);
3006
4739
  console.log(` \u2705 Loaded module: ${modulePath}`);
@@ -3078,8 +4811,8 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
3078
4811
  provider,
3079
4812
  services: allServices
3080
4813
  };
3081
- const tempManifestPath = (0, import_path2.join)(DEFAULT_CONFIG_DIR, "combined-manifest.json");
3082
- (0, import_fs4.writeFileSync)(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
4814
+ const tempManifestPath = (0, import_path3.join)(DEFAULT_CONFIG_DIR2, "combined-manifest.json");
4815
+ (0, import_fs5.writeFileSync)(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
3083
4816
  console.log(`
3084
4817
  \u{1F4CB} Combined manifest: ${allServices.length} services`);
3085
4818
  console.log(` Provider: ${provider.name}`);
@@ -3092,12 +4825,12 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
3092
4825
  server.skill(serviceId, handler);
3093
4826
  }
3094
4827
  const pidData = { pid: process.pid, port, paths: allPaths };
3095
- (0, import_fs4.writeFileSync)(PID_FILE, JSON.stringify(pidData, null, 2));
4828
+ (0, import_fs5.writeFileSync)(PID_FILE, JSON.stringify(pidData, null, 2));
3096
4829
  server.listen(port);
3097
4830
  const cleanup = () => {
3098
4831
  try {
3099
- if ((0, import_fs4.existsSync)(PID_FILE)) (0, import_fs4.unlinkSync)(PID_FILE);
3100
- if ((0, import_fs4.existsSync)(tempManifestPath)) (0, import_fs4.unlinkSync)(tempManifestPath);
4832
+ if ((0, import_fs5.existsSync)(PID_FILE)) (0, import_fs5.unlinkSync)(PID_FILE);
4833
+ if ((0, import_fs5.existsSync)(tempManifestPath)) (0, import_fs5.unlinkSync)(tempManifestPath);
3101
4834
  } catch {
3102
4835
  }
3103
4836
  };
@@ -3118,12 +4851,12 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
3118
4851
  }
3119
4852
  });
3120
4853
  program.command("stop").description("Stop the running MoltsPay server").action(async () => {
3121
- if (!(0, import_fs4.existsSync)(PID_FILE)) {
4854
+ if (!(0, import_fs5.existsSync)(PID_FILE)) {
3122
4855
  console.log("\u274C No running server found (no PID file)");
3123
4856
  process.exit(1);
3124
4857
  }
3125
4858
  try {
3126
- const pidData = JSON.parse((0, import_fs4.readFileSync)(PID_FILE, "utf-8"));
4859
+ const pidData = JSON.parse((0, import_fs5.readFileSync)(PID_FILE, "utf-8"));
3127
4860
  const { pid, port, manifest } = pidData;
3128
4861
  console.log(`
3129
4862
  \u{1F6D1} Stopping MoltsPay Server
@@ -3136,7 +4869,7 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
3136
4869
  process.kill(pid, 0);
3137
4870
  } catch {
3138
4871
  console.log("\u26A0\uFE0F Process not running, cleaning up PID file...");
3139
- (0, import_fs4.unlinkSync)(PID_FILE);
4872
+ (0, import_fs5.unlinkSync)(PID_FILE);
3140
4873
  process.exit(0);
3141
4874
  }
3142
4875
  process.kill(pid, "SIGTERM");
@@ -3148,8 +4881,8 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
3148
4881
  process.kill(pid, "SIGKILL");
3149
4882
  } catch {
3150
4883
  }
3151
- if ((0, import_fs4.existsSync)(PID_FILE)) {
3152
- (0, import_fs4.unlinkSync)(PID_FILE);
4884
+ if ((0, import_fs5.existsSync)(PID_FILE)) {
4885
+ (0, import_fs5.unlinkSync)(PID_FILE);
3153
4886
  }
3154
4887
  console.log("\u2705 Server stopped\n");
3155
4888
  } catch (err) {
@@ -3157,14 +4890,23 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
3157
4890
  process.exit(1);
3158
4891
  }
3159
4892
  });
3160
- program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, base_sepolia, or tempo_moderato).").option("--config-dir <dir>", "Config directory with wallet.json", DEFAULT_CONFIG_DIR).option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
4893
+ program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--data <json>", "Raw JSON data to send (for custom input formats)").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, base_sepolia, tempo_moderato, solana, or solana_devnet).").option("--config-dir <dir>", "Config directory with wallet.json", DEFAULT_CONFIG_DIR2).option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
3161
4894
  const client = new MoltsPayClient({ configDir: options.configDir });
3162
4895
  if (!client.isInitialized) {
3163
4896
  console.error("\u274C Wallet not initialized. Run: npx moltspay init");
3164
4897
  process.exit(1);
3165
4898
  }
3166
4899
  let params = {};
3167
- if (paramsJson) {
4900
+ let useRawData = false;
4901
+ if (options.data) {
4902
+ try {
4903
+ params = JSON.parse(options.data);
4904
+ useRawData = true;
4905
+ } catch {
4906
+ console.error("\u274C Invalid JSON in --data flag");
4907
+ process.exit(1);
4908
+ }
4909
+ } else if (paramsJson) {
3168
4910
  try {
3169
4911
  params = JSON.parse(paramsJson);
3170
4912
  } catch {
@@ -3172,24 +4914,25 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
3172
4914
  process.exit(1);
3173
4915
  }
3174
4916
  }
3175
- if (options.prompt) params.prompt = options.prompt;
4917
+ if (!useRawData && options.prompt) params.prompt = options.prompt;
3176
4918
  if (options.image) {
3177
4919
  const imagePath = options.image;
3178
4920
  if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
3179
4921
  params.image_url = imagePath;
3180
4922
  } else {
3181
- const filePath = (0, import_path2.resolve)(imagePath);
3182
- if (!(0, import_fs4.existsSync)(filePath)) {
4923
+ const filePath = (0, import_path3.resolve)(imagePath);
4924
+ if (!(0, import_fs5.existsSync)(filePath)) {
3183
4925
  console.error(`\u274C Image file not found: ${filePath}`);
3184
4926
  process.exit(1);
3185
4927
  }
3186
- const imageData = (0, import_fs4.readFileSync)(filePath);
4928
+ const imageData = (0, import_fs5.readFileSync)(filePath);
3187
4929
  params.image_base64 = imageData.toString("base64");
3188
4930
  }
3189
4931
  }
4932
+ const supportedPayChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet", "solana", "solana_devnet"];
3190
4933
  const chain = options.chain?.toLowerCase();
3191
- if (chain && !["base", "polygon", "base_sepolia", "tempo_moderato"].includes(chain)) {
3192
- console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon, base_sepolia, tempo_moderato`);
4934
+ if (chain && !supportedPayChains.includes(chain)) {
4935
+ console.error(`\u274C Unknown chain: ${chain}. Supported: ${supportedPayChains.join(", ")}`);
3193
4936
  process.exit(1);
3194
4937
  }
3195
4938
  const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);
@@ -3212,7 +4955,11 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
3212
4955
  `);
3213
4956
  console.log(` Server: ${server}`);
3214
4957
  console.log(` Service: ${service}`);
3215
- console.log(` Prompt: ${params.prompt}`);
4958
+ if (useRawData) {
4959
+ console.log(` Data: ${JSON.stringify(params).slice(0, 50)}${JSON.stringify(params).length > 50 ? "..." : ""}`);
4960
+ } else {
4961
+ console.log(` Prompt: ${params.prompt}`);
4962
+ }
3216
4963
  if (imageDisplay) console.log(` Image: ${imageDisplay}`);
3217
4964
  console.log(` Chain: ${chain || "(auto)"}`);
3218
4965
  console.log(` Token: ${token}`);
@@ -3220,22 +4967,11 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
3220
4967
  console.log("");
3221
4968
  }
3222
4969
  try {
3223
- let result;
3224
- if (chain === "tempo_moderato") {
3225
- if (!options.json) {
3226
- console.log(" Protocol: MPP (Machine Payments Protocol)");
3227
- console.log("");
3228
- }
3229
- const mppUrl = server.includes(service) ? server : `${server}/${service}`;
3230
- result = await client.payWithMPP(mppUrl, {
3231
- body: params
3232
- });
3233
- } else {
3234
- result = await client.pay(server, service, params, {
3235
- token,
3236
- chain
3237
- });
3238
- }
4970
+ const result = await client.pay(server, service, params, {
4971
+ token,
4972
+ chain,
4973
+ rawData: useRawData
4974
+ });
3239
4975
  if (options.json) {
3240
4976
  console.log(JSON.stringify(result));
3241
4977
  } else {
@@ -3253,11 +4989,11 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
3253
4989
  }
3254
4990
  });
3255
4991
  program.command("validate <path>").description("Validate a moltspay.services.json file against the schema").action(async (inputPath) => {
3256
- const resolvedPath = (0, import_path2.resolve)(inputPath);
4992
+ const resolvedPath = (0, import_path3.resolve)(inputPath);
3257
4993
  let manifestPath;
3258
- if ((0, import_fs4.existsSync)((0, import_path2.join)(resolvedPath, "moltspay.services.json"))) {
3259
- manifestPath = (0, import_path2.join)(resolvedPath, "moltspay.services.json");
3260
- } else if (resolvedPath.endsWith(".json") && (0, import_fs4.existsSync)(resolvedPath)) {
4994
+ if ((0, import_fs5.existsSync)((0, import_path3.join)(resolvedPath, "moltspay.services.json"))) {
4995
+ manifestPath = (0, import_path3.join)(resolvedPath, "moltspay.services.json");
4996
+ } else if (resolvedPath.endsWith(".json") && (0, import_fs5.existsSync)(resolvedPath)) {
3261
4997
  manifestPath = resolvedPath;
3262
4998
  } else {
3263
4999
  console.error(`\u274C Not found: ${resolvedPath}`);
@@ -3267,7 +5003,7 @@ program.command("validate <path>").description("Validate a moltspay.services.jso
3267
5003
  \u{1F4CB} Validating: ${manifestPath}
3268
5004
  `);
3269
5005
  try {
3270
- const content = JSON.parse((0, import_fs4.readFileSync)(manifestPath, "utf-8"));
5006
+ const content = JSON.parse((0, import_fs5.readFileSync)(manifestPath, "utf-8"));
3271
5007
  const errors = [];
3272
5008
  if (!content.provider) {
3273
5009
  errors.push("Missing required field: provider");