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
@@ -21,16 +21,17 @@ var init_esm_shims = __esm({
21
21
  init_esm_shims();
22
22
  import { webcrypto } from "crypto";
23
23
  import { Command } from "commander";
24
- import { homedir as homedir2 } from "os";
25
- import { join as join4, dirname, resolve } from "path";
26
- import { existsSync as existsSync4, writeFileSync as writeFileSync2, readFileSync as readFileSync4, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
24
+ import { homedir as homedir3 } from "os";
25
+ import { join as join5, dirname, resolve } from "path";
26
+ import { existsSync as existsSync5, writeFileSync as writeFileSync3, readFileSync as readFileSync5, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
27
27
  import { spawn } from "child_process";
28
+ import { ethers as ethers2 } from "ethers";
28
29
 
29
30
  // src/client/index.ts
30
31
  init_esm_shims();
31
- import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, chmodSync } from "fs";
32
- import { homedir } from "os";
33
- import { join } from "path";
32
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync, chmodSync } from "fs";
33
+ import { homedir as homedir2 } from "os";
34
+ import { join as join2 } from "path";
34
35
  import { Wallet, ethers } from "ethers";
35
36
 
36
37
  // src/chains/index.ts
@@ -141,6 +142,63 @@ var CHAINS = {
141
142
  explorerTx: "https://explore.testnet.tempo.xyz/tx/",
142
143
  avgBlockTime: 0.5
143
144
  // ~500ms finality
145
+ },
146
+ // ============ BNB Chain Testnet ============
147
+ bnb_testnet: {
148
+ name: "BNB Testnet",
149
+ chainId: 97,
150
+ rpc: "https://data-seed-prebsc-1-s1.binance.org:8545",
151
+ tokens: {
152
+ // Note: BNB uses 18 decimals for stablecoins (unlike Base/Polygon which use 6)
153
+ // Using official Binance-Peg testnet tokens
154
+ USDC: {
155
+ address: "0x64544969ed7EBf5f083679233325356EbE738930",
156
+ // Testnet USDC
157
+ decimals: 18,
158
+ symbol: "USDC",
159
+ eip712Name: "USD Coin"
160
+ },
161
+ USDT: {
162
+ address: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd",
163
+ // Testnet USDT
164
+ decimals: 18,
165
+ symbol: "USDT",
166
+ eip712Name: "Tether USD"
167
+ }
168
+ },
169
+ usdc: "0x64544969ed7EBf5f083679233325356EbE738930",
170
+ explorer: "https://testnet.bscscan.com/address/",
171
+ explorerTx: "https://testnet.bscscan.com/tx/",
172
+ avgBlockTime: 3,
173
+ // BNB-specific: requires approval for pay-for-success flow
174
+ requiresApproval: true
175
+ },
176
+ // ============ BNB Chain Mainnet ============
177
+ bnb: {
178
+ name: "BNB Smart Chain",
179
+ chainId: 56,
180
+ rpc: "https://bsc-dataseed.binance.org",
181
+ tokens: {
182
+ // Note: BNB uses 18 decimals for stablecoins
183
+ USDC: {
184
+ address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
185
+ decimals: 18,
186
+ symbol: "USDC",
187
+ eip712Name: "USD Coin"
188
+ },
189
+ USDT: {
190
+ address: "0x55d398326f99059fF775485246999027B3197955",
191
+ decimals: 18,
192
+ symbol: "USDT",
193
+ eip712Name: "Tether USD"
194
+ }
195
+ },
196
+ usdc: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
197
+ explorer: "https://bscscan.com/address/",
198
+ explorerTx: "https://bscscan.com/tx/",
199
+ avgBlockTime: 3,
200
+ // BNB-specific: requires approval for pay-for-success flow
201
+ requiresApproval: true
144
202
  }
145
203
  };
146
204
  function getChain(name) {
@@ -151,6 +209,372 @@ function getChain(name) {
151
209
  return config;
152
210
  }
153
211
 
212
+ // src/wallet/solana.ts
213
+ init_esm_shims();
214
+ import { Keypair, PublicKey as PublicKey2, LAMPORTS_PER_SOL } from "@solana/web3.js";
215
+ import { getAssociatedTokenAddress, getAccount } from "@solana/spl-token";
216
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
217
+ import { join } from "path";
218
+ import { homedir } from "os";
219
+ import bs58 from "bs58";
220
+
221
+ // src/chains/solana.ts
222
+ init_esm_shims();
223
+ import { Connection, PublicKey } from "@solana/web3.js";
224
+ var SOLANA_CHAINS = {
225
+ solana: {
226
+ name: "Solana Mainnet",
227
+ cluster: "mainnet-beta",
228
+ rpc: "https://api.mainnet-beta.solana.com",
229
+ explorer: "https://solscan.io/account/",
230
+ explorerTx: "https://solscan.io/tx/",
231
+ tokens: {
232
+ USDC: {
233
+ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
234
+ // Circle official USDC
235
+ decimals: 6
236
+ }
237
+ }
238
+ },
239
+ solana_devnet: {
240
+ name: "Solana Devnet",
241
+ cluster: "devnet",
242
+ rpc: "https://api.devnet.solana.com",
243
+ explorer: "https://solscan.io/account/",
244
+ explorerTx: "https://solscan.io/tx/",
245
+ tokens: {
246
+ USDC: {
247
+ // Circle's devnet USDC (if not available, we'll deploy our own test token)
248
+ mint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
249
+ decimals: 6
250
+ }
251
+ }
252
+ }
253
+ };
254
+ function getSolanaConnection(chain) {
255
+ const config = SOLANA_CHAINS[chain];
256
+ return new Connection(config.rpc, "confirmed");
257
+ }
258
+ function getUSDCMint(chain) {
259
+ return new PublicKey(SOLANA_CHAINS[chain].tokens.USDC.mint);
260
+ }
261
+
262
+ // src/wallet/solana.ts
263
+ var DEFAULT_CONFIG_DIR = join(homedir(), ".moltspay");
264
+ var SOLANA_WALLET_FILE = "wallet-solana.json";
265
+ function getSolanaWalletPath(configDir = DEFAULT_CONFIG_DIR) {
266
+ return join(configDir, SOLANA_WALLET_FILE);
267
+ }
268
+ function solanaWalletExists(configDir = DEFAULT_CONFIG_DIR) {
269
+ return existsSync(getSolanaWalletPath(configDir));
270
+ }
271
+ function loadSolanaWallet(configDir = DEFAULT_CONFIG_DIR) {
272
+ const walletPath = getSolanaWalletPath(configDir);
273
+ if (!existsSync(walletPath)) {
274
+ return null;
275
+ }
276
+ try {
277
+ const data = JSON.parse(readFileSync(walletPath, "utf-8"));
278
+ const secretKey = bs58.decode(data.secretKey);
279
+ return Keypair.fromSecretKey(secretKey);
280
+ } catch (error) {
281
+ console.error("Failed to load Solana wallet:", error);
282
+ return null;
283
+ }
284
+ }
285
+ function createSolanaWallet(configDir = DEFAULT_CONFIG_DIR) {
286
+ if (!existsSync(configDir)) {
287
+ mkdirSync(configDir, { recursive: true });
288
+ }
289
+ const keypair = Keypair.generate();
290
+ const data = {
291
+ publicKey: keypair.publicKey.toBase58(),
292
+ secretKey: bs58.encode(keypair.secretKey),
293
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
294
+ };
295
+ const walletPath = getSolanaWalletPath(configDir);
296
+ writeFileSync(walletPath, JSON.stringify(data, null, 2));
297
+ return keypair;
298
+ }
299
+ function getSolanaAddress(configDir = DEFAULT_CONFIG_DIR) {
300
+ const wallet = loadSolanaWallet(configDir);
301
+ return wallet?.publicKey.toBase58() || null;
302
+ }
303
+ async function getSolanaBalance(address, chain) {
304
+ const connection = getSolanaConnection(chain);
305
+ const pubkey = new PublicKey2(address);
306
+ const balance = await connection.getBalance(pubkey);
307
+ return balance / LAMPORTS_PER_SOL;
308
+ }
309
+ async function getSolanaUSDCBalance(address, chain) {
310
+ const connection = getSolanaConnection(chain);
311
+ const owner = new PublicKey2(address);
312
+ const mint = getUSDCMint(chain);
313
+ try {
314
+ const ata = await getAssociatedTokenAddress(mint, owner);
315
+ const account = await getAccount(connection, ata);
316
+ return Number(account.amount) / 1e6;
317
+ } catch (error) {
318
+ if (error.name === "TokenAccountNotFoundError" || error.message?.includes("could not find account")) {
319
+ return 0;
320
+ }
321
+ throw error;
322
+ }
323
+ }
324
+ async function getSolanaBalances(address, chain) {
325
+ const [sol, usdc] = await Promise.all([
326
+ getSolanaBalance(address, chain),
327
+ getSolanaUSDCBalance(address, chain)
328
+ ]);
329
+ return { sol, usdc };
330
+ }
331
+ function isValidSolanaAddress(address) {
332
+ try {
333
+ new PublicKey2(address);
334
+ return true;
335
+ } catch {
336
+ return false;
337
+ }
338
+ }
339
+
340
+ // src/facilitators/solana.ts
341
+ init_esm_shims();
342
+ import {
343
+ Connection as Connection3,
344
+ PublicKey as PublicKey3,
345
+ Transaction,
346
+ VersionedTransaction
347
+ } from "@solana/web3.js";
348
+ import {
349
+ getAssociatedTokenAddress as getAssociatedTokenAddress2,
350
+ createTransferCheckedInstruction,
351
+ getAccount as getAccount2,
352
+ createAssociatedTokenAccountInstruction
353
+ } from "@solana/spl-token";
354
+
355
+ // src/facilitators/interface.ts
356
+ init_esm_shims();
357
+ var BaseFacilitator = class {
358
+ supportsNetwork(network) {
359
+ return this.supportedNetworks.includes(network);
360
+ }
361
+ };
362
+
363
+ // src/facilitators/solana.ts
364
+ var SolanaFacilitator = class extends BaseFacilitator {
365
+ name = "solana";
366
+ displayName = "Solana Direct";
367
+ supportedNetworks = ["solana:mainnet", "solana:devnet"];
368
+ connections = /* @__PURE__ */ new Map();
369
+ feePayerKeypair;
370
+ constructor(config) {
371
+ super();
372
+ this.feePayerKeypair = config?.feePayerKeypair;
373
+ for (const [chain, config2] of Object.entries(SOLANA_CHAINS)) {
374
+ this.connections.set(
375
+ chain,
376
+ new Connection3(config2.rpc, "confirmed")
377
+ );
378
+ }
379
+ if (this.feePayerKeypair) {
380
+ console.log(`[SolanaFacilitator] Gasless mode enabled. Fee payer: ${this.feePayerKeypair.publicKey.toBase58()}`);
381
+ }
382
+ }
383
+ /**
384
+ * Get fee payer public key (for gasless transactions)
385
+ */
386
+ getFeePayerPubkey() {
387
+ return this.feePayerKeypair?.publicKey.toBase58() || null;
388
+ }
389
+ getConnection(chain) {
390
+ const conn = this.connections.get(chain);
391
+ if (!conn) {
392
+ throw new Error(`No connection for chain: ${chain}`);
393
+ }
394
+ return conn;
395
+ }
396
+ /**
397
+ * Convert our chain name to network identifier
398
+ */
399
+ static chainToNetwork(chain) {
400
+ return chain === "solana" ? "solana:mainnet" : "solana:devnet";
401
+ }
402
+ /**
403
+ * Convert network identifier to chain name
404
+ */
405
+ static networkToChain(network) {
406
+ if (network === "solana:mainnet") return "solana";
407
+ if (network === "solana:devnet") return "solana_devnet";
408
+ return null;
409
+ }
410
+ async healthCheck() {
411
+ const start = Date.now();
412
+ try {
413
+ const conn = this.getConnection("solana_devnet");
414
+ await conn.getSlot();
415
+ return {
416
+ healthy: true,
417
+ latencyMs: Date.now() - start
418
+ };
419
+ } catch (error) {
420
+ return {
421
+ healthy: false,
422
+ error: error.message
423
+ };
424
+ }
425
+ }
426
+ /**
427
+ * Verify a Solana payment
428
+ *
429
+ * Checks:
430
+ * 1. Transaction is valid and properly signed
431
+ * 2. Transfer instruction matches expected amount and recipient
432
+ */
433
+ async verify(paymentPayload, requirements) {
434
+ try {
435
+ const solanaPayload = paymentPayload.payload;
436
+ if (!solanaPayload || !solanaPayload.signedTransaction) {
437
+ return { valid: false, error: "Missing signed transaction" };
438
+ }
439
+ const chain = solanaPayload.chain || "solana_devnet";
440
+ const chainConfig = SOLANA_CHAINS[chain];
441
+ if (!chainConfig) {
442
+ return { valid: false, error: `Invalid chain: ${chain}` };
443
+ }
444
+ const txBuffer = Buffer.from(solanaPayload.signedTransaction, "base64");
445
+ let tx;
446
+ try {
447
+ tx = Transaction.from(txBuffer);
448
+ } catch {
449
+ tx = VersionedTransaction.deserialize(txBuffer);
450
+ }
451
+ if (tx instanceof Transaction) {
452
+ const hasAnySignature = tx.signatures.some(
453
+ (sig) => sig.signature && !sig.signature.every((b) => b === 0)
454
+ );
455
+ if (!hasAnySignature) {
456
+ return { valid: false, error: "Transaction not signed" };
457
+ }
458
+ }
459
+ const expectedAmount = BigInt(requirements.amount);
460
+ const expectedRecipient = new PublicKey3(requirements.payTo);
461
+ return {
462
+ valid: true,
463
+ details: {
464
+ chain,
465
+ sender: solanaPayload.sender,
466
+ recipient: requirements.payTo,
467
+ amount: requirements.amount
468
+ }
469
+ };
470
+ } catch (error) {
471
+ return { valid: false, error: error.message };
472
+ }
473
+ }
474
+ /**
475
+ * Settle a Solana payment
476
+ *
477
+ * Submits the signed transaction to the network.
478
+ * In gasless mode, adds fee payer signature before submitting.
479
+ */
480
+ async settle(paymentPayload, requirements) {
481
+ try {
482
+ const solanaPayload = paymentPayload.payload;
483
+ if (!solanaPayload || !solanaPayload.signedTransaction) {
484
+ return { success: false, error: "Missing signed transaction" };
485
+ }
486
+ const chain = solanaPayload.chain || "solana_devnet";
487
+ const connection = this.getConnection(chain);
488
+ const txBuffer = Buffer.from(solanaPayload.signedTransaction, "base64");
489
+ let txToSend;
490
+ try {
491
+ const tx = Transaction.from(txBuffer);
492
+ if (this.feePayerKeypair && tx.feePayer) {
493
+ const feePayerPubkey = this.feePayerKeypair.publicKey.toBase58();
494
+ const txFeePayer = tx.feePayer.toBase58();
495
+ if (txFeePayer === feePayerPubkey) {
496
+ console.log(`[SolanaFacilitator] Gasless mode: adding fee payer signature`);
497
+ tx.partialSign(this.feePayerKeypair);
498
+ }
499
+ }
500
+ txToSend = tx.serialize();
501
+ } catch (e) {
502
+ txToSend = txBuffer;
503
+ }
504
+ const signature = await connection.sendRawTransaction(txToSend, {
505
+ skipPreflight: false,
506
+ preflightCommitment: "confirmed"
507
+ });
508
+ const confirmation = await connection.confirmTransaction(signature, "confirmed");
509
+ if (confirmation.value.err) {
510
+ return {
511
+ success: false,
512
+ error: `Transaction failed: ${JSON.stringify(confirmation.value.err)}`,
513
+ transaction: signature
514
+ };
515
+ }
516
+ return {
517
+ success: true,
518
+ transaction: signature,
519
+ status: "confirmed"
520
+ };
521
+ } catch (error) {
522
+ return { success: false, error: error.message };
523
+ }
524
+ }
525
+ supportsNetwork(network) {
526
+ return this.supportedNetworks.includes(network);
527
+ }
528
+ };
529
+ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
530
+ const chainConfig = SOLANA_CHAINS[chain];
531
+ const connection = new Connection3(chainConfig.rpc, "confirmed");
532
+ const mint = new PublicKey3(chainConfig.tokens.USDC.mint);
533
+ const actualFeePayer = feePayerPubkey || senderPubkey;
534
+ const senderATA = await getAssociatedTokenAddress2(mint, senderPubkey);
535
+ const recipientATA = await getAssociatedTokenAddress2(mint, recipientPubkey);
536
+ const transaction = new Transaction();
537
+ try {
538
+ await getAccount2(connection, recipientATA);
539
+ } catch {
540
+ transaction.add(
541
+ createAssociatedTokenAccountInstruction(
542
+ actualFeePayer,
543
+ // payer (fee payer in gasless mode)
544
+ recipientATA,
545
+ // ata to create
546
+ recipientPubkey,
547
+ // owner
548
+ mint
549
+ // mint
550
+ )
551
+ );
552
+ }
553
+ transaction.add(
554
+ createTransferCheckedInstruction(
555
+ senderATA,
556
+ // source
557
+ mint,
558
+ // mint
559
+ recipientATA,
560
+ // destination
561
+ senderPubkey,
562
+ // owner (sender still authorizes the transfer)
563
+ amount,
564
+ // amount
565
+ chainConfig.tokens.USDC.decimals
566
+ // decimals
567
+ )
568
+ );
569
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
570
+ transaction.recentBlockhash = blockhash;
571
+ transaction.feePayer = actualFeePayer;
572
+ return transaction;
573
+ }
574
+
575
+ // src/client/index.ts
576
+ import { PublicKey as PublicKey4 } from "@solana/web3.js";
577
+
154
578
  // src/client/types.ts
155
579
  init_esm_shims();
156
580
 
@@ -173,7 +597,7 @@ var MoltsPayClient = class {
173
597
  todaySpending = 0;
174
598
  lastSpendingReset = 0;
175
599
  constructor(options = {}) {
176
- this.configDir = options.configDir || join(homedir(), ".moltspay");
600
+ this.configDir = options.configDir || join2(homedir2(), ".moltspay");
177
601
  this.config = this.loadConfig();
178
602
  this.walletData = this.loadWallet();
179
603
  this.loadSpending();
@@ -193,6 +617,12 @@ var MoltsPayClient = class {
193
617
  get address() {
194
618
  return this.wallet?.address || null;
195
619
  }
620
+ /**
621
+ * Get wallet instance (for direct operations like approvals)
622
+ */
623
+ getWallet() {
624
+ return this.wallet;
625
+ }
196
626
  /**
197
627
  * Get current config
198
628
  */
@@ -262,9 +692,14 @@ var MoltsPayClient = class {
262
692
  }
263
693
  throw new Error(data.error || "Unexpected response");
264
694
  }
695
+ const wwwAuthHeader = initialRes.headers.get("www-authenticate");
265
696
  const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER);
697
+ if (wwwAuthHeader && wwwAuthHeader.toLowerCase().includes("payment")) {
698
+ console.log("[MoltsPay] Detected MPP protocol, using Tempo flow...");
699
+ return await this.handleMPPPayment(serverUrl, service, params, wwwAuthHeader);
700
+ }
266
701
  if (!paymentRequiredHeader) {
267
- throw new Error("Missing x-payment-required header");
702
+ throw new Error("Missing payment header (x-payment-required or www-authenticate)");
268
703
  }
269
704
  let requirements;
270
705
  try {
@@ -281,17 +716,22 @@ var MoltsPayClient = class {
281
716
  throw new Error("Invalid x-payment-required header");
282
717
  }
283
718
  const networkToChainName = (network2) => {
719
+ if (network2 === "solana:mainnet") return "solana";
720
+ if (network2 === "solana:devnet") return "solana_devnet";
284
721
  const match = network2.match(/^eip155:(\d+)$/);
285
722
  if (!match) return null;
286
723
  const chainId = parseInt(match[1]);
287
724
  if (chainId === 8453) return "base";
288
725
  if (chainId === 137) return "polygon";
289
726
  if (chainId === 84532) return "base_sepolia";
727
+ if (chainId === 42431) return "tempo_moderato";
728
+ if (chainId === 56) return "bnb";
729
+ if (chainId === 97) return "bnb_testnet";
290
730
  return null;
291
731
  };
292
732
  const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
293
- let chainName;
294
733
  const userSpecifiedChain = options.chain;
734
+ let selectedChain;
295
735
  if (userSpecifiedChain) {
296
736
  if (!serverChains.includes(userSpecifiedChain)) {
297
737
  throw new Error(
@@ -299,17 +739,27 @@ var MoltsPayClient = class {
299
739
  Server accepts: ${serverChains.join(", ")}`
300
740
  );
301
741
  }
302
- chainName = userSpecifiedChain;
742
+ selectedChain = userSpecifiedChain;
303
743
  } else {
304
744
  if (serverChains.length === 1 && serverChains[0] === "base") {
305
- chainName = "base";
745
+ selectedChain = "base";
306
746
  } else {
307
747
  throw new Error(
308
748
  `Server accepts: ${serverChains.join(", ")}
309
- Please specify: --chain base, --chain polygon, or --chain base_sepolia`
749
+ Please specify: --chain <chain_name>`
310
750
  );
311
751
  }
312
752
  }
753
+ if (selectedChain === "solana" || selectedChain === "solana_devnet") {
754
+ const solanaChain = selectedChain;
755
+ const network2 = solanaChain === "solana" ? "solana:mainnet" : "solana:devnet";
756
+ const req2 = requirements.find((r) => r.network === network2);
757
+ if (!req2) {
758
+ throw new Error(`Failed to find payment requirement for ${selectedChain}`);
759
+ }
760
+ return await this.handleSolanaPayment(serverUrl, service, params, req2, solanaChain);
761
+ }
762
+ const chainName = selectedChain;
313
763
  const chain = getChain(chainName);
314
764
  const network = `eip155:${chain.chainId}`;
315
765
  const req = requirements.find((r) => r.scheme === "exact" && r.network === network);
@@ -344,6 +794,25 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
344
794
  } else {
345
795
  console.log(`[MoltsPay] Signing payment: $${amount} ${token} (gasless)`);
346
796
  }
797
+ if (chainName === "bnb" || chainName === "bnb_testnet") {
798
+ console.log(`[MoltsPay] Using BNB intent-based payment flow...`);
799
+ const payTo2 = req.payTo || req.resource;
800
+ if (!payTo2) {
801
+ throw new Error("Missing payTo address in payment requirements");
802
+ }
803
+ const bnbSpender = req.extra?.bnbSpender;
804
+ if (!bnbSpender) {
805
+ throw new Error("Server did not provide bnbSpender address. Server may not support BNB payments.");
806
+ }
807
+ return await this.handleBNBPayment(serverUrl, service, params, {
808
+ to: payTo2,
809
+ amount,
810
+ token,
811
+ chainName,
812
+ chain,
813
+ spender: bnbSpender
814
+ });
815
+ }
347
816
  const payTo = req.payTo || req.resource;
348
817
  if (!payTo) {
349
818
  throw new Error("Missing payTo address in payment requirements");
@@ -393,6 +862,300 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
393
862
  console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
394
863
  return result.result;
395
864
  }
865
+ /**
866
+ * Handle MPP (Machine Payments Protocol) payment flow
867
+ * Called when pay() detects WWW-Authenticate header in 402 response
868
+ */
869
+ async handleMPPPayment(serverUrl, service, params, wwwAuthHeader) {
870
+ const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
871
+ const { createWalletClient, createPublicClient, http } = await import("viem");
872
+ const { tempoModerato } = await import("viem/chains");
873
+ const { Actions } = await import("viem/tempo");
874
+ const privateKey = this.walletData.privateKey;
875
+ const account = privateKeyToAccount2(privateKey);
876
+ console.log(`[MoltsPay] Using MPP protocol on Tempo`);
877
+ console.log(`[MoltsPay] Account: ${account.address}`);
878
+ const parseAuthParam = (header, key) => {
879
+ const match = header.match(new RegExp(`${key}="([^"]+)"`, "i"));
880
+ return match ? match[1] : null;
881
+ };
882
+ const challengeId = parseAuthParam(wwwAuthHeader, "id");
883
+ const method = parseAuthParam(wwwAuthHeader, "method");
884
+ const realm = parseAuthParam(wwwAuthHeader, "realm");
885
+ const requestB64 = parseAuthParam(wwwAuthHeader, "request");
886
+ if (method !== "tempo") {
887
+ throw new Error(`Unsupported payment method: ${method}`);
888
+ }
889
+ if (!requestB64) {
890
+ throw new Error("Missing request in WWW-Authenticate");
891
+ }
892
+ const requestJson = Buffer.from(requestB64, "base64").toString("utf-8");
893
+ const paymentRequest = JSON.parse(requestJson);
894
+ const { amount, currency, recipient, methodDetails } = paymentRequest;
895
+ const chainId = methodDetails?.chainId || 42431;
896
+ const amountDisplay = Number(amount) / 1e6;
897
+ console.log(`[MoltsPay] Payment: $${amountDisplay} to ${recipient}`);
898
+ this.checkLimits(amountDisplay);
899
+ console.log(`[MoltsPay] Sending transaction on Tempo...`);
900
+ const tempoChain = { ...tempoModerato, feeToken: currency };
901
+ const publicClient = createPublicClient({
902
+ chain: tempoChain,
903
+ transport: http("https://rpc.moderato.tempo.xyz")
904
+ });
905
+ const walletClient = createWalletClient({
906
+ account,
907
+ chain: tempoChain,
908
+ transport: http("https://rpc.moderato.tempo.xyz")
909
+ });
910
+ const txHash = await Actions.token.transfer(walletClient, {
911
+ to: recipient,
912
+ amount: BigInt(amount),
913
+ token: currency
914
+ });
915
+ console.log(`[MoltsPay] Transaction: ${txHash}`);
916
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
917
+ console.log(`[MoltsPay] Confirmed! Retrying with credential...`);
918
+ const credential = {
919
+ challenge: {
920
+ id: challengeId,
921
+ realm,
922
+ method: "tempo",
923
+ intent: "charge",
924
+ request: paymentRequest
925
+ },
926
+ payload: { hash: txHash, type: "hash" },
927
+ source: `did:pkh:eip155:${chainId}:${account.address}`
928
+ };
929
+ const credentialB64 = Buffer.from(JSON.stringify(credential)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
930
+ const paidRes = await fetch(`${serverUrl}/execute`, {
931
+ method: "POST",
932
+ headers: {
933
+ "Content-Type": "application/json",
934
+ "Authorization": `Payment ${credentialB64}`
935
+ },
936
+ body: JSON.stringify({ service, params, chain: "tempo_moderato" })
937
+ });
938
+ const result = await paidRes.json();
939
+ if (!paidRes.ok) {
940
+ throw new Error(result.error || "Payment verification failed");
941
+ }
942
+ this.recordSpending(amountDisplay);
943
+ console.log(`[MoltsPay] Success!`);
944
+ return result.result || result;
945
+ }
946
+ /**
947
+ * Handle BNB Chain payment flow (pre-approval + intent signature)
948
+ *
949
+ * Flow:
950
+ * 1. Check client has approved server wallet (done via `moltspay init`)
951
+ * 2. Sign EIP-712 payment intent (no gas, just signature)
952
+ * 3. Send intent to server
953
+ * 4. Server executes service
954
+ * 5. Server calls transferFrom if successful (pay-for-success)
955
+ */
956
+ async handleBNBPayment(serverUrl, service, params, paymentDetails) {
957
+ const { to, amount, token, chainName, chain, spender } = paymentDetails;
958
+ const tokenConfig = chain.tokens[token];
959
+ const provider = new ethers.JsonRpcProvider(chain.rpc);
960
+ const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
961
+ const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
962
+ if (allowance < amountWeiCheck) {
963
+ const nativeBalance = await provider.getBalance(this.wallet.address);
964
+ const minGasBalance = ethers.parseEther("0.0005");
965
+ if (nativeBalance < minGasBalance) {
966
+ const nativeBNB = parseFloat(ethers.formatEther(nativeBalance)).toFixed(4);
967
+ const isTestnet = chainName === "bnb_testnet";
968
+ if (isTestnet) {
969
+ throw new Error(
970
+ `\u274C Insufficient tBNB for approval transaction
971
+
972
+ Current tBNB: ${nativeBNB}
973
+ Required: ~0.001 tBNB
974
+
975
+ Get testnet tokens: npx moltspay faucet --chain bnb_testnet
976
+ (Gives USDC + tBNB for gas)`
977
+ );
978
+ } else {
979
+ throw new Error(
980
+ `\u274C Insufficient BNB for approval transaction
981
+
982
+ Current BNB: ${nativeBNB}
983
+ Required: ~0.001 BNB (~$0.60)
984
+
985
+ To get BNB:
986
+ \u2022 Withdraw from Binance/exchange to your wallet
987
+ \u2022 Most exchanges include BNB dust with withdrawals
988
+
989
+ After funding, run:
990
+ npx moltspay approve --chain ${chainName} --spender ${spender}`
991
+ );
992
+ }
993
+ }
994
+ throw new Error(
995
+ `Insufficient allowance for ${spender.slice(0, 10)}...
996
+ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
997
+ );
998
+ }
999
+ const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
1000
+ const intent = {
1001
+ from: this.wallet.address,
1002
+ to,
1003
+ amount: amountWei,
1004
+ token: tokenConfig.address,
1005
+ service,
1006
+ nonce: Date.now(),
1007
+ // Use timestamp as nonce for simplicity
1008
+ deadline: Date.now() + 36e5
1009
+ // 1 hour
1010
+ };
1011
+ const domain = {
1012
+ name: "MoltsPay",
1013
+ version: "1",
1014
+ chainId: chain.chainId
1015
+ };
1016
+ const types = {
1017
+ PaymentIntent: [
1018
+ { name: "from", type: "address" },
1019
+ { name: "to", type: "address" },
1020
+ { name: "amount", type: "uint256" },
1021
+ { name: "token", type: "address" },
1022
+ { name: "service", type: "string" },
1023
+ { name: "nonce", type: "uint256" },
1024
+ { name: "deadline", type: "uint256" }
1025
+ ]
1026
+ };
1027
+ console.log(`[MoltsPay] Signing BNB payment intent...`);
1028
+ const signature = await this.wallet.signTypedData(domain, types, intent);
1029
+ const network = `eip155:${chain.chainId}`;
1030
+ const payload = {
1031
+ x402Version: 2,
1032
+ scheme: "exact",
1033
+ network,
1034
+ payload: {
1035
+ intent: {
1036
+ ...intent,
1037
+ signature
1038
+ },
1039
+ chainId: chain.chainId
1040
+ },
1041
+ accepted: {
1042
+ scheme: "exact",
1043
+ network,
1044
+ asset: tokenConfig.address,
1045
+ amount: amountWei,
1046
+ payTo: to,
1047
+ maxTimeoutSeconds: 300
1048
+ }
1049
+ };
1050
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
1051
+ console.log(`[MoltsPay] Sending BNB payment request...`);
1052
+ const paidRes = await fetch(`${serverUrl}/execute`, {
1053
+ method: "POST",
1054
+ headers: {
1055
+ "Content-Type": "application/json",
1056
+ "X-Payment": paymentHeader
1057
+ },
1058
+ body: JSON.stringify({ service, params, chain: chainName })
1059
+ });
1060
+ const result = await paidRes.json();
1061
+ if (!paidRes.ok) {
1062
+ throw new Error(result.error || "BNB payment failed");
1063
+ }
1064
+ this.recordSpending(amount);
1065
+ console.log(`[MoltsPay] Success! BNB payment settled.`);
1066
+ return result.result || result;
1067
+ }
1068
+ /**
1069
+ * Handle Solana payment flow
1070
+ *
1071
+ * Solana uses SPL token transfers with pay-for-success model:
1072
+ * 1. Client creates and signs a transfer transaction
1073
+ * 2. Server submits the transaction after service completes
1074
+ */
1075
+ async handleSolanaPayment(serverUrl, service, params, requirements, chain) {
1076
+ const solanaWallet = loadSolanaWallet(this.configDir);
1077
+ if (!solanaWallet) {
1078
+ throw new Error("No Solana wallet found. Run: npx moltspay init --chain solana_devnet");
1079
+ }
1080
+ const amount = Number(requirements.amount);
1081
+ const amountUSDC = amount / 1e6;
1082
+ this.checkLimits(amountUSDC);
1083
+ console.log(`[MoltsPay] Creating Solana payment: $${amountUSDC} USDC`);
1084
+ if (!requirements.payTo) {
1085
+ throw new Error("Missing payTo address in payment requirements");
1086
+ }
1087
+ const solanaFeePayer = requirements.extra?.solanaFeePayer;
1088
+ const feePayerPubkey = solanaFeePayer ? new PublicKey4(solanaFeePayer) : void 0;
1089
+ if (feePayerPubkey) {
1090
+ console.log(`[MoltsPay] Gasless mode: server pays fees`);
1091
+ }
1092
+ const recipientPubkey = new PublicKey4(requirements.payTo);
1093
+ const transaction = await createSolanaPaymentTransaction(
1094
+ solanaWallet.publicKey,
1095
+ recipientPubkey,
1096
+ BigInt(amount),
1097
+ chain,
1098
+ feePayerPubkey
1099
+ // Optional fee payer for gasless mode
1100
+ );
1101
+ if (feePayerPubkey) {
1102
+ transaction.partialSign(solanaWallet);
1103
+ } else {
1104
+ transaction.sign(solanaWallet);
1105
+ }
1106
+ const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
1107
+ console.log(`[MoltsPay] Transaction signed, sending to server...`);
1108
+ const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
1109
+ const payload = {
1110
+ x402Version: 2,
1111
+ scheme: "exact",
1112
+ network,
1113
+ payload: {
1114
+ signedTransaction: signedTx,
1115
+ sender: solanaWallet.publicKey.toBase58(),
1116
+ chain
1117
+ },
1118
+ accepted: {
1119
+ scheme: "exact",
1120
+ network,
1121
+ asset: requirements.asset,
1122
+ amount: requirements.amount,
1123
+ payTo: requirements.payTo,
1124
+ maxTimeoutSeconds: 300
1125
+ }
1126
+ };
1127
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
1128
+ const paidRes = await fetch(`${serverUrl}/execute`, {
1129
+ method: "POST",
1130
+ headers: {
1131
+ "Content-Type": "application/json",
1132
+ "X-Payment": paymentHeader
1133
+ },
1134
+ body: JSON.stringify({ service, params, chain })
1135
+ });
1136
+ const result = await paidRes.json();
1137
+ if (!paidRes.ok) {
1138
+ throw new Error(result.error || "Solana payment failed");
1139
+ }
1140
+ this.recordSpending(amountUSDC);
1141
+ console.log(`[MoltsPay] Success! Solana payment settled.`);
1142
+ if (result.payment?.transaction) {
1143
+ const explorerUrl = chain === "solana" ? `https://solscan.io/tx/${result.payment.transaction}` : `https://solscan.io/tx/${result.payment.transaction}?cluster=devnet`;
1144
+ console.log(`[MoltsPay] Transaction: ${explorerUrl}`);
1145
+ }
1146
+ return result.result || result;
1147
+ }
1148
+ /**
1149
+ * Check ERC20 allowance for a spender
1150
+ */
1151
+ async checkAllowance(tokenAddress, spender, provider) {
1152
+ const contract = new ethers.Contract(
1153
+ tokenAddress,
1154
+ ["function allowance(address owner, address spender) view returns (uint256)"],
1155
+ provider
1156
+ );
1157
+ return await contract.allowance(this.wallet.address, spender);
1158
+ }
396
1159
  /**
397
1160
  * Sign EIP-3009 transferWithAuthorization (GASLESS)
398
1161
  * This only signs - no on-chain transaction, no gas needed.
@@ -463,26 +1226,26 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
463
1226
  }
464
1227
  // --- Config & Wallet Management ---
465
1228
  loadConfig() {
466
- const configPath = join(this.configDir, "config.json");
467
- if (existsSync(configPath)) {
468
- const content = readFileSync(configPath, "utf-8");
1229
+ const configPath = join2(this.configDir, "config.json");
1230
+ if (existsSync2(configPath)) {
1231
+ const content = readFileSync2(configPath, "utf-8");
469
1232
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
470
1233
  }
471
1234
  return { ...DEFAULT_CONFIG };
472
1235
  }
473
1236
  saveConfig() {
474
- mkdirSync(this.configDir, { recursive: true });
475
- const configPath = join(this.configDir, "config.json");
476
- writeFileSync(configPath, JSON.stringify(this.config, null, 2));
1237
+ mkdirSync2(this.configDir, { recursive: true });
1238
+ const configPath = join2(this.configDir, "config.json");
1239
+ writeFileSync2(configPath, JSON.stringify(this.config, null, 2));
477
1240
  }
478
1241
  /**
479
1242
  * Load spending data from disk
480
1243
  */
481
1244
  loadSpending() {
482
- const spendingPath = join(this.configDir, "spending.json");
483
- if (existsSync(spendingPath)) {
1245
+ const spendingPath = join2(this.configDir, "spending.json");
1246
+ if (existsSync2(spendingPath)) {
484
1247
  try {
485
- const data = JSON.parse(readFileSync(spendingPath, "utf-8"));
1248
+ const data = JSON.parse(readFileSync2(spendingPath, "utf-8"));
486
1249
  const today = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
487
1250
  if (data.date && data.date === today) {
488
1251
  this.todaySpending = data.amount || 0;
@@ -501,18 +1264,18 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
501
1264
  * Save spending data to disk
502
1265
  */
503
1266
  saveSpending() {
504
- mkdirSync(this.configDir, { recursive: true });
505
- const spendingPath = join(this.configDir, "spending.json");
1267
+ mkdirSync2(this.configDir, { recursive: true });
1268
+ const spendingPath = join2(this.configDir, "spending.json");
506
1269
  const data = {
507
1270
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
508
1271
  amount: this.todaySpending,
509
1272
  updatedAt: Date.now()
510
1273
  };
511
- writeFileSync(spendingPath, JSON.stringify(data, null, 2));
1274
+ writeFileSync2(spendingPath, JSON.stringify(data, null, 2));
512
1275
  }
513
1276
  loadWallet() {
514
- const walletPath = join(this.configDir, "wallet.json");
515
- if (existsSync(walletPath)) {
1277
+ const walletPath = join2(this.configDir, "wallet.json");
1278
+ if (existsSync2(walletPath)) {
516
1279
  try {
517
1280
  const stats = statSync(walletPath);
518
1281
  const mode = stats.mode & 511;
@@ -523,7 +1286,7 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
523
1286
  }
524
1287
  } catch (err) {
525
1288
  }
526
- const content = readFileSync(walletPath, "utf-8");
1289
+ const content = readFileSync2(walletPath, "utf-8");
527
1290
  return JSON.parse(content);
528
1291
  }
529
1292
  return null;
@@ -532,15 +1295,15 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
532
1295
  * Initialize a new wallet (called by CLI)
533
1296
  */
534
1297
  static init(configDir, options) {
535
- mkdirSync(configDir, { recursive: true });
1298
+ mkdirSync2(configDir, { recursive: true });
536
1299
  const wallet = Wallet.createRandom();
537
1300
  const walletData = {
538
1301
  address: wallet.address,
539
1302
  privateKey: wallet.privateKey,
540
1303
  createdAt: Date.now()
541
1304
  };
542
- const walletPath = join(configDir, "wallet.json");
543
- writeFileSync(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
1305
+ const walletPath = join2(configDir, "wallet.json");
1306
+ writeFileSync2(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
544
1307
  const config = {
545
1308
  chain: options.chain,
546
1309
  limits: {
@@ -548,8 +1311,8 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
548
1311
  maxPerDay: options.maxPerDay
549
1312
  }
550
1313
  };
551
- const configPath = join(configDir, "config.json");
552
- writeFileSync(configPath, JSON.stringify(config, null, 2));
1314
+ const configPath = join2(configDir, "config.json");
1315
+ writeFileSync2(configPath, JSON.stringify(config, null, 2));
553
1316
  return { address: wallet.address, configDir };
554
1317
  }
555
1318
  /**
@@ -585,7 +1348,7 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
585
1348
  if (!this.wallet) {
586
1349
  throw new Error("Client not initialized");
587
1350
  }
588
- const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
1351
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet"];
589
1352
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
590
1353
  const results = {};
591
1354
  const tempoTokens = {
@@ -656,12 +1419,12 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
656
1419
  if (!this.wallet || !this.walletData) {
657
1420
  throw new Error("Client not initialized. Run: npx moltspay init");
658
1421
  }
659
- const { privateKeyToAccount } = await import("viem/accounts");
1422
+ const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
660
1423
  const { createWalletClient, createPublicClient, http } = await import("viem");
661
1424
  const { tempoModerato } = await import("viem/chains");
662
1425
  const { Actions } = await import("viem/tempo");
663
1426
  const privateKey = this.walletData.privateKey;
664
- const account = privateKeyToAccount(privateKey);
1427
+ const account = privateKeyToAccount2(privateKey);
665
1428
  console.log(`[MoltsPay] Making MPP request to: ${url}`);
666
1429
  console.log(`[MoltsPay] Using account: ${account.address}`);
667
1430
  const initResponse = await fetch(url, {
@@ -758,24 +1521,16 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
758
1521
 
759
1522
  // src/server/index.ts
760
1523
  init_esm_shims();
761
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
1524
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
762
1525
  import { createServer } from "http";
763
1526
  import * as path3 from "path";
764
1527
 
765
1528
  // src/facilitators/index.ts
766
1529
  init_esm_shims();
767
1530
 
768
- // src/facilitators/interface.ts
769
- init_esm_shims();
770
- var BaseFacilitator = class {
771
- supportsNetwork(network) {
772
- return this.supportedNetworks.includes(network);
773
- }
774
- };
775
-
776
1531
  // src/facilitators/cdp.ts
777
1532
  init_esm_shims();
778
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
1533
+ import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
779
1534
  import * as path2 from "path";
780
1535
  var X402_VERSION2 = 2;
781
1536
  var CDP_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
@@ -786,9 +1541,9 @@ function loadEnvFile() {
786
1541
  path2.join(process.env.HOME || "", ".moltspay", ".env")
787
1542
  ];
788
1543
  for (const envPath of envPaths) {
789
- if (existsSync2(envPath)) {
1544
+ if (existsSync3(envPath)) {
790
1545
  try {
791
- const content = readFileSync2(envPath, "utf-8");
1546
+ const content = readFileSync3(envPath, "utf-8");
792
1547
  for (const line of content.split("\n")) {
793
1548
  const trimmed = line.trim();
794
1549
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -1026,16 +1781,278 @@ var TempoFacilitator = class extends BaseFacilitator {
1026
1781
  }
1027
1782
  return { healthy: true, latencyMs: Date.now() - start };
1028
1783
  } catch (error) {
1029
- return { healthy: false, error: String(error) };
1784
+ return { healthy: false, error: String(error) };
1785
+ }
1786
+ }
1787
+ async verify(paymentPayload, requirements) {
1788
+ try {
1789
+ const tempoPayload = paymentPayload.payload;
1790
+ if (!tempoPayload?.txHash) {
1791
+ return { valid: false, error: "Missing txHash in payment payload" };
1792
+ }
1793
+ const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
1794
+ if (!receipt) {
1795
+ return { valid: false, error: "Transaction not found" };
1796
+ }
1797
+ if (receipt.status !== "0x1") {
1798
+ return { valid: false, error: "Transaction failed" };
1799
+ }
1800
+ const transferLog = receipt.logs.find(
1801
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC
1802
+ );
1803
+ if (!transferLog) {
1804
+ return { valid: false, error: "No Transfer event found" };
1805
+ }
1806
+ const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
1807
+ const expectedTo = requirements.payTo.toLowerCase();
1808
+ if (toAddress !== expectedTo) {
1809
+ return {
1810
+ valid: false,
1811
+ error: `Wrong recipient: ${toAddress}, expected ${expectedTo}`
1812
+ };
1813
+ }
1814
+ const amount = BigInt(transferLog.data);
1815
+ const expectedAmount = BigInt(requirements.amount);
1816
+ if (amount < expectedAmount) {
1817
+ return {
1818
+ valid: false,
1819
+ error: `Insufficient amount: ${amount}, expected ${expectedAmount}`
1820
+ };
1821
+ }
1822
+ const tokenAddress = transferLog.address.toLowerCase();
1823
+ const expectedToken = requirements.asset.toLowerCase();
1824
+ if (tokenAddress !== expectedToken) {
1825
+ return {
1826
+ valid: false,
1827
+ error: `Wrong token: ${tokenAddress}, expected ${expectedToken}`
1828
+ };
1829
+ }
1830
+ return {
1831
+ valid: true,
1832
+ details: {
1833
+ txHash: tempoPayload.txHash,
1834
+ from: "0x" + transferLog.topics[1].slice(26),
1835
+ to: toAddress,
1836
+ amount: amount.toString(),
1837
+ token: tokenAddress
1838
+ }
1839
+ };
1840
+ } catch (error) {
1841
+ return { valid: false, error: `Verification failed: ${error}` };
1842
+ }
1843
+ }
1844
+ async settle(paymentPayload, requirements) {
1845
+ const verifyResult = await this.verify(paymentPayload, requirements);
1846
+ if (!verifyResult.valid) {
1847
+ return { success: false, error: verifyResult.error };
1848
+ }
1849
+ const tempoPayload = paymentPayload.payload;
1850
+ return {
1851
+ success: true,
1852
+ transaction: tempoPayload.txHash,
1853
+ status: "settled"
1854
+ };
1855
+ }
1856
+ async getTransactionReceipt(txHash) {
1857
+ const response = await fetch(this.rpcUrl, {
1858
+ method: "POST",
1859
+ headers: { "Content-Type": "application/json" },
1860
+ body: JSON.stringify({
1861
+ jsonrpc: "2.0",
1862
+ method: "eth_getTransactionReceipt",
1863
+ params: [txHash],
1864
+ id: 1
1865
+ })
1866
+ });
1867
+ const data = await response.json();
1868
+ return data.result;
1869
+ }
1870
+ };
1871
+
1872
+ // src/facilitators/bnb.ts
1873
+ init_esm_shims();
1874
+ import { privateKeyToAccount } from "viem/accounts";
1875
+ var TRANSFER_EVENT_TOPIC2 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
1876
+ var EIP712_DOMAIN = {
1877
+ name: "MoltsPay",
1878
+ version: "1"
1879
+ };
1880
+ var INTENT_TYPES = {
1881
+ PaymentIntent: [
1882
+ { name: "from", type: "address" },
1883
+ { name: "to", type: "address" },
1884
+ { name: "amount", type: "uint256" },
1885
+ { name: "token", type: "address" },
1886
+ { name: "service", type: "string" },
1887
+ { name: "nonce", type: "uint256" },
1888
+ { name: "deadline", type: "uint256" }
1889
+ ]
1890
+ };
1891
+ var BNBFacilitator = class extends BaseFacilitator {
1892
+ name = "bnb";
1893
+ displayName = "BNB Smart Chain";
1894
+ supportedNetworks = ["eip155:56", "eip155:97"];
1895
+ // Mainnet + Testnet
1896
+ serverPrivateKey;
1897
+ spenderAddress = null;
1898
+ chainConfigs;
1899
+ constructor(serverPrivateKey) {
1900
+ super();
1901
+ this.serverPrivateKey = serverPrivateKey || process.env.BNB_SERVER_PRIVATE_KEY || "";
1902
+ if (this.serverPrivateKey) {
1903
+ const key = this.serverPrivateKey.startsWith("0x") ? this.serverPrivateKey : `0x${this.serverPrivateKey}`;
1904
+ const account = privateKeyToAccount(key);
1905
+ this.spenderAddress = account.address;
1906
+ }
1907
+ this.chainConfigs = {
1908
+ 56: { rpc: CHAINS.bnb.rpc, chain: CHAINS.bnb },
1909
+ 97: { rpc: CHAINS.bnb_testnet.rpc, chain: CHAINS.bnb_testnet }
1910
+ };
1911
+ }
1912
+ async healthCheck() {
1913
+ const start = Date.now();
1914
+ try {
1915
+ const response = await fetch(this.chainConfigs[56].rpc, {
1916
+ method: "POST",
1917
+ headers: { "Content-Type": "application/json" },
1918
+ body: JSON.stringify({
1919
+ jsonrpc: "2.0",
1920
+ method: "eth_chainId",
1921
+ params: [],
1922
+ id: 1
1923
+ })
1924
+ });
1925
+ const data = await response.json();
1926
+ const chainId = parseInt(data.result, 16);
1927
+ if (chainId !== 56) {
1928
+ return { healthy: false, error: `Wrong chainId: ${chainId}` };
1929
+ }
1930
+ return { healthy: true, latencyMs: Date.now() - start };
1931
+ } catch (error) {
1932
+ return { healthy: false, error: String(error) };
1933
+ }
1934
+ }
1935
+ /**
1936
+ * Verify a payment intent signature (before service execution)
1937
+ *
1938
+ * This verifies:
1939
+ * 1. Signature is valid for the intent
1940
+ * 2. Client has approved server wallet
1941
+ * 3. Client has sufficient balance
1942
+ * 4. Intent hasn't expired
1943
+ */
1944
+ async verify(paymentPayload, requirements) {
1945
+ try {
1946
+ const bnbPayload = paymentPayload.payload;
1947
+ if (!bnbPayload?.intent) {
1948
+ return { valid: false, error: "Missing intent in payment payload" };
1949
+ }
1950
+ const { intent, chainId } = bnbPayload;
1951
+ const config = this.chainConfigs[chainId];
1952
+ if (!config) {
1953
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
1954
+ }
1955
+ if (intent.deadline < Date.now()) {
1956
+ return { valid: false, error: "Intent expired" };
1957
+ }
1958
+ const recoveredAddress = await this.recoverIntentSigner(intent, chainId);
1959
+ if (recoveredAddress.toLowerCase() !== intent.from.toLowerCase()) {
1960
+ return { valid: false, error: "Invalid signature" };
1961
+ }
1962
+ if (intent.to.toLowerCase() !== requirements.payTo.toLowerCase()) {
1963
+ return { valid: false, error: `Wrong recipient: ${intent.to}` };
1964
+ }
1965
+ if (BigInt(intent.amount) < BigInt(requirements.amount)) {
1966
+ return { valid: false, error: `Insufficient amount: ${intent.amount}` };
1967
+ }
1968
+ if (intent.token.toLowerCase() !== requirements.asset.toLowerCase()) {
1969
+ return { valid: false, error: `Wrong token: ${intent.token}` };
1970
+ }
1971
+ const serverAddress = await this.getServerAddress();
1972
+ const allowance = await this.getAllowance(intent.from, serverAddress, intent.token, config.rpc);
1973
+ if (BigInt(allowance) < BigInt(intent.amount)) {
1974
+ return { valid: false, error: "Insufficient allowance. Run: npx moltspay init --chain bnb" };
1975
+ }
1976
+ const balance = await this.getBalance(intent.from, intent.token, config.rpc);
1977
+ if (BigInt(balance) < BigInt(intent.amount)) {
1978
+ return { valid: false, error: "Insufficient balance" };
1979
+ }
1980
+ return {
1981
+ valid: true,
1982
+ details: {
1983
+ from: intent.from,
1984
+ to: intent.to,
1985
+ amount: intent.amount,
1986
+ token: intent.token,
1987
+ service: intent.service,
1988
+ nonce: intent.nonce,
1989
+ deadline: intent.deadline
1990
+ }
1991
+ };
1992
+ } catch (error) {
1993
+ return { valid: false, error: `Verification failed: ${error}` };
1030
1994
  }
1031
1995
  }
1032
- async verify(paymentPayload, requirements) {
1996
+ /**
1997
+ * Settle a payment by executing transferFrom
1998
+ *
1999
+ * This is called AFTER the service has been successfully delivered.
2000
+ * Server pays gas, transfers tokens from client to provider.
2001
+ */
2002
+ async settle(paymentPayload, requirements) {
2003
+ if (!this.serverPrivateKey) {
2004
+ return { success: false, error: "Server wallet not configured (BNB_SERVER_PRIVATE_KEY)" };
2005
+ }
1033
2006
  try {
1034
- const tempoPayload = paymentPayload.payload;
1035
- if (!tempoPayload?.txHash) {
1036
- return { valid: false, error: "Missing txHash in payment payload" };
2007
+ const verifyResult = await this.verify(paymentPayload, requirements);
2008
+ if (!verifyResult.valid) {
2009
+ return { success: false, error: verifyResult.error };
1037
2010
  }
1038
- const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
2011
+ const bnbPayload = paymentPayload.payload;
2012
+ const { intent, chainId } = bnbPayload;
2013
+ const config = this.chainConfigs[chainId];
2014
+ const txHash = await this.executeTransferFrom(
2015
+ intent.from,
2016
+ intent.to,
2017
+ intent.amount,
2018
+ intent.token,
2019
+ config.rpc
2020
+ );
2021
+ return {
2022
+ success: true,
2023
+ transaction: txHash,
2024
+ status: "settled"
2025
+ };
2026
+ } catch (error) {
2027
+ return { success: false, error: `Settlement failed: ${error}` };
2028
+ }
2029
+ }
2030
+ /**
2031
+ * Check if client has approved the server wallet
2032
+ */
2033
+ async checkApproval(clientAddress, token, chainId) {
2034
+ const config = this.chainConfigs[chainId];
2035
+ if (!config) {
2036
+ throw new Error(`Unsupported chainId: ${chainId}`);
2037
+ }
2038
+ const serverAddress = await this.getServerAddress();
2039
+ const allowance = await this.getAllowance(clientAddress, serverAddress, token, config.rpc);
2040
+ const minAllowance = BigInt("1000000000000000000000");
2041
+ return {
2042
+ approved: BigInt(allowance) >= minAllowance,
2043
+ allowance
2044
+ };
2045
+ }
2046
+ /**
2047
+ * Verify a completed transaction (for checking past payments)
2048
+ */
2049
+ async verifyTransaction(txHash, expected, chainId) {
2050
+ const config = this.chainConfigs[chainId];
2051
+ if (!config) {
2052
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
2053
+ }
2054
+ try {
2055
+ const receipt = await this.getTransactionReceipt(txHash, config.rpc);
1039
2056
  if (!receipt) {
1040
2057
  return { valid: false, error: "Transaction not found" };
1041
2058
  }
@@ -1043,63 +2060,117 @@ var TempoFacilitator = class extends BaseFacilitator {
1043
2060
  return { valid: false, error: "Transaction failed" };
1044
2061
  }
1045
2062
  const transferLog = receipt.logs.find(
1046
- (log) => log.topics[0] === TRANSFER_EVENT_TOPIC
2063
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC2 && log.address.toLowerCase() === expected.token.toLowerCase()
1047
2064
  );
1048
2065
  if (!transferLog) {
1049
2066
  return { valid: false, error: "No Transfer event found" };
1050
2067
  }
1051
2068
  const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
1052
- const expectedTo = requirements.payTo.toLowerCase();
1053
- if (toAddress !== expectedTo) {
1054
- return {
1055
- valid: false,
1056
- error: `Wrong recipient: ${toAddress}, expected ${expectedTo}`
1057
- };
2069
+ if (toAddress !== expected.to.toLowerCase()) {
2070
+ return { valid: false, error: `Wrong recipient: ${toAddress}` };
1058
2071
  }
1059
2072
  const amount = BigInt(transferLog.data);
1060
- const expectedAmount = BigInt(requirements.amount);
1061
- if (amount < expectedAmount) {
1062
- return {
1063
- valid: false,
1064
- error: `Insufficient amount: ${amount}, expected ${expectedAmount}`
1065
- };
1066
- }
1067
- const tokenAddress = transferLog.address.toLowerCase();
1068
- const expectedToken = requirements.asset.toLowerCase();
1069
- if (tokenAddress !== expectedToken) {
1070
- return {
1071
- valid: false,
1072
- error: `Wrong token: ${tokenAddress}, expected ${expectedToken}`
1073
- };
2073
+ if (amount < BigInt(expected.amount)) {
2074
+ return { valid: false, error: `Insufficient amount: ${amount}` };
1074
2075
  }
1075
2076
  return {
1076
2077
  valid: true,
1077
2078
  details: {
1078
- txHash: tempoPayload.txHash,
2079
+ txHash,
1079
2080
  from: "0x" + transferLog.topics[1].slice(26),
1080
2081
  to: toAddress,
1081
2082
  amount: amount.toString(),
1082
- token: tokenAddress
2083
+ token: transferLog.address
1083
2084
  }
1084
2085
  };
1085
2086
  } catch (error) {
1086
2087
  return { valid: false, error: `Verification failed: ${error}` };
1087
2088
  }
1088
2089
  }
1089
- async settle(paymentPayload, requirements) {
1090
- const verifyResult = await this.verify(paymentPayload, requirements);
1091
- if (!verifyResult.valid) {
1092
- return { success: false, error: verifyResult.error };
1093
- }
1094
- const tempoPayload = paymentPayload.payload;
1095
- return {
1096
- success: true,
1097
- transaction: tempoPayload.txHash,
1098
- status: "settled"
2090
+ // ==================== Private Methods ====================
2091
+ /**
2092
+ * Get the server's spender address (public, for 402 responses)
2093
+ * Returns cached value computed at construction time.
2094
+ */
2095
+ getSpenderAddress() {
2096
+ return this.spenderAddress;
2097
+ }
2098
+ async getServerAddress() {
2099
+ const { ethers: ethers3 } = await import("ethers");
2100
+ const wallet = new ethers3.Wallet(this.serverPrivateKey);
2101
+ return wallet.address;
2102
+ }
2103
+ async recoverIntentSigner(intent, chainId) {
2104
+ const { ethers: ethers3 } = await import("ethers");
2105
+ const domain = {
2106
+ ...EIP712_DOMAIN,
2107
+ chainId
1099
2108
  };
2109
+ const message = {
2110
+ from: intent.from,
2111
+ to: intent.to,
2112
+ amount: intent.amount,
2113
+ token: intent.token,
2114
+ service: intent.service,
2115
+ nonce: intent.nonce,
2116
+ deadline: intent.deadline
2117
+ };
2118
+ const recoveredAddress = ethers3.verifyTypedData(
2119
+ domain,
2120
+ INTENT_TYPES,
2121
+ message,
2122
+ intent.signature
2123
+ );
2124
+ return recoveredAddress;
1100
2125
  }
1101
- async getTransactionReceipt(txHash) {
1102
- const response = await fetch(this.rpcUrl, {
2126
+ async getAllowance(owner, spender, token, rpcUrl) {
2127
+ const selector = "0xdd62ed3e";
2128
+ const ownerPadded = owner.toLowerCase().replace("0x", "").padStart(64, "0");
2129
+ const spenderPadded = spender.toLowerCase().replace("0x", "").padStart(64, "0");
2130
+ const data = selector + ownerPadded + spenderPadded;
2131
+ const response = await fetch(rpcUrl, {
2132
+ method: "POST",
2133
+ headers: { "Content-Type": "application/json" },
2134
+ body: JSON.stringify({
2135
+ jsonrpc: "2.0",
2136
+ method: "eth_call",
2137
+ params: [{ to: token, data }, "latest"],
2138
+ id: 1
2139
+ })
2140
+ });
2141
+ const result = await response.json();
2142
+ return result.result || "0x0";
2143
+ }
2144
+ async getBalance(account, token, rpcUrl) {
2145
+ const selector = "0x70a08231";
2146
+ const accountPadded = account.toLowerCase().replace("0x", "").padStart(64, "0");
2147
+ const data = selector + accountPadded;
2148
+ const response = await fetch(rpcUrl, {
2149
+ method: "POST",
2150
+ headers: { "Content-Type": "application/json" },
2151
+ body: JSON.stringify({
2152
+ jsonrpc: "2.0",
2153
+ method: "eth_call",
2154
+ params: [{ to: token, data }, "latest"],
2155
+ id: 1
2156
+ })
2157
+ });
2158
+ const result = await response.json();
2159
+ return result.result || "0x0";
2160
+ }
2161
+ async executeTransferFrom(from, to, amount, token, rpcUrl) {
2162
+ const { ethers: ethers3 } = await import("ethers");
2163
+ const provider = new ethers3.JsonRpcProvider(rpcUrl);
2164
+ const wallet = new ethers3.Wallet(this.serverPrivateKey, provider);
2165
+ const tokenContract = new ethers3.Contract(token, [
2166
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)"
2167
+ ], wallet);
2168
+ const tx = await tokenContract.transferFrom(from, to, amount);
2169
+ const receipt = await tx.wait();
2170
+ return receipt.hash;
2171
+ }
2172
+ async getTransactionReceipt(txHash, rpcUrl) {
2173
+ const response = await fetch(rpcUrl, {
1103
2174
  method: "POST",
1104
2175
  headers: { "Content-Type": "application/json" },
1105
2176
  body: JSON.stringify({
@@ -1116,6 +2187,8 @@ var TempoFacilitator = class extends BaseFacilitator {
1116
2187
 
1117
2188
  // src/facilitators/registry.ts
1118
2189
  init_esm_shims();
2190
+ import { Keypair as Keypair4 } from "@solana/web3.js";
2191
+ import bs582 from "bs58";
1119
2192
  var FacilitatorRegistry = class {
1120
2193
  factories = /* @__PURE__ */ new Map();
1121
2194
  instances = /* @__PURE__ */ new Map();
@@ -1124,7 +2197,20 @@ var FacilitatorRegistry = class {
1124
2197
  constructor(selection) {
1125
2198
  this.registerFactory("cdp", (config) => new CDPFacilitator(config));
1126
2199
  this.registerFactory("tempo", () => new TempoFacilitator());
1127
- this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
2200
+ this.registerFactory("bnb", (config) => new BNBFacilitator(config?.serverPrivateKey));
2201
+ this.registerFactory("solana", (config) => {
2202
+ let feePayerKeypair;
2203
+ const feePayerKey = config?.feePayerPrivateKey || process.env.SOLANA_FEE_PAYER_KEY;
2204
+ if (feePayerKey) {
2205
+ try {
2206
+ feePayerKeypair = Keypair4.fromSecretKey(bs582.decode(feePayerKey));
2207
+ } catch (e) {
2208
+ console.warn(`[SolanaFacilitator] Invalid fee payer key: ${e.message}`);
2209
+ }
2210
+ }
2211
+ return new SolanaFacilitator({ feePayerKeypair });
2212
+ });
2213
+ this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
1128
2214
  }
1129
2215
  /**
1130
2216
  * Register a new facilitator factory
@@ -1371,14 +2457,40 @@ var TOKEN_ADDRESSES = {
1371
2457
  // pathUSD
1372
2458
  USDT: "0x20c0000000000000000000000000000000000001"
1373
2459
  // alphaUSD
2460
+ },
2461
+ // BNB Smart Chain mainnet
2462
+ "eip155:56": {
2463
+ USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
2464
+ USDT: "0x55d398326f99059fF775485246999027B3197955"
2465
+ },
2466
+ // BNB Smart Chain testnet
2467
+ "eip155:97": {
2468
+ USDC: "0x64544969ed7EBf5f083679233325356EbE738930",
2469
+ USDT: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd"
2470
+ },
2471
+ // Solana networks use mint addresses (SPL tokens)
2472
+ "solana:mainnet": {
2473
+ USDC: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
2474
+ // Circle USDC
2475
+ },
2476
+ "solana:devnet": {
2477
+ USDC: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
2478
+ // Devnet USDC
1374
2479
  }
1375
2480
  };
1376
2481
  var CHAIN_TO_NETWORK = {
1377
2482
  "base": "eip155:8453",
1378
2483
  "base_sepolia": "eip155:84532",
1379
2484
  "polygon": "eip155:137",
1380
- "tempo_moderato": "eip155:42431"
2485
+ "tempo_moderato": "eip155:42431",
2486
+ "bnb": "eip155:56",
2487
+ "bnb_testnet": "eip155:97",
2488
+ "solana": "solana:mainnet",
2489
+ "solana_devnet": "solana:devnet"
1381
2490
  };
2491
+ function isSolanaNetwork(network) {
2492
+ return network.startsWith("solana:");
2493
+ }
1382
2494
  var TOKEN_DOMAINS = {
1383
2495
  // Base mainnet
1384
2496
  "eip155:8453": {
@@ -1400,6 +2512,16 @@ var TOKEN_DOMAINS = {
1400
2512
  "eip155:42431": {
1401
2513
  USDC: { name: "pathUSD", version: "1" },
1402
2514
  USDT: { name: "alphaUSD", version: "1" }
2515
+ },
2516
+ // BNB Smart Chain mainnet
2517
+ "eip155:56": {
2518
+ USDC: { name: "USD Coin", version: "1" },
2519
+ USDT: { name: "Tether USD", version: "1" }
2520
+ },
2521
+ // BNB Smart Chain testnet
2522
+ "eip155:97": {
2523
+ USDC: { name: "USD Coin", version: "1" },
2524
+ USDT: { name: "Tether USD", version: "1" }
1403
2525
  }
1404
2526
  };
1405
2527
  function getTokenDomain(network, token) {
@@ -1415,9 +2537,9 @@ function loadEnvFile2() {
1415
2537
  path3.join(process.env.HOME || "", ".moltspay", ".env")
1416
2538
  ];
1417
2539
  for (const envPath of envPaths) {
1418
- if (existsSync3(envPath)) {
2540
+ if (existsSync4(envPath)) {
1419
2541
  try {
1420
- const content = readFileSync3(envPath, "utf-8");
2542
+ const content = readFileSync4(envPath, "utf-8");
1421
2543
  for (const line of content.split("\n")) {
1422
2544
  const trimmed = line.trim();
1423
2545
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -1448,7 +2570,7 @@ var MoltsPayServer = class {
1448
2570
  useMainnet;
1449
2571
  constructor(servicesPath, options = {}) {
1450
2572
  loadEnvFile2();
1451
- const content = readFileSync3(servicesPath, "utf-8");
2573
+ const content = readFileSync4(servicesPath, "utf-8");
1452
2574
  this.manifest = JSON.parse(content);
1453
2575
  this.options = {
1454
2576
  port: options.port || 3e3,
@@ -1457,7 +2579,7 @@ var MoltsPayServer = class {
1457
2579
  };
1458
2580
  this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
1459
2581
  this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
1460
- const defaultFallback = ["tempo"];
2582
+ const defaultFallback = ["tempo", "bnb", "solana"];
1461
2583
  const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
1462
2584
  const facilitatorConfig = options.facilitators || {
1463
2585
  primary: process.env.FACILITATOR_PRIMARY || "cdp",
@@ -1500,12 +2622,20 @@ var MoltsPayServer = class {
1500
2622
  */
1501
2623
  getProviderChains() {
1502
2624
  const provider = this.manifest.provider;
2625
+ const getWalletForChain = (chainName, explicitWallet) => {
2626
+ if (explicitWallet) return explicitWallet;
2627
+ if ((chainName === "solana" || chainName === "solana_devnet") && provider.solana_wallet) {
2628
+ return provider.solana_wallet;
2629
+ }
2630
+ return provider.wallet;
2631
+ };
1503
2632
  if (provider.chains && provider.chains.length > 0) {
1504
2633
  return provider.chains.map((c) => {
1505
2634
  const chainName = typeof c === "string" ? c : c.chain;
2635
+ const explicitWallet = typeof c === "object" ? c.wallet : null;
1506
2636
  return {
1507
2637
  network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
1508
- wallet: (typeof c === "object" ? c.wallet : null) || provider.wallet,
2638
+ wallet: getWalletForChain(chainName, explicitWallet || void 0),
1509
2639
  tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
1510
2640
  };
1511
2641
  });
@@ -1514,7 +2644,7 @@ var MoltsPayServer = class {
1514
2644
  const network = CHAIN_TO_NETWORK[chain] || this.networkId;
1515
2645
  return [{
1516
2646
  network,
1517
- wallet: provider.wallet,
2647
+ wallet: getWalletForChain(chain),
1518
2648
  tokens: ["USDC"]
1519
2649
  }];
1520
2650
  }
@@ -1585,7 +2715,8 @@ var MoltsPayServer = class {
1585
2715
  }
1586
2716
  const body = await this.readBody(req);
1587
2717
  const paymentHeader = req.headers[PAYMENT_HEADER2];
1588
- return await this.handleProxy(body, paymentHeader, res);
2718
+ const authHeader = req.headers[MPP_AUTH_HEADER];
2719
+ return await this.handleProxy(body, paymentHeader, authHeader, res);
1589
2720
  }
1590
2721
  const servicePath = url.pathname.replace(/^\//, "");
1591
2722
  const skill = this.skills.get(servicePath);
@@ -1622,7 +2753,9 @@ var MoltsPayServer = class {
1622
2753
  name: this.manifest.provider.name,
1623
2754
  description: this.manifest.provider.description,
1624
2755
  wallet: this.manifest.provider.wallet,
1625
- chain: this.manifest.provider.chain || "base"
2756
+ chain: this.manifest.provider.chain || "base",
2757
+ solana_wallet: this.manifest.provider.solana_wallet,
2758
+ chains: this.manifest.provider.chains
1626
2759
  },
1627
2760
  services,
1628
2761
  endpoints: {
@@ -1735,6 +2868,21 @@ var MoltsPayServer = class {
1735
2868
  });
1736
2869
  }
1737
2870
  console.log(`[MoltsPay] Verified by ${verifyResult.facilitator}`);
2871
+ const isSolana = isSolanaNetwork(paymentNetwork);
2872
+ let settlement = null;
2873
+ if (isSolana) {
2874
+ console.log(`[MoltsPay] Solana detected - settling payment FIRST (blockhash expiry protection)`);
2875
+ try {
2876
+ settlement = await this.registry.settle(payment, requirements);
2877
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2878
+ } catch (err) {
2879
+ console.error("[MoltsPay] Solana settlement failed:", err.message);
2880
+ return this.sendJson(res, 402, {
2881
+ error: "Payment settlement failed",
2882
+ message: err.message
2883
+ });
2884
+ }
2885
+ }
1738
2886
  const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
1739
2887
  console.log(`[MoltsPay] Executing skill: ${service} (timeout: ${timeoutSeconds}s)`);
1740
2888
  let result;
@@ -1749,16 +2897,19 @@ var MoltsPayServer = class {
1749
2897
  console.error("[MoltsPay] Skill execution failed:", err.message);
1750
2898
  return this.sendJson(res, 500, {
1751
2899
  error: "Service execution failed",
1752
- message: err.message
2900
+ message: err.message,
2901
+ paymentSettled: isSolana ? true : false,
2902
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
1753
2903
  });
1754
2904
  }
1755
- console.log(`[MoltsPay] Skill succeeded, settling payment...`);
1756
- let settlement = null;
1757
- try {
1758
- settlement = await this.registry.settle(payment, requirements);
1759
- console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1760
- } catch (err) {
1761
- console.error("[MoltsPay] Settlement failed:", err.message);
2905
+ if (!isSolana) {
2906
+ console.log(`[MoltsPay] Skill succeeded, settling payment...`);
2907
+ try {
2908
+ settlement = await this.registry.settle(payment, requirements);
2909
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2910
+ } catch (err) {
2911
+ console.error("[MoltsPay] Settlement failed:", err.message);
2912
+ }
1762
2913
  }
1763
2914
  const responseHeaders = {};
1764
2915
  if (settlement?.success) {
@@ -2034,7 +3185,7 @@ var MoltsPayServer = class {
2034
3185
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
2035
3186
  const tokenAddress = tokenAddresses[selectedToken];
2036
3187
  const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
2037
- return {
3188
+ const requirements = {
2038
3189
  scheme: "exact",
2039
3190
  network: selectedNetwork,
2040
3191
  asset: tokenAddress,
@@ -2043,6 +3194,27 @@ var MoltsPayServer = class {
2043
3194
  maxTimeoutSeconds: 300,
2044
3195
  extra: tokenDomain
2045
3196
  };
3197
+ if (selectedNetwork === "solana:mainnet" || selectedNetwork === "solana:devnet") {
3198
+ const solanaFacilitator = this.registry.get("solana");
3199
+ const feePayerPubkey = solanaFacilitator?.getFeePayerPubkey?.();
3200
+ if (feePayerPubkey) {
3201
+ requirements.extra = {
3202
+ ...requirements.extra || {},
3203
+ solanaFeePayer: feePayerPubkey
3204
+ };
3205
+ }
3206
+ }
3207
+ if (selectedNetwork === "eip155:56" || selectedNetwork === "eip155:97") {
3208
+ const bnbFacilitator = this.registry.get("bnb");
3209
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
3210
+ if (spenderAddress) {
3211
+ requirements.extra = {
3212
+ ...requirements.extra || {},
3213
+ bnbSpender: spenderAddress
3214
+ };
3215
+ }
3216
+ }
3217
+ return requirements;
2046
3218
  }
2047
3219
  /**
2048
3220
  * Detect which token is being used in the payment
@@ -2108,31 +3280,42 @@ var MoltsPayServer = class {
2108
3280
  /**
2109
3281
  * POST /proxy - Handle payment for external services (moltspay-creators)
2110
3282
  *
2111
- * This endpoint allows other services to delegate x402 payment handling.
3283
+ * This endpoint allows other services to delegate x402/MPP payment handling.
2112
3284
  * It does NOT execute any skill - just handles payment verification/settlement.
2113
3285
  *
2114
3286
  * Request body:
2115
3287
  * { wallet, amount, currency, chain, memo, serviceId, description }
2116
3288
  *
2117
- * Without X-Payment header: returns 402 with payment requirements
2118
- * With X-Payment header: verifies payment and returns result
3289
+ * For x402 (base, polygon, base_sepolia):
3290
+ * Without X-Payment header: returns 402 with X-Payment-Required
3291
+ * With X-Payment header: verifies payment via CDP
3292
+ *
3293
+ * For MPP (tempo_moderato):
3294
+ * Without Authorization header: returns 402 with WWW-Authenticate
3295
+ * With Authorization: Payment header: verifies tx on Tempo chain
2119
3296
  */
2120
- async handleProxy(body, paymentHeader, res) {
3297
+ async handleProxy(body, paymentHeader, authHeader, res) {
2121
3298
  const { wallet, amount, currency, chain, memo, serviceId, description } = body;
2122
3299
  if (!wallet || !amount) {
2123
3300
  return this.sendJson(res, 400, { error: "Missing required fields: wallet, amount" });
2124
3301
  }
2125
- if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
2126
- return this.sendJson(res, 400, { error: "Invalid wallet address format" });
3302
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet", "solana", "solana_devnet"];
3303
+ if (chain && !supportedChains.includes(chain)) {
3304
+ return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
3305
+ }
3306
+ const isSolanaChain = chain === "solana" || chain === "solana_devnet";
3307
+ const isValidEvmAddress = /^0x[a-fA-F0-9]{40}$/.test(wallet);
3308
+ const isValidSolanaAddress2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(wallet);
3309
+ if (isSolanaChain && !isValidSolanaAddress2) {
3310
+ return this.sendJson(res, 400, { error: "Invalid Solana wallet address format" });
3311
+ }
3312
+ if (!isSolanaChain && !isValidEvmAddress) {
3313
+ return this.sendJson(res, 400, { error: "Invalid EVM wallet address format" });
2127
3314
  }
2128
3315
  const amountNum = parseFloat(amount);
2129
3316
  if (isNaN(amountNum) || amountNum <= 0) {
2130
3317
  return this.sendJson(res, 400, { error: "Invalid amount" });
2131
3318
  }
2132
- const supportedChains = ["base", "polygon", "base_sepolia"];
2133
- if (chain && !supportedChains.includes(chain)) {
2134
- return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
2135
- }
2136
3319
  const proxyConfig = {
2137
3320
  id: serviceId || "proxy",
2138
3321
  name: description || "Proxy Payment",
@@ -2144,6 +3327,9 @@ var MoltsPayServer = class {
2144
3327
  input: {},
2145
3328
  output: {}
2146
3329
  };
3330
+ if (chain === "tempo_moderato") {
3331
+ return await this.handleProxyMPP(body, proxyConfig, authHeader, res);
3332
+ }
2147
3333
  const requirements = this.buildProxyPaymentRequirements(proxyConfig, wallet, currency, chain);
2148
3334
  if (!paymentHeader) {
2149
3335
  return this.sendProxyPaymentRequired(proxyConfig, wallet, memo, chain, res);
@@ -2155,37 +3341,225 @@ var MoltsPayServer = class {
2155
3341
  } catch {
2156
3342
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
2157
3343
  }
2158
- if (payment.x402Version !== X402_VERSION3) {
2159
- return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
3344
+ if (payment.x402Version !== X402_VERSION3) {
3345
+ return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
3346
+ }
3347
+ const scheme = payment.accepted?.scheme || payment.scheme;
3348
+ const network = payment.accepted?.network || payment.network;
3349
+ if (scheme !== "exact") {
3350
+ return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
3351
+ }
3352
+ const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
3353
+ if (network !== expectedNetwork) {
3354
+ return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
3355
+ }
3356
+ console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
3357
+ const verifyResult = await this.registry.verify(payment, requirements);
3358
+ if (!verifyResult.valid) {
3359
+ return this.sendJson(res, 402, {
3360
+ success: false,
3361
+ error: `Payment verification failed: ${verifyResult.error}`,
3362
+ facilitator: verifyResult.facilitator
3363
+ });
3364
+ }
3365
+ console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
3366
+ const { execute, service, params } = body;
3367
+ if (execute && service) {
3368
+ const skill = this.skills.get(service);
3369
+ if (!skill) {
3370
+ console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
3371
+ return this.sendJson(res, 404, {
3372
+ success: false,
3373
+ paymentSettled: false,
3374
+ error: `Service not found: ${service}`
3375
+ });
3376
+ }
3377
+ const isSolana = isSolanaNetwork(network);
3378
+ let settlement2 = null;
3379
+ if (isSolana) {
3380
+ console.log(`[MoltsPay] /proxy: Solana detected - settling payment FIRST`);
3381
+ try {
3382
+ settlement2 = await this.registry.settle(payment, requirements);
3383
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
3384
+ if (!settlement2.success) {
3385
+ console.error(`[MoltsPay] /proxy: Solana settlement failed: ${settlement2.error}`);
3386
+ return this.sendJson(res, 402, {
3387
+ success: false,
3388
+ paymentSettled: false,
3389
+ error: `Payment settlement failed: ${settlement2.error || "Unknown error"}`
3390
+ });
3391
+ }
3392
+ } catch (err) {
3393
+ console.error("[MoltsPay] /proxy: Solana settlement failed:", err.message);
3394
+ return this.sendJson(res, 402, {
3395
+ success: false,
3396
+ paymentSettled: false,
3397
+ error: `Payment settlement failed: ${err.message}`
3398
+ });
3399
+ }
3400
+ } else {
3401
+ console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
3402
+ }
3403
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
3404
+ let result;
3405
+ try {
3406
+ result = await Promise.race([
3407
+ skill.handler(params || {}),
3408
+ new Promise(
3409
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
3410
+ )
3411
+ ]);
3412
+ console.log(`[MoltsPay] /proxy: Skill succeeded`);
3413
+ } catch (err) {
3414
+ console.error(`[MoltsPay] /proxy: Skill failed: ${err.message}`);
3415
+ return this.sendJson(res, 500, {
3416
+ success: false,
3417
+ paymentSettled: isSolana ? true : false,
3418
+ error: `Service execution failed: ${err.message}`,
3419
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
3420
+ });
3421
+ }
3422
+ if (!isSolana) {
3423
+ console.log(`[MoltsPay] /proxy: Settling payment...`);
3424
+ try {
3425
+ settlement2 = await this.registry.settle(payment, requirements);
3426
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
3427
+ } catch (err) {
3428
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
3429
+ return this.sendJson(res, 200, {
3430
+ success: true,
3431
+ verified: true,
3432
+ settled: false,
3433
+ settlementError: err.message,
3434
+ from: payment.payload?.authorization?.from,
3435
+ paidTo: wallet,
3436
+ amount: amountNum,
3437
+ currency: currency || "USDC",
3438
+ memo,
3439
+ result
3440
+ });
3441
+ }
3442
+ }
3443
+ return this.sendJson(res, 200, {
3444
+ success: true,
3445
+ verified: true,
3446
+ settled: settlement2?.success || false,
3447
+ txHash: settlement2?.transaction,
3448
+ from: payment.payload?.authorization?.from,
3449
+ paidTo: wallet,
3450
+ amount: amountNum,
3451
+ currency: currency || "USDC",
3452
+ facilitator: settlement2?.facilitator,
3453
+ memo,
3454
+ result
3455
+ });
3456
+ }
3457
+ console.log(`[MoltsPay] /proxy: Settling payment (no execution)...`);
3458
+ let settlement = null;
3459
+ try {
3460
+ settlement = await this.registry.settle(payment, requirements);
3461
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
3462
+ } catch (err) {
3463
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
3464
+ return this.sendJson(res, 500, {
3465
+ success: false,
3466
+ error: `Settlement failed: ${err.message}`
3467
+ });
3468
+ }
3469
+ this.sendJson(res, 200, {
3470
+ success: true,
3471
+ verified: true,
3472
+ settled: settlement?.success || false,
3473
+ txHash: settlement?.transaction,
3474
+ from: payment.payload?.authorization?.from,
3475
+ // Buyer's wallet address
3476
+ paidTo: wallet,
3477
+ amount: amountNum,
3478
+ currency: currency || "USDC",
3479
+ facilitator: settlement?.facilitator,
3480
+ memo
3481
+ });
3482
+ }
3483
+ /**
3484
+ * Handle MPP payment flow for /proxy endpoint (tempo_moderato chain)
3485
+ */
3486
+ async handleProxyMPP(body, config, authHeader, res) {
3487
+ const { wallet, amount, memo, serviceId } = body;
3488
+ const amountNum = parseFloat(amount);
3489
+ const amountInUnits = Math.floor(amountNum * 1e6).toString();
3490
+ if (!authHeader || !authHeader.toLowerCase().startsWith("payment ")) {
3491
+ const challengeId = this.generateChallengeId();
3492
+ const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
3493
+ const mppRequest = {
3494
+ amount: amountInUnits,
3495
+ currency: tokenAddress,
3496
+ methodDetails: {
3497
+ chainId: 42431,
3498
+ feePayer: true
3499
+ },
3500
+ recipient: wallet
3501
+ };
3502
+ const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
3503
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
3504
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
3505
+ res.writeHead(402, {
3506
+ "Content-Type": "application/problem+json",
3507
+ [MPP_WWW_AUTH_HEADER]: wwwAuth
3508
+ });
3509
+ res.end(JSON.stringify({
3510
+ type: "https://paymentauth.org/problems/payment-required",
3511
+ title: "Payment Required",
3512
+ status: 402,
3513
+ detail: `Payment is required (${config.name}).`,
3514
+ service: serviceId || "proxy",
3515
+ price: amountNum,
3516
+ currency: "USDC"
3517
+ }, null, 2));
3518
+ return;
3519
+ }
3520
+ const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
3521
+ if (!credentialMatch) {
3522
+ return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
2160
3523
  }
2161
- const scheme = payment.accepted?.scheme || payment.scheme;
2162
- const network = payment.accepted?.network || payment.network;
2163
- if (scheme !== "exact") {
2164
- return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
3524
+ let mppCredential;
3525
+ try {
3526
+ const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
3527
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
3528
+ mppCredential = JSON.parse(decoded);
3529
+ } catch (err) {
3530
+ console.error("[MoltsPay] /proxy MPP: Failed to parse credential:", err);
3531
+ return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
2165
3532
  }
2166
- const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
2167
- if (network !== expectedNetwork) {
2168
- return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
3533
+ let txHash;
3534
+ if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
3535
+ txHash = mppCredential.payload.hash;
3536
+ } else {
3537
+ return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
2169
3538
  }
2170
- console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
2171
- const verifyResult = await this.registry.verify(payment, requirements);
2172
- if (!verifyResult.valid) {
3539
+ console.log(`[MoltsPay] /proxy MPP: Verifying tx ${txHash} on Tempo...`);
3540
+ const requirements = this.buildPaymentRequirements(config, "eip155:42431", wallet, "USDC");
3541
+ const paymentPayload = {
3542
+ x402Version: X402_VERSION3,
3543
+ scheme: "exact",
3544
+ network: "eip155:42431",
3545
+ payload: { txHash, chainId: 42431 }
3546
+ };
3547
+ const verification = await this.registry.verify(paymentPayload, requirements);
3548
+ if (!verification.valid) {
2173
3549
  return this.sendJson(res, 402, {
2174
- success: false,
2175
- error: `Payment verification failed: ${verifyResult.error}`,
2176
- facilitator: verifyResult.facilitator
3550
+ error: `Payment verification failed: ${verification.error}`
2177
3551
  });
2178
3552
  }
2179
- console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
3553
+ console.log(`[MoltsPay] /proxy MPP: Payment verified by ${verification.facilitator}`);
2180
3554
  const { execute, service, params } = body;
2181
3555
  if (execute && service) {
2182
- console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
3556
+ console.log(`[MoltsPay] /proxy MPP: Executing skill: ${service}`);
2183
3557
  const skill = this.skills.get(service);
2184
3558
  if (!skill) {
2185
- console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
2186
3559
  return this.sendJson(res, 404, {
2187
3560
  success: false,
2188
- paymentSettled: false,
3561
+ paymentSettled: true,
3562
+ // Payment already happened on Tempo
2189
3563
  error: `Service not found: ${service}`
2190
3564
  });
2191
3565
  }
@@ -2198,73 +3572,36 @@ var MoltsPayServer = class {
2198
3572
  (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
2199
3573
  )
2200
3574
  ]);
2201
- console.log(`[MoltsPay] /proxy: Skill succeeded, now settling payment...`);
2202
3575
  } catch (err) {
2203
- console.error(`[MoltsPay] /proxy: Skill failed: ${err.message} - NOT settling`);
3576
+ console.error(`[MoltsPay] /proxy MPP: Skill failed: ${err.message}`);
2204
3577
  return this.sendJson(res, 500, {
2205
3578
  success: false,
2206
- paymentSettled: false,
3579
+ paymentSettled: true,
2207
3580
  error: `Service execution failed: ${err.message}`
2208
3581
  });
2209
3582
  }
2210
- let settlement2 = null;
2211
- try {
2212
- settlement2 = await this.registry.settle(payment, requirements);
2213
- console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
2214
- } catch (err) {
2215
- console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
2216
- return this.sendJson(res, 200, {
2217
- success: true,
2218
- verified: true,
2219
- settled: false,
2220
- settlementError: err.message,
2221
- from: payment.payload?.authorization?.from,
2222
- // Buyer's wallet address
2223
- paidTo: wallet,
2224
- amount: amountNum,
2225
- currency: currency || "USDC",
2226
- memo,
2227
- result
2228
- });
2229
- }
2230
3583
  return this.sendJson(res, 200, {
2231
3584
  success: true,
2232
3585
  verified: true,
2233
- settled: settlement2?.success || false,
2234
- txHash: settlement2?.transaction,
2235
- from: payment.payload?.authorization?.from,
2236
- // Buyer's wallet address
3586
+ txHash,
3587
+ chain: "tempo_moderato",
2237
3588
  paidTo: wallet,
2238
3589
  amount: amountNum,
2239
- currency: currency || "USDC",
2240
- facilitator: settlement2?.facilitator,
3590
+ currency: "USDC",
3591
+ facilitator: verification.facilitator,
2241
3592
  memo,
2242
3593
  result
2243
3594
  });
2244
3595
  }
2245
- console.log(`[MoltsPay] /proxy: Settling payment (no execution)...`);
2246
- let settlement = null;
2247
- try {
2248
- settlement = await this.registry.settle(payment, requirements);
2249
- console.log(`[MoltsPay] /proxy: Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2250
- } catch (err) {
2251
- console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
2252
- return this.sendJson(res, 500, {
2253
- success: false,
2254
- error: `Settlement failed: ${err.message}`
2255
- });
2256
- }
2257
3596
  this.sendJson(res, 200, {
2258
3597
  success: true,
2259
3598
  verified: true,
2260
- settled: settlement?.success || false,
2261
- txHash: settlement?.transaction,
2262
- from: payment.payload?.authorization?.from,
2263
- // Buyer's wallet address
3599
+ txHash,
3600
+ chain: "tempo_moderato",
2264
3601
  paidTo: wallet,
2265
3602
  amount: amountNum,
2266
- currency: currency || "USDC",
2267
- facilitator: settlement?.facilitator,
3603
+ currency: "USDC",
3604
+ facilitator: verification.facilitator,
2268
3605
  memo
2269
3606
  });
2270
3607
  }
@@ -2279,7 +3616,7 @@ var MoltsPayServer = class {
2279
3616
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
2280
3617
  const tokenAddress = tokenAddresses[selectedToken];
2281
3618
  const tokenDomain = getTokenDomain(networkId, selectedToken);
2282
- return {
3619
+ const requirements = {
2283
3620
  scheme: "exact",
2284
3621
  network: networkId,
2285
3622
  asset: tokenAddress,
@@ -2289,6 +3626,17 @@ var MoltsPayServer = class {
2289
3626
  maxTimeoutSeconds: 300,
2290
3627
  extra: tokenDomain
2291
3628
  };
3629
+ if (networkId === "eip155:56" || networkId === "eip155:97") {
3630
+ const bnbFacilitator = this.registry.get("bnb");
3631
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
3632
+ if (spenderAddress) {
3633
+ requirements.extra = {
3634
+ ...requirements.extra || {},
3635
+ bnbSpender: spenderAddress
3636
+ };
3637
+ }
3638
+ }
3639
+ return requirements;
2292
3640
  }
2293
3641
  /**
2294
3642
  * Return 402 with x402 payment requirements for proxy endpoint
@@ -2338,14 +3686,14 @@ if (!globalThis.crypto) {
2338
3686
  }
2339
3687
  function getVersion() {
2340
3688
  const locations = [
2341
- join4(__dirname, "../../package.json"),
2342
- join4(__dirname, "../package.json"),
2343
- join4(process.cwd(), "node_modules/moltspay/package.json")
3689
+ join5(__dirname, "../../package.json"),
3690
+ join5(__dirname, "../package.json"),
3691
+ join5(process.cwd(), "node_modules/moltspay/package.json")
2344
3692
  ];
2345
3693
  for (const loc of locations) {
2346
3694
  try {
2347
- if (existsSync4(loc)) {
2348
- const pkg = JSON.parse(readFileSync4(loc, "utf-8"));
3695
+ if (existsSync5(loc)) {
3696
+ const pkg = JSON.parse(readFileSync5(loc, "utf-8"));
2349
3697
  if (pkg.name === "moltspay") return pkg.version;
2350
3698
  }
2351
3699
  } catch {
@@ -2353,11 +3701,94 @@ function getVersion() {
2353
3701
  }
2354
3702
  return "0.0.0";
2355
3703
  }
3704
+ var BNB_SPONSOR_KEY = process.env.MOLTSPAY_BNB_SPONSOR_KEY;
3705
+ var BNB_SPENDER_ADDRESS = process.env.MOLTSPAY_BNB_SPENDER || "0xEBB45208D806A0c73F9673E0c5713FF720DD6b79";
3706
+ var ERC20_APPROVE_ABI = [
3707
+ "function approve(address spender, uint256 amount) returns (bool)",
3708
+ "function allowance(address owner, address spender) view returns (uint256)"
3709
+ ];
3710
+ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = false) {
3711
+ const chainConfig = CHAINS[chain];
3712
+ const provider = new ethers2.JsonRpcProvider(chainConfig.rpc);
3713
+ const wallet = client.getWallet();
3714
+ if (!wallet) {
3715
+ console.log(" \u274C No wallet found");
3716
+ return;
3717
+ }
3718
+ const signer = wallet.connect(provider);
3719
+ console.log(` Spender: ${spenderAddress}`);
3720
+ let bnbBalance = await provider.getBalance(wallet.address);
3721
+ const minGasRequired = ethers2.parseEther("0.0005");
3722
+ if (bnbBalance < minGasRequired) {
3723
+ if (sponsorGas && BNB_SPONSOR_KEY) {
3724
+ console.log(" \u23F3 Sponsoring BNB gas for approvals...");
3725
+ try {
3726
+ const sponsorWallet = new ethers2.Wallet(BNB_SPONSOR_KEY, provider);
3727
+ const tx = await sponsorWallet.sendTransaction({
3728
+ to: wallet.address,
3729
+ value: ethers2.parseEther("0.001")
3730
+ });
3731
+ await tx.wait();
3732
+ console.log(` \u2705 Sponsored 0.001 BNB (tx: ${tx.hash.slice(0, 10)}...)`);
3733
+ bnbBalance = await provider.getBalance(wallet.address);
3734
+ } catch (err) {
3735
+ console.log(` \u26A0\uFE0F Gas sponsorship failed: ${err.message}`);
3736
+ console.log(` \u{1F4A1} Get testnet BNB: https://testnet.bnbchain.org/faucet-smart`);
3737
+ return;
3738
+ }
3739
+ } else {
3740
+ console.log(` \u26A0\uFE0F Need BNB for gas (~0.0005 BNB)`);
3741
+ console.log(` \u{1F4A1} Run: npx moltspay faucet --chain bnb_testnet`);
3742
+ console.log(` Then run: npx moltspay approve --chain ${chain} --spender ${spenderAddress}`);
3743
+ return;
3744
+ }
3745
+ }
3746
+ for (const tokenSymbol of ["USDT", "USDC"]) {
3747
+ const tokenConfig = chainConfig.tokens[tokenSymbol];
3748
+ const tokenContract = new ethers2.Contract(tokenConfig.address, ERC20_APPROVE_ABI, signer);
3749
+ const allowance = await tokenContract.allowance(wallet.address, spenderAddress);
3750
+ if (allowance > 0n) {
3751
+ console.log(` \u2705 ${tokenSymbol}: already approved for ${spenderAddress.slice(0, 10)}...`);
3752
+ continue;
3753
+ }
3754
+ console.log(` \u23F3 Approving ${tokenSymbol}...`);
3755
+ try {
3756
+ const tx = await tokenContract.approve(spenderAddress, ethers2.MaxUint256);
3757
+ await tx.wait();
3758
+ console.log(` \u2705 ${tokenSymbol}: approved (tx: ${tx.hash.slice(0, 10)}...)`);
3759
+ } catch (err) {
3760
+ console.log(` \u274C ${tokenSymbol}: approval failed - ${err.message}`);
3761
+ }
3762
+ }
3763
+ console.log("");
3764
+ }
3765
+ async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2) {
3766
+ const chainConfig = CHAINS[chain];
3767
+ const provider = new ethers2.JsonRpcProvider(chainConfig.rpc);
3768
+ let spenderAddress = null;
3769
+ try {
3770
+ const walletPath = join5(configDir, "wallet.json");
3771
+ const walletData = JSON.parse(readFileSync5(walletPath, "utf-8"));
3772
+ spenderAddress = walletData.approvals?.[chain] || null;
3773
+ } catch {
3774
+ }
3775
+ const result = { usdt: false, usdc: false, spender: spenderAddress };
3776
+ if (!spenderAddress) {
3777
+ return result;
3778
+ }
3779
+ for (const tokenSymbol of ["USDT", "USDC"]) {
3780
+ const tokenConfig = chainConfig.tokens[tokenSymbol];
3781
+ const tokenContract = new ethers2.Contract(tokenConfig.address, ERC20_APPROVE_ABI, provider);
3782
+ const allowance = await tokenContract.allowance(address, spenderAddress);
3783
+ result[tokenSymbol.toLowerCase()] = allowance > 0n;
3784
+ }
3785
+ return result;
3786
+ }
2356
3787
  var program = new Command();
2357
- var DEFAULT_CONFIG_DIR = join4(homedir2(), ".moltspay");
2358
- var PID_FILE = join4(DEFAULT_CONFIG_DIR, "server.pid");
2359
- if (!existsSync4(DEFAULT_CONFIG_DIR)) {
2360
- mkdirSync2(DEFAULT_CONFIG_DIR, { recursive: true });
3788
+ var DEFAULT_CONFIG_DIR2 = join5(homedir3(), ".moltspay");
3789
+ var PID_FILE = join5(DEFAULT_CONFIG_DIR2, "server.pid");
3790
+ if (!existsSync5(DEFAULT_CONFIG_DIR2)) {
3791
+ mkdirSync3(DEFAULT_CONFIG_DIR2, { recursive: true });
2361
3792
  }
2362
3793
  function prompt(question) {
2363
3794
  const rl = readline.createInterface({
@@ -2372,19 +3803,49 @@ function prompt(question) {
2372
3803
  });
2373
3804
  }
2374
3805
  program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version(getVersion());
2375
- 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) => {
2376
- console.log("\n\u{1F510} MoltsPay Client Setup\n");
2377
- if (existsSync4(join4(options.configDir, "wallet.json"))) {
2378
- console.log('\u26A0\uFE0F Already initialized. Use "moltspay config" to update settings.');
2379
- console.log(` Config dir: ${options.configDir}`);
2380
- return;
2381
- }
3806
+ 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) => {
2382
3807
  let chain = options.chain;
2383
- const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
3808
+ const supportedEVMChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet"];
3809
+ const supportedSolanaChains = ["solana", "solana_devnet"];
3810
+ const supportedChains = [...supportedEVMChains, ...supportedSolanaChains];
2384
3811
  if (!supportedChains.includes(chain)) {
2385
3812
  console.error(`\u274C Unknown chain: ${chain}. Supported: ${supportedChains.join(", ")}`);
2386
3813
  process.exit(1);
2387
3814
  }
3815
+ if (supportedSolanaChains.includes(chain)) {
3816
+ console.log("\n\u{1F7E3} Solana Wallet Setup\n");
3817
+ if (solanaWalletExists(options.configDir)) {
3818
+ const existingAddress = getSolanaAddress(options.configDir);
3819
+ console.log(`\u26A0\uFE0F Solana wallet already exists: ${existingAddress}`);
3820
+ console.log(` Config dir: ${options.configDir}`);
3821
+ return;
3822
+ }
3823
+ console.log("Creating Solana wallet...");
3824
+ const keypair = createSolanaWallet(options.configDir);
3825
+ const address = keypair.publicKey.toBase58();
3826
+ console.log(`
3827
+ \u2705 Solana wallet created: ${address}`);
3828
+ console.log(`
3829
+ \u{1F4C1} Config saved to: ${join5(options.configDir, "wallet-solana.json")}`);
3830
+ console.log(`
3831
+ \u26A0\uFE0F IMPORTANT: Back up your wallet file!`);
3832
+ console.log(` This file contains your private key!
3833
+ `);
3834
+ if (chain === "solana_devnet") {
3835
+ console.log("\u{1F4A1} Get testnet tokens:");
3836
+ console.log(" npx moltspay faucet --chain solana_devnet\n");
3837
+ } else {
3838
+ console.log(`\u{1F4B0} Fund your wallet with USDC on Solana to start (gasless - no SOL needed).
3839
+ `);
3840
+ }
3841
+ return;
3842
+ }
3843
+ console.log("\n\u{1F510} MoltsPay Client Setup\n");
3844
+ if (existsSync5(join5(options.configDir, "wallet.json"))) {
3845
+ console.log('\u26A0\uFE0F EVM wallet already initialized. Use "moltspay config" to update settings.');
3846
+ console.log(` Config dir: ${options.configDir}`);
3847
+ return;
3848
+ }
2388
3849
  let maxPerTx = options.maxPerTx ? parseFloat(options.maxPerTx) : null;
2389
3850
  let maxPerDay = options.maxPerDay ? parseFloat(options.maxPerDay) : null;
2390
3851
  if (!maxPerTx) {
@@ -2406,13 +3867,21 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
2406
3867
  console.log(`
2407
3868
  \u{1F4C1} Config saved to: ${result.configDir}`);
2408
3869
  console.log(`
2409
- \u26A0\uFE0F IMPORTANT: Back up ${join4(result.configDir, "wallet.json")}`);
3870
+ \u26A0\uFE0F IMPORTANT: Back up ${join5(result.configDir, "wallet.json")}`);
2410
3871
  console.log(` This file contains your private key!
2411
3872
  `);
3873
+ if (chain === "bnb" || chain === "bnb_testnet") {
3874
+ console.log("\u{1F4CB} Setting up BNB chain approvals...\n");
3875
+ console.log(" \u2139\uFE0F Using default spender. For other services, run:");
3876
+ console.log(` npx moltspay approve --chain ${chain} --spender <address>
3877
+ `);
3878
+ const client = new MoltsPayClient({ configDir: options.configDir });
3879
+ await setupBNBApprovals(client, chain, BNB_SPENDER_ADDRESS, true);
3880
+ }
2412
3881
  console.log(`\u{1F4B0} Fund your wallet with USDC on ${chain} to start using services.
2413
3882
  `);
2414
3883
  });
2415
- 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) => {
3884
+ 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) => {
2416
3885
  const client = new MoltsPayClient({ configDir: options.configDir });
2417
3886
  if (!client.isInitialized) {
2418
3887
  console.log("\u274C Not initialized. Run: npx moltspay init");
@@ -2447,25 +3916,40 @@ program.command("config").description("Update MoltsPay settings").option("--max-
2447
3916
  }
2448
3917
  }
2449
3918
  });
2450
- 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) => {
3919
+ 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) => {
2451
3920
  const client = new MoltsPayClient({ configDir: options.configDir });
2452
- if (!client.isInitialized) {
2453
- console.log("\u274C Not initialized. Run: npx moltspay init");
2454
- return;
2455
- }
2456
3921
  const amount = parseFloat(amountStr);
2457
3922
  if (isNaN(amount) || amount < 5) {
2458
3923
  console.log("\u274C Minimum $5.");
2459
3924
  return;
2460
3925
  }
2461
3926
  const chain = options.chain?.toLowerCase() || "base";
2462
- if (!["base", "polygon", "base_sepolia"].includes(chain)) {
2463
- console.log("\u274C Invalid chain. Use: base, polygon, or base_sepolia");
3927
+ if (!["base", "polygon", "base_sepolia", "solana", "bnb", "bnb_testnet"].includes(chain)) {
3928
+ console.log("\u274C Invalid chain. Use: base, polygon, solana, base_sepolia, bnb, or bnb_testnet");
2464
3929
  return;
2465
3930
  }
3931
+ let walletAddress;
3932
+ if (chain === "solana") {
3933
+ const solanaWallet = loadSolanaWallet(options.configDir || DEFAULT_CONFIG_DIR2);
3934
+ if (!solanaWallet) {
3935
+ console.log("\u274C No Solana wallet found. Run: npx moltspay init --chain solana");
3936
+ return;
3937
+ }
3938
+ walletAddress = getSolanaAddress(options.configDir || DEFAULT_CONFIG_DIR2) || "";
3939
+ if (!walletAddress) {
3940
+ console.log("\u274C Could not get Solana wallet address.");
3941
+ return;
3942
+ }
3943
+ } else {
3944
+ if (!client.isInitialized) {
3945
+ console.log("\u274C Not initialized. Run: npx moltspay init");
3946
+ return;
3947
+ }
3948
+ walletAddress = client.address;
3949
+ }
2466
3950
  if (chain === "base_sepolia") {
2467
3951
  console.log("\n\u{1F9EA} Testnet Funding\n");
2468
- console.log(` Wallet: ${client.address}`);
3952
+ console.log(` Wallet: ${walletAddress}`);
2469
3953
  console.log(` Chain: Base Sepolia (testnet)
2470
3954
  `);
2471
3955
  console.log("\u{1F4A1} Use the MoltsPay faucet to get free testnet USDC:\n");
@@ -2473,9 +3957,36 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
2473
3957
  console.log(" Or get from Circle Faucet: https://faucet.circle.com/\n");
2474
3958
  return;
2475
3959
  }
3960
+ if (chain === "bnb_testnet") {
3961
+ console.log("\n\u{1F9EA} BNB Testnet Funding\n");
3962
+ console.log(` Wallet: ${walletAddress}`);
3963
+ console.log(` Chain: BNB Testnet
3964
+ `);
3965
+ console.log("\u{1F4A1} Use the MoltsPay faucet to get testnet USDC + tBNB:\n");
3966
+ console.log(" npx moltspay faucet --chain bnb_testnet\n");
3967
+ console.log(" This gives you:\n");
3968
+ console.log(" \u2022 1 USDC (testnet) for payments");
3969
+ console.log(" \u2022 0.001 tBNB for gas (first approval tx)\n");
3970
+ return;
3971
+ }
3972
+ if (chain === "bnb") {
3973
+ console.log("\n\u{1F4CB} BNB Chain Funding\n");
3974
+ console.log(` Wallet: ${walletAddress}
3975
+ `);
3976
+ console.log(" To use MoltsPay on BNB Chain, you need:\n");
3977
+ console.log(" 1. USDC for payments");
3978
+ console.log(" \u2192 Withdraw from Binance/exchange to your wallet address\n");
3979
+ console.log(" 2. Small amount of BNB for gas (~0.001 BNB / ~$0.60)");
3980
+ console.log(" \u2192 First approval transaction requires gas");
3981
+ console.log(" \u2192 After approval, all payments are gasless\n");
3982
+ console.log(" \u{1F4A1} Tip: Most exchanges include BNB dust when you withdraw to BNB Chain\n");
3983
+ 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");
3984
+ console.log(" After funding, check status: npx moltspay status\n");
3985
+ return;
3986
+ }
2476
3987
  console.log("\n\u{1F4B3} Fund your agent wallet\n");
2477
- console.log(` Wallet: ${client.address}`);
2478
- console.log(` Chain: ${chain}`);
3988
+ console.log(` Wallet: ${walletAddress}`);
3989
+ console.log(` Chain: ${chain === "solana" ? "Solana" : chain}`);
2479
3990
  console.log(` Amount: $${amount.toFixed(2)}
2480
3991
  `);
2481
3992
  try {
@@ -2484,7 +3995,7 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
2484
3995
  method: "POST",
2485
3996
  headers: { "Content-Type": "application/json" },
2486
3997
  body: JSON.stringify({
2487
- address: client.address,
3998
+ address: walletAddress,
2488
3999
  amount,
2489
4000
  chain
2490
4001
  })
@@ -2502,11 +4013,92 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
2502
4013
  console.log(`\u274C ${error.message}`);
2503
4014
  }
2504
4015
  });
2505
- 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) => {
4016
+ 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) => {
4017
+ const chain = options.chain;
4018
+ if (chain !== "bnb" && chain !== "bnb_testnet") {
4019
+ console.log("\u274C approve command is only for BNB chains (bnb or bnb_testnet)");
4020
+ return;
4021
+ }
4022
+ if (!options.spender.match(/^0x[a-fA-F0-9]{40}$/)) {
4023
+ console.log("\u274C Invalid spender address format");
4024
+ return;
4025
+ }
4026
+ const client = new MoltsPayClient({ configDir: options.configDir });
4027
+ if (!client.isInitialized) {
4028
+ console.log("\u274C Wallet not initialized. Run: npx moltspay init --chain " + chain);
4029
+ return;
4030
+ }
4031
+ console.log(`
4032
+ \u{1F510} Approving spender for ${chain}...
4033
+ `);
4034
+ await setupBNBApprovals(client, chain, options.spender, false);
4035
+ const walletPath = join5(options.configDir || DEFAULT_CONFIG_DIR2, "wallet.json");
4036
+ try {
4037
+ const walletData = JSON.parse(readFileSync5(walletPath, "utf-8"));
4038
+ walletData.approvals = walletData.approvals || {};
4039
+ walletData.approvals[chain] = options.spender;
4040
+ writeFileSync3(walletPath, JSON.stringify(walletData, null, 2));
4041
+ console.log(`\u2705 Approval complete! Spender saved for ${chain}.
4042
+ `);
4043
+ } catch (err) {
4044
+ console.log("\u2705 Approval complete!\n");
4045
+ console.log("\u26A0\uFE0F Could not save spender to wallet config");
4046
+ }
4047
+ });
4048
+ 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) => {
2506
4049
  let address = options.address;
2507
4050
  const chain = options.chain?.toLowerCase() || "base_sepolia";
2508
- if (!["base_sepolia", "tempo_moderato"].includes(chain)) {
2509
- console.log("\u274C Invalid chain. Use: base_sepolia or tempo_moderato");
4051
+ if (!["base_sepolia", "tempo_moderato", "bnb_testnet", "solana_devnet"].includes(chain)) {
4052
+ console.log("\u274C Invalid chain. Use: base_sepolia, tempo_moderato, bnb_testnet, or solana_devnet");
4053
+ return;
4054
+ }
4055
+ if (chain === "solana_devnet") {
4056
+ if (!address) {
4057
+ address = getSolanaAddress(options.configDir);
4058
+ if (!address) {
4059
+ console.log("\u274C No Solana wallet found. Run: npx moltspay init --chain solana_devnet");
4060
+ return;
4061
+ }
4062
+ }
4063
+ if (!isValidSolanaAddress(address)) {
4064
+ console.log("\u274C Invalid Solana address");
4065
+ return;
4066
+ }
4067
+ console.log("\n\u{1F6B0} Solana Devnet Faucet (Gasless Mode)\n");
4068
+ console.log(` Address: ${address}
4069
+ `);
4070
+ let usdcSuccess = false;
4071
+ try {
4072
+ console.log(" \u23F3 Requesting 1 USDC from faucet...");
4073
+ const FAUCET_API = process.env.MOLTSPAY_FAUCET_API || "https://moltspay.com/api/v1/faucet";
4074
+ const response = await fetch(FAUCET_API, {
4075
+ method: "POST",
4076
+ headers: { "Content-Type": "application/json" },
4077
+ body: JSON.stringify({ address, chain: "solana_devnet" })
4078
+ });
4079
+ const result = await response.json();
4080
+ if (!response.ok) {
4081
+ console.log(` \u26A0\uFE0F USDC faucet: ${result.error || "Request failed"}`);
4082
+ if (result.hint) console.log(` ${result.hint}`);
4083
+ if (result.retry_after) console.log(` Retry after: ${result.retry_after}`);
4084
+ } else {
4085
+ console.log(` \u2705 Received ${result.amount} USDC!`);
4086
+ console.log(` Transaction: ${result.explorer}`);
4087
+ if (result.faucet_balance) {
4088
+ console.log(` Faucet balance: ${result.faucet_balance} USDC remaining`);
4089
+ }
4090
+ usdcSuccess = true;
4091
+ }
4092
+ } catch (error) {
4093
+ console.log(` \u26A0\uFE0F USDC faucet error: ${error.message}`);
4094
+ }
4095
+ console.log("");
4096
+ if (usdcSuccess) {
4097
+ console.log("\u{1F4A1} Check your balance:");
4098
+ console.log(" npx moltspay status\n");
4099
+ } else {
4100
+ console.log("\u274C Faucet request failed. Try again in a few minutes.\n");
4101
+ }
2510
4102
  return;
2511
4103
  }
2512
4104
  if (!address) {
@@ -2554,6 +4146,46 @@ program.command("faucet").description("Request testnet tokens from faucet (Base
2554
4146
  console.log(`\u274C ${error.message}`);
2555
4147
  console.log("\n Try Tempo Wallet instead: https://wallet.tempo.xyz\n");
2556
4148
  }
4149
+ } else if (chain === "bnb_testnet") {
4150
+ console.log(` Requesting 1 USDC on BNB Testnet...`);
4151
+ console.log(` Address: ${address}
4152
+ `);
4153
+ try {
4154
+ const FAUCET_API = process.env.MOLTSPAY_FAUCET_API || "https://moltspay.com/api/v1/faucet";
4155
+ const response = await fetch(FAUCET_API, {
4156
+ method: "POST",
4157
+ headers: { "Content-Type": "application/json" },
4158
+ body: JSON.stringify({ address, chain: "bnb_testnet" })
4159
+ });
4160
+ const result = await response.json();
4161
+ if (!response.ok) {
4162
+ console.log(`\u274C ${result.error || "Request failed"}`);
4163
+ if (result.hint) console.log(` ${result.hint}`);
4164
+ if (result.retry_after) console.log(` Retry after: ${result.retry_after}`);
4165
+ console.log("\n\u{1F4A1} Alternatively, get tokens manually:");
4166
+ console.log(` 1. Get test BNB: https://www.bnbchain.org/en/testnet-faucet`);
4167
+ console.log(` 2. Select "Peggy Tokens" -> USDC`);
4168
+ console.log(` 3. Enter: ${address}
4169
+ `);
4170
+ return;
4171
+ }
4172
+ console.log(`\u2705 Received ${result.amount} ${result.token || "USDC"} on ${result.chain_name || "BNB Testnet"}!
4173
+ `);
4174
+ console.log(` Transaction: ${result.explorer || `https://testnet.bscscan.com/tx/${result.transaction}`}`);
4175
+ if (result.faucet_balance) {
4176
+ console.log(` Faucet balance: ${result.faucet_balance} USDC`);
4177
+ }
4178
+ console.log("\n\u{1F4A1} Now you can test BNB payments:");
4179
+ console.log(` npx moltspay pay <service-url> <service-id> --chain bnb_testnet
4180
+ `);
4181
+ } catch (error) {
4182
+ console.log(`\u274C ${error.message}`);
4183
+ console.log("\n\u{1F4A1} Get tokens manually:");
4184
+ console.log(` 1. Get test BNB: https://www.bnbchain.org/en/testnet-faucet`);
4185
+ console.log(` 2. Select "Peggy Tokens" -> USDC`);
4186
+ console.log(` 3. Enter: ${address}
4187
+ `);
4188
+ }
2557
4189
  } else {
2558
4190
  console.log(` Requesting 1 USDC on Base Sepolia...`);
2559
4191
  console.log(` Address: ${address}
@@ -2563,7 +4195,7 @@ program.command("faucet").description("Request testnet tokens from faucet (Base
2563
4195
  const response = await fetch(FAUCET_API, {
2564
4196
  method: "POST",
2565
4197
  headers: { "Content-Type": "application/json" },
2566
- body: JSON.stringify({ address })
4198
+ body: JSON.stringify({ address, chain: "base_sepolia" })
2567
4199
  });
2568
4200
  const result = await response.json();
2569
4201
  if (!response.ok) {
@@ -2586,7 +4218,7 @@ program.command("faucet").description("Request testnet tokens from faucet (Base
2586
4218
  }
2587
4219
  }
2588
4220
  });
2589
- 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) => {
4221
+ 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) => {
2590
4222
  const client = new MoltsPayClient({ configDir: options.configDir });
2591
4223
  if (!client.isInitialized) {
2592
4224
  if (options.json) {
@@ -2603,12 +4235,31 @@ program.command("status").description("Show wallet status and balance").option("
2603
4235
  } catch (err) {
2604
4236
  console.error("Warning: Could not fetch balances:", err.message);
2605
4237
  }
4238
+ const solanaAddress = getSolanaAddress(options.configDir);
4239
+ let solanaBalances = {};
4240
+ if (solanaAddress) {
4241
+ try {
4242
+ solanaBalances.devnet = await getSolanaBalances(solanaAddress, "solana_devnet");
4243
+ } catch {
4244
+ }
4245
+ try {
4246
+ solanaBalances.mainnet = await getSolanaBalances(solanaAddress, "solana");
4247
+ } catch {
4248
+ }
4249
+ }
2606
4250
  if (options.json) {
2607
- console.log(JSON.stringify({
4251
+ const output = {
2608
4252
  address: client.address,
2609
4253
  balances: allBalances,
2610
4254
  limits: config.limits
2611
- }, null, 2));
4255
+ };
4256
+ if (solanaAddress) {
4257
+ output.solana = {
4258
+ address: solanaAddress,
4259
+ balances: solanaBalances
4260
+ };
4261
+ }
4262
+ console.log(JSON.stringify(output, null, 2));
2612
4263
  } else {
2613
4264
  console.log("\n\u{1F4CA} MoltsPay Wallet Status\n");
2614
4265
  console.log(` Address: ${client.address}`);
@@ -2632,18 +4283,90 @@ program.command("status").description("Show wallet status and balance").option("
2632
4283
  console.log(` alphaUSD: ${tempo.alphaUSD.toFixed(2)}`);
2633
4284
  console.log(` betaUSD: ${tempo.betaUSD.toFixed(2)}`);
2634
4285
  console.log(` thetaUSD: ${tempo.thetaUSD.toFixed(2)}`);
4286
+ } else if (chainName === "bnb" || chainName === "bnb_testnet") {
4287
+ const bnbBalance = balance.native;
4288
+ const bnbWarning = bnbBalance < 5e-4 ? " \u26A0\uFE0F Low gas" : "";
4289
+ console.log(` ${chainLabel.padEnd(14)} ${balance.usdc.toFixed(2)} USDC | ${balance.usdt.toFixed(2)} USDT | ${bnbBalance.toFixed(4)} BNB${bnbWarning}`);
2635
4290
  } else {
2636
4291
  console.log(` ${chainLabel.padEnd(14)} ${balance.usdc.toFixed(2)} USDC | ${balance.usdt.toFixed(2)} USDT`);
2637
4292
  }
2638
4293
  }
4294
+ const address = client.address;
4295
+ let bnbApprovalStatus = null;
4296
+ let bnbTestnetApprovalStatus = null;
4297
+ try {
4298
+ if (allBalances["bnb"]) {
4299
+ bnbApprovalStatus = await checkBNBApprovals(address, "bnb", options.configDir);
4300
+ }
4301
+ if (allBalances["bnb_testnet"]) {
4302
+ bnbTestnetApprovalStatus = await checkBNBApprovals(address, "bnb_testnet", options.configDir);
4303
+ }
4304
+ } catch {
4305
+ }
4306
+ if (bnbApprovalStatus || bnbTestnetApprovalStatus) {
4307
+ console.log("");
4308
+ console.log(" BNB Approvals (pay-for-success):");
4309
+ if (bnbApprovalStatus) {
4310
+ if (!bnbApprovalStatus.spender) {
4311
+ console.log(" BNB: \u26A0\uFE0F No spender configured");
4312
+ console.log(" \u2514\u2500 Run a payment first, or: npx moltspay approve --chain bnb --spender <address>");
4313
+ } else {
4314
+ const status = bnbApprovalStatus.usdt && bnbApprovalStatus.usdc ? "\u2705" : "\u26A0\uFE0F";
4315
+ const tokens = [
4316
+ bnbApprovalStatus.usdt ? "USDT\u2713" : "USDT\u2717",
4317
+ bnbApprovalStatus.usdc ? "USDC\u2713" : "USDC\u2717"
4318
+ ].join(", ");
4319
+ console.log(` BNB: ${status} ${tokens}`);
4320
+ const bnbNative = allBalances["bnb"]?.native || 0;
4321
+ if (!bnbApprovalStatus.usdc && !bnbApprovalStatus.usdt && bnbNative < 5e-4) {
4322
+ console.log(" \u26A0\uFE0F Need ~0.001 BNB for first approval tx. Get from exchange.");
4323
+ }
4324
+ }
4325
+ }
4326
+ if (bnbTestnetApprovalStatus) {
4327
+ if (!bnbTestnetApprovalStatus.spender) {
4328
+ console.log(" BNB Testnet: \u26A0\uFE0F No spender configured");
4329
+ console.log(" \u2514\u2500 Run a payment first, or: npx moltspay approve --chain bnb_testnet --spender <address>");
4330
+ } else {
4331
+ const status = bnbTestnetApprovalStatus.usdt && bnbTestnetApprovalStatus.usdc ? "\u2705" : "\u26A0\uFE0F";
4332
+ const tokens = [
4333
+ bnbTestnetApprovalStatus.usdt ? "USDT\u2713" : "USDT\u2717",
4334
+ bnbTestnetApprovalStatus.usdc ? "USDC\u2713" : "USDC\u2717"
4335
+ ].join(", ");
4336
+ console.log(` BNB Testnet: ${status} ${tokens}`);
4337
+ const tbnbNative = allBalances["bnb_testnet"]?.native || 0;
4338
+ if (!bnbTestnetApprovalStatus.usdc && !bnbTestnetApprovalStatus.usdt && tbnbNative < 5e-4) {
4339
+ console.log(" \u26A0\uFE0F Need tBNB for approval. Run: npx moltspay faucet --chain bnb_testnet");
4340
+ }
4341
+ }
4342
+ }
4343
+ }
2639
4344
  console.log("");
2640
4345
  console.log(" Spending Limits:");
2641
4346
  console.log(` Per Transaction: $${config.limits.maxPerTx}`);
2642
4347
  console.log(` Daily: $${config.limits.maxPerDay}`);
4348
+ const solanaAddress2 = getSolanaAddress(options.configDir);
4349
+ if (solanaAddress2) {
4350
+ console.log("");
4351
+ 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");
4352
+ console.log(` \u{1F7E3} Solana: ${solanaAddress2}`);
4353
+ try {
4354
+ const devnetBalances = await getSolanaBalances(solanaAddress2, "solana_devnet");
4355
+ console.log(` Devnet: ${devnetBalances.sol.toFixed(4)} SOL | ${devnetBalances.usdc.toFixed(2)} USDC`);
4356
+ } catch (err) {
4357
+ console.log(` Devnet: (unable to fetch)`);
4358
+ }
4359
+ try {
4360
+ const mainnetBalances = await getSolanaBalances(solanaAddress2, "solana");
4361
+ console.log(` Mainnet: ${mainnetBalances.sol.toFixed(4)} SOL | ${mainnetBalances.usdc.toFixed(2)} USDC`);
4362
+ } catch (err) {
4363
+ console.log(` Mainnet: (unable to fetch)`);
4364
+ }
4365
+ }
2643
4366
  console.log("");
2644
4367
  }
2645
4368
  });
2646
- 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) => {
4369
+ 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) => {
2647
4370
  const client = new MoltsPayClient({ configDir: options.configDir });
2648
4371
  if (!client.isInitialized) {
2649
4372
  console.log("\u274C Not initialized. Run: npx moltspay init");
@@ -2950,14 +4673,14 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
2950
4673
  let manifestPath;
2951
4674
  let skillDir;
2952
4675
  let isSkillDir = false;
2953
- if (existsSync4(join4(resolvedPath, "moltspay.services.json"))) {
2954
- manifestPath = join4(resolvedPath, "moltspay.services.json");
4676
+ if (existsSync5(join5(resolvedPath, "moltspay.services.json"))) {
4677
+ manifestPath = join5(resolvedPath, "moltspay.services.json");
2955
4678
  skillDir = resolvedPath;
2956
4679
  isSkillDir = true;
2957
- } else if (existsSync4(resolvedPath) && resolvedPath.endsWith(".json")) {
4680
+ } else if (existsSync5(resolvedPath) && resolvedPath.endsWith(".json")) {
2958
4681
  manifestPath = resolvedPath;
2959
4682
  skillDir = dirname(resolvedPath);
2960
- } else if (existsSync4(resolvedPath)) {
4683
+ } else if (existsSync5(resolvedPath)) {
2961
4684
  console.error(`\u274C No moltspay.services.json found in: ${resolvedPath}`);
2962
4685
  continue;
2963
4686
  } else {
@@ -2966,25 +4689,25 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
2966
4689
  }
2967
4690
  console.log(`\u{1F4E6} Loading: ${manifestPath}`);
2968
4691
  try {
2969
- const manifestContent = JSON.parse(readFileSync4(manifestPath, "utf-8"));
4692
+ const manifestContent = JSON.parse(readFileSync5(manifestPath, "utf-8"));
2970
4693
  if (!provider) {
2971
4694
  provider = manifestContent.provider;
2972
4695
  }
2973
4696
  let skillModule = null;
2974
4697
  if (isSkillDir) {
2975
4698
  let entryPoint = "index.js";
2976
- const pkgJsonPath = join4(skillDir, "package.json");
2977
- if (existsSync4(pkgJsonPath)) {
4699
+ const pkgJsonPath = join5(skillDir, "package.json");
4700
+ if (existsSync5(pkgJsonPath)) {
2978
4701
  try {
2979
- const pkgJson = JSON.parse(readFileSync4(pkgJsonPath, "utf-8"));
4702
+ const pkgJson = JSON.parse(readFileSync5(pkgJsonPath, "utf-8"));
2980
4703
  if (pkgJson.main) {
2981
4704
  entryPoint = pkgJson.main;
2982
4705
  }
2983
4706
  } catch {
2984
4707
  }
2985
4708
  }
2986
- const modulePath = join4(skillDir, entryPoint);
2987
- if (existsSync4(modulePath)) {
4709
+ const modulePath = join5(skillDir, entryPoint);
4710
+ if (existsSync5(modulePath)) {
2988
4711
  try {
2989
4712
  skillModule = await import(modulePath);
2990
4713
  console.log(` \u2705 Loaded module: ${modulePath}`);
@@ -3062,8 +4785,8 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
3062
4785
  provider,
3063
4786
  services: allServices
3064
4787
  };
3065
- const tempManifestPath = join4(DEFAULT_CONFIG_DIR, "combined-manifest.json");
3066
- writeFileSync2(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
4788
+ const tempManifestPath = join5(DEFAULT_CONFIG_DIR2, "combined-manifest.json");
4789
+ writeFileSync3(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
3067
4790
  console.log(`
3068
4791
  \u{1F4CB} Combined manifest: ${allServices.length} services`);
3069
4792
  console.log(` Provider: ${provider.name}`);
@@ -3076,12 +4799,12 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
3076
4799
  server.skill(serviceId, handler);
3077
4800
  }
3078
4801
  const pidData = { pid: process.pid, port, paths: allPaths };
3079
- writeFileSync2(PID_FILE, JSON.stringify(pidData, null, 2));
4802
+ writeFileSync3(PID_FILE, JSON.stringify(pidData, null, 2));
3080
4803
  server.listen(port);
3081
4804
  const cleanup = () => {
3082
4805
  try {
3083
- if (existsSync4(PID_FILE)) unlinkSync(PID_FILE);
3084
- if (existsSync4(tempManifestPath)) unlinkSync(tempManifestPath);
4806
+ if (existsSync5(PID_FILE)) unlinkSync(PID_FILE);
4807
+ if (existsSync5(tempManifestPath)) unlinkSync(tempManifestPath);
3085
4808
  } catch {
3086
4809
  }
3087
4810
  };
@@ -3102,12 +4825,12 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
3102
4825
  }
3103
4826
  });
3104
4827
  program.command("stop").description("Stop the running MoltsPay server").action(async () => {
3105
- if (!existsSync4(PID_FILE)) {
4828
+ if (!existsSync5(PID_FILE)) {
3106
4829
  console.log("\u274C No running server found (no PID file)");
3107
4830
  process.exit(1);
3108
4831
  }
3109
4832
  try {
3110
- const pidData = JSON.parse(readFileSync4(PID_FILE, "utf-8"));
4833
+ const pidData = JSON.parse(readFileSync5(PID_FILE, "utf-8"));
3111
4834
  const { pid, port, manifest } = pidData;
3112
4835
  console.log(`
3113
4836
  \u{1F6D1} Stopping MoltsPay Server
@@ -3132,7 +4855,7 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
3132
4855
  process.kill(pid, "SIGKILL");
3133
4856
  } catch {
3134
4857
  }
3135
- if (existsSync4(PID_FILE)) {
4858
+ if (existsSync5(PID_FILE)) {
3136
4859
  unlinkSync(PID_FILE);
3137
4860
  }
3138
4861
  console.log("\u2705 Server stopped\n");
@@ -3141,7 +4864,7 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
3141
4864
  process.exit(1);
3142
4865
  }
3143
4866
  });
3144
- 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) => {
4867
+ 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) => {
3145
4868
  const client = new MoltsPayClient({ configDir: options.configDir });
3146
4869
  if (!client.isInitialized) {
3147
4870
  console.error("\u274C Wallet not initialized. Run: npx moltspay init");
@@ -3163,17 +4886,18 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
3163
4886
  params.image_url = imagePath;
3164
4887
  } else {
3165
4888
  const filePath = resolve(imagePath);
3166
- if (!existsSync4(filePath)) {
4889
+ if (!existsSync5(filePath)) {
3167
4890
  console.error(`\u274C Image file not found: ${filePath}`);
3168
4891
  process.exit(1);
3169
4892
  }
3170
- const imageData = readFileSync4(filePath);
4893
+ const imageData = readFileSync5(filePath);
3171
4894
  params.image_base64 = imageData.toString("base64");
3172
4895
  }
3173
4896
  }
4897
+ const supportedPayChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet", "solana", "solana_devnet"];
3174
4898
  const chain = options.chain?.toLowerCase();
3175
- if (chain && !["base", "polygon", "base_sepolia", "tempo_moderato"].includes(chain)) {
3176
- console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon, base_sepolia, tempo_moderato`);
4899
+ if (chain && !supportedPayChains.includes(chain)) {
4900
+ console.error(`\u274C Unknown chain: ${chain}. Supported: ${supportedPayChains.join(", ")}`);
3177
4901
  process.exit(1);
3178
4902
  }
3179
4903
  const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);
@@ -3204,22 +4928,10 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
3204
4928
  console.log("");
3205
4929
  }
3206
4930
  try {
3207
- let result;
3208
- if (chain === "tempo_moderato") {
3209
- if (!options.json) {
3210
- console.log(" Protocol: MPP (Machine Payments Protocol)");
3211
- console.log("");
3212
- }
3213
- const mppUrl = server.includes(service) ? server : `${server}/${service}`;
3214
- result = await client.payWithMPP(mppUrl, {
3215
- body: params
3216
- });
3217
- } else {
3218
- result = await client.pay(server, service, params, {
3219
- token,
3220
- chain
3221
- });
3222
- }
4931
+ const result = await client.pay(server, service, params, {
4932
+ token,
4933
+ chain
4934
+ });
3223
4935
  if (options.json) {
3224
4936
  console.log(JSON.stringify(result));
3225
4937
  } else {
@@ -3239,9 +4951,9 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
3239
4951
  program.command("validate <path>").description("Validate a moltspay.services.json file against the schema").action(async (inputPath) => {
3240
4952
  const resolvedPath = resolve(inputPath);
3241
4953
  let manifestPath;
3242
- if (existsSync4(join4(resolvedPath, "moltspay.services.json"))) {
3243
- manifestPath = join4(resolvedPath, "moltspay.services.json");
3244
- } else if (resolvedPath.endsWith(".json") && existsSync4(resolvedPath)) {
4954
+ if (existsSync5(join5(resolvedPath, "moltspay.services.json"))) {
4955
+ manifestPath = join5(resolvedPath, "moltspay.services.json");
4956
+ } else if (resolvedPath.endsWith(".json") && existsSync5(resolvedPath)) {
3245
4957
  manifestPath = resolvedPath;
3246
4958
  } else {
3247
4959
  console.error(`\u274C Not found: ${resolvedPath}`);
@@ -3251,7 +4963,7 @@ program.command("validate <path>").description("Validate a moltspay.services.jso
3251
4963
  \u{1F4CB} Validating: ${manifestPath}
3252
4964
  `);
3253
4965
  try {
3254
- const content = JSON.parse(readFileSync4(manifestPath, "utf-8"));
4966
+ const content = JSON.parse(readFileSync5(manifestPath, "utf-8"));
3255
4967
  const errors = [];
3256
4968
  if (!content.provider) {
3257
4969
  errors.push("Missing required field: provider");