moltspay 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +221 -38
  2. package/dist/cdp/index.d.mts +4 -4
  3. package/dist/cdp/index.d.ts +4 -4
  4. package/dist/cdp/index.js +57 -0
  5. package/dist/cdp/index.js.map +1 -1
  6. package/dist/cdp/index.mjs +57 -0
  7. package/dist/cdp/index.mjs.map +1 -1
  8. package/dist/chains/index.d.mts +9 -8
  9. package/dist/chains/index.d.ts +9 -8
  10. package/dist/chains/index.js +57 -0
  11. package/dist/chains/index.js.map +1 -1
  12. package/dist/chains/index.mjs +57 -0
  13. package/dist/chains/index.mjs.map +1 -1
  14. package/dist/cli/index.js +1975 -273
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/cli/index.mjs +1977 -265
  17. package/dist/cli/index.mjs.map +1 -1
  18. package/dist/client/index.d.mts +36 -3
  19. package/dist/client/index.d.ts +36 -3
  20. package/dist/client/index.js +540 -32
  21. package/dist/client/index.js.map +1 -1
  22. package/dist/client/index.mjs +548 -30
  23. package/dist/client/index.mjs.map +1 -1
  24. package/dist/facilitators/index.d.mts +220 -1
  25. package/dist/facilitators/index.d.ts +220 -1
  26. package/dist/facilitators/index.js +664 -1
  27. package/dist/facilitators/index.js.map +1 -1
  28. package/dist/facilitators/index.mjs +670 -1
  29. package/dist/facilitators/index.mjs.map +1 -1
  30. package/dist/{index-On9ZaGDW.d.mts → index-D_2FkLwV.d.mts} +6 -2
  31. package/dist/{index-On9ZaGDW.d.ts → index-D_2FkLwV.d.ts} +6 -2
  32. package/dist/index.d.mts +2 -1
  33. package/dist/index.d.ts +2 -1
  34. package/dist/index.js +1413 -146
  35. package/dist/index.js.map +1 -1
  36. package/dist/index.mjs +1421 -144
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/server/index.d.mts +13 -3
  39. package/dist/server/index.d.ts +13 -3
  40. package/dist/server/index.js +905 -52
  41. package/dist/server/index.js.map +1 -1
  42. package/dist/server/index.mjs +915 -52
  43. package/dist/server/index.mjs.map +1 -1
  44. package/dist/verify/index.d.mts +1 -1
  45. package/dist/verify/index.d.ts +1 -1
  46. package/dist/verify/index.js +57 -0
  47. package/dist/verify/index.js.map +1 -1
  48. package/dist/verify/index.mjs +57 -0
  49. package/dist/verify/index.mjs.map +1 -1
  50. package/dist/wallet/index.d.mts +3 -3
  51. package/dist/wallet/index.d.ts +3 -3
  52. package/dist/wallet/index.js +57 -0
  53. package/dist/wallet/index.js.map +1 -1
  54. package/dist/wallet/index.mjs +57 -0
  55. package/dist/wallet/index.mjs.map +1 -1
  56. package/package.json +4 -1
  57. 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
  */
@@ -278,9 +698,14 @@ var MoltsPayClient = class {
278
698
  }
279
699
  throw new Error(data.error || "Unexpected response");
280
700
  }
701
+ const wwwAuthHeader = initialRes.headers.get("www-authenticate");
281
702
  const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER);
703
+ if (wwwAuthHeader && wwwAuthHeader.toLowerCase().includes("payment")) {
704
+ console.log("[MoltsPay] Detected MPP protocol, using Tempo flow...");
705
+ return await this.handleMPPPayment(serverUrl, service, params, wwwAuthHeader);
706
+ }
282
707
  if (!paymentRequiredHeader) {
283
- throw new Error("Missing x-payment-required header");
708
+ throw new Error("Missing payment header (x-payment-required or www-authenticate)");
284
709
  }
285
710
  let requirements;
286
711
  try {
@@ -297,17 +722,22 @@ var MoltsPayClient = class {
297
722
  throw new Error("Invalid x-payment-required header");
298
723
  }
299
724
  const networkToChainName = (network2) => {
725
+ if (network2 === "solana:mainnet") return "solana";
726
+ if (network2 === "solana:devnet") return "solana_devnet";
300
727
  const match = network2.match(/^eip155:(\d+)$/);
301
728
  if (!match) return null;
302
729
  const chainId = parseInt(match[1]);
303
730
  if (chainId === 8453) return "base";
304
731
  if (chainId === 137) return "polygon";
305
732
  if (chainId === 84532) return "base_sepolia";
733
+ if (chainId === 42431) return "tempo_moderato";
734
+ if (chainId === 56) return "bnb";
735
+ if (chainId === 97) return "bnb_testnet";
306
736
  return null;
307
737
  };
308
738
  const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
309
- let chainName;
310
739
  const userSpecifiedChain = options.chain;
740
+ let selectedChain;
311
741
  if (userSpecifiedChain) {
312
742
  if (!serverChains.includes(userSpecifiedChain)) {
313
743
  throw new Error(
@@ -315,17 +745,27 @@ var MoltsPayClient = class {
315
745
  Server accepts: ${serverChains.join(", ")}`
316
746
  );
317
747
  }
318
- chainName = userSpecifiedChain;
748
+ selectedChain = userSpecifiedChain;
319
749
  } else {
320
750
  if (serverChains.length === 1 && serverChains[0] === "base") {
321
- chainName = "base";
751
+ selectedChain = "base";
322
752
  } else {
323
753
  throw new Error(
324
754
  `Server accepts: ${serverChains.join(", ")}
325
- Please specify: --chain base, --chain polygon, or --chain base_sepolia`
755
+ Please specify: --chain <chain_name>`
326
756
  );
327
757
  }
328
758
  }
759
+ if (selectedChain === "solana" || selectedChain === "solana_devnet") {
760
+ const solanaChain = selectedChain;
761
+ const network2 = solanaChain === "solana" ? "solana:mainnet" : "solana:devnet";
762
+ const req2 = requirements.find((r) => r.network === network2);
763
+ if (!req2) {
764
+ throw new Error(`Failed to find payment requirement for ${selectedChain}`);
765
+ }
766
+ return await this.handleSolanaPayment(serverUrl, service, params, req2, solanaChain);
767
+ }
768
+ const chainName = selectedChain;
329
769
  const chain = getChain(chainName);
330
770
  const network = `eip155:${chain.chainId}`;
331
771
  const req = requirements.find((r) => r.scheme === "exact" && r.network === network);
@@ -360,6 +800,25 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
360
800
  } else {
361
801
  console.log(`[MoltsPay] Signing payment: $${amount} ${token} (gasless)`);
362
802
  }
803
+ if (chainName === "bnb" || chainName === "bnb_testnet") {
804
+ console.log(`[MoltsPay] Using BNB intent-based payment flow...`);
805
+ const payTo2 = req.payTo || req.resource;
806
+ if (!payTo2) {
807
+ throw new Error("Missing payTo address in payment requirements");
808
+ }
809
+ const bnbSpender = req.extra?.bnbSpender;
810
+ if (!bnbSpender) {
811
+ throw new Error("Server did not provide bnbSpender address. Server may not support BNB payments.");
812
+ }
813
+ return await this.handleBNBPayment(serverUrl, service, params, {
814
+ to: payTo2,
815
+ amount,
816
+ token,
817
+ chainName,
818
+ chain,
819
+ spender: bnbSpender
820
+ });
821
+ }
363
822
  const payTo = req.payTo || req.resource;
364
823
  if (!payTo) {
365
824
  throw new Error("Missing payTo address in payment requirements");
@@ -409,6 +868,300 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
409
868
  console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
410
869
  return result.result;
411
870
  }
871
+ /**
872
+ * Handle MPP (Machine Payments Protocol) payment flow
873
+ * Called when pay() detects WWW-Authenticate header in 402 response
874
+ */
875
+ async handleMPPPayment(serverUrl, service, params, wwwAuthHeader) {
876
+ const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
877
+ const { createWalletClient, createPublicClient, http } = await import("viem");
878
+ const { tempoModerato } = await import("viem/chains");
879
+ const { Actions } = await import("viem/tempo");
880
+ const privateKey = this.walletData.privateKey;
881
+ const account = privateKeyToAccount2(privateKey);
882
+ console.log(`[MoltsPay] Using MPP protocol on Tempo`);
883
+ console.log(`[MoltsPay] Account: ${account.address}`);
884
+ const parseAuthParam = (header, key) => {
885
+ const match = header.match(new RegExp(`${key}="([^"]+)"`, "i"));
886
+ return match ? match[1] : null;
887
+ };
888
+ const challengeId = parseAuthParam(wwwAuthHeader, "id");
889
+ const method = parseAuthParam(wwwAuthHeader, "method");
890
+ const realm = parseAuthParam(wwwAuthHeader, "realm");
891
+ const requestB64 = parseAuthParam(wwwAuthHeader, "request");
892
+ if (method !== "tempo") {
893
+ throw new Error(`Unsupported payment method: ${method}`);
894
+ }
895
+ if (!requestB64) {
896
+ throw new Error("Missing request in WWW-Authenticate");
897
+ }
898
+ const requestJson = Buffer.from(requestB64, "base64").toString("utf-8");
899
+ const paymentRequest = JSON.parse(requestJson);
900
+ const { amount, currency, recipient, methodDetails } = paymentRequest;
901
+ const chainId = methodDetails?.chainId || 42431;
902
+ const amountDisplay = Number(amount) / 1e6;
903
+ console.log(`[MoltsPay] Payment: $${amountDisplay} to ${recipient}`);
904
+ this.checkLimits(amountDisplay);
905
+ console.log(`[MoltsPay] Sending transaction on Tempo...`);
906
+ const tempoChain = { ...tempoModerato, feeToken: currency };
907
+ const publicClient = createPublicClient({
908
+ chain: tempoChain,
909
+ transport: http("https://rpc.moderato.tempo.xyz")
910
+ });
911
+ const walletClient = createWalletClient({
912
+ account,
913
+ chain: tempoChain,
914
+ transport: http("https://rpc.moderato.tempo.xyz")
915
+ });
916
+ const txHash = await Actions.token.transfer(walletClient, {
917
+ to: recipient,
918
+ amount: BigInt(amount),
919
+ token: currency
920
+ });
921
+ console.log(`[MoltsPay] Transaction: ${txHash}`);
922
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
923
+ console.log(`[MoltsPay] Confirmed! Retrying with credential...`);
924
+ const credential = {
925
+ challenge: {
926
+ id: challengeId,
927
+ realm,
928
+ method: "tempo",
929
+ intent: "charge",
930
+ request: paymentRequest
931
+ },
932
+ payload: { hash: txHash, type: "hash" },
933
+ source: `did:pkh:eip155:${chainId}:${account.address}`
934
+ };
935
+ const credentialB64 = Buffer.from(JSON.stringify(credential)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
936
+ const paidRes = await fetch(`${serverUrl}/execute`, {
937
+ method: "POST",
938
+ headers: {
939
+ "Content-Type": "application/json",
940
+ "Authorization": `Payment ${credentialB64}`
941
+ },
942
+ body: JSON.stringify({ service, params, chain: "tempo_moderato" })
943
+ });
944
+ const result = await paidRes.json();
945
+ if (!paidRes.ok) {
946
+ throw new Error(result.error || "Payment verification failed");
947
+ }
948
+ this.recordSpending(amountDisplay);
949
+ console.log(`[MoltsPay] Success!`);
950
+ return result.result || result;
951
+ }
952
+ /**
953
+ * Handle BNB Chain payment flow (pre-approval + intent signature)
954
+ *
955
+ * Flow:
956
+ * 1. Check client has approved server wallet (done via `moltspay init`)
957
+ * 2. Sign EIP-712 payment intent (no gas, just signature)
958
+ * 3. Send intent to server
959
+ * 4. Server executes service
960
+ * 5. Server calls transferFrom if successful (pay-for-success)
961
+ */
962
+ async handleBNBPayment(serverUrl, service, params, paymentDetails) {
963
+ const { to, amount, token, chainName, chain, spender } = paymentDetails;
964
+ const tokenConfig = chain.tokens[token];
965
+ const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
966
+ const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
967
+ const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
968
+ if (allowance < amountWeiCheck) {
969
+ const nativeBalance = await provider.getBalance(this.wallet.address);
970
+ const minGasBalance = import_ethers.ethers.parseEther("0.0005");
971
+ if (nativeBalance < minGasBalance) {
972
+ const nativeBNB = parseFloat(import_ethers.ethers.formatEther(nativeBalance)).toFixed(4);
973
+ const isTestnet = chainName === "bnb_testnet";
974
+ if (isTestnet) {
975
+ throw new Error(
976
+ `\u274C Insufficient tBNB for approval transaction
977
+
978
+ Current tBNB: ${nativeBNB}
979
+ Required: ~0.001 tBNB
980
+
981
+ Get testnet tokens: npx moltspay faucet --chain bnb_testnet
982
+ (Gives USDC + tBNB for gas)`
983
+ );
984
+ } else {
985
+ throw new Error(
986
+ `\u274C Insufficient BNB for approval transaction
987
+
988
+ Current BNB: ${nativeBNB}
989
+ Required: ~0.001 BNB (~$0.60)
990
+
991
+ To get BNB:
992
+ \u2022 Withdraw from Binance/exchange to your wallet
993
+ \u2022 Most exchanges include BNB dust with withdrawals
994
+
995
+ After funding, run:
996
+ npx moltspay approve --chain ${chainName} --spender ${spender}`
997
+ );
998
+ }
999
+ }
1000
+ throw new Error(
1001
+ `Insufficient allowance for ${spender.slice(0, 10)}...
1002
+ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1003
+ );
1004
+ }
1005
+ const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
1006
+ const intent = {
1007
+ from: this.wallet.address,
1008
+ to,
1009
+ amount: amountWei,
1010
+ token: tokenConfig.address,
1011
+ service,
1012
+ nonce: Date.now(),
1013
+ // Use timestamp as nonce for simplicity
1014
+ deadline: Date.now() + 36e5
1015
+ // 1 hour
1016
+ };
1017
+ const domain = {
1018
+ name: "MoltsPay",
1019
+ version: "1",
1020
+ chainId: chain.chainId
1021
+ };
1022
+ const types = {
1023
+ PaymentIntent: [
1024
+ { name: "from", type: "address" },
1025
+ { name: "to", type: "address" },
1026
+ { name: "amount", type: "uint256" },
1027
+ { name: "token", type: "address" },
1028
+ { name: "service", type: "string" },
1029
+ { name: "nonce", type: "uint256" },
1030
+ { name: "deadline", type: "uint256" }
1031
+ ]
1032
+ };
1033
+ console.log(`[MoltsPay] Signing BNB payment intent...`);
1034
+ const signature = await this.wallet.signTypedData(domain, types, intent);
1035
+ const network = `eip155:${chain.chainId}`;
1036
+ const payload = {
1037
+ x402Version: 2,
1038
+ scheme: "exact",
1039
+ network,
1040
+ payload: {
1041
+ intent: {
1042
+ ...intent,
1043
+ signature
1044
+ },
1045
+ chainId: chain.chainId
1046
+ },
1047
+ accepted: {
1048
+ scheme: "exact",
1049
+ network,
1050
+ asset: tokenConfig.address,
1051
+ amount: amountWei,
1052
+ payTo: to,
1053
+ maxTimeoutSeconds: 300
1054
+ }
1055
+ };
1056
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
1057
+ console.log(`[MoltsPay] Sending BNB payment request...`);
1058
+ const paidRes = await fetch(`${serverUrl}/execute`, {
1059
+ method: "POST",
1060
+ headers: {
1061
+ "Content-Type": "application/json",
1062
+ "X-Payment": paymentHeader
1063
+ },
1064
+ body: JSON.stringify({ service, params, chain: chainName })
1065
+ });
1066
+ const result = await paidRes.json();
1067
+ if (!paidRes.ok) {
1068
+ throw new Error(result.error || "BNB payment failed");
1069
+ }
1070
+ this.recordSpending(amount);
1071
+ console.log(`[MoltsPay] Success! BNB payment settled.`);
1072
+ return result.result || result;
1073
+ }
1074
+ /**
1075
+ * Handle Solana payment flow
1076
+ *
1077
+ * Solana uses SPL token transfers with pay-for-success model:
1078
+ * 1. Client creates and signs a transfer transaction
1079
+ * 2. Server submits the transaction after service completes
1080
+ */
1081
+ async handleSolanaPayment(serverUrl, service, params, requirements, chain) {
1082
+ const solanaWallet = loadSolanaWallet(this.configDir);
1083
+ if (!solanaWallet) {
1084
+ throw new Error("No Solana wallet found. Run: npx moltspay init --chain solana_devnet");
1085
+ }
1086
+ const amount = Number(requirements.amount);
1087
+ const amountUSDC = amount / 1e6;
1088
+ this.checkLimits(amountUSDC);
1089
+ console.log(`[MoltsPay] Creating Solana payment: $${amountUSDC} USDC`);
1090
+ if (!requirements.payTo) {
1091
+ throw new Error("Missing payTo address in payment requirements");
1092
+ }
1093
+ const solanaFeePayer = requirements.extra?.solanaFeePayer;
1094
+ const feePayerPubkey = solanaFeePayer ? new import_web34.PublicKey(solanaFeePayer) : void 0;
1095
+ if (feePayerPubkey) {
1096
+ console.log(`[MoltsPay] Gasless mode: server pays fees`);
1097
+ }
1098
+ const recipientPubkey = new import_web34.PublicKey(requirements.payTo);
1099
+ const transaction = await createSolanaPaymentTransaction(
1100
+ solanaWallet.publicKey,
1101
+ recipientPubkey,
1102
+ BigInt(amount),
1103
+ chain,
1104
+ feePayerPubkey
1105
+ // Optional fee payer for gasless mode
1106
+ );
1107
+ if (feePayerPubkey) {
1108
+ transaction.partialSign(solanaWallet);
1109
+ } else {
1110
+ transaction.sign(solanaWallet);
1111
+ }
1112
+ const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
1113
+ console.log(`[MoltsPay] Transaction signed, sending to server...`);
1114
+ const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
1115
+ const payload = {
1116
+ x402Version: 2,
1117
+ scheme: "exact",
1118
+ network,
1119
+ payload: {
1120
+ signedTransaction: signedTx,
1121
+ sender: solanaWallet.publicKey.toBase58(),
1122
+ chain
1123
+ },
1124
+ accepted: {
1125
+ scheme: "exact",
1126
+ network,
1127
+ asset: requirements.asset,
1128
+ amount: requirements.amount,
1129
+ payTo: requirements.payTo,
1130
+ maxTimeoutSeconds: 300
1131
+ }
1132
+ };
1133
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
1134
+ const paidRes = await fetch(`${serverUrl}/execute`, {
1135
+ method: "POST",
1136
+ headers: {
1137
+ "Content-Type": "application/json",
1138
+ "X-Payment": paymentHeader
1139
+ },
1140
+ body: JSON.stringify({ service, params, chain })
1141
+ });
1142
+ const result = await paidRes.json();
1143
+ if (!paidRes.ok) {
1144
+ throw new Error(result.error || "Solana payment failed");
1145
+ }
1146
+ this.recordSpending(amountUSDC);
1147
+ console.log(`[MoltsPay] Success! Solana payment settled.`);
1148
+ if (result.payment?.transaction) {
1149
+ const explorerUrl = chain === "solana" ? `https://solscan.io/tx/${result.payment.transaction}` : `https://solscan.io/tx/${result.payment.transaction}?cluster=devnet`;
1150
+ console.log(`[MoltsPay] Transaction: ${explorerUrl}`);
1151
+ }
1152
+ return result.result || result;
1153
+ }
1154
+ /**
1155
+ * Check ERC20 allowance for a spender
1156
+ */
1157
+ async checkAllowance(tokenAddress, spender, provider) {
1158
+ const contract = new import_ethers.ethers.Contract(
1159
+ tokenAddress,
1160
+ ["function allowance(address owner, address spender) view returns (uint256)"],
1161
+ provider
1162
+ );
1163
+ return await contract.allowance(this.wallet.address, spender);
1164
+ }
412
1165
  /**
413
1166
  * Sign EIP-3009 transferWithAuthorization (GASLESS)
414
1167
  * This only signs - no on-chain transaction, no gas needed.
@@ -479,26 +1232,26 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
479
1232
  }
480
1233
  // --- Config & Wallet Management ---
481
1234
  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");
1235
+ const configPath = (0, import_path2.join)(this.configDir, "config.json");
1236
+ if ((0, import_fs2.existsSync)(configPath)) {
1237
+ const content = (0, import_fs2.readFileSync)(configPath, "utf-8");
485
1238
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
486
1239
  }
487
1240
  return { ...DEFAULT_CONFIG };
488
1241
  }
489
1242
  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));
1243
+ (0, import_fs2.mkdirSync)(this.configDir, { recursive: true });
1244
+ const configPath = (0, import_path2.join)(this.configDir, "config.json");
1245
+ (0, import_fs2.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
493
1246
  }
494
1247
  /**
495
1248
  * Load spending data from disk
496
1249
  */
497
1250
  loadSpending() {
498
- const spendingPath = (0, import_path.join)(this.configDir, "spending.json");
499
- if ((0, import_fs.existsSync)(spendingPath)) {
1251
+ const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
1252
+ if ((0, import_fs2.existsSync)(spendingPath)) {
500
1253
  try {
501
- const data = JSON.parse((0, import_fs.readFileSync)(spendingPath, "utf-8"));
1254
+ const data = JSON.parse((0, import_fs2.readFileSync)(spendingPath, "utf-8"));
502
1255
  const today = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
503
1256
  if (data.date && data.date === today) {
504
1257
  this.todaySpending = data.amount || 0;
@@ -517,29 +1270,29 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
517
1270
  * Save spending data to disk
518
1271
  */
519
1272
  saveSpending() {
520
- (0, import_fs.mkdirSync)(this.configDir, { recursive: true });
521
- const spendingPath = (0, import_path.join)(this.configDir, "spending.json");
1273
+ (0, import_fs2.mkdirSync)(this.configDir, { recursive: true });
1274
+ const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
522
1275
  const data = {
523
1276
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
524
1277
  amount: this.todaySpending,
525
1278
  updatedAt: Date.now()
526
1279
  };
527
- (0, import_fs.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
1280
+ (0, import_fs2.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
528
1281
  }
529
1282
  loadWallet() {
530
- const walletPath = (0, import_path.join)(this.configDir, "wallet.json");
531
- if ((0, import_fs.existsSync)(walletPath)) {
1283
+ const walletPath = (0, import_path2.join)(this.configDir, "wallet.json");
1284
+ if ((0, import_fs2.existsSync)(walletPath)) {
532
1285
  try {
533
- const stats = (0, import_fs.statSync)(walletPath);
1286
+ const stats = (0, import_fs2.statSync)(walletPath);
534
1287
  const mode = stats.mode & 511;
535
1288
  if (mode !== 384) {
536
1289
  console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
537
1290
  console.warn(`[MoltsPay] Fixing permissions to 0600...`);
538
- (0, import_fs.chmodSync)(walletPath, 384);
1291
+ (0, import_fs2.chmodSync)(walletPath, 384);
539
1292
  }
540
1293
  } catch (err) {
541
1294
  }
542
- const content = (0, import_fs.readFileSync)(walletPath, "utf-8");
1295
+ const content = (0, import_fs2.readFileSync)(walletPath, "utf-8");
543
1296
  return JSON.parse(content);
544
1297
  }
545
1298
  return null;
@@ -548,15 +1301,15 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
548
1301
  * Initialize a new wallet (called by CLI)
549
1302
  */
550
1303
  static init(configDir, options) {
551
- (0, import_fs.mkdirSync)(configDir, { recursive: true });
1304
+ (0, import_fs2.mkdirSync)(configDir, { recursive: true });
552
1305
  const wallet = import_ethers.Wallet.createRandom();
553
1306
  const walletData = {
554
1307
  address: wallet.address,
555
1308
  privateKey: wallet.privateKey,
556
1309
  createdAt: Date.now()
557
1310
  };
558
- const walletPath = (0, import_path.join)(configDir, "wallet.json");
559
- (0, import_fs.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
1311
+ const walletPath = (0, import_path2.join)(configDir, "wallet.json");
1312
+ (0, import_fs2.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
560
1313
  const config = {
561
1314
  chain: options.chain,
562
1315
  limits: {
@@ -564,8 +1317,8 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
564
1317
  maxPerDay: options.maxPerDay
565
1318
  }
566
1319
  };
567
- const configPath = (0, import_path.join)(configDir, "config.json");
568
- (0, import_fs.writeFileSync)(configPath, JSON.stringify(config, null, 2));
1320
+ const configPath = (0, import_path2.join)(configDir, "config.json");
1321
+ (0, import_fs2.writeFileSync)(configPath, JSON.stringify(config, null, 2));
569
1322
  return { address: wallet.address, configDir };
570
1323
  }
571
1324
  /**
@@ -601,7 +1354,7 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
601
1354
  if (!this.wallet) {
602
1355
  throw new Error("Client not initialized");
603
1356
  }
604
- const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
1357
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet"];
605
1358
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
606
1359
  const results = {};
607
1360
  const tempoTokens = {
@@ -672,12 +1425,12 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
672
1425
  if (!this.wallet || !this.walletData) {
673
1426
  throw new Error("Client not initialized. Run: npx moltspay init");
674
1427
  }
675
- const { privateKeyToAccount } = await import("viem/accounts");
1428
+ const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
676
1429
  const { createWalletClient, createPublicClient, http } = await import("viem");
677
1430
  const { tempoModerato } = await import("viem/chains");
678
1431
  const { Actions } = await import("viem/tempo");
679
1432
  const privateKey = this.walletData.privateKey;
680
- const account = privateKeyToAccount(privateKey);
1433
+ const account = privateKeyToAccount2(privateKey);
681
1434
  console.log(`[MoltsPay] Making MPP request to: ${url}`);
682
1435
  console.log(`[MoltsPay] Using account: ${account.address}`);
683
1436
  const initResponse = await fetch(url, {
@@ -774,24 +1527,16 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
774
1527
 
775
1528
  // src/server/index.ts
776
1529
  init_cjs_shims();
777
- var import_fs3 = require("fs");
1530
+ var import_fs4 = require("fs");
778
1531
  var import_http = require("http");
779
1532
  var path2 = __toESM(require("path"));
780
1533
 
781
1534
  // src/facilitators/index.ts
782
1535
  init_cjs_shims();
783
1536
 
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
1537
  // src/facilitators/cdp.ts
793
1538
  init_cjs_shims();
794
- var import_fs2 = require("fs");
1539
+ var import_fs3 = require("fs");
795
1540
  var path = __toESM(require("path"));
796
1541
  var X402_VERSION2 = 2;
797
1542
  var CDP_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
@@ -802,9 +1547,9 @@ function loadEnvFile() {
802
1547
  path.join(process.env.HOME || "", ".moltspay", ".env")
803
1548
  ];
804
1549
  for (const envPath of envPaths) {
805
- if ((0, import_fs2.existsSync)(envPath)) {
1550
+ if ((0, import_fs3.existsSync)(envPath)) {
806
1551
  try {
807
- const content = (0, import_fs2.readFileSync)(envPath, "utf-8");
1552
+ const content = (0, import_fs3.readFileSync)(envPath, "utf-8");
808
1553
  for (const line of content.split("\n")) {
809
1554
  const trimmed = line.trim();
810
1555
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -1042,16 +1787,278 @@ var TempoFacilitator = class extends BaseFacilitator {
1042
1787
  }
1043
1788
  return { healthy: true, latencyMs: Date.now() - start };
1044
1789
  } catch (error) {
1045
- return { healthy: false, error: String(error) };
1790
+ return { healthy: false, error: String(error) };
1791
+ }
1792
+ }
1793
+ async verify(paymentPayload, requirements) {
1794
+ try {
1795
+ const tempoPayload = paymentPayload.payload;
1796
+ if (!tempoPayload?.txHash) {
1797
+ return { valid: false, error: "Missing txHash in payment payload" };
1798
+ }
1799
+ const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
1800
+ if (!receipt) {
1801
+ return { valid: false, error: "Transaction not found" };
1802
+ }
1803
+ if (receipt.status !== "0x1") {
1804
+ return { valid: false, error: "Transaction failed" };
1805
+ }
1806
+ const transferLog = receipt.logs.find(
1807
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC
1808
+ );
1809
+ if (!transferLog) {
1810
+ return { valid: false, error: "No Transfer event found" };
1811
+ }
1812
+ const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
1813
+ const expectedTo = requirements.payTo.toLowerCase();
1814
+ if (toAddress !== expectedTo) {
1815
+ return {
1816
+ valid: false,
1817
+ error: `Wrong recipient: ${toAddress}, expected ${expectedTo}`
1818
+ };
1819
+ }
1820
+ const amount = BigInt(transferLog.data);
1821
+ const expectedAmount = BigInt(requirements.amount);
1822
+ if (amount < expectedAmount) {
1823
+ return {
1824
+ valid: false,
1825
+ error: `Insufficient amount: ${amount}, expected ${expectedAmount}`
1826
+ };
1827
+ }
1828
+ const tokenAddress = transferLog.address.toLowerCase();
1829
+ const expectedToken = requirements.asset.toLowerCase();
1830
+ if (tokenAddress !== expectedToken) {
1831
+ return {
1832
+ valid: false,
1833
+ error: `Wrong token: ${tokenAddress}, expected ${expectedToken}`
1834
+ };
1835
+ }
1836
+ return {
1837
+ valid: true,
1838
+ details: {
1839
+ txHash: tempoPayload.txHash,
1840
+ from: "0x" + transferLog.topics[1].slice(26),
1841
+ to: toAddress,
1842
+ amount: amount.toString(),
1843
+ token: tokenAddress
1844
+ }
1845
+ };
1846
+ } catch (error) {
1847
+ return { valid: false, error: `Verification failed: ${error}` };
1848
+ }
1849
+ }
1850
+ async settle(paymentPayload, requirements) {
1851
+ const verifyResult = await this.verify(paymentPayload, requirements);
1852
+ if (!verifyResult.valid) {
1853
+ return { success: false, error: verifyResult.error };
1854
+ }
1855
+ const tempoPayload = paymentPayload.payload;
1856
+ return {
1857
+ success: true,
1858
+ transaction: tempoPayload.txHash,
1859
+ status: "settled"
1860
+ };
1861
+ }
1862
+ async getTransactionReceipt(txHash) {
1863
+ const response = await fetch(this.rpcUrl, {
1864
+ method: "POST",
1865
+ headers: { "Content-Type": "application/json" },
1866
+ body: JSON.stringify({
1867
+ jsonrpc: "2.0",
1868
+ method: "eth_getTransactionReceipt",
1869
+ params: [txHash],
1870
+ id: 1
1871
+ })
1872
+ });
1873
+ const data = await response.json();
1874
+ return data.result;
1875
+ }
1876
+ };
1877
+
1878
+ // src/facilitators/bnb.ts
1879
+ init_cjs_shims();
1880
+ var import_accounts = require("viem/accounts");
1881
+ var TRANSFER_EVENT_TOPIC2 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
1882
+ var EIP712_DOMAIN = {
1883
+ name: "MoltsPay",
1884
+ version: "1"
1885
+ };
1886
+ var INTENT_TYPES = {
1887
+ PaymentIntent: [
1888
+ { name: "from", type: "address" },
1889
+ { name: "to", type: "address" },
1890
+ { name: "amount", type: "uint256" },
1891
+ { name: "token", type: "address" },
1892
+ { name: "service", type: "string" },
1893
+ { name: "nonce", type: "uint256" },
1894
+ { name: "deadline", type: "uint256" }
1895
+ ]
1896
+ };
1897
+ var BNBFacilitator = class extends BaseFacilitator {
1898
+ name = "bnb";
1899
+ displayName = "BNB Smart Chain";
1900
+ supportedNetworks = ["eip155:56", "eip155:97"];
1901
+ // Mainnet + Testnet
1902
+ serverPrivateKey;
1903
+ spenderAddress = null;
1904
+ chainConfigs;
1905
+ constructor(serverPrivateKey) {
1906
+ super();
1907
+ this.serverPrivateKey = serverPrivateKey || process.env.BNB_SERVER_PRIVATE_KEY || "";
1908
+ if (this.serverPrivateKey) {
1909
+ const key = this.serverPrivateKey.startsWith("0x") ? this.serverPrivateKey : `0x${this.serverPrivateKey}`;
1910
+ const account = (0, import_accounts.privateKeyToAccount)(key);
1911
+ this.spenderAddress = account.address;
1912
+ }
1913
+ this.chainConfigs = {
1914
+ 56: { rpc: CHAINS.bnb.rpc, chain: CHAINS.bnb },
1915
+ 97: { rpc: CHAINS.bnb_testnet.rpc, chain: CHAINS.bnb_testnet }
1916
+ };
1917
+ }
1918
+ async healthCheck() {
1919
+ const start = Date.now();
1920
+ try {
1921
+ const response = await fetch(this.chainConfigs[56].rpc, {
1922
+ method: "POST",
1923
+ headers: { "Content-Type": "application/json" },
1924
+ body: JSON.stringify({
1925
+ jsonrpc: "2.0",
1926
+ method: "eth_chainId",
1927
+ params: [],
1928
+ id: 1
1929
+ })
1930
+ });
1931
+ const data = await response.json();
1932
+ const chainId = parseInt(data.result, 16);
1933
+ if (chainId !== 56) {
1934
+ return { healthy: false, error: `Wrong chainId: ${chainId}` };
1935
+ }
1936
+ return { healthy: true, latencyMs: Date.now() - start };
1937
+ } catch (error) {
1938
+ return { healthy: false, error: String(error) };
1939
+ }
1940
+ }
1941
+ /**
1942
+ * Verify a payment intent signature (before service execution)
1943
+ *
1944
+ * This verifies:
1945
+ * 1. Signature is valid for the intent
1946
+ * 2. Client has approved server wallet
1947
+ * 3. Client has sufficient balance
1948
+ * 4. Intent hasn't expired
1949
+ */
1950
+ async verify(paymentPayload, requirements) {
1951
+ try {
1952
+ const bnbPayload = paymentPayload.payload;
1953
+ if (!bnbPayload?.intent) {
1954
+ return { valid: false, error: "Missing intent in payment payload" };
1955
+ }
1956
+ const { intent, chainId } = bnbPayload;
1957
+ const config = this.chainConfigs[chainId];
1958
+ if (!config) {
1959
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
1960
+ }
1961
+ if (intent.deadline < Date.now()) {
1962
+ return { valid: false, error: "Intent expired" };
1963
+ }
1964
+ const recoveredAddress = await this.recoverIntentSigner(intent, chainId);
1965
+ if (recoveredAddress.toLowerCase() !== intent.from.toLowerCase()) {
1966
+ return { valid: false, error: "Invalid signature" };
1967
+ }
1968
+ if (intent.to.toLowerCase() !== requirements.payTo.toLowerCase()) {
1969
+ return { valid: false, error: `Wrong recipient: ${intent.to}` };
1970
+ }
1971
+ if (BigInt(intent.amount) < BigInt(requirements.amount)) {
1972
+ return { valid: false, error: `Insufficient amount: ${intent.amount}` };
1973
+ }
1974
+ if (intent.token.toLowerCase() !== requirements.asset.toLowerCase()) {
1975
+ return { valid: false, error: `Wrong token: ${intent.token}` };
1976
+ }
1977
+ const serverAddress = await this.getServerAddress();
1978
+ const allowance = await this.getAllowance(intent.from, serverAddress, intent.token, config.rpc);
1979
+ if (BigInt(allowance) < BigInt(intent.amount)) {
1980
+ return { valid: false, error: "Insufficient allowance. Run: npx moltspay init --chain bnb" };
1981
+ }
1982
+ const balance = await this.getBalance(intent.from, intent.token, config.rpc);
1983
+ if (BigInt(balance) < BigInt(intent.amount)) {
1984
+ return { valid: false, error: "Insufficient balance" };
1985
+ }
1986
+ return {
1987
+ valid: true,
1988
+ details: {
1989
+ from: intent.from,
1990
+ to: intent.to,
1991
+ amount: intent.amount,
1992
+ token: intent.token,
1993
+ service: intent.service,
1994
+ nonce: intent.nonce,
1995
+ deadline: intent.deadline
1996
+ }
1997
+ };
1998
+ } catch (error) {
1999
+ return { valid: false, error: `Verification failed: ${error}` };
1046
2000
  }
1047
2001
  }
1048
- async verify(paymentPayload, requirements) {
2002
+ /**
2003
+ * Settle a payment by executing transferFrom
2004
+ *
2005
+ * This is called AFTER the service has been successfully delivered.
2006
+ * Server pays gas, transfers tokens from client to provider.
2007
+ */
2008
+ async settle(paymentPayload, requirements) {
2009
+ if (!this.serverPrivateKey) {
2010
+ return { success: false, error: "Server wallet not configured (BNB_SERVER_PRIVATE_KEY)" };
2011
+ }
1049
2012
  try {
1050
- const tempoPayload = paymentPayload.payload;
1051
- if (!tempoPayload?.txHash) {
1052
- return { valid: false, error: "Missing txHash in payment payload" };
2013
+ const verifyResult = await this.verify(paymentPayload, requirements);
2014
+ if (!verifyResult.valid) {
2015
+ return { success: false, error: verifyResult.error };
1053
2016
  }
1054
- const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
2017
+ const bnbPayload = paymentPayload.payload;
2018
+ const { intent, chainId } = bnbPayload;
2019
+ const config = this.chainConfigs[chainId];
2020
+ const txHash = await this.executeTransferFrom(
2021
+ intent.from,
2022
+ intent.to,
2023
+ intent.amount,
2024
+ intent.token,
2025
+ config.rpc
2026
+ );
2027
+ return {
2028
+ success: true,
2029
+ transaction: txHash,
2030
+ status: "settled"
2031
+ };
2032
+ } catch (error) {
2033
+ return { success: false, error: `Settlement failed: ${error}` };
2034
+ }
2035
+ }
2036
+ /**
2037
+ * Check if client has approved the server wallet
2038
+ */
2039
+ async checkApproval(clientAddress, token, chainId) {
2040
+ const config = this.chainConfigs[chainId];
2041
+ if (!config) {
2042
+ throw new Error(`Unsupported chainId: ${chainId}`);
2043
+ }
2044
+ const serverAddress = await this.getServerAddress();
2045
+ const allowance = await this.getAllowance(clientAddress, serverAddress, token, config.rpc);
2046
+ const minAllowance = BigInt("1000000000000000000000");
2047
+ return {
2048
+ approved: BigInt(allowance) >= minAllowance,
2049
+ allowance
2050
+ };
2051
+ }
2052
+ /**
2053
+ * Verify a completed transaction (for checking past payments)
2054
+ */
2055
+ async verifyTransaction(txHash, expected, chainId) {
2056
+ const config = this.chainConfigs[chainId];
2057
+ if (!config) {
2058
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
2059
+ }
2060
+ try {
2061
+ const receipt = await this.getTransactionReceipt(txHash, config.rpc);
1055
2062
  if (!receipt) {
1056
2063
  return { valid: false, error: "Transaction not found" };
1057
2064
  }
@@ -1059,63 +2066,117 @@ var TempoFacilitator = class extends BaseFacilitator {
1059
2066
  return { valid: false, error: "Transaction failed" };
1060
2067
  }
1061
2068
  const transferLog = receipt.logs.find(
1062
- (log) => log.topics[0] === TRANSFER_EVENT_TOPIC
2069
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC2 && log.address.toLowerCase() === expected.token.toLowerCase()
1063
2070
  );
1064
2071
  if (!transferLog) {
1065
2072
  return { valid: false, error: "No Transfer event found" };
1066
2073
  }
1067
2074
  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
- };
2075
+ if (toAddress !== expected.to.toLowerCase()) {
2076
+ return { valid: false, error: `Wrong recipient: ${toAddress}` };
1074
2077
  }
1075
2078
  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
- };
2079
+ if (amount < BigInt(expected.amount)) {
2080
+ return { valid: false, error: `Insufficient amount: ${amount}` };
1090
2081
  }
1091
2082
  return {
1092
2083
  valid: true,
1093
2084
  details: {
1094
- txHash: tempoPayload.txHash,
2085
+ txHash,
1095
2086
  from: "0x" + transferLog.topics[1].slice(26),
1096
2087
  to: toAddress,
1097
2088
  amount: amount.toString(),
1098
- token: tokenAddress
2089
+ token: transferLog.address
1099
2090
  }
1100
2091
  };
1101
2092
  } catch (error) {
1102
2093
  return { valid: false, error: `Verification failed: ${error}` };
1103
2094
  }
1104
2095
  }
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"
2096
+ // ==================== Private Methods ====================
2097
+ /**
2098
+ * Get the server's spender address (public, for 402 responses)
2099
+ * Returns cached value computed at construction time.
2100
+ */
2101
+ getSpenderAddress() {
2102
+ return this.spenderAddress;
2103
+ }
2104
+ async getServerAddress() {
2105
+ const { ethers: ethers3 } = await import("ethers");
2106
+ const wallet = new ethers3.Wallet(this.serverPrivateKey);
2107
+ return wallet.address;
2108
+ }
2109
+ async recoverIntentSigner(intent, chainId) {
2110
+ const { ethers: ethers3 } = await import("ethers");
2111
+ const domain = {
2112
+ ...EIP712_DOMAIN,
2113
+ chainId
1115
2114
  };
2115
+ const message = {
2116
+ from: intent.from,
2117
+ to: intent.to,
2118
+ amount: intent.amount,
2119
+ token: intent.token,
2120
+ service: intent.service,
2121
+ nonce: intent.nonce,
2122
+ deadline: intent.deadline
2123
+ };
2124
+ const recoveredAddress = ethers3.verifyTypedData(
2125
+ domain,
2126
+ INTENT_TYPES,
2127
+ message,
2128
+ intent.signature
2129
+ );
2130
+ return recoveredAddress;
1116
2131
  }
1117
- async getTransactionReceipt(txHash) {
1118
- const response = await fetch(this.rpcUrl, {
2132
+ async getAllowance(owner, spender, token, rpcUrl) {
2133
+ const selector = "0xdd62ed3e";
2134
+ const ownerPadded = owner.toLowerCase().replace("0x", "").padStart(64, "0");
2135
+ const spenderPadded = spender.toLowerCase().replace("0x", "").padStart(64, "0");
2136
+ const data = selector + ownerPadded + spenderPadded;
2137
+ const response = await fetch(rpcUrl, {
2138
+ method: "POST",
2139
+ headers: { "Content-Type": "application/json" },
2140
+ body: JSON.stringify({
2141
+ jsonrpc: "2.0",
2142
+ method: "eth_call",
2143
+ params: [{ to: token, data }, "latest"],
2144
+ id: 1
2145
+ })
2146
+ });
2147
+ const result = await response.json();
2148
+ return result.result || "0x0";
2149
+ }
2150
+ async getBalance(account, token, rpcUrl) {
2151
+ const selector = "0x70a08231";
2152
+ const accountPadded = account.toLowerCase().replace("0x", "").padStart(64, "0");
2153
+ const data = selector + accountPadded;
2154
+ const response = await fetch(rpcUrl, {
2155
+ method: "POST",
2156
+ headers: { "Content-Type": "application/json" },
2157
+ body: JSON.stringify({
2158
+ jsonrpc: "2.0",
2159
+ method: "eth_call",
2160
+ params: [{ to: token, data }, "latest"],
2161
+ id: 1
2162
+ })
2163
+ });
2164
+ const result = await response.json();
2165
+ return result.result || "0x0";
2166
+ }
2167
+ async executeTransferFrom(from, to, amount, token, rpcUrl) {
2168
+ const { ethers: ethers3 } = await import("ethers");
2169
+ const provider = new ethers3.JsonRpcProvider(rpcUrl);
2170
+ const wallet = new ethers3.Wallet(this.serverPrivateKey, provider);
2171
+ const tokenContract = new ethers3.Contract(token, [
2172
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)"
2173
+ ], wallet);
2174
+ const tx = await tokenContract.transferFrom(from, to, amount);
2175
+ const receipt = await tx.wait();
2176
+ return receipt.hash;
2177
+ }
2178
+ async getTransactionReceipt(txHash, rpcUrl) {
2179
+ const response = await fetch(rpcUrl, {
1119
2180
  method: "POST",
1120
2181
  headers: { "Content-Type": "application/json" },
1121
2182
  body: JSON.stringify({
@@ -1132,6 +2193,8 @@ var TempoFacilitator = class extends BaseFacilitator {
1132
2193
 
1133
2194
  // src/facilitators/registry.ts
1134
2195
  init_cjs_shims();
2196
+ var import_web35 = require("@solana/web3.js");
2197
+ var import_bs582 = __toESM(require("bs58"));
1135
2198
  var FacilitatorRegistry = class {
1136
2199
  factories = /* @__PURE__ */ new Map();
1137
2200
  instances = /* @__PURE__ */ new Map();
@@ -1140,7 +2203,20 @@ var FacilitatorRegistry = class {
1140
2203
  constructor(selection) {
1141
2204
  this.registerFactory("cdp", (config) => new CDPFacilitator(config));
1142
2205
  this.registerFactory("tempo", () => new TempoFacilitator());
1143
- this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
2206
+ this.registerFactory("bnb", (config) => new BNBFacilitator(config?.serverPrivateKey));
2207
+ this.registerFactory("solana", (config) => {
2208
+ let feePayerKeypair;
2209
+ const feePayerKey = config?.feePayerPrivateKey || process.env.SOLANA_FEE_PAYER_KEY;
2210
+ if (feePayerKey) {
2211
+ try {
2212
+ feePayerKeypair = import_web35.Keypair.fromSecretKey(import_bs582.default.decode(feePayerKey));
2213
+ } catch (e) {
2214
+ console.warn(`[SolanaFacilitator] Invalid fee payer key: ${e.message}`);
2215
+ }
2216
+ }
2217
+ return new SolanaFacilitator({ feePayerKeypair });
2218
+ });
2219
+ this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
1144
2220
  }
1145
2221
  /**
1146
2222
  * Register a new facilitator factory
@@ -1387,14 +2463,40 @@ var TOKEN_ADDRESSES = {
1387
2463
  // pathUSD
1388
2464
  USDT: "0x20c0000000000000000000000000000000000001"
1389
2465
  // alphaUSD
2466
+ },
2467
+ // BNB Smart Chain mainnet
2468
+ "eip155:56": {
2469
+ USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
2470
+ USDT: "0x55d398326f99059fF775485246999027B3197955"
2471
+ },
2472
+ // BNB Smart Chain testnet
2473
+ "eip155:97": {
2474
+ USDC: "0x64544969ed7EBf5f083679233325356EbE738930",
2475
+ USDT: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd"
2476
+ },
2477
+ // Solana networks use mint addresses (SPL tokens)
2478
+ "solana:mainnet": {
2479
+ USDC: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
2480
+ // Circle USDC
2481
+ },
2482
+ "solana:devnet": {
2483
+ USDC: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
2484
+ // Devnet USDC
1390
2485
  }
1391
2486
  };
1392
2487
  var CHAIN_TO_NETWORK = {
1393
2488
  "base": "eip155:8453",
1394
2489
  "base_sepolia": "eip155:84532",
1395
2490
  "polygon": "eip155:137",
1396
- "tempo_moderato": "eip155:42431"
2491
+ "tempo_moderato": "eip155:42431",
2492
+ "bnb": "eip155:56",
2493
+ "bnb_testnet": "eip155:97",
2494
+ "solana": "solana:mainnet",
2495
+ "solana_devnet": "solana:devnet"
1397
2496
  };
2497
+ function isSolanaNetwork(network) {
2498
+ return network.startsWith("solana:");
2499
+ }
1398
2500
  var TOKEN_DOMAINS = {
1399
2501
  // Base mainnet
1400
2502
  "eip155:8453": {
@@ -1416,6 +2518,16 @@ var TOKEN_DOMAINS = {
1416
2518
  "eip155:42431": {
1417
2519
  USDC: { name: "pathUSD", version: "1" },
1418
2520
  USDT: { name: "alphaUSD", version: "1" }
2521
+ },
2522
+ // BNB Smart Chain mainnet
2523
+ "eip155:56": {
2524
+ USDC: { name: "USD Coin", version: "1" },
2525
+ USDT: { name: "Tether USD", version: "1" }
2526
+ },
2527
+ // BNB Smart Chain testnet
2528
+ "eip155:97": {
2529
+ USDC: { name: "USD Coin", version: "1" },
2530
+ USDT: { name: "Tether USD", version: "1" }
1419
2531
  }
1420
2532
  };
1421
2533
  function getTokenDomain(network, token) {
@@ -1431,9 +2543,9 @@ function loadEnvFile2() {
1431
2543
  path2.join(process.env.HOME || "", ".moltspay", ".env")
1432
2544
  ];
1433
2545
  for (const envPath of envPaths) {
1434
- if ((0, import_fs3.existsSync)(envPath)) {
2546
+ if ((0, import_fs4.existsSync)(envPath)) {
1435
2547
  try {
1436
- const content = (0, import_fs3.readFileSync)(envPath, "utf-8");
2548
+ const content = (0, import_fs4.readFileSync)(envPath, "utf-8");
1437
2549
  for (const line of content.split("\n")) {
1438
2550
  const trimmed = line.trim();
1439
2551
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -1464,7 +2576,7 @@ var MoltsPayServer = class {
1464
2576
  useMainnet;
1465
2577
  constructor(servicesPath, options = {}) {
1466
2578
  loadEnvFile2();
1467
- const content = (0, import_fs3.readFileSync)(servicesPath, "utf-8");
2579
+ const content = (0, import_fs4.readFileSync)(servicesPath, "utf-8");
1468
2580
  this.manifest = JSON.parse(content);
1469
2581
  this.options = {
1470
2582
  port: options.port || 3e3,
@@ -1473,7 +2585,7 @@ var MoltsPayServer = class {
1473
2585
  };
1474
2586
  this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
1475
2587
  this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
1476
- const defaultFallback = ["tempo"];
2588
+ const defaultFallback = ["tempo", "bnb", "solana"];
1477
2589
  const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
1478
2590
  const facilitatorConfig = options.facilitators || {
1479
2591
  primary: process.env.FACILITATOR_PRIMARY || "cdp",
@@ -1516,12 +2628,20 @@ var MoltsPayServer = class {
1516
2628
  */
1517
2629
  getProviderChains() {
1518
2630
  const provider = this.manifest.provider;
2631
+ const getWalletForChain = (chainName, explicitWallet) => {
2632
+ if (explicitWallet) return explicitWallet;
2633
+ if ((chainName === "solana" || chainName === "solana_devnet") && provider.solana_wallet) {
2634
+ return provider.solana_wallet;
2635
+ }
2636
+ return provider.wallet;
2637
+ };
1519
2638
  if (provider.chains && provider.chains.length > 0) {
1520
2639
  return provider.chains.map((c) => {
1521
2640
  const chainName = typeof c === "string" ? c : c.chain;
2641
+ const explicitWallet = typeof c === "object" ? c.wallet : null;
1522
2642
  return {
1523
2643
  network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
1524
- wallet: (typeof c === "object" ? c.wallet : null) || provider.wallet,
2644
+ wallet: getWalletForChain(chainName, explicitWallet || void 0),
1525
2645
  tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
1526
2646
  };
1527
2647
  });
@@ -1530,7 +2650,7 @@ var MoltsPayServer = class {
1530
2650
  const network = CHAIN_TO_NETWORK[chain] || this.networkId;
1531
2651
  return [{
1532
2652
  network,
1533
- wallet: provider.wallet,
2653
+ wallet: getWalletForChain(chain),
1534
2654
  tokens: ["USDC"]
1535
2655
  }];
1536
2656
  }
@@ -1601,7 +2721,8 @@ var MoltsPayServer = class {
1601
2721
  }
1602
2722
  const body = await this.readBody(req);
1603
2723
  const paymentHeader = req.headers[PAYMENT_HEADER2];
1604
- return await this.handleProxy(body, paymentHeader, res);
2724
+ const authHeader = req.headers[MPP_AUTH_HEADER];
2725
+ return await this.handleProxy(body, paymentHeader, authHeader, res);
1605
2726
  }
1606
2727
  const servicePath = url.pathname.replace(/^\//, "");
1607
2728
  const skill = this.skills.get(servicePath);
@@ -1638,7 +2759,9 @@ var MoltsPayServer = class {
1638
2759
  name: this.manifest.provider.name,
1639
2760
  description: this.manifest.provider.description,
1640
2761
  wallet: this.manifest.provider.wallet,
1641
- chain: this.manifest.provider.chain || "base"
2762
+ chain: this.manifest.provider.chain || "base",
2763
+ solana_wallet: this.manifest.provider.solana_wallet,
2764
+ chains: this.manifest.provider.chains
1642
2765
  },
1643
2766
  services,
1644
2767
  endpoints: {
@@ -1751,6 +2874,21 @@ var MoltsPayServer = class {
1751
2874
  });
1752
2875
  }
1753
2876
  console.log(`[MoltsPay] Verified by ${verifyResult.facilitator}`);
2877
+ const isSolana = isSolanaNetwork(paymentNetwork);
2878
+ let settlement = null;
2879
+ if (isSolana) {
2880
+ console.log(`[MoltsPay] Solana detected - settling payment FIRST (blockhash expiry protection)`);
2881
+ try {
2882
+ settlement = await this.registry.settle(payment, requirements);
2883
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2884
+ } catch (err) {
2885
+ console.error("[MoltsPay] Solana settlement failed:", err.message);
2886
+ return this.sendJson(res, 402, {
2887
+ error: "Payment settlement failed",
2888
+ message: err.message
2889
+ });
2890
+ }
2891
+ }
1754
2892
  const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
1755
2893
  console.log(`[MoltsPay] Executing skill: ${service} (timeout: ${timeoutSeconds}s)`);
1756
2894
  let result;
@@ -1765,16 +2903,19 @@ var MoltsPayServer = class {
1765
2903
  console.error("[MoltsPay] Skill execution failed:", err.message);
1766
2904
  return this.sendJson(res, 500, {
1767
2905
  error: "Service execution failed",
1768
- message: err.message
2906
+ message: err.message,
2907
+ paymentSettled: isSolana ? true : false,
2908
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
1769
2909
  });
1770
2910
  }
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);
2911
+ if (!isSolana) {
2912
+ console.log(`[MoltsPay] Skill succeeded, settling payment...`);
2913
+ try {
2914
+ settlement = await this.registry.settle(payment, requirements);
2915
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2916
+ } catch (err) {
2917
+ console.error("[MoltsPay] Settlement failed:", err.message);
2918
+ }
1778
2919
  }
1779
2920
  const responseHeaders = {};
1780
2921
  if (settlement?.success) {
@@ -2050,7 +3191,7 @@ var MoltsPayServer = class {
2050
3191
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
2051
3192
  const tokenAddress = tokenAddresses[selectedToken];
2052
3193
  const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
2053
- return {
3194
+ const requirements = {
2054
3195
  scheme: "exact",
2055
3196
  network: selectedNetwork,
2056
3197
  asset: tokenAddress,
@@ -2059,6 +3200,27 @@ var MoltsPayServer = class {
2059
3200
  maxTimeoutSeconds: 300,
2060
3201
  extra: tokenDomain
2061
3202
  };
3203
+ if (selectedNetwork === "solana:mainnet" || selectedNetwork === "solana:devnet") {
3204
+ const solanaFacilitator = this.registry.get("solana");
3205
+ const feePayerPubkey = solanaFacilitator?.getFeePayerPubkey?.();
3206
+ if (feePayerPubkey) {
3207
+ requirements.extra = {
3208
+ ...requirements.extra || {},
3209
+ solanaFeePayer: feePayerPubkey
3210
+ };
3211
+ }
3212
+ }
3213
+ if (selectedNetwork === "eip155:56" || selectedNetwork === "eip155:97") {
3214
+ const bnbFacilitator = this.registry.get("bnb");
3215
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
3216
+ if (spenderAddress) {
3217
+ requirements.extra = {
3218
+ ...requirements.extra || {},
3219
+ bnbSpender: spenderAddress
3220
+ };
3221
+ }
3222
+ }
3223
+ return requirements;
2062
3224
  }
2063
3225
  /**
2064
3226
  * Detect which token is being used in the payment
@@ -2124,31 +3286,42 @@ var MoltsPayServer = class {
2124
3286
  /**
2125
3287
  * POST /proxy - Handle payment for external services (moltspay-creators)
2126
3288
  *
2127
- * This endpoint allows other services to delegate x402 payment handling.
3289
+ * This endpoint allows other services to delegate x402/MPP payment handling.
2128
3290
  * It does NOT execute any skill - just handles payment verification/settlement.
2129
3291
  *
2130
3292
  * Request body:
2131
3293
  * { wallet, amount, currency, chain, memo, serviceId, description }
2132
3294
  *
2133
- * Without X-Payment header: returns 402 with payment requirements
2134
- * With X-Payment header: verifies payment and returns result
3295
+ * For x402 (base, polygon, base_sepolia):
3296
+ * Without X-Payment header: returns 402 with X-Payment-Required
3297
+ * With X-Payment header: verifies payment via CDP
3298
+ *
3299
+ * For MPP (tempo_moderato):
3300
+ * Without Authorization header: returns 402 with WWW-Authenticate
3301
+ * With Authorization: Payment header: verifies tx on Tempo chain
2135
3302
  */
2136
- async handleProxy(body, paymentHeader, res) {
3303
+ async handleProxy(body, paymentHeader, authHeader, res) {
2137
3304
  const { wallet, amount, currency, chain, memo, serviceId, description } = body;
2138
3305
  if (!wallet || !amount) {
2139
3306
  return this.sendJson(res, 400, { error: "Missing required fields: wallet, amount" });
2140
3307
  }
2141
- if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
2142
- return this.sendJson(res, 400, { error: "Invalid wallet address format" });
3308
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet", "solana", "solana_devnet"];
3309
+ if (chain && !supportedChains.includes(chain)) {
3310
+ return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
3311
+ }
3312
+ const isSolanaChain = chain === "solana" || chain === "solana_devnet";
3313
+ const isValidEvmAddress = /^0x[a-fA-F0-9]{40}$/.test(wallet);
3314
+ const isValidSolanaAddress2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(wallet);
3315
+ if (isSolanaChain && !isValidSolanaAddress2) {
3316
+ return this.sendJson(res, 400, { error: "Invalid Solana wallet address format" });
3317
+ }
3318
+ if (!isSolanaChain && !isValidEvmAddress) {
3319
+ return this.sendJson(res, 400, { error: "Invalid EVM wallet address format" });
2143
3320
  }
2144
3321
  const amountNum = parseFloat(amount);
2145
3322
  if (isNaN(amountNum) || amountNum <= 0) {
2146
3323
  return this.sendJson(res, 400, { error: "Invalid amount" });
2147
3324
  }
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
3325
  const proxyConfig = {
2153
3326
  id: serviceId || "proxy",
2154
3327
  name: description || "Proxy Payment",
@@ -2160,6 +3333,9 @@ var MoltsPayServer = class {
2160
3333
  input: {},
2161
3334
  output: {}
2162
3335
  };
3336
+ if (chain === "tempo_moderato") {
3337
+ return await this.handleProxyMPP(body, proxyConfig, authHeader, res);
3338
+ }
2163
3339
  const requirements = this.buildProxyPaymentRequirements(proxyConfig, wallet, currency, chain);
2164
3340
  if (!paymentHeader) {
2165
3341
  return this.sendProxyPaymentRequired(proxyConfig, wallet, memo, chain, res);
@@ -2171,37 +3347,225 @@ var MoltsPayServer = class {
2171
3347
  } catch {
2172
3348
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
2173
3349
  }
2174
- if (payment.x402Version !== X402_VERSION3) {
2175
- return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
3350
+ if (payment.x402Version !== X402_VERSION3) {
3351
+ return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
3352
+ }
3353
+ const scheme = payment.accepted?.scheme || payment.scheme;
3354
+ const network = payment.accepted?.network || payment.network;
3355
+ if (scheme !== "exact") {
3356
+ return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
3357
+ }
3358
+ const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
3359
+ if (network !== expectedNetwork) {
3360
+ return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
3361
+ }
3362
+ console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
3363
+ const verifyResult = await this.registry.verify(payment, requirements);
3364
+ if (!verifyResult.valid) {
3365
+ return this.sendJson(res, 402, {
3366
+ success: false,
3367
+ error: `Payment verification failed: ${verifyResult.error}`,
3368
+ facilitator: verifyResult.facilitator
3369
+ });
3370
+ }
3371
+ console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
3372
+ const { execute, service, params } = body;
3373
+ if (execute && service) {
3374
+ const skill = this.skills.get(service);
3375
+ if (!skill) {
3376
+ console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
3377
+ return this.sendJson(res, 404, {
3378
+ success: false,
3379
+ paymentSettled: false,
3380
+ error: `Service not found: ${service}`
3381
+ });
3382
+ }
3383
+ const isSolana = isSolanaNetwork(network);
3384
+ let settlement2 = null;
3385
+ if (isSolana) {
3386
+ console.log(`[MoltsPay] /proxy: Solana detected - settling payment FIRST`);
3387
+ try {
3388
+ settlement2 = await this.registry.settle(payment, requirements);
3389
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
3390
+ if (!settlement2.success) {
3391
+ console.error(`[MoltsPay] /proxy: Solana settlement failed: ${settlement2.error}`);
3392
+ return this.sendJson(res, 402, {
3393
+ success: false,
3394
+ paymentSettled: false,
3395
+ error: `Payment settlement failed: ${settlement2.error || "Unknown error"}`
3396
+ });
3397
+ }
3398
+ } catch (err) {
3399
+ console.error("[MoltsPay] /proxy: Solana settlement failed:", err.message);
3400
+ return this.sendJson(res, 402, {
3401
+ success: false,
3402
+ paymentSettled: false,
3403
+ error: `Payment settlement failed: ${err.message}`
3404
+ });
3405
+ }
3406
+ } else {
3407
+ console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
3408
+ }
3409
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
3410
+ let result;
3411
+ try {
3412
+ result = await Promise.race([
3413
+ skill.handler(params || {}),
3414
+ new Promise(
3415
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
3416
+ )
3417
+ ]);
3418
+ console.log(`[MoltsPay] /proxy: Skill succeeded`);
3419
+ } catch (err) {
3420
+ console.error(`[MoltsPay] /proxy: Skill failed: ${err.message}`);
3421
+ return this.sendJson(res, 500, {
3422
+ success: false,
3423
+ paymentSettled: isSolana ? true : false,
3424
+ error: `Service execution failed: ${err.message}`,
3425
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
3426
+ });
3427
+ }
3428
+ if (!isSolana) {
3429
+ console.log(`[MoltsPay] /proxy: Settling payment...`);
3430
+ try {
3431
+ settlement2 = await this.registry.settle(payment, requirements);
3432
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
3433
+ } catch (err) {
3434
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
3435
+ return this.sendJson(res, 200, {
3436
+ success: true,
3437
+ verified: true,
3438
+ settled: false,
3439
+ settlementError: err.message,
3440
+ from: payment.payload?.authorization?.from,
3441
+ paidTo: wallet,
3442
+ amount: amountNum,
3443
+ currency: currency || "USDC",
3444
+ memo,
3445
+ result
3446
+ });
3447
+ }
3448
+ }
3449
+ return this.sendJson(res, 200, {
3450
+ success: true,
3451
+ verified: true,
3452
+ settled: settlement2?.success || false,
3453
+ txHash: settlement2?.transaction,
3454
+ from: payment.payload?.authorization?.from,
3455
+ paidTo: wallet,
3456
+ amount: amountNum,
3457
+ currency: currency || "USDC",
3458
+ facilitator: settlement2?.facilitator,
3459
+ memo,
3460
+ result
3461
+ });
3462
+ }
3463
+ console.log(`[MoltsPay] /proxy: Settling payment (no execution)...`);
3464
+ let settlement = null;
3465
+ try {
3466
+ settlement = await this.registry.settle(payment, requirements);
3467
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
3468
+ } catch (err) {
3469
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
3470
+ return this.sendJson(res, 500, {
3471
+ success: false,
3472
+ error: `Settlement failed: ${err.message}`
3473
+ });
3474
+ }
3475
+ this.sendJson(res, 200, {
3476
+ success: true,
3477
+ verified: true,
3478
+ settled: settlement?.success || false,
3479
+ txHash: settlement?.transaction,
3480
+ from: payment.payload?.authorization?.from,
3481
+ // Buyer's wallet address
3482
+ paidTo: wallet,
3483
+ amount: amountNum,
3484
+ currency: currency || "USDC",
3485
+ facilitator: settlement?.facilitator,
3486
+ memo
3487
+ });
3488
+ }
3489
+ /**
3490
+ * Handle MPP payment flow for /proxy endpoint (tempo_moderato chain)
3491
+ */
3492
+ async handleProxyMPP(body, config, authHeader, res) {
3493
+ const { wallet, amount, memo, serviceId } = body;
3494
+ const amountNum = parseFloat(amount);
3495
+ const amountInUnits = Math.floor(amountNum * 1e6).toString();
3496
+ if (!authHeader || !authHeader.toLowerCase().startsWith("payment ")) {
3497
+ const challengeId = this.generateChallengeId();
3498
+ const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
3499
+ const mppRequest = {
3500
+ amount: amountInUnits,
3501
+ currency: tokenAddress,
3502
+ methodDetails: {
3503
+ chainId: 42431,
3504
+ feePayer: true
3505
+ },
3506
+ recipient: wallet
3507
+ };
3508
+ const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
3509
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
3510
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
3511
+ res.writeHead(402, {
3512
+ "Content-Type": "application/problem+json",
3513
+ [MPP_WWW_AUTH_HEADER]: wwwAuth
3514
+ });
3515
+ res.end(JSON.stringify({
3516
+ type: "https://paymentauth.org/problems/payment-required",
3517
+ title: "Payment Required",
3518
+ status: 402,
3519
+ detail: `Payment is required (${config.name}).`,
3520
+ service: serviceId || "proxy",
3521
+ price: amountNum,
3522
+ currency: "USDC"
3523
+ }, null, 2));
3524
+ return;
3525
+ }
3526
+ const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
3527
+ if (!credentialMatch) {
3528
+ return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
2176
3529
  }
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}` });
3530
+ let mppCredential;
3531
+ try {
3532
+ const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
3533
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
3534
+ mppCredential = JSON.parse(decoded);
3535
+ } catch (err) {
3536
+ console.error("[MoltsPay] /proxy MPP: Failed to parse credential:", err);
3537
+ return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
2181
3538
  }
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}` });
3539
+ let txHash;
3540
+ if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
3541
+ txHash = mppCredential.payload.hash;
3542
+ } else {
3543
+ return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
2185
3544
  }
2186
- console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
2187
- const verifyResult = await this.registry.verify(payment, requirements);
2188
- if (!verifyResult.valid) {
3545
+ console.log(`[MoltsPay] /proxy MPP: Verifying tx ${txHash} on Tempo...`);
3546
+ const requirements = this.buildPaymentRequirements(config, "eip155:42431", wallet, "USDC");
3547
+ const paymentPayload = {
3548
+ x402Version: X402_VERSION3,
3549
+ scheme: "exact",
3550
+ network: "eip155:42431",
3551
+ payload: { txHash, chainId: 42431 }
3552
+ };
3553
+ const verification = await this.registry.verify(paymentPayload, requirements);
3554
+ if (!verification.valid) {
2189
3555
  return this.sendJson(res, 402, {
2190
- success: false,
2191
- error: `Payment verification failed: ${verifyResult.error}`,
2192
- facilitator: verifyResult.facilitator
3556
+ error: `Payment verification failed: ${verification.error}`
2193
3557
  });
2194
3558
  }
2195
- console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
3559
+ console.log(`[MoltsPay] /proxy MPP: Payment verified by ${verification.facilitator}`);
2196
3560
  const { execute, service, params } = body;
2197
3561
  if (execute && service) {
2198
- console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
3562
+ console.log(`[MoltsPay] /proxy MPP: Executing skill: ${service}`);
2199
3563
  const skill = this.skills.get(service);
2200
3564
  if (!skill) {
2201
- console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
2202
3565
  return this.sendJson(res, 404, {
2203
3566
  success: false,
2204
- paymentSettled: false,
3567
+ paymentSettled: true,
3568
+ // Payment already happened on Tempo
2205
3569
  error: `Service not found: ${service}`
2206
3570
  });
2207
3571
  }
@@ -2214,73 +3578,36 @@ var MoltsPayServer = class {
2214
3578
  (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
2215
3579
  )
2216
3580
  ]);
2217
- console.log(`[MoltsPay] /proxy: Skill succeeded, now settling payment...`);
2218
3581
  } catch (err) {
2219
- console.error(`[MoltsPay] /proxy: Skill failed: ${err.message} - NOT settling`);
3582
+ console.error(`[MoltsPay] /proxy MPP: Skill failed: ${err.message}`);
2220
3583
  return this.sendJson(res, 500, {
2221
3584
  success: false,
2222
- paymentSettled: false,
3585
+ paymentSettled: true,
2223
3586
  error: `Service execution failed: ${err.message}`
2224
3587
  });
2225
3588
  }
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
3589
  return this.sendJson(res, 200, {
2247
3590
  success: true,
2248
3591
  verified: true,
2249
- settled: settlement2?.success || false,
2250
- txHash: settlement2?.transaction,
2251
- from: payment.payload?.authorization?.from,
2252
- // Buyer's wallet address
3592
+ txHash,
3593
+ chain: "tempo_moderato",
2253
3594
  paidTo: wallet,
2254
3595
  amount: amountNum,
2255
- currency: currency || "USDC",
2256
- facilitator: settlement2?.facilitator,
3596
+ currency: "USDC",
3597
+ facilitator: verification.facilitator,
2257
3598
  memo,
2258
3599
  result
2259
3600
  });
2260
3601
  }
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
3602
  this.sendJson(res, 200, {
2274
3603
  success: true,
2275
3604
  verified: true,
2276
- settled: settlement?.success || false,
2277
- txHash: settlement?.transaction,
2278
- from: payment.payload?.authorization?.from,
2279
- // Buyer's wallet address
3605
+ txHash,
3606
+ chain: "tempo_moderato",
2280
3607
  paidTo: wallet,
2281
3608
  amount: amountNum,
2282
- currency: currency || "USDC",
2283
- facilitator: settlement?.facilitator,
3609
+ currency: "USDC",
3610
+ facilitator: verification.facilitator,
2284
3611
  memo
2285
3612
  });
2286
3613
  }
@@ -2295,7 +3622,7 @@ var MoltsPayServer = class {
2295
3622
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
2296
3623
  const tokenAddress = tokenAddresses[selectedToken];
2297
3624
  const tokenDomain = getTokenDomain(networkId, selectedToken);
2298
- return {
3625
+ const requirements = {
2299
3626
  scheme: "exact",
2300
3627
  network: networkId,
2301
3628
  asset: tokenAddress,
@@ -2305,6 +3632,17 @@ var MoltsPayServer = class {
2305
3632
  maxTimeoutSeconds: 300,
2306
3633
  extra: tokenDomain
2307
3634
  };
3635
+ if (networkId === "eip155:56" || networkId === "eip155:97") {
3636
+ const bnbFacilitator = this.registry.get("bnb");
3637
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
3638
+ if (spenderAddress) {
3639
+ requirements.extra = {
3640
+ ...requirements.extra || {},
3641
+ bnbSpender: spenderAddress
3642
+ };
3643
+ }
3644
+ }
3645
+ return requirements;
2308
3646
  }
2309
3647
  /**
2310
3648
  * Return 402 with x402 payment requirements for proxy endpoint
@@ -2354,14 +3692,14 @@ if (!globalThis.crypto) {
2354
3692
  }
2355
3693
  function getVersion() {
2356
3694
  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")
3695
+ (0, import_path3.join)(__dirname, "../../package.json"),
3696
+ (0, import_path3.join)(__dirname, "../package.json"),
3697
+ (0, import_path3.join)(process.cwd(), "node_modules/moltspay/package.json")
2360
3698
  ];
2361
3699
  for (const loc of locations) {
2362
3700
  try {
2363
- if ((0, import_fs4.existsSync)(loc)) {
2364
- const pkg = JSON.parse((0, import_fs4.readFileSync)(loc, "utf-8"));
3701
+ if ((0, import_fs5.existsSync)(loc)) {
3702
+ const pkg = JSON.parse((0, import_fs5.readFileSync)(loc, "utf-8"));
2365
3703
  if (pkg.name === "moltspay") return pkg.version;
2366
3704
  }
2367
3705
  } catch {
@@ -2369,11 +3707,94 @@ function getVersion() {
2369
3707
  }
2370
3708
  return "0.0.0";
2371
3709
  }
3710
+ var BNB_SPONSOR_KEY = process.env.MOLTSPAY_BNB_SPONSOR_KEY;
3711
+ var BNB_SPENDER_ADDRESS = process.env.MOLTSPAY_BNB_SPENDER || "0xEBB45208D806A0c73F9673E0c5713FF720DD6b79";
3712
+ var ERC20_APPROVE_ABI = [
3713
+ "function approve(address spender, uint256 amount) returns (bool)",
3714
+ "function allowance(address owner, address spender) view returns (uint256)"
3715
+ ];
3716
+ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = false) {
3717
+ const chainConfig = CHAINS[chain];
3718
+ const provider = new import_ethers2.ethers.JsonRpcProvider(chainConfig.rpc);
3719
+ const wallet = client.getWallet();
3720
+ if (!wallet) {
3721
+ console.log(" \u274C No wallet found");
3722
+ return;
3723
+ }
3724
+ const signer = wallet.connect(provider);
3725
+ console.log(` Spender: ${spenderAddress}`);
3726
+ let bnbBalance = await provider.getBalance(wallet.address);
3727
+ const minGasRequired = import_ethers2.ethers.parseEther("0.0005");
3728
+ if (bnbBalance < minGasRequired) {
3729
+ if (sponsorGas && BNB_SPONSOR_KEY) {
3730
+ console.log(" \u23F3 Sponsoring BNB gas for approvals...");
3731
+ try {
3732
+ const sponsorWallet = new import_ethers2.ethers.Wallet(BNB_SPONSOR_KEY, provider);
3733
+ const tx = await sponsorWallet.sendTransaction({
3734
+ to: wallet.address,
3735
+ value: import_ethers2.ethers.parseEther("0.001")
3736
+ });
3737
+ await tx.wait();
3738
+ console.log(` \u2705 Sponsored 0.001 BNB (tx: ${tx.hash.slice(0, 10)}...)`);
3739
+ bnbBalance = await provider.getBalance(wallet.address);
3740
+ } catch (err) {
3741
+ console.log(` \u26A0\uFE0F Gas sponsorship failed: ${err.message}`);
3742
+ console.log(` \u{1F4A1} Get testnet BNB: https://testnet.bnbchain.org/faucet-smart`);
3743
+ return;
3744
+ }
3745
+ } else {
3746
+ console.log(` \u26A0\uFE0F Need BNB for gas (~0.0005 BNB)`);
3747
+ console.log(` \u{1F4A1} Run: npx moltspay faucet --chain bnb_testnet`);
3748
+ console.log(` Then run: npx moltspay approve --chain ${chain} --spender ${spenderAddress}`);
3749
+ return;
3750
+ }
3751
+ }
3752
+ for (const tokenSymbol of ["USDT", "USDC"]) {
3753
+ const tokenConfig = chainConfig.tokens[tokenSymbol];
3754
+ const tokenContract = new import_ethers2.ethers.Contract(tokenConfig.address, ERC20_APPROVE_ABI, signer);
3755
+ const allowance = await tokenContract.allowance(wallet.address, spenderAddress);
3756
+ if (allowance > 0n) {
3757
+ console.log(` \u2705 ${tokenSymbol}: already approved for ${spenderAddress.slice(0, 10)}...`);
3758
+ continue;
3759
+ }
3760
+ console.log(` \u23F3 Approving ${tokenSymbol}...`);
3761
+ try {
3762
+ const tx = await tokenContract.approve(spenderAddress, import_ethers2.ethers.MaxUint256);
3763
+ await tx.wait();
3764
+ console.log(` \u2705 ${tokenSymbol}: approved (tx: ${tx.hash.slice(0, 10)}...)`);
3765
+ } catch (err) {
3766
+ console.log(` \u274C ${tokenSymbol}: approval failed - ${err.message}`);
3767
+ }
3768
+ }
3769
+ console.log("");
3770
+ }
3771
+ async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2) {
3772
+ const chainConfig = CHAINS[chain];
3773
+ const provider = new import_ethers2.ethers.JsonRpcProvider(chainConfig.rpc);
3774
+ let spenderAddress = null;
3775
+ try {
3776
+ const walletPath = (0, import_path3.join)(configDir, "wallet.json");
3777
+ const walletData = JSON.parse((0, import_fs5.readFileSync)(walletPath, "utf-8"));
3778
+ spenderAddress = walletData.approvals?.[chain] || null;
3779
+ } catch {
3780
+ }
3781
+ const result = { usdt: false, usdc: false, spender: spenderAddress };
3782
+ if (!spenderAddress) {
3783
+ return result;
3784
+ }
3785
+ for (const tokenSymbol of ["USDT", "USDC"]) {
3786
+ const tokenConfig = chainConfig.tokens[tokenSymbol];
3787
+ const tokenContract = new import_ethers2.ethers.Contract(tokenConfig.address, ERC20_APPROVE_ABI, provider);
3788
+ const allowance = await tokenContract.allowance(address, spenderAddress);
3789
+ result[tokenSymbol.toLowerCase()] = allowance > 0n;
3790
+ }
3791
+ return result;
3792
+ }
2372
3793
  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 });
3794
+ var DEFAULT_CONFIG_DIR2 = (0, import_path3.join)((0, import_os3.homedir)(), ".moltspay");
3795
+ var PID_FILE = (0, import_path3.join)(DEFAULT_CONFIG_DIR2, "server.pid");
3796
+ if (!(0, import_fs5.existsSync)(DEFAULT_CONFIG_DIR2)) {
3797
+ (0, import_fs5.mkdirSync)(DEFAULT_CONFIG_DIR2, { recursive: true });
2377
3798
  }
2378
3799
  function prompt(question) {
2379
3800
  const rl = readline.createInterface({
@@ -2388,19 +3809,49 @@ function prompt(question) {
2388
3809
  });
2389
3810
  }
2390
3811
  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
- }
3812
+ 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
3813
  let chain = options.chain;
2399
- const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
3814
+ const supportedEVMChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet"];
3815
+ const supportedSolanaChains = ["solana", "solana_devnet"];
3816
+ const supportedChains = [...supportedEVMChains, ...supportedSolanaChains];
2400
3817
  if (!supportedChains.includes(chain)) {
2401
3818
  console.error(`\u274C Unknown chain: ${chain}. Supported: ${supportedChains.join(", ")}`);
2402
3819
  process.exit(1);
2403
3820
  }
3821
+ if (supportedSolanaChains.includes(chain)) {
3822
+ console.log("\n\u{1F7E3} Solana Wallet Setup\n");
3823
+ if (solanaWalletExists(options.configDir)) {
3824
+ const existingAddress = getSolanaAddress(options.configDir);
3825
+ console.log(`\u26A0\uFE0F Solana wallet already exists: ${existingAddress}`);
3826
+ console.log(` Config dir: ${options.configDir}`);
3827
+ return;
3828
+ }
3829
+ console.log("Creating Solana wallet...");
3830
+ const keypair = createSolanaWallet(options.configDir);
3831
+ const address = keypair.publicKey.toBase58();
3832
+ console.log(`
3833
+ \u2705 Solana wallet created: ${address}`);
3834
+ console.log(`
3835
+ \u{1F4C1} Config saved to: ${(0, import_path3.join)(options.configDir, "wallet-solana.json")}`);
3836
+ console.log(`
3837
+ \u26A0\uFE0F IMPORTANT: Back up your wallet file!`);
3838
+ console.log(` This file contains your private key!
3839
+ `);
3840
+ if (chain === "solana_devnet") {
3841
+ console.log("\u{1F4A1} Get testnet tokens:");
3842
+ console.log(" npx moltspay faucet --chain solana_devnet\n");
3843
+ } else {
3844
+ console.log(`\u{1F4B0} Fund your wallet with USDC on Solana to start (gasless - no SOL needed).
3845
+ `);
3846
+ }
3847
+ return;
3848
+ }
3849
+ console.log("\n\u{1F510} MoltsPay Client Setup\n");
3850
+ if ((0, import_fs5.existsSync)((0, import_path3.join)(options.configDir, "wallet.json"))) {
3851
+ console.log('\u26A0\uFE0F EVM wallet already initialized. Use "moltspay config" to update settings.');
3852
+ console.log(` Config dir: ${options.configDir}`);
3853
+ return;
3854
+ }
2404
3855
  let maxPerTx = options.maxPerTx ? parseFloat(options.maxPerTx) : null;
2405
3856
  let maxPerDay = options.maxPerDay ? parseFloat(options.maxPerDay) : null;
2406
3857
  if (!maxPerTx) {
@@ -2422,13 +3873,21 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
2422
3873
  console.log(`
2423
3874
  \u{1F4C1} Config saved to: ${result.configDir}`);
2424
3875
  console.log(`
2425
- \u26A0\uFE0F IMPORTANT: Back up ${(0, import_path2.join)(result.configDir, "wallet.json")}`);
3876
+ \u26A0\uFE0F IMPORTANT: Back up ${(0, import_path3.join)(result.configDir, "wallet.json")}`);
2426
3877
  console.log(` This file contains your private key!
2427
3878
  `);
3879
+ if (chain === "bnb" || chain === "bnb_testnet") {
3880
+ console.log("\u{1F4CB} Setting up BNB chain approvals...\n");
3881
+ console.log(" \u2139\uFE0F Using default spender. For other services, run:");
3882
+ console.log(` npx moltspay approve --chain ${chain} --spender <address>
3883
+ `);
3884
+ const client = new MoltsPayClient({ configDir: options.configDir });
3885
+ await setupBNBApprovals(client, chain, BNB_SPENDER_ADDRESS, true);
3886
+ }
2428
3887
  console.log(`\u{1F4B0} Fund your wallet with USDC on ${chain} to start using services.
2429
3888
  `);
2430
3889
  });
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) => {
3890
+ 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
3891
  const client = new MoltsPayClient({ configDir: options.configDir });
2433
3892
  if (!client.isInitialized) {
2434
3893
  console.log("\u274C Not initialized. Run: npx moltspay init");
@@ -2463,25 +3922,40 @@ program.command("config").description("Update MoltsPay settings").option("--max-
2463
3922
  }
2464
3923
  }
2465
3924
  });
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) => {
3925
+ 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
3926
  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
3927
  const amount = parseFloat(amountStr);
2473
3928
  if (isNaN(amount) || amount < 5) {
2474
3929
  console.log("\u274C Minimum $5.");
2475
3930
  return;
2476
3931
  }
2477
3932
  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");
3933
+ if (!["base", "polygon", "base_sepolia", "solana", "bnb", "bnb_testnet"].includes(chain)) {
3934
+ console.log("\u274C Invalid chain. Use: base, polygon, solana, base_sepolia, bnb, or bnb_testnet");
2480
3935
  return;
2481
3936
  }
3937
+ let walletAddress;
3938
+ if (chain === "solana") {
3939
+ const solanaWallet = loadSolanaWallet(options.configDir || DEFAULT_CONFIG_DIR2);
3940
+ if (!solanaWallet) {
3941
+ console.log("\u274C No Solana wallet found. Run: npx moltspay init --chain solana");
3942
+ return;
3943
+ }
3944
+ walletAddress = getSolanaAddress(options.configDir || DEFAULT_CONFIG_DIR2) || "";
3945
+ if (!walletAddress) {
3946
+ console.log("\u274C Could not get Solana wallet address.");
3947
+ return;
3948
+ }
3949
+ } else {
3950
+ if (!client.isInitialized) {
3951
+ console.log("\u274C Not initialized. Run: npx moltspay init");
3952
+ return;
3953
+ }
3954
+ walletAddress = client.address;
3955
+ }
2482
3956
  if (chain === "base_sepolia") {
2483
3957
  console.log("\n\u{1F9EA} Testnet Funding\n");
2484
- console.log(` Wallet: ${client.address}`);
3958
+ console.log(` Wallet: ${walletAddress}`);
2485
3959
  console.log(` Chain: Base Sepolia (testnet)
2486
3960
  `);
2487
3961
  console.log("\u{1F4A1} Use the MoltsPay faucet to get free testnet USDC:\n");
@@ -2489,9 +3963,36 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
2489
3963
  console.log(" Or get from Circle Faucet: https://faucet.circle.com/\n");
2490
3964
  return;
2491
3965
  }
3966
+ if (chain === "bnb_testnet") {
3967
+ console.log("\n\u{1F9EA} BNB Testnet Funding\n");
3968
+ console.log(` Wallet: ${walletAddress}`);
3969
+ console.log(` Chain: BNB Testnet
3970
+ `);
3971
+ console.log("\u{1F4A1} Use the MoltsPay faucet to get testnet USDC + tBNB:\n");
3972
+ console.log(" npx moltspay faucet --chain bnb_testnet\n");
3973
+ console.log(" This gives you:\n");
3974
+ console.log(" \u2022 1 USDC (testnet) for payments");
3975
+ console.log(" \u2022 0.001 tBNB for gas (first approval tx)\n");
3976
+ return;
3977
+ }
3978
+ if (chain === "bnb") {
3979
+ console.log("\n\u{1F4CB} BNB Chain Funding\n");
3980
+ console.log(` Wallet: ${walletAddress}
3981
+ `);
3982
+ console.log(" To use MoltsPay on BNB Chain, you need:\n");
3983
+ console.log(" 1. USDC for payments");
3984
+ console.log(" \u2192 Withdraw from Binance/exchange to your wallet address\n");
3985
+ console.log(" 2. Small amount of BNB for gas (~0.001 BNB / ~$0.60)");
3986
+ console.log(" \u2192 First approval transaction requires gas");
3987
+ console.log(" \u2192 After approval, all payments are gasless\n");
3988
+ console.log(" \u{1F4A1} Tip: Most exchanges include BNB dust when you withdraw to BNB Chain\n");
3989
+ 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");
3990
+ console.log(" After funding, check status: npx moltspay status\n");
3991
+ return;
3992
+ }
2492
3993
  console.log("\n\u{1F4B3} Fund your agent wallet\n");
2493
- console.log(` Wallet: ${client.address}`);
2494
- console.log(` Chain: ${chain}`);
3994
+ console.log(` Wallet: ${walletAddress}`);
3995
+ console.log(` Chain: ${chain === "solana" ? "Solana" : chain}`);
2495
3996
  console.log(` Amount: $${amount.toFixed(2)}
2496
3997
  `);
2497
3998
  try {
@@ -2500,7 +4001,7 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
2500
4001
  method: "POST",
2501
4002
  headers: { "Content-Type": "application/json" },
2502
4003
  body: JSON.stringify({
2503
- address: client.address,
4004
+ address: walletAddress,
2504
4005
  amount,
2505
4006
  chain
2506
4007
  })
@@ -2518,11 +4019,92 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
2518
4019
  console.log(`\u274C ${error.message}`);
2519
4020
  }
2520
4021
  });
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) => {
4022
+ 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) => {
4023
+ const chain = options.chain;
4024
+ if (chain !== "bnb" && chain !== "bnb_testnet") {
4025
+ console.log("\u274C approve command is only for BNB chains (bnb or bnb_testnet)");
4026
+ return;
4027
+ }
4028
+ if (!options.spender.match(/^0x[a-fA-F0-9]{40}$/)) {
4029
+ console.log("\u274C Invalid spender address format");
4030
+ return;
4031
+ }
4032
+ const client = new MoltsPayClient({ configDir: options.configDir });
4033
+ if (!client.isInitialized) {
4034
+ console.log("\u274C Wallet not initialized. Run: npx moltspay init --chain " + chain);
4035
+ return;
4036
+ }
4037
+ console.log(`
4038
+ \u{1F510} Approving spender for ${chain}...
4039
+ `);
4040
+ await setupBNBApprovals(client, chain, options.spender, false);
4041
+ const walletPath = (0, import_path3.join)(options.configDir || DEFAULT_CONFIG_DIR2, "wallet.json");
4042
+ try {
4043
+ const walletData = JSON.parse((0, import_fs5.readFileSync)(walletPath, "utf-8"));
4044
+ walletData.approvals = walletData.approvals || {};
4045
+ walletData.approvals[chain] = options.spender;
4046
+ (0, import_fs5.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2));
4047
+ console.log(`\u2705 Approval complete! Spender saved for ${chain}.
4048
+ `);
4049
+ } catch (err) {
4050
+ console.log("\u2705 Approval complete!\n");
4051
+ console.log("\u26A0\uFE0F Could not save spender to wallet config");
4052
+ }
4053
+ });
4054
+ 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
4055
  let address = options.address;
2523
4056
  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");
4057
+ if (!["base_sepolia", "tempo_moderato", "bnb_testnet", "solana_devnet"].includes(chain)) {
4058
+ console.log("\u274C Invalid chain. Use: base_sepolia, tempo_moderato, bnb_testnet, or solana_devnet");
4059
+ return;
4060
+ }
4061
+ if (chain === "solana_devnet") {
4062
+ if (!address) {
4063
+ address = getSolanaAddress(options.configDir);
4064
+ if (!address) {
4065
+ console.log("\u274C No Solana wallet found. Run: npx moltspay init --chain solana_devnet");
4066
+ return;
4067
+ }
4068
+ }
4069
+ if (!isValidSolanaAddress(address)) {
4070
+ console.log("\u274C Invalid Solana address");
4071
+ return;
4072
+ }
4073
+ console.log("\n\u{1F6B0} Solana Devnet Faucet (Gasless Mode)\n");
4074
+ console.log(` Address: ${address}
4075
+ `);
4076
+ let usdcSuccess = false;
4077
+ try {
4078
+ console.log(" \u23F3 Requesting 1 USDC from faucet...");
4079
+ const FAUCET_API = process.env.MOLTSPAY_FAUCET_API || "https://moltspay.com/api/v1/faucet";
4080
+ const response = await fetch(FAUCET_API, {
4081
+ method: "POST",
4082
+ headers: { "Content-Type": "application/json" },
4083
+ body: JSON.stringify({ address, chain: "solana_devnet" })
4084
+ });
4085
+ const result = await response.json();
4086
+ if (!response.ok) {
4087
+ console.log(` \u26A0\uFE0F USDC faucet: ${result.error || "Request failed"}`);
4088
+ if (result.hint) console.log(` ${result.hint}`);
4089
+ if (result.retry_after) console.log(` Retry after: ${result.retry_after}`);
4090
+ } else {
4091
+ console.log(` \u2705 Received ${result.amount} USDC!`);
4092
+ console.log(` Transaction: ${result.explorer}`);
4093
+ if (result.faucet_balance) {
4094
+ console.log(` Faucet balance: ${result.faucet_balance} USDC remaining`);
4095
+ }
4096
+ usdcSuccess = true;
4097
+ }
4098
+ } catch (error) {
4099
+ console.log(` \u26A0\uFE0F USDC faucet error: ${error.message}`);
4100
+ }
4101
+ console.log("");
4102
+ if (usdcSuccess) {
4103
+ console.log("\u{1F4A1} Check your balance:");
4104
+ console.log(" npx moltspay status\n");
4105
+ } else {
4106
+ console.log("\u274C Faucet request failed. Try again in a few minutes.\n");
4107
+ }
2526
4108
  return;
2527
4109
  }
2528
4110
  if (!address) {
@@ -2570,6 +4152,46 @@ program.command("faucet").description("Request testnet tokens from faucet (Base
2570
4152
  console.log(`\u274C ${error.message}`);
2571
4153
  console.log("\n Try Tempo Wallet instead: https://wallet.tempo.xyz\n");
2572
4154
  }
4155
+ } else if (chain === "bnb_testnet") {
4156
+ console.log(` Requesting 1 USDC on BNB Testnet...`);
4157
+ console.log(` Address: ${address}
4158
+ `);
4159
+ try {
4160
+ const FAUCET_API = process.env.MOLTSPAY_FAUCET_API || "https://moltspay.com/api/v1/faucet";
4161
+ const response = await fetch(FAUCET_API, {
4162
+ method: "POST",
4163
+ headers: { "Content-Type": "application/json" },
4164
+ body: JSON.stringify({ address, chain: "bnb_testnet" })
4165
+ });
4166
+ const result = await response.json();
4167
+ if (!response.ok) {
4168
+ console.log(`\u274C ${result.error || "Request failed"}`);
4169
+ if (result.hint) console.log(` ${result.hint}`);
4170
+ if (result.retry_after) console.log(` Retry after: ${result.retry_after}`);
4171
+ console.log("\n\u{1F4A1} Alternatively, get tokens manually:");
4172
+ console.log(` 1. Get test BNB: https://www.bnbchain.org/en/testnet-faucet`);
4173
+ console.log(` 2. Select "Peggy Tokens" -> USDC`);
4174
+ console.log(` 3. Enter: ${address}
4175
+ `);
4176
+ return;
4177
+ }
4178
+ console.log(`\u2705 Received ${result.amount} ${result.token || "USDC"} on ${result.chain_name || "BNB Testnet"}!
4179
+ `);
4180
+ console.log(` Transaction: ${result.explorer || `https://testnet.bscscan.com/tx/${result.transaction}`}`);
4181
+ if (result.faucet_balance) {
4182
+ console.log(` Faucet balance: ${result.faucet_balance} USDC`);
4183
+ }
4184
+ console.log("\n\u{1F4A1} Now you can test BNB payments:");
4185
+ console.log(` npx moltspay pay <service-url> <service-id> --chain bnb_testnet
4186
+ `);
4187
+ } catch (error) {
4188
+ console.log(`\u274C ${error.message}`);
4189
+ console.log("\n\u{1F4A1} Get tokens manually:");
4190
+ console.log(` 1. Get test BNB: https://www.bnbchain.org/en/testnet-faucet`);
4191
+ console.log(` 2. Select "Peggy Tokens" -> USDC`);
4192
+ console.log(` 3. Enter: ${address}
4193
+ `);
4194
+ }
2573
4195
  } else {
2574
4196
  console.log(` Requesting 1 USDC on Base Sepolia...`);
2575
4197
  console.log(` Address: ${address}
@@ -2579,7 +4201,7 @@ program.command("faucet").description("Request testnet tokens from faucet (Base
2579
4201
  const response = await fetch(FAUCET_API, {
2580
4202
  method: "POST",
2581
4203
  headers: { "Content-Type": "application/json" },
2582
- body: JSON.stringify({ address })
4204
+ body: JSON.stringify({ address, chain: "base_sepolia" })
2583
4205
  });
2584
4206
  const result = await response.json();
2585
4207
  if (!response.ok) {
@@ -2602,7 +4224,7 @@ program.command("faucet").description("Request testnet tokens from faucet (Base
2602
4224
  }
2603
4225
  }
2604
4226
  });
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) => {
4227
+ 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
4228
  const client = new MoltsPayClient({ configDir: options.configDir });
2607
4229
  if (!client.isInitialized) {
2608
4230
  if (options.json) {
@@ -2619,12 +4241,31 @@ program.command("status").description("Show wallet status and balance").option("
2619
4241
  } catch (err) {
2620
4242
  console.error("Warning: Could not fetch balances:", err.message);
2621
4243
  }
4244
+ const solanaAddress = getSolanaAddress(options.configDir);
4245
+ let solanaBalances = {};
4246
+ if (solanaAddress) {
4247
+ try {
4248
+ solanaBalances.devnet = await getSolanaBalances(solanaAddress, "solana_devnet");
4249
+ } catch {
4250
+ }
4251
+ try {
4252
+ solanaBalances.mainnet = await getSolanaBalances(solanaAddress, "solana");
4253
+ } catch {
4254
+ }
4255
+ }
2622
4256
  if (options.json) {
2623
- console.log(JSON.stringify({
4257
+ const output = {
2624
4258
  address: client.address,
2625
4259
  balances: allBalances,
2626
4260
  limits: config.limits
2627
- }, null, 2));
4261
+ };
4262
+ if (solanaAddress) {
4263
+ output.solana = {
4264
+ address: solanaAddress,
4265
+ balances: solanaBalances
4266
+ };
4267
+ }
4268
+ console.log(JSON.stringify(output, null, 2));
2628
4269
  } else {
2629
4270
  console.log("\n\u{1F4CA} MoltsPay Wallet Status\n");
2630
4271
  console.log(` Address: ${client.address}`);
@@ -2648,18 +4289,90 @@ program.command("status").description("Show wallet status and balance").option("
2648
4289
  console.log(` alphaUSD: ${tempo.alphaUSD.toFixed(2)}`);
2649
4290
  console.log(` betaUSD: ${tempo.betaUSD.toFixed(2)}`);
2650
4291
  console.log(` thetaUSD: ${tempo.thetaUSD.toFixed(2)}`);
4292
+ } else if (chainName === "bnb" || chainName === "bnb_testnet") {
4293
+ const bnbBalance = balance.native;
4294
+ const bnbWarning = bnbBalance < 5e-4 ? " \u26A0\uFE0F Low gas" : "";
4295
+ console.log(` ${chainLabel.padEnd(14)} ${balance.usdc.toFixed(2)} USDC | ${balance.usdt.toFixed(2)} USDT | ${bnbBalance.toFixed(4)} BNB${bnbWarning}`);
2651
4296
  } else {
2652
4297
  console.log(` ${chainLabel.padEnd(14)} ${balance.usdc.toFixed(2)} USDC | ${balance.usdt.toFixed(2)} USDT`);
2653
4298
  }
2654
4299
  }
4300
+ const address = client.address;
4301
+ let bnbApprovalStatus = null;
4302
+ let bnbTestnetApprovalStatus = null;
4303
+ try {
4304
+ if (allBalances["bnb"]) {
4305
+ bnbApprovalStatus = await checkBNBApprovals(address, "bnb", options.configDir);
4306
+ }
4307
+ if (allBalances["bnb_testnet"]) {
4308
+ bnbTestnetApprovalStatus = await checkBNBApprovals(address, "bnb_testnet", options.configDir);
4309
+ }
4310
+ } catch {
4311
+ }
4312
+ if (bnbApprovalStatus || bnbTestnetApprovalStatus) {
4313
+ console.log("");
4314
+ console.log(" BNB Approvals (pay-for-success):");
4315
+ if (bnbApprovalStatus) {
4316
+ if (!bnbApprovalStatus.spender) {
4317
+ console.log(" BNB: \u26A0\uFE0F No spender configured");
4318
+ console.log(" \u2514\u2500 Run a payment first, or: npx moltspay approve --chain bnb --spender <address>");
4319
+ } else {
4320
+ const status = bnbApprovalStatus.usdt && bnbApprovalStatus.usdc ? "\u2705" : "\u26A0\uFE0F";
4321
+ const tokens = [
4322
+ bnbApprovalStatus.usdt ? "USDT\u2713" : "USDT\u2717",
4323
+ bnbApprovalStatus.usdc ? "USDC\u2713" : "USDC\u2717"
4324
+ ].join(", ");
4325
+ console.log(` BNB: ${status} ${tokens}`);
4326
+ const bnbNative = allBalances["bnb"]?.native || 0;
4327
+ if (!bnbApprovalStatus.usdc && !bnbApprovalStatus.usdt && bnbNative < 5e-4) {
4328
+ console.log(" \u26A0\uFE0F Need ~0.001 BNB for first approval tx. Get from exchange.");
4329
+ }
4330
+ }
4331
+ }
4332
+ if (bnbTestnetApprovalStatus) {
4333
+ if (!bnbTestnetApprovalStatus.spender) {
4334
+ console.log(" BNB Testnet: \u26A0\uFE0F No spender configured");
4335
+ console.log(" \u2514\u2500 Run a payment first, or: npx moltspay approve --chain bnb_testnet --spender <address>");
4336
+ } else {
4337
+ const status = bnbTestnetApprovalStatus.usdt && bnbTestnetApprovalStatus.usdc ? "\u2705" : "\u26A0\uFE0F";
4338
+ const tokens = [
4339
+ bnbTestnetApprovalStatus.usdt ? "USDT\u2713" : "USDT\u2717",
4340
+ bnbTestnetApprovalStatus.usdc ? "USDC\u2713" : "USDC\u2717"
4341
+ ].join(", ");
4342
+ console.log(` BNB Testnet: ${status} ${tokens}`);
4343
+ const tbnbNative = allBalances["bnb_testnet"]?.native || 0;
4344
+ if (!bnbTestnetApprovalStatus.usdc && !bnbTestnetApprovalStatus.usdt && tbnbNative < 5e-4) {
4345
+ console.log(" \u26A0\uFE0F Need tBNB for approval. Run: npx moltspay faucet --chain bnb_testnet");
4346
+ }
4347
+ }
4348
+ }
4349
+ }
2655
4350
  console.log("");
2656
4351
  console.log(" Spending Limits:");
2657
4352
  console.log(` Per Transaction: $${config.limits.maxPerTx}`);
2658
4353
  console.log(` Daily: $${config.limits.maxPerDay}`);
4354
+ const solanaAddress2 = getSolanaAddress(options.configDir);
4355
+ if (solanaAddress2) {
4356
+ console.log("");
4357
+ 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");
4358
+ console.log(` \u{1F7E3} Solana: ${solanaAddress2}`);
4359
+ try {
4360
+ const devnetBalances = await getSolanaBalances(solanaAddress2, "solana_devnet");
4361
+ console.log(` Devnet: ${devnetBalances.sol.toFixed(4)} SOL | ${devnetBalances.usdc.toFixed(2)} USDC`);
4362
+ } catch (err) {
4363
+ console.log(` Devnet: (unable to fetch)`);
4364
+ }
4365
+ try {
4366
+ const mainnetBalances = await getSolanaBalances(solanaAddress2, "solana");
4367
+ console.log(` Mainnet: ${mainnetBalances.sol.toFixed(4)} SOL | ${mainnetBalances.usdc.toFixed(2)} USDC`);
4368
+ } catch (err) {
4369
+ console.log(` Mainnet: (unable to fetch)`);
4370
+ }
4371
+ }
2659
4372
  console.log("");
2660
4373
  }
2661
4374
  });
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) => {
4375
+ 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
4376
  const client = new MoltsPayClient({ configDir: options.configDir });
2664
4377
  if (!client.isInitialized) {
2665
4378
  console.log("\u274C Not initialized. Run: npx moltspay init");
@@ -2962,18 +4675,18 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
2962
4675
  const handlers = /* @__PURE__ */ new Map();
2963
4676
  let provider = null;
2964
4677
  for (const inputPath of allPaths) {
2965
- const resolvedPath = (0, import_path2.resolve)(inputPath);
4678
+ const resolvedPath = (0, import_path3.resolve)(inputPath);
2966
4679
  let manifestPath;
2967
4680
  let skillDir;
2968
4681
  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");
4682
+ if ((0, import_fs5.existsSync)((0, import_path3.join)(resolvedPath, "moltspay.services.json"))) {
4683
+ manifestPath = (0, import_path3.join)(resolvedPath, "moltspay.services.json");
2971
4684
  skillDir = resolvedPath;
2972
4685
  isSkillDir = true;
2973
- } else if ((0, import_fs4.existsSync)(resolvedPath) && resolvedPath.endsWith(".json")) {
4686
+ } else if ((0, import_fs5.existsSync)(resolvedPath) && resolvedPath.endsWith(".json")) {
2974
4687
  manifestPath = resolvedPath;
2975
- skillDir = (0, import_path2.dirname)(resolvedPath);
2976
- } else if ((0, import_fs4.existsSync)(resolvedPath)) {
4688
+ skillDir = (0, import_path3.dirname)(resolvedPath);
4689
+ } else if ((0, import_fs5.existsSync)(resolvedPath)) {
2977
4690
  console.error(`\u274C No moltspay.services.json found in: ${resolvedPath}`);
2978
4691
  continue;
2979
4692
  } else {
@@ -2982,25 +4695,25 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
2982
4695
  }
2983
4696
  console.log(`\u{1F4E6} Loading: ${manifestPath}`);
2984
4697
  try {
2985
- const manifestContent = JSON.parse((0, import_fs4.readFileSync)(manifestPath, "utf-8"));
4698
+ const manifestContent = JSON.parse((0, import_fs5.readFileSync)(manifestPath, "utf-8"));
2986
4699
  if (!provider) {
2987
4700
  provider = manifestContent.provider;
2988
4701
  }
2989
4702
  let skillModule = null;
2990
4703
  if (isSkillDir) {
2991
4704
  let entryPoint = "index.js";
2992
- const pkgJsonPath = (0, import_path2.join)(skillDir, "package.json");
2993
- if ((0, import_fs4.existsSync)(pkgJsonPath)) {
4705
+ const pkgJsonPath = (0, import_path3.join)(skillDir, "package.json");
4706
+ if ((0, import_fs5.existsSync)(pkgJsonPath)) {
2994
4707
  try {
2995
- const pkgJson = JSON.parse((0, import_fs4.readFileSync)(pkgJsonPath, "utf-8"));
4708
+ const pkgJson = JSON.parse((0, import_fs5.readFileSync)(pkgJsonPath, "utf-8"));
2996
4709
  if (pkgJson.main) {
2997
4710
  entryPoint = pkgJson.main;
2998
4711
  }
2999
4712
  } catch {
3000
4713
  }
3001
4714
  }
3002
- const modulePath = (0, import_path2.join)(skillDir, entryPoint);
3003
- if ((0, import_fs4.existsSync)(modulePath)) {
4715
+ const modulePath = (0, import_path3.join)(skillDir, entryPoint);
4716
+ if ((0, import_fs5.existsSync)(modulePath)) {
3004
4717
  try {
3005
4718
  skillModule = await import(modulePath);
3006
4719
  console.log(` \u2705 Loaded module: ${modulePath}`);
@@ -3078,8 +4791,8 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
3078
4791
  provider,
3079
4792
  services: allServices
3080
4793
  };
3081
- const tempManifestPath = (0, import_path2.join)(DEFAULT_CONFIG_DIR, "combined-manifest.json");
3082
- (0, import_fs4.writeFileSync)(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
4794
+ const tempManifestPath = (0, import_path3.join)(DEFAULT_CONFIG_DIR2, "combined-manifest.json");
4795
+ (0, import_fs5.writeFileSync)(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
3083
4796
  console.log(`
3084
4797
  \u{1F4CB} Combined manifest: ${allServices.length} services`);
3085
4798
  console.log(` Provider: ${provider.name}`);
@@ -3092,12 +4805,12 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
3092
4805
  server.skill(serviceId, handler);
3093
4806
  }
3094
4807
  const pidData = { pid: process.pid, port, paths: allPaths };
3095
- (0, import_fs4.writeFileSync)(PID_FILE, JSON.stringify(pidData, null, 2));
4808
+ (0, import_fs5.writeFileSync)(PID_FILE, JSON.stringify(pidData, null, 2));
3096
4809
  server.listen(port);
3097
4810
  const cleanup = () => {
3098
4811
  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);
4812
+ if ((0, import_fs5.existsSync)(PID_FILE)) (0, import_fs5.unlinkSync)(PID_FILE);
4813
+ if ((0, import_fs5.existsSync)(tempManifestPath)) (0, import_fs5.unlinkSync)(tempManifestPath);
3101
4814
  } catch {
3102
4815
  }
3103
4816
  };
@@ -3118,12 +4831,12 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
3118
4831
  }
3119
4832
  });
3120
4833
  program.command("stop").description("Stop the running MoltsPay server").action(async () => {
3121
- if (!(0, import_fs4.existsSync)(PID_FILE)) {
4834
+ if (!(0, import_fs5.existsSync)(PID_FILE)) {
3122
4835
  console.log("\u274C No running server found (no PID file)");
3123
4836
  process.exit(1);
3124
4837
  }
3125
4838
  try {
3126
- const pidData = JSON.parse((0, import_fs4.readFileSync)(PID_FILE, "utf-8"));
4839
+ const pidData = JSON.parse((0, import_fs5.readFileSync)(PID_FILE, "utf-8"));
3127
4840
  const { pid, port, manifest } = pidData;
3128
4841
  console.log(`
3129
4842
  \u{1F6D1} Stopping MoltsPay Server
@@ -3136,7 +4849,7 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
3136
4849
  process.kill(pid, 0);
3137
4850
  } catch {
3138
4851
  console.log("\u26A0\uFE0F Process not running, cleaning up PID file...");
3139
- (0, import_fs4.unlinkSync)(PID_FILE);
4852
+ (0, import_fs5.unlinkSync)(PID_FILE);
3140
4853
  process.exit(0);
3141
4854
  }
3142
4855
  process.kill(pid, "SIGTERM");
@@ -3148,8 +4861,8 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
3148
4861
  process.kill(pid, "SIGKILL");
3149
4862
  } catch {
3150
4863
  }
3151
- if ((0, import_fs4.existsSync)(PID_FILE)) {
3152
- (0, import_fs4.unlinkSync)(PID_FILE);
4864
+ if ((0, import_fs5.existsSync)(PID_FILE)) {
4865
+ (0, import_fs5.unlinkSync)(PID_FILE);
3153
4866
  }
3154
4867
  console.log("\u2705 Server stopped\n");
3155
4868
  } catch (err) {
@@ -3157,7 +4870,7 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
3157
4870
  process.exit(1);
3158
4871
  }
3159
4872
  });
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) => {
4873
+ 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, 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
4874
  const client = new MoltsPayClient({ configDir: options.configDir });
3162
4875
  if (!client.isInitialized) {
3163
4876
  console.error("\u274C Wallet not initialized. Run: npx moltspay init");
@@ -3178,18 +4891,19 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
3178
4891
  if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
3179
4892
  params.image_url = imagePath;
3180
4893
  } else {
3181
- const filePath = (0, import_path2.resolve)(imagePath);
3182
- if (!(0, import_fs4.existsSync)(filePath)) {
4894
+ const filePath = (0, import_path3.resolve)(imagePath);
4895
+ if (!(0, import_fs5.existsSync)(filePath)) {
3183
4896
  console.error(`\u274C Image file not found: ${filePath}`);
3184
4897
  process.exit(1);
3185
4898
  }
3186
- const imageData = (0, import_fs4.readFileSync)(filePath);
4899
+ const imageData = (0, import_fs5.readFileSync)(filePath);
3187
4900
  params.image_base64 = imageData.toString("base64");
3188
4901
  }
3189
4902
  }
4903
+ const supportedPayChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet", "solana", "solana_devnet"];
3190
4904
  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`);
4905
+ if (chain && !supportedPayChains.includes(chain)) {
4906
+ console.error(`\u274C Unknown chain: ${chain}. Supported: ${supportedPayChains.join(", ")}`);
3193
4907
  process.exit(1);
3194
4908
  }
3195
4909
  const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);
@@ -3220,22 +4934,10 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
3220
4934
  console.log("");
3221
4935
  }
3222
4936
  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
- }
4937
+ const result = await client.pay(server, service, params, {
4938
+ token,
4939
+ chain
4940
+ });
3239
4941
  if (options.json) {
3240
4942
  console.log(JSON.stringify(result));
3241
4943
  } else {
@@ -3253,11 +4955,11 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
3253
4955
  }
3254
4956
  });
3255
4957
  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);
4958
+ const resolvedPath = (0, import_path3.resolve)(inputPath);
3257
4959
  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)) {
4960
+ if ((0, import_fs5.existsSync)((0, import_path3.join)(resolvedPath, "moltspay.services.json"))) {
4961
+ manifestPath = (0, import_path3.join)(resolvedPath, "moltspay.services.json");
4962
+ } else if (resolvedPath.endsWith(".json") && (0, import_fs5.existsSync)(resolvedPath)) {
3261
4963
  manifestPath = resolvedPath;
3262
4964
  } else {
3263
4965
  console.error(`\u274C Not found: ${resolvedPath}`);
@@ -3267,7 +4969,7 @@ program.command("validate <path>").description("Validate a moltspay.services.jso
3267
4969
  \u{1F4CB} Validating: ${manifestPath}
3268
4970
  `);
3269
4971
  try {
3270
- const content = JSON.parse((0, import_fs4.readFileSync)(manifestPath, "utf-8"));
4972
+ const content = JSON.parse((0, import_fs5.readFileSync)(manifestPath, "utf-8"));
3271
4973
  const errors = [];
3272
4974
  if (!content.provider) {
3273
4975
  errors.push("Missing required field: provider");