moltspay 1.2.1 → 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 (59) hide show
  1. package/README.md +292 -34
  2. package/dist/cdp/index.d.mts +4 -4
  3. package/dist/cdp/index.d.ts +4 -4
  4. package/dist/cdp/index.js +110 -30368
  5. package/dist/cdp/index.js.map +1 -1
  6. package/dist/cdp/index.mjs +94 -30360
  7. package/dist/cdp/index.mjs.map +1 -1
  8. package/dist/cdp-DeohBe1o.d.ts +66 -0
  9. package/dist/cdp-p_eHuQpb.d.mts +66 -0
  10. package/dist/chains/index.d.mts +9 -8
  11. package/dist/chains/index.d.ts +9 -8
  12. package/dist/chains/index.js +86 -0
  13. package/dist/chains/index.js.map +1 -1
  14. package/dist/chains/index.mjs +86 -0
  15. package/dist/chains/index.mjs.map +1 -1
  16. package/dist/cli/index.js +2746 -290
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/cli/index.mjs +2752 -282
  19. package/dist/cli/index.mjs.map +1 -1
  20. package/dist/client/index.d.mts +60 -4
  21. package/dist/client/index.d.ts +60 -4
  22. package/dist/client/index.js +734 -43
  23. package/dist/client/index.js.map +1 -1
  24. package/dist/client/index.mjs +732 -41
  25. package/dist/client/index.mjs.map +1 -1
  26. package/dist/facilitators/index.d.mts +220 -39
  27. package/dist/facilitators/index.d.ts +220 -39
  28. package/dist/facilitators/index.js +897 -1
  29. package/dist/facilitators/index.js.map +1 -1
  30. package/dist/facilitators/index.mjs +902 -1
  31. package/dist/facilitators/index.mjs.map +1 -1
  32. package/dist/{index-DgJPZMBG.d.mts → index-D_2FkLwV.d.mts} +6 -2
  33. package/dist/{index-DgJPZMBG.d.ts → index-D_2FkLwV.d.ts} +6 -2
  34. package/dist/index.d.mts +3 -2
  35. package/dist/index.d.ts +3 -2
  36. package/dist/index.js +2238 -30837
  37. package/dist/index.js.map +1 -1
  38. package/dist/index.mjs +2167 -30766
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/server/index.d.mts +30 -3
  41. package/dist/server/index.d.ts +30 -3
  42. package/dist/server/index.js +1345 -54
  43. package/dist/server/index.js.map +1 -1
  44. package/dist/server/index.mjs +1355 -54
  45. package/dist/server/index.mjs.map +1 -1
  46. package/dist/verify/index.d.mts +1 -1
  47. package/dist/verify/index.d.ts +1 -1
  48. package/dist/verify/index.js +86 -0
  49. package/dist/verify/index.js.map +1 -1
  50. package/dist/verify/index.mjs +86 -0
  51. package/dist/verify/index.mjs.map +1 -1
  52. package/dist/wallet/index.d.mts +3 -3
  53. package/dist/wallet/index.d.ts +3 -3
  54. package/dist/wallet/index.js +86 -0
  55. package/dist/wallet/index.js.map +1 -1
  56. package/dist/wallet/index.mjs +86 -0
  57. package/dist/wallet/index.mjs.map +1 -1
  58. package/package.json +8 -2
  59. package/schemas/moltspay.services.schema.json +27 -132
@@ -263,7 +263,815 @@ var CDPFacilitator = class extends BaseFacilitator {
263
263
  }
264
264
  };
265
265
 
266
+ // src/chains/index.ts
267
+ var CHAINS = {
268
+ // ============ Mainnet ============
269
+ base: {
270
+ name: "Base",
271
+ chainId: 8453,
272
+ rpc: "https://mainnet.base.org",
273
+ tokens: {
274
+ USDC: {
275
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
276
+ decimals: 6,
277
+ symbol: "USDC",
278
+ eip712Name: "USD Coin"
279
+ // EIP-712 domain name
280
+ },
281
+ USDT: {
282
+ address: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
283
+ decimals: 6,
284
+ symbol: "USDT",
285
+ eip712Name: "Tether USD"
286
+ }
287
+ },
288
+ usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
289
+ // deprecated, for backward compat
290
+ explorer: "https://basescan.org/address/",
291
+ explorerTx: "https://basescan.org/tx/",
292
+ avgBlockTime: 2
293
+ },
294
+ polygon: {
295
+ name: "Polygon",
296
+ chainId: 137,
297
+ rpc: "https://polygon-bor-rpc.publicnode.com",
298
+ tokens: {
299
+ USDC: {
300
+ address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
301
+ decimals: 6,
302
+ symbol: "USDC",
303
+ eip712Name: "USD Coin"
304
+ },
305
+ USDT: {
306
+ address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
307
+ decimals: 6,
308
+ symbol: "USDT",
309
+ eip712Name: "(PoS) Tether USD"
310
+ // Polygon uses this name
311
+ }
312
+ },
313
+ usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
314
+ explorer: "https://polygonscan.com/address/",
315
+ explorerTx: "https://polygonscan.com/tx/",
316
+ avgBlockTime: 2
317
+ },
318
+ // ============ Testnet ============
319
+ base_sepolia: {
320
+ name: "Base Sepolia",
321
+ chainId: 84532,
322
+ rpc: "https://sepolia.base.org",
323
+ tokens: {
324
+ USDC: {
325
+ address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
326
+ decimals: 6,
327
+ symbol: "USDC",
328
+ eip712Name: "USDC"
329
+ // Testnet USDC uses 'USDC' not 'USD Coin'
330
+ },
331
+ USDT: {
332
+ address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
333
+ // Same as USDC on testnet (no official USDT)
334
+ decimals: 6,
335
+ symbol: "USDT",
336
+ eip712Name: "USDC"
337
+ // Uses same contract as USDC
338
+ }
339
+ },
340
+ usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
341
+ explorer: "https://sepolia.basescan.org/address/",
342
+ explorerTx: "https://sepolia.basescan.org/tx/",
343
+ avgBlockTime: 2
344
+ },
345
+ // ============ Tempo Testnet (Moderato) ============
346
+ tempo_moderato: {
347
+ name: "Tempo Moderato",
348
+ chainId: 42431,
349
+ rpc: "https://rpc.moderato.tempo.xyz",
350
+ tokens: {
351
+ // TIP-20 stablecoins on Tempo testnet (from mppx SDK)
352
+ // Note: Tempo uses USD as native gas token, not ETH
353
+ USDC: {
354
+ address: "0x20c0000000000000000000000000000000000000",
355
+ // pathUSD - primary testnet stablecoin
356
+ decimals: 6,
357
+ symbol: "USDC",
358
+ eip712Name: "pathUSD"
359
+ },
360
+ USDT: {
361
+ address: "0x20c0000000000000000000000000000000000001",
362
+ // alphaUSD
363
+ decimals: 6,
364
+ symbol: "USDT",
365
+ eip712Name: "alphaUSD"
366
+ }
367
+ },
368
+ usdc: "0x20c0000000000000000000000000000000000000",
369
+ explorer: "https://explore.testnet.tempo.xyz/address/",
370
+ explorerTx: "https://explore.testnet.tempo.xyz/tx/",
371
+ avgBlockTime: 0.5
372
+ // ~500ms finality
373
+ },
374
+ // ============ BNB Chain Testnet ============
375
+ bnb_testnet: {
376
+ name: "BNB Testnet",
377
+ chainId: 97,
378
+ rpc: "https://data-seed-prebsc-1-s1.binance.org:8545",
379
+ tokens: {
380
+ // Note: BNB uses 18 decimals for stablecoins (unlike Base/Polygon which use 6)
381
+ // Using official Binance-Peg testnet tokens
382
+ USDC: {
383
+ address: "0x64544969ed7EBf5f083679233325356EbE738930",
384
+ // Testnet USDC
385
+ decimals: 18,
386
+ symbol: "USDC",
387
+ eip712Name: "USD Coin"
388
+ },
389
+ USDT: {
390
+ address: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd",
391
+ // Testnet USDT
392
+ decimals: 18,
393
+ symbol: "USDT",
394
+ eip712Name: "Tether USD"
395
+ }
396
+ },
397
+ usdc: "0x64544969ed7EBf5f083679233325356EbE738930",
398
+ explorer: "https://testnet.bscscan.com/address/",
399
+ explorerTx: "https://testnet.bscscan.com/tx/",
400
+ avgBlockTime: 3,
401
+ // BNB-specific: requires approval for pay-for-success flow
402
+ requiresApproval: true
403
+ },
404
+ // ============ BNB Chain Mainnet ============
405
+ bnb: {
406
+ name: "BNB Smart Chain",
407
+ chainId: 56,
408
+ rpc: "https://bsc-dataseed.binance.org",
409
+ tokens: {
410
+ // Note: BNB uses 18 decimals for stablecoins
411
+ USDC: {
412
+ address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
413
+ decimals: 18,
414
+ symbol: "USDC",
415
+ eip712Name: "USD Coin"
416
+ },
417
+ USDT: {
418
+ address: "0x55d398326f99059fF775485246999027B3197955",
419
+ decimals: 18,
420
+ symbol: "USDT",
421
+ eip712Name: "Tether USD"
422
+ }
423
+ },
424
+ usdc: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
425
+ explorer: "https://bscscan.com/address/",
426
+ explorerTx: "https://bscscan.com/tx/",
427
+ avgBlockTime: 3,
428
+ // BNB-specific: requires approval for pay-for-success flow
429
+ requiresApproval: true
430
+ }
431
+ };
432
+
433
+ // src/facilitators/tempo.ts
434
+ var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
435
+ var TempoFacilitator = class extends BaseFacilitator {
436
+ name = "tempo";
437
+ displayName = "Tempo Testnet";
438
+ supportedNetworks = ["eip155:42431"];
439
+ // Tempo Moderato
440
+ rpcUrl;
441
+ constructor() {
442
+ super();
443
+ this.rpcUrl = CHAINS.tempo_moderato.rpc;
444
+ }
445
+ async healthCheck() {
446
+ const start = Date.now();
447
+ try {
448
+ const response = await fetch(this.rpcUrl, {
449
+ method: "POST",
450
+ headers: { "Content-Type": "application/json" },
451
+ body: JSON.stringify({
452
+ jsonrpc: "2.0",
453
+ method: "eth_chainId",
454
+ params: [],
455
+ id: 1
456
+ })
457
+ });
458
+ const data = await response.json();
459
+ const chainId = parseInt(data.result, 16);
460
+ if (chainId !== 42431) {
461
+ return { healthy: false, error: `Wrong chainId: ${chainId}` };
462
+ }
463
+ return { healthy: true, latencyMs: Date.now() - start };
464
+ } catch (error) {
465
+ return { healthy: false, error: String(error) };
466
+ }
467
+ }
468
+ async verify(paymentPayload, requirements) {
469
+ try {
470
+ const tempoPayload = paymentPayload.payload;
471
+ if (!tempoPayload?.txHash) {
472
+ return { valid: false, error: "Missing txHash in payment payload" };
473
+ }
474
+ const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
475
+ if (!receipt) {
476
+ return { valid: false, error: "Transaction not found" };
477
+ }
478
+ if (receipt.status !== "0x1") {
479
+ return { valid: false, error: "Transaction failed" };
480
+ }
481
+ const transferLog = receipt.logs.find(
482
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC
483
+ );
484
+ if (!transferLog) {
485
+ return { valid: false, error: "No Transfer event found" };
486
+ }
487
+ const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
488
+ const expectedTo = requirements.payTo.toLowerCase();
489
+ if (toAddress !== expectedTo) {
490
+ return {
491
+ valid: false,
492
+ error: `Wrong recipient: ${toAddress}, expected ${expectedTo}`
493
+ };
494
+ }
495
+ const amount = BigInt(transferLog.data);
496
+ const expectedAmount = BigInt(requirements.amount);
497
+ if (amount < expectedAmount) {
498
+ return {
499
+ valid: false,
500
+ error: `Insufficient amount: ${amount}, expected ${expectedAmount}`
501
+ };
502
+ }
503
+ const tokenAddress = transferLog.address.toLowerCase();
504
+ const expectedToken = requirements.asset.toLowerCase();
505
+ if (tokenAddress !== expectedToken) {
506
+ return {
507
+ valid: false,
508
+ error: `Wrong token: ${tokenAddress}, expected ${expectedToken}`
509
+ };
510
+ }
511
+ return {
512
+ valid: true,
513
+ details: {
514
+ txHash: tempoPayload.txHash,
515
+ from: "0x" + transferLog.topics[1].slice(26),
516
+ to: toAddress,
517
+ amount: amount.toString(),
518
+ token: tokenAddress
519
+ }
520
+ };
521
+ } catch (error) {
522
+ return { valid: false, error: `Verification failed: ${error}` };
523
+ }
524
+ }
525
+ async settle(paymentPayload, requirements) {
526
+ const verifyResult = await this.verify(paymentPayload, requirements);
527
+ if (!verifyResult.valid) {
528
+ return { success: false, error: verifyResult.error };
529
+ }
530
+ const tempoPayload = paymentPayload.payload;
531
+ return {
532
+ success: true,
533
+ transaction: tempoPayload.txHash,
534
+ status: "settled"
535
+ };
536
+ }
537
+ async getTransactionReceipt(txHash) {
538
+ const response = await fetch(this.rpcUrl, {
539
+ method: "POST",
540
+ headers: { "Content-Type": "application/json" },
541
+ body: JSON.stringify({
542
+ jsonrpc: "2.0",
543
+ method: "eth_getTransactionReceipt",
544
+ params: [txHash],
545
+ id: 1
546
+ })
547
+ });
548
+ const data = await response.json();
549
+ return data.result;
550
+ }
551
+ };
552
+
553
+ // src/facilitators/bnb.ts
554
+ var import_accounts = require("viem/accounts");
555
+ var TRANSFER_EVENT_TOPIC2 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
556
+ var EIP712_DOMAIN = {
557
+ name: "MoltsPay",
558
+ version: "1"
559
+ };
560
+ var INTENT_TYPES = {
561
+ PaymentIntent: [
562
+ { name: "from", type: "address" },
563
+ { name: "to", type: "address" },
564
+ { name: "amount", type: "uint256" },
565
+ { name: "token", type: "address" },
566
+ { name: "service", type: "string" },
567
+ { name: "nonce", type: "uint256" },
568
+ { name: "deadline", type: "uint256" }
569
+ ]
570
+ };
571
+ var BNBFacilitator = class extends BaseFacilitator {
572
+ name = "bnb";
573
+ displayName = "BNB Smart Chain";
574
+ supportedNetworks = ["eip155:56", "eip155:97"];
575
+ // Mainnet + Testnet
576
+ serverPrivateKey;
577
+ spenderAddress = null;
578
+ chainConfigs;
579
+ constructor(serverPrivateKey) {
580
+ super();
581
+ this.serverPrivateKey = serverPrivateKey || process.env.BNB_SERVER_PRIVATE_KEY || "";
582
+ if (this.serverPrivateKey) {
583
+ const key = this.serverPrivateKey.startsWith("0x") ? this.serverPrivateKey : `0x${this.serverPrivateKey}`;
584
+ const account = (0, import_accounts.privateKeyToAccount)(key);
585
+ this.spenderAddress = account.address;
586
+ }
587
+ this.chainConfigs = {
588
+ 56: { rpc: CHAINS.bnb.rpc, chain: CHAINS.bnb },
589
+ 97: { rpc: CHAINS.bnb_testnet.rpc, chain: CHAINS.bnb_testnet }
590
+ };
591
+ }
592
+ async healthCheck() {
593
+ const start = Date.now();
594
+ try {
595
+ const response = await fetch(this.chainConfigs[56].rpc, {
596
+ method: "POST",
597
+ headers: { "Content-Type": "application/json" },
598
+ body: JSON.stringify({
599
+ jsonrpc: "2.0",
600
+ method: "eth_chainId",
601
+ params: [],
602
+ id: 1
603
+ })
604
+ });
605
+ const data = await response.json();
606
+ const chainId = parseInt(data.result, 16);
607
+ if (chainId !== 56) {
608
+ return { healthy: false, error: `Wrong chainId: ${chainId}` };
609
+ }
610
+ return { healthy: true, latencyMs: Date.now() - start };
611
+ } catch (error) {
612
+ return { healthy: false, error: String(error) };
613
+ }
614
+ }
615
+ /**
616
+ * Verify a payment intent signature (before service execution)
617
+ *
618
+ * This verifies:
619
+ * 1. Signature is valid for the intent
620
+ * 2. Client has approved server wallet
621
+ * 3. Client has sufficient balance
622
+ * 4. Intent hasn't expired
623
+ */
624
+ async verify(paymentPayload, requirements) {
625
+ try {
626
+ const bnbPayload = paymentPayload.payload;
627
+ if (!bnbPayload?.intent) {
628
+ return { valid: false, error: "Missing intent in payment payload" };
629
+ }
630
+ const { intent, chainId } = bnbPayload;
631
+ const config = this.chainConfigs[chainId];
632
+ if (!config) {
633
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
634
+ }
635
+ if (intent.deadline < Date.now()) {
636
+ return { valid: false, error: "Intent expired" };
637
+ }
638
+ const recoveredAddress = await this.recoverIntentSigner(intent, chainId);
639
+ if (recoveredAddress.toLowerCase() !== intent.from.toLowerCase()) {
640
+ return { valid: false, error: "Invalid signature" };
641
+ }
642
+ if (intent.to.toLowerCase() !== requirements.payTo.toLowerCase()) {
643
+ return { valid: false, error: `Wrong recipient: ${intent.to}` };
644
+ }
645
+ if (BigInt(intent.amount) < BigInt(requirements.amount)) {
646
+ return { valid: false, error: `Insufficient amount: ${intent.amount}` };
647
+ }
648
+ if (intent.token.toLowerCase() !== requirements.asset.toLowerCase()) {
649
+ return { valid: false, error: `Wrong token: ${intent.token}` };
650
+ }
651
+ const serverAddress = await this.getServerAddress();
652
+ const allowance = await this.getAllowance(intent.from, serverAddress, intent.token, config.rpc);
653
+ if (BigInt(allowance) < BigInt(intent.amount)) {
654
+ return { valid: false, error: "Insufficient allowance. Run: npx moltspay init --chain bnb" };
655
+ }
656
+ const balance = await this.getBalance(intent.from, intent.token, config.rpc);
657
+ if (BigInt(balance) < BigInt(intent.amount)) {
658
+ return { valid: false, error: "Insufficient balance" };
659
+ }
660
+ return {
661
+ valid: true,
662
+ details: {
663
+ from: intent.from,
664
+ to: intent.to,
665
+ amount: intent.amount,
666
+ token: intent.token,
667
+ service: intent.service,
668
+ nonce: intent.nonce,
669
+ deadline: intent.deadline
670
+ }
671
+ };
672
+ } catch (error) {
673
+ return { valid: false, error: `Verification failed: ${error}` };
674
+ }
675
+ }
676
+ /**
677
+ * Settle a payment by executing transferFrom
678
+ *
679
+ * This is called AFTER the service has been successfully delivered.
680
+ * Server pays gas, transfers tokens from client to provider.
681
+ */
682
+ async settle(paymentPayload, requirements) {
683
+ if (!this.serverPrivateKey) {
684
+ return { success: false, error: "Server wallet not configured (BNB_SERVER_PRIVATE_KEY)" };
685
+ }
686
+ try {
687
+ const verifyResult = await this.verify(paymentPayload, requirements);
688
+ if (!verifyResult.valid) {
689
+ return { success: false, error: verifyResult.error };
690
+ }
691
+ const bnbPayload = paymentPayload.payload;
692
+ const { intent, chainId } = bnbPayload;
693
+ const config = this.chainConfigs[chainId];
694
+ const txHash = await this.executeTransferFrom(
695
+ intent.from,
696
+ intent.to,
697
+ intent.amount,
698
+ intent.token,
699
+ config.rpc
700
+ );
701
+ return {
702
+ success: true,
703
+ transaction: txHash,
704
+ status: "settled"
705
+ };
706
+ } catch (error) {
707
+ return { success: false, error: `Settlement failed: ${error}` };
708
+ }
709
+ }
710
+ /**
711
+ * Check if client has approved the server wallet
712
+ */
713
+ async checkApproval(clientAddress, token, chainId) {
714
+ const config = this.chainConfigs[chainId];
715
+ if (!config) {
716
+ throw new Error(`Unsupported chainId: ${chainId}`);
717
+ }
718
+ const serverAddress = await this.getServerAddress();
719
+ const allowance = await this.getAllowance(clientAddress, serverAddress, token, config.rpc);
720
+ const minAllowance = BigInt("1000000000000000000000");
721
+ return {
722
+ approved: BigInt(allowance) >= minAllowance,
723
+ allowance
724
+ };
725
+ }
726
+ /**
727
+ * Verify a completed transaction (for checking past payments)
728
+ */
729
+ async verifyTransaction(txHash, expected, chainId) {
730
+ const config = this.chainConfigs[chainId];
731
+ if (!config) {
732
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
733
+ }
734
+ try {
735
+ const receipt = await this.getTransactionReceipt(txHash, config.rpc);
736
+ if (!receipt) {
737
+ return { valid: false, error: "Transaction not found" };
738
+ }
739
+ if (receipt.status !== "0x1") {
740
+ return { valid: false, error: "Transaction failed" };
741
+ }
742
+ const transferLog = receipt.logs.find(
743
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC2 && log.address.toLowerCase() === expected.token.toLowerCase()
744
+ );
745
+ if (!transferLog) {
746
+ return { valid: false, error: "No Transfer event found" };
747
+ }
748
+ const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
749
+ if (toAddress !== expected.to.toLowerCase()) {
750
+ return { valid: false, error: `Wrong recipient: ${toAddress}` };
751
+ }
752
+ const amount = BigInt(transferLog.data);
753
+ if (amount < BigInt(expected.amount)) {
754
+ return { valid: false, error: `Insufficient amount: ${amount}` };
755
+ }
756
+ return {
757
+ valid: true,
758
+ details: {
759
+ txHash,
760
+ from: "0x" + transferLog.topics[1].slice(26),
761
+ to: toAddress,
762
+ amount: amount.toString(),
763
+ token: transferLog.address
764
+ }
765
+ };
766
+ } catch (error) {
767
+ return { valid: false, error: `Verification failed: ${error}` };
768
+ }
769
+ }
770
+ // ==================== Private Methods ====================
771
+ /**
772
+ * Get the server's spender address (public, for 402 responses)
773
+ * Returns cached value computed at construction time.
774
+ */
775
+ getSpenderAddress() {
776
+ return this.spenderAddress;
777
+ }
778
+ async getServerAddress() {
779
+ const { ethers } = await import("ethers");
780
+ const wallet = new ethers.Wallet(this.serverPrivateKey);
781
+ return wallet.address;
782
+ }
783
+ async recoverIntentSigner(intent, chainId) {
784
+ const { ethers } = await import("ethers");
785
+ const domain = {
786
+ ...EIP712_DOMAIN,
787
+ chainId
788
+ };
789
+ const message = {
790
+ from: intent.from,
791
+ to: intent.to,
792
+ amount: intent.amount,
793
+ token: intent.token,
794
+ service: intent.service,
795
+ nonce: intent.nonce,
796
+ deadline: intent.deadline
797
+ };
798
+ const recoveredAddress = ethers.verifyTypedData(
799
+ domain,
800
+ INTENT_TYPES,
801
+ message,
802
+ intent.signature
803
+ );
804
+ return recoveredAddress;
805
+ }
806
+ async getAllowance(owner, spender, token, rpcUrl) {
807
+ const selector = "0xdd62ed3e";
808
+ const ownerPadded = owner.toLowerCase().replace("0x", "").padStart(64, "0");
809
+ const spenderPadded = spender.toLowerCase().replace("0x", "").padStart(64, "0");
810
+ const data = selector + ownerPadded + spenderPadded;
811
+ const response = await fetch(rpcUrl, {
812
+ method: "POST",
813
+ headers: { "Content-Type": "application/json" },
814
+ body: JSON.stringify({
815
+ jsonrpc: "2.0",
816
+ method: "eth_call",
817
+ params: [{ to: token, data }, "latest"],
818
+ id: 1
819
+ })
820
+ });
821
+ const result = await response.json();
822
+ return result.result || "0x0";
823
+ }
824
+ async getBalance(account, token, rpcUrl) {
825
+ const selector = "0x70a08231";
826
+ const accountPadded = account.toLowerCase().replace("0x", "").padStart(64, "0");
827
+ const data = selector + accountPadded;
828
+ const response = await fetch(rpcUrl, {
829
+ method: "POST",
830
+ headers: { "Content-Type": "application/json" },
831
+ body: JSON.stringify({
832
+ jsonrpc: "2.0",
833
+ method: "eth_call",
834
+ params: [{ to: token, data }, "latest"],
835
+ id: 1
836
+ })
837
+ });
838
+ const result = await response.json();
839
+ return result.result || "0x0";
840
+ }
841
+ async executeTransferFrom(from, to, amount, token, rpcUrl) {
842
+ const { ethers } = await import("ethers");
843
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
844
+ const wallet = new ethers.Wallet(this.serverPrivateKey, provider);
845
+ const tokenContract = new ethers.Contract(token, [
846
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)"
847
+ ], wallet);
848
+ const tx = await tokenContract.transferFrom(from, to, amount);
849
+ const receipt = await tx.wait();
850
+ return receipt.hash;
851
+ }
852
+ async getTransactionReceipt(txHash, rpcUrl) {
853
+ const response = await fetch(rpcUrl, {
854
+ method: "POST",
855
+ headers: { "Content-Type": "application/json" },
856
+ body: JSON.stringify({
857
+ jsonrpc: "2.0",
858
+ method: "eth_getTransactionReceipt",
859
+ params: [txHash],
860
+ id: 1
861
+ })
862
+ });
863
+ const data = await response.json();
864
+ return data.result;
865
+ }
866
+ };
867
+
868
+ // src/facilitators/solana.ts
869
+ var import_web32 = require("@solana/web3.js");
870
+ var import_spl_token = require("@solana/spl-token");
871
+
872
+ // src/chains/solana.ts
873
+ var import_web3 = require("@solana/web3.js");
874
+ var SOLANA_CHAINS = {
875
+ solana: {
876
+ name: "Solana Mainnet",
877
+ cluster: "mainnet-beta",
878
+ rpc: "https://api.mainnet-beta.solana.com",
879
+ explorer: "https://solscan.io/account/",
880
+ explorerTx: "https://solscan.io/tx/",
881
+ tokens: {
882
+ USDC: {
883
+ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
884
+ // Circle official USDC
885
+ decimals: 6
886
+ }
887
+ }
888
+ },
889
+ solana_devnet: {
890
+ name: "Solana Devnet",
891
+ cluster: "devnet",
892
+ rpc: "https://api.devnet.solana.com",
893
+ explorer: "https://solscan.io/account/",
894
+ explorerTx: "https://solscan.io/tx/",
895
+ tokens: {
896
+ USDC: {
897
+ // Circle's devnet USDC (if not available, we'll deploy our own test token)
898
+ mint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
899
+ decimals: 6
900
+ }
901
+ }
902
+ }
903
+ };
904
+
905
+ // src/facilitators/solana.ts
906
+ var SolanaFacilitator = class extends BaseFacilitator {
907
+ name = "solana";
908
+ displayName = "Solana Direct";
909
+ supportedNetworks = ["solana:mainnet", "solana:devnet"];
910
+ connections = /* @__PURE__ */ new Map();
911
+ feePayerKeypair;
912
+ constructor(config) {
913
+ super();
914
+ this.feePayerKeypair = config?.feePayerKeypair;
915
+ for (const [chain, config2] of Object.entries(SOLANA_CHAINS)) {
916
+ this.connections.set(
917
+ chain,
918
+ new import_web32.Connection(config2.rpc, "confirmed")
919
+ );
920
+ }
921
+ if (this.feePayerKeypair) {
922
+ console.log(`[SolanaFacilitator] Gasless mode enabled. Fee payer: ${this.feePayerKeypair.publicKey.toBase58()}`);
923
+ }
924
+ }
925
+ /**
926
+ * Get fee payer public key (for gasless transactions)
927
+ */
928
+ getFeePayerPubkey() {
929
+ return this.feePayerKeypair?.publicKey.toBase58() || null;
930
+ }
931
+ getConnection(chain) {
932
+ const conn = this.connections.get(chain);
933
+ if (!conn) {
934
+ throw new Error(`No connection for chain: ${chain}`);
935
+ }
936
+ return conn;
937
+ }
938
+ /**
939
+ * Convert our chain name to network identifier
940
+ */
941
+ static chainToNetwork(chain) {
942
+ return chain === "solana" ? "solana:mainnet" : "solana:devnet";
943
+ }
944
+ /**
945
+ * Convert network identifier to chain name
946
+ */
947
+ static networkToChain(network) {
948
+ if (network === "solana:mainnet") return "solana";
949
+ if (network === "solana:devnet") return "solana_devnet";
950
+ return null;
951
+ }
952
+ async healthCheck() {
953
+ const start = Date.now();
954
+ try {
955
+ const conn = this.getConnection("solana_devnet");
956
+ await conn.getSlot();
957
+ return {
958
+ healthy: true,
959
+ latencyMs: Date.now() - start
960
+ };
961
+ } catch (error) {
962
+ return {
963
+ healthy: false,
964
+ error: error.message
965
+ };
966
+ }
967
+ }
968
+ /**
969
+ * Verify a Solana payment
970
+ *
971
+ * Checks:
972
+ * 1. Transaction is valid and properly signed
973
+ * 2. Transfer instruction matches expected amount and recipient
974
+ */
975
+ async verify(paymentPayload, requirements) {
976
+ try {
977
+ const solanaPayload = paymentPayload.payload;
978
+ if (!solanaPayload || !solanaPayload.signedTransaction) {
979
+ return { valid: false, error: "Missing signed transaction" };
980
+ }
981
+ const chain = solanaPayload.chain || "solana_devnet";
982
+ const chainConfig = SOLANA_CHAINS[chain];
983
+ if (!chainConfig) {
984
+ return { valid: false, error: `Invalid chain: ${chain}` };
985
+ }
986
+ const txBuffer = Buffer.from(solanaPayload.signedTransaction, "base64");
987
+ let tx;
988
+ try {
989
+ tx = import_web32.Transaction.from(txBuffer);
990
+ } catch {
991
+ tx = import_web32.VersionedTransaction.deserialize(txBuffer);
992
+ }
993
+ if (tx instanceof import_web32.Transaction) {
994
+ const hasAnySignature = tx.signatures.some(
995
+ (sig) => sig.signature && !sig.signature.every((b) => b === 0)
996
+ );
997
+ if (!hasAnySignature) {
998
+ return { valid: false, error: "Transaction not signed" };
999
+ }
1000
+ }
1001
+ const expectedAmount = BigInt(requirements.amount);
1002
+ const expectedRecipient = new import_web32.PublicKey(requirements.payTo);
1003
+ return {
1004
+ valid: true,
1005
+ details: {
1006
+ chain,
1007
+ sender: solanaPayload.sender,
1008
+ recipient: requirements.payTo,
1009
+ amount: requirements.amount
1010
+ }
1011
+ };
1012
+ } catch (error) {
1013
+ return { valid: false, error: error.message };
1014
+ }
1015
+ }
1016
+ /**
1017
+ * Settle a Solana payment
1018
+ *
1019
+ * Submits the signed transaction to the network.
1020
+ * In gasless mode, adds fee payer signature before submitting.
1021
+ */
1022
+ async settle(paymentPayload, requirements) {
1023
+ try {
1024
+ const solanaPayload = paymentPayload.payload;
1025
+ if (!solanaPayload || !solanaPayload.signedTransaction) {
1026
+ return { success: false, error: "Missing signed transaction" };
1027
+ }
1028
+ const chain = solanaPayload.chain || "solana_devnet";
1029
+ const connection = this.getConnection(chain);
1030
+ const txBuffer = Buffer.from(solanaPayload.signedTransaction, "base64");
1031
+ let txToSend;
1032
+ try {
1033
+ const tx = import_web32.Transaction.from(txBuffer);
1034
+ if (this.feePayerKeypair && tx.feePayer) {
1035
+ const feePayerPubkey = this.feePayerKeypair.publicKey.toBase58();
1036
+ const txFeePayer = tx.feePayer.toBase58();
1037
+ if (txFeePayer === feePayerPubkey) {
1038
+ console.log(`[SolanaFacilitator] Gasless mode: adding fee payer signature`);
1039
+ tx.partialSign(this.feePayerKeypair);
1040
+ }
1041
+ }
1042
+ txToSend = tx.serialize();
1043
+ } catch (e) {
1044
+ txToSend = txBuffer;
1045
+ }
1046
+ const signature = await connection.sendRawTransaction(txToSend, {
1047
+ skipPreflight: false,
1048
+ preflightCommitment: "confirmed"
1049
+ });
1050
+ const confirmation = await connection.confirmTransaction(signature, "confirmed");
1051
+ if (confirmation.value.err) {
1052
+ return {
1053
+ success: false,
1054
+ error: `Transaction failed: ${JSON.stringify(confirmation.value.err)}`,
1055
+ transaction: signature
1056
+ };
1057
+ }
1058
+ return {
1059
+ success: true,
1060
+ transaction: signature,
1061
+ status: "confirmed"
1062
+ };
1063
+ } catch (error) {
1064
+ return { success: false, error: error.message };
1065
+ }
1066
+ }
1067
+ supportsNetwork(network) {
1068
+ return this.supportedNetworks.includes(network);
1069
+ }
1070
+ };
1071
+
266
1072
  // src/facilitators/registry.ts
1073
+ var import_web33 = require("@solana/web3.js");
1074
+ var import_bs58 = __toESM(require("bs58"));
267
1075
  var FacilitatorRegistry = class {
268
1076
  factories = /* @__PURE__ */ new Map();
269
1077
  instances = /* @__PURE__ */ new Map();
@@ -271,7 +1079,21 @@ var FacilitatorRegistry = class {
271
1079
  roundRobinIndex = 0;
272
1080
  constructor(selection) {
273
1081
  this.registerFactory("cdp", (config) => new CDPFacilitator(config));
274
- this.selection = selection || { primary: "cdp", strategy: "failover" };
1082
+ this.registerFactory("tempo", () => new TempoFacilitator());
1083
+ this.registerFactory("bnb", (config) => new BNBFacilitator(config?.serverPrivateKey));
1084
+ this.registerFactory("solana", (config) => {
1085
+ let feePayerKeypair;
1086
+ const feePayerKey = config?.feePayerPrivateKey || process.env.SOLANA_FEE_PAYER_KEY;
1087
+ if (feePayerKey) {
1088
+ try {
1089
+ feePayerKeypair = import_web33.Keypair.fromSecretKey(import_bs58.default.decode(feePayerKey));
1090
+ } catch (e) {
1091
+ console.warn(`[SolanaFacilitator] Invalid fee payer key: ${e.message}`);
1092
+ }
1093
+ }
1094
+ return new SolanaFacilitator({ feePayerKeypair });
1095
+ });
1096
+ this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
275
1097
  }
276
1098
  /**
277
1099
  * Register a new facilitator factory
@@ -492,6 +1314,9 @@ var X402_VERSION2 = 2;
492
1314
  var PAYMENT_REQUIRED_HEADER = "x-payment-required";
493
1315
  var PAYMENT_HEADER = "x-payment";
494
1316
  var PAYMENT_RESPONSE_HEADER = "x-payment-response";
1317
+ var MPP_AUTH_HEADER = "authorization";
1318
+ var MPP_WWW_AUTH_HEADER = "www-authenticate";
1319
+ var MPP_RECEIPT_HEADER = "payment-receipt";
495
1320
  var TOKEN_ADDRESSES = {
496
1321
  "eip155:8453": {
497
1322
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -505,13 +1330,47 @@ var TOKEN_ADDRESSES = {
505
1330
  "eip155:137": {
506
1331
  USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
507
1332
  USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
1333
+ },
1334
+ "eip155:42431": {
1335
+ // Tempo Moderato testnet - TIP-20 stablecoins
1336
+ USDC: "0x20c0000000000000000000000000000000000000",
1337
+ // pathUSD
1338
+ USDT: "0x20c0000000000000000000000000000000000001"
1339
+ // alphaUSD
1340
+ },
1341
+ // BNB Smart Chain mainnet
1342
+ "eip155:56": {
1343
+ USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
1344
+ USDT: "0x55d398326f99059fF775485246999027B3197955"
1345
+ },
1346
+ // BNB Smart Chain testnet
1347
+ "eip155:97": {
1348
+ USDC: "0x64544969ed7EBf5f083679233325356EbE738930",
1349
+ USDT: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd"
1350
+ },
1351
+ // Solana networks use mint addresses (SPL tokens)
1352
+ "solana:mainnet": {
1353
+ USDC: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
1354
+ // Circle USDC
1355
+ },
1356
+ "solana:devnet": {
1357
+ USDC: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
1358
+ // Devnet USDC
508
1359
  }
509
1360
  };
510
1361
  var CHAIN_TO_NETWORK = {
511
1362
  "base": "eip155:8453",
512
1363
  "base_sepolia": "eip155:84532",
513
- "polygon": "eip155:137"
1364
+ "polygon": "eip155:137",
1365
+ "tempo_moderato": "eip155:42431",
1366
+ "bnb": "eip155:56",
1367
+ "bnb_testnet": "eip155:97",
1368
+ "solana": "solana:mainnet",
1369
+ "solana_devnet": "solana:devnet"
514
1370
  };
1371
+ function isSolanaNetwork(network) {
1372
+ return network.startsWith("solana:");
1373
+ }
515
1374
  var TOKEN_DOMAINS = {
516
1375
  // Base mainnet
517
1376
  "eip155:8453": {
@@ -528,6 +1387,21 @@ var TOKEN_DOMAINS = {
528
1387
  "eip155:137": {
529
1388
  USDC: { name: "USD Coin", version: "2" },
530
1389
  USDT: { name: "(PoS) Tether USD", version: "2" }
1390
+ },
1391
+ // Tempo Moderato testnet - TIP-20 stablecoins
1392
+ "eip155:42431": {
1393
+ USDC: { name: "pathUSD", version: "1" },
1394
+ USDT: { name: "alphaUSD", version: "1" }
1395
+ },
1396
+ // BNB Smart Chain mainnet
1397
+ "eip155:56": {
1398
+ USDC: { name: "USD Coin", version: "1" },
1399
+ USDT: { name: "Tether USD", version: "1" }
1400
+ },
1401
+ // BNB Smart Chain testnet
1402
+ "eip155:97": {
1403
+ USDC: { name: "USD Coin", version: "1" },
1404
+ USDT: { name: "Tether USD", version: "1" }
531
1405
  }
532
1406
  };
533
1407
  function getTokenDomain(network, token) {
@@ -585,9 +1459,11 @@ var MoltsPayServer = class {
585
1459
  };
586
1460
  this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
587
1461
  this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
1462
+ const defaultFallback = ["tempo", "bnb", "solana"];
1463
+ const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
588
1464
  const facilitatorConfig = options.facilitators || {
589
1465
  primary: process.env.FACILITATOR_PRIMARY || "cdp",
590
- fallback: process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean),
1466
+ fallback: envFallback || defaultFallback,
591
1467
  strategy: process.env.FACILITATOR_STRATEGY || "failover",
592
1468
  config: {
593
1469
  cdp: { useMainnet: this.useMainnet }
@@ -626,12 +1502,20 @@ var MoltsPayServer = class {
626
1502
  */
627
1503
  getProviderChains() {
628
1504
  const provider = this.manifest.provider;
1505
+ const getWalletForChain = (chainName, explicitWallet) => {
1506
+ if (explicitWallet) return explicitWallet;
1507
+ if ((chainName === "solana" || chainName === "solana_devnet") && provider.solana_wallet) {
1508
+ return provider.solana_wallet;
1509
+ }
1510
+ return provider.wallet;
1511
+ };
629
1512
  if (provider.chains && provider.chains.length > 0) {
630
1513
  return provider.chains.map((c) => {
631
1514
  const chainName = typeof c === "string" ? c : c.chain;
1515
+ const explicitWallet = typeof c === "object" ? c.wallet : null;
632
1516
  return {
633
1517
  network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
634
- wallet: (typeof c === "object" ? c.wallet : null) || provider.wallet,
1518
+ wallet: getWalletForChain(chainName, explicitWallet || void 0),
635
1519
  tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
636
1520
  };
637
1521
  });
@@ -640,7 +1524,7 @@ var MoltsPayServer = class {
640
1524
  const network = CHAIN_TO_NETWORK[chain] || this.networkId;
641
1525
  return [{
642
1526
  network,
643
- wallet: provider.wallet,
1527
+ wallet: getWalletForChain(chain),
644
1528
  tokens: ["USDC"]
645
1529
  }];
646
1530
  }
@@ -681,8 +1565,8 @@ var MoltsPayServer = class {
681
1565
  async handleRequest(req, res) {
682
1566
  res.setHeader("Access-Control-Allow-Origin", "*");
683
1567
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
684
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
685
- res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
1568
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
1569
+ res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
686
1570
  if (req.method === "OPTIONS") {
687
1571
  res.writeHead(204);
688
1572
  res.end();
@@ -711,7 +1595,16 @@ var MoltsPayServer = class {
711
1595
  }
712
1596
  const body = await this.readBody(req);
713
1597
  const paymentHeader = req.headers[PAYMENT_HEADER];
714
- return await this.handleProxy(body, paymentHeader, res);
1598
+ const authHeader = req.headers[MPP_AUTH_HEADER];
1599
+ return await this.handleProxy(body, paymentHeader, authHeader, res);
1600
+ }
1601
+ const servicePath = url.pathname.replace(/^\//, "");
1602
+ const skill = this.skills.get(servicePath);
1603
+ if (skill && (req.method === "POST" || req.method === "GET")) {
1604
+ const body = req.method === "POST" ? await this.readBody(req) : {};
1605
+ const authHeader = req.headers[MPP_AUTH_HEADER];
1606
+ const x402Header = req.headers[PAYMENT_HEADER];
1607
+ return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
715
1608
  }
716
1609
  this.sendJson(res, 404, { error: "Not found" });
717
1610
  } catch (err) {
@@ -740,7 +1633,9 @@ var MoltsPayServer = class {
740
1633
  name: this.manifest.provider.name,
741
1634
  description: this.manifest.provider.description,
742
1635
  wallet: this.manifest.provider.wallet,
743
- chain: this.manifest.provider.chain || "base"
1636
+ chain: this.manifest.provider.chain || "base",
1637
+ solana_wallet: this.manifest.provider.solana_wallet,
1638
+ chains: this.manifest.provider.chains
744
1639
  },
745
1640
  services,
746
1641
  endpoints: {
@@ -853,6 +1748,21 @@ var MoltsPayServer = class {
853
1748
  });
854
1749
  }
855
1750
  console.log(`[MoltsPay] Verified by ${verifyResult.facilitator}`);
1751
+ const isSolana = isSolanaNetwork(paymentNetwork);
1752
+ let settlement = null;
1753
+ if (isSolana) {
1754
+ console.log(`[MoltsPay] Solana detected - settling payment FIRST (blockhash expiry protection)`);
1755
+ try {
1756
+ settlement = await this.registry.settle(payment, requirements);
1757
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1758
+ } catch (err) {
1759
+ console.error("[MoltsPay] Solana settlement failed:", err.message);
1760
+ return this.sendJson(res, 402, {
1761
+ error: "Payment settlement failed",
1762
+ message: err.message
1763
+ });
1764
+ }
1765
+ }
856
1766
  const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
857
1767
  console.log(`[MoltsPay] Executing skill: ${service} (timeout: ${timeoutSeconds}s)`);
858
1768
  let result;
@@ -867,16 +1777,19 @@ var MoltsPayServer = class {
867
1777
  console.error("[MoltsPay] Skill execution failed:", err.message);
868
1778
  return this.sendJson(res, 500, {
869
1779
  error: "Service execution failed",
870
- message: err.message
1780
+ message: err.message,
1781
+ paymentSettled: isSolana ? true : false,
1782
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
871
1783
  });
872
1784
  }
873
- console.log(`[MoltsPay] Skill succeeded, settling payment...`);
874
- let settlement = null;
875
- try {
876
- settlement = await this.registry.settle(payment, requirements);
877
- console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
878
- } catch (err) {
879
- console.error("[MoltsPay] Settlement failed:", err.message);
1785
+ if (!isSolana) {
1786
+ console.log(`[MoltsPay] Skill succeeded, settling payment...`);
1787
+ try {
1788
+ settlement = await this.registry.settle(payment, requirements);
1789
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1790
+ } catch (err) {
1791
+ console.error("[MoltsPay] Settlement failed:", err.message);
1792
+ }
880
1793
  }
881
1794
  const responseHeaders = {};
882
1795
  if (settlement?.success) {
@@ -896,6 +1809,187 @@ var MoltsPayServer = class {
896
1809
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
897
1810
  }, responseHeaders);
898
1811
  }
1812
+ /**
1813
+ * Handle MPP (Machine Payments Protocol) request
1814
+ * Supports both x402 and MPP protocols on service endpoints
1815
+ */
1816
+ async handleMPPRequest(skill, body, authHeader, x402Header, res) {
1817
+ const config = skill.config;
1818
+ const params = body || {};
1819
+ if (x402Header) {
1820
+ return await this.handleExecute({ service: config.id, params }, x402Header, res);
1821
+ }
1822
+ if (authHeader && authHeader.toLowerCase().startsWith("payment ")) {
1823
+ return await this.handleMPPPayment(skill, params, authHeader, res);
1824
+ }
1825
+ return this.sendMPPPaymentRequired(config, res);
1826
+ }
1827
+ /**
1828
+ * Handle MPP payment verification and service execution
1829
+ */
1830
+ async handleMPPPayment(skill, params, authHeader, res) {
1831
+ const config = skill.config;
1832
+ const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
1833
+ if (!credentialMatch) {
1834
+ return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
1835
+ }
1836
+ let mppCredential;
1837
+ try {
1838
+ const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
1839
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
1840
+ mppCredential = JSON.parse(decoded);
1841
+ } catch (err) {
1842
+ console.error("[MoltsPay] Failed to parse MPP credential:", err);
1843
+ return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
1844
+ }
1845
+ let txHash;
1846
+ if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
1847
+ txHash = mppCredential.payload.hash;
1848
+ } else if (mppCredential.payload?.type === "transaction") {
1849
+ return this.sendJson(res, 400, {
1850
+ error: "Transaction type not supported. Please use push mode (hash type)."
1851
+ });
1852
+ }
1853
+ if (!txHash) {
1854
+ return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
1855
+ }
1856
+ let chainId = mppCredential.challenge?.request?.methodDetails?.chainId;
1857
+ if (!chainId && mppCredential.source) {
1858
+ const chainMatch = mppCredential.source.match(/eip155:(\d+)/);
1859
+ if (chainMatch) chainId = parseInt(chainMatch[1], 10);
1860
+ }
1861
+ chainId = chainId || 42431;
1862
+ const network = `eip155:${chainId}`;
1863
+ if (!this.isNetworkAccepted(network)) {
1864
+ return this.sendJson(res, 402, {
1865
+ error: `Network not accepted: ${network}`
1866
+ });
1867
+ }
1868
+ const requirements = this.buildPaymentRequirements(
1869
+ config,
1870
+ network,
1871
+ this.getWalletForNetwork(network),
1872
+ "USDC"
1873
+ );
1874
+ const paymentPayload = {
1875
+ x402Version: X402_VERSION2,
1876
+ scheme: "exact",
1877
+ network,
1878
+ payload: {
1879
+ txHash,
1880
+ chainId
1881
+ }
1882
+ };
1883
+ console.log(`[MoltsPay] Verifying MPP payment: txHash=${txHash}, chainId=${chainId}`);
1884
+ const verification = await this.registry.verify(paymentPayload, requirements);
1885
+ if (!verification.valid) {
1886
+ return this.sendJson(res, 402, {
1887
+ error: `Payment verification failed: ${verification.error}`
1888
+ });
1889
+ }
1890
+ console.log(`[MoltsPay] Payment verified! Executing service: ${config.id}`);
1891
+ let result;
1892
+ try {
1893
+ result = await skill.handler(params);
1894
+ } catch (err) {
1895
+ console.error(`[MoltsPay] Skill execution error:`, err);
1896
+ return this.sendJson(res, 500, {
1897
+ error: `Service execution failed: ${err.message}`
1898
+ });
1899
+ }
1900
+ const receipt = {
1901
+ success: true,
1902
+ txHash,
1903
+ network,
1904
+ facilitator: verification.facilitator
1905
+ };
1906
+ const receiptEncoded = Buffer.from(JSON.stringify(receipt)).toString("base64");
1907
+ res.writeHead(200, {
1908
+ "Content-Type": "application/json",
1909
+ [MPP_RECEIPT_HEADER]: receiptEncoded
1910
+ });
1911
+ res.end(JSON.stringify({
1912
+ success: true,
1913
+ result,
1914
+ payment: {
1915
+ txHash,
1916
+ status: "verified",
1917
+ facilitator: verification.facilitator
1918
+ }
1919
+ }, null, 2));
1920
+ }
1921
+ /**
1922
+ * Return 402 with both x402 and MPP payment requirements
1923
+ */
1924
+ sendMPPPaymentRequired(config, res) {
1925
+ const acceptedTokens = getAcceptedCurrencies(config);
1926
+ const providerChains = this.getProviderChains();
1927
+ const accepts = [];
1928
+ for (const chainConfig of providerChains) {
1929
+ for (const token of acceptedTokens) {
1930
+ if (chainConfig.tokens.includes(token)) {
1931
+ accepts.push(this.buildPaymentRequirements(config, chainConfig.network, chainConfig.wallet, token));
1932
+ }
1933
+ }
1934
+ }
1935
+ const x402PaymentRequired = {
1936
+ x402Version: X402_VERSION2,
1937
+ accepts,
1938
+ acceptedCurrencies: acceptedTokens,
1939
+ resource: {
1940
+ url: `/${config.id}`,
1941
+ description: `${config.name} - $${config.price} ${config.currency}`
1942
+ }
1943
+ };
1944
+ const x402Encoded = Buffer.from(JSON.stringify(x402PaymentRequired)).toString("base64");
1945
+ const tempoChain = providerChains.find((c) => c.network === "eip155:42431");
1946
+ let mppWwwAuth = "";
1947
+ if (tempoChain) {
1948
+ const challengeId = this.generateChallengeId();
1949
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
1950
+ const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
1951
+ const mppRequest = {
1952
+ amount: amountInUnits,
1953
+ currency: tokenAddress,
1954
+ methodDetails: {
1955
+ chainId: 42431,
1956
+ feePayer: true
1957
+ },
1958
+ recipient: tempoChain.wallet
1959
+ };
1960
+ const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
1961
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
1962
+ mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
1963
+ }
1964
+ const headers = {
1965
+ "Content-Type": "application/problem+json",
1966
+ [PAYMENT_REQUIRED_HEADER]: x402Encoded
1967
+ };
1968
+ if (mppWwwAuth) {
1969
+ headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
1970
+ }
1971
+ res.writeHead(402, headers);
1972
+ res.end(JSON.stringify({
1973
+ type: "https://paymentauth.org/problems/payment-required",
1974
+ title: "Payment Required",
1975
+ status: 402,
1976
+ detail: `Payment is required (${config.name}).`,
1977
+ service: config.id,
1978
+ price: config.price,
1979
+ currency: config.currency,
1980
+ acceptedCurrencies: acceptedTokens
1981
+ }, null, 2));
1982
+ }
1983
+ /**
1984
+ * Generate a unique challenge ID for MPP
1985
+ */
1986
+ generateChallengeId() {
1987
+ const bytes = new Uint8Array(24);
1988
+ for (let i = 0; i < bytes.length; i++) {
1989
+ bytes[i] = Math.floor(Math.random() * 256);
1990
+ }
1991
+ return Buffer.from(bytes).toString("base64url");
1992
+ }
899
1993
  /**
900
1994
  * Return 402 with x402 payment requirements (v2 format)
901
1995
  * Includes requirements for all chains and all accepted currencies
@@ -971,7 +2065,7 @@ var MoltsPayServer = class {
971
2065
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
972
2066
  const tokenAddress = tokenAddresses[selectedToken];
973
2067
  const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
974
- return {
2068
+ const requirements = {
975
2069
  scheme: "exact",
976
2070
  network: selectedNetwork,
977
2071
  asset: tokenAddress,
@@ -980,6 +2074,27 @@ var MoltsPayServer = class {
980
2074
  maxTimeoutSeconds: 300,
981
2075
  extra: tokenDomain
982
2076
  };
2077
+ if (selectedNetwork === "solana:mainnet" || selectedNetwork === "solana:devnet") {
2078
+ const solanaFacilitator = this.registry.get("solana");
2079
+ const feePayerPubkey = solanaFacilitator?.getFeePayerPubkey?.();
2080
+ if (feePayerPubkey) {
2081
+ requirements.extra = {
2082
+ ...requirements.extra || {},
2083
+ solanaFeePayer: feePayerPubkey
2084
+ };
2085
+ }
2086
+ }
2087
+ if (selectedNetwork === "eip155:56" || selectedNetwork === "eip155:97") {
2088
+ const bnbFacilitator = this.registry.get("bnb");
2089
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
2090
+ if (spenderAddress) {
2091
+ requirements.extra = {
2092
+ ...requirements.extra || {},
2093
+ bnbSpender: spenderAddress
2094
+ };
2095
+ }
2096
+ }
2097
+ return requirements;
983
2098
  }
984
2099
  /**
985
2100
  * Detect which token is being used in the payment
@@ -1045,31 +2160,42 @@ var MoltsPayServer = class {
1045
2160
  /**
1046
2161
  * POST /proxy - Handle payment for external services (moltspay-creators)
1047
2162
  *
1048
- * This endpoint allows other services to delegate x402 payment handling.
2163
+ * This endpoint allows other services to delegate x402/MPP payment handling.
1049
2164
  * It does NOT execute any skill - just handles payment verification/settlement.
1050
2165
  *
1051
2166
  * Request body:
1052
2167
  * { wallet, amount, currency, chain, memo, serviceId, description }
1053
2168
  *
1054
- * Without X-Payment header: returns 402 with payment requirements
1055
- * With X-Payment header: verifies payment and returns result
2169
+ * For x402 (base, polygon, base_sepolia):
2170
+ * Without X-Payment header: returns 402 with X-Payment-Required
2171
+ * With X-Payment header: verifies payment via CDP
2172
+ *
2173
+ * For MPP (tempo_moderato):
2174
+ * Without Authorization header: returns 402 with WWW-Authenticate
2175
+ * With Authorization: Payment header: verifies tx on Tempo chain
1056
2176
  */
1057
- async handleProxy(body, paymentHeader, res) {
2177
+ async handleProxy(body, paymentHeader, authHeader, res) {
1058
2178
  const { wallet, amount, currency, chain, memo, serviceId, description } = body;
1059
2179
  if (!wallet || !amount) {
1060
2180
  return this.sendJson(res, 400, { error: "Missing required fields: wallet, amount" });
1061
2181
  }
1062
- if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
1063
- return this.sendJson(res, 400, { error: "Invalid wallet address format" });
2182
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet", "solana", "solana_devnet"];
2183
+ if (chain && !supportedChains.includes(chain)) {
2184
+ return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
2185
+ }
2186
+ const isSolanaChain = chain === "solana" || chain === "solana_devnet";
2187
+ const isValidEvmAddress = /^0x[a-fA-F0-9]{40}$/.test(wallet);
2188
+ const isValidSolanaAddress = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(wallet);
2189
+ if (isSolanaChain && !isValidSolanaAddress) {
2190
+ return this.sendJson(res, 400, { error: "Invalid Solana wallet address format" });
2191
+ }
2192
+ if (!isSolanaChain && !isValidEvmAddress) {
2193
+ return this.sendJson(res, 400, { error: "Invalid EVM wallet address format" });
1064
2194
  }
1065
2195
  const amountNum = parseFloat(amount);
1066
2196
  if (isNaN(amountNum) || amountNum <= 0) {
1067
2197
  return this.sendJson(res, 400, { error: "Invalid amount" });
1068
2198
  }
1069
- const supportedChains = ["base", "polygon", "base_sepolia"];
1070
- if (chain && !supportedChains.includes(chain)) {
1071
- return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
1072
- }
1073
2199
  const proxyConfig = {
1074
2200
  id: serviceId || "proxy",
1075
2201
  name: description || "Proxy Payment",
@@ -1081,6 +2207,9 @@ var MoltsPayServer = class {
1081
2207
  input: {},
1082
2208
  output: {}
1083
2209
  };
2210
+ if (chain === "tempo_moderato") {
2211
+ return await this.handleProxyMPP(body, proxyConfig, authHeader, res);
2212
+ }
1084
2213
  const requirements = this.buildProxyPaymentRequirements(proxyConfig, wallet, currency, chain);
1085
2214
  if (!paymentHeader) {
1086
2215
  return this.sendProxyPaymentRequired(proxyConfig, wallet, memo, chain, res);
@@ -1116,7 +2245,6 @@ var MoltsPayServer = class {
1116
2245
  console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
1117
2246
  const { execute, service, params } = body;
1118
2247
  if (execute && service) {
1119
- console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
1120
2248
  const skill = this.skills.get(service);
1121
2249
  if (!skill) {
1122
2250
  console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
@@ -1126,6 +2254,32 @@ var MoltsPayServer = class {
1126
2254
  error: `Service not found: ${service}`
1127
2255
  });
1128
2256
  }
2257
+ const isSolana = isSolanaNetwork(network);
2258
+ let settlement2 = null;
2259
+ if (isSolana) {
2260
+ console.log(`[MoltsPay] /proxy: Solana detected - settling payment FIRST`);
2261
+ try {
2262
+ settlement2 = await this.registry.settle(payment, requirements);
2263
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
2264
+ if (!settlement2.success) {
2265
+ console.error(`[MoltsPay] /proxy: Solana settlement failed: ${settlement2.error}`);
2266
+ return this.sendJson(res, 402, {
2267
+ success: false,
2268
+ paymentSettled: false,
2269
+ error: `Payment settlement failed: ${settlement2.error || "Unknown error"}`
2270
+ });
2271
+ }
2272
+ } catch (err) {
2273
+ console.error("[MoltsPay] /proxy: Solana settlement failed:", err.message);
2274
+ return this.sendJson(res, 402, {
2275
+ success: false,
2276
+ paymentSettled: false,
2277
+ error: `Payment settlement failed: ${err.message}`
2278
+ });
2279
+ }
2280
+ } else {
2281
+ console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
2282
+ }
1129
2283
  const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
1130
2284
  let result;
1131
2285
  try {
@@ -1135,34 +2289,36 @@ var MoltsPayServer = class {
1135
2289
  (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
1136
2290
  )
1137
2291
  ]);
1138
- console.log(`[MoltsPay] /proxy: Skill succeeded, now settling payment...`);
2292
+ console.log(`[MoltsPay] /proxy: Skill succeeded`);
1139
2293
  } catch (err) {
1140
- console.error(`[MoltsPay] /proxy: Skill failed: ${err.message} - NOT settling`);
2294
+ console.error(`[MoltsPay] /proxy: Skill failed: ${err.message}`);
1141
2295
  return this.sendJson(res, 500, {
1142
2296
  success: false,
1143
- paymentSettled: false,
1144
- error: `Service execution failed: ${err.message}`
2297
+ paymentSettled: isSolana ? true : false,
2298
+ error: `Service execution failed: ${err.message}`,
2299
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
1145
2300
  });
1146
2301
  }
1147
- let settlement2 = null;
1148
- try {
1149
- settlement2 = await this.registry.settle(payment, requirements);
1150
- console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
1151
- } catch (err) {
1152
- console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
1153
- return this.sendJson(res, 200, {
1154
- success: true,
1155
- verified: true,
1156
- settled: false,
1157
- settlementError: err.message,
1158
- from: payment.payload?.authorization?.from,
1159
- // Buyer's wallet address
1160
- paidTo: wallet,
1161
- amount: amountNum,
1162
- currency: currency || "USDC",
1163
- memo,
1164
- result
1165
- });
2302
+ if (!isSolana) {
2303
+ console.log(`[MoltsPay] /proxy: Settling payment...`);
2304
+ try {
2305
+ settlement2 = await this.registry.settle(payment, requirements);
2306
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
2307
+ } catch (err) {
2308
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
2309
+ return this.sendJson(res, 200, {
2310
+ success: true,
2311
+ verified: true,
2312
+ settled: false,
2313
+ settlementError: err.message,
2314
+ from: payment.payload?.authorization?.from,
2315
+ paidTo: wallet,
2316
+ amount: amountNum,
2317
+ currency: currency || "USDC",
2318
+ memo,
2319
+ result
2320
+ });
2321
+ }
1166
2322
  }
1167
2323
  return this.sendJson(res, 200, {
1168
2324
  success: true,
@@ -1170,7 +2326,6 @@ var MoltsPayServer = class {
1170
2326
  settled: settlement2?.success || false,
1171
2327
  txHash: settlement2?.transaction,
1172
2328
  from: payment.payload?.authorization?.from,
1173
- // Buyer's wallet address
1174
2329
  paidTo: wallet,
1175
2330
  amount: amountNum,
1176
2331
  currency: currency || "USDC",
@@ -1205,6 +2360,131 @@ var MoltsPayServer = class {
1205
2360
  memo
1206
2361
  });
1207
2362
  }
2363
+ /**
2364
+ * Handle MPP payment flow for /proxy endpoint (tempo_moderato chain)
2365
+ */
2366
+ async handleProxyMPP(body, config, authHeader, res) {
2367
+ const { wallet, amount, memo, serviceId } = body;
2368
+ const amountNum = parseFloat(amount);
2369
+ const amountInUnits = Math.floor(amountNum * 1e6).toString();
2370
+ if (!authHeader || !authHeader.toLowerCase().startsWith("payment ")) {
2371
+ const challengeId = this.generateChallengeId();
2372
+ const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
2373
+ const mppRequest = {
2374
+ amount: amountInUnits,
2375
+ currency: tokenAddress,
2376
+ methodDetails: {
2377
+ chainId: 42431,
2378
+ feePayer: true
2379
+ },
2380
+ recipient: wallet
2381
+ };
2382
+ const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
2383
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
2384
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
2385
+ res.writeHead(402, {
2386
+ "Content-Type": "application/problem+json",
2387
+ [MPP_WWW_AUTH_HEADER]: wwwAuth
2388
+ });
2389
+ res.end(JSON.stringify({
2390
+ type: "https://paymentauth.org/problems/payment-required",
2391
+ title: "Payment Required",
2392
+ status: 402,
2393
+ detail: `Payment is required (${config.name}).`,
2394
+ service: serviceId || "proxy",
2395
+ price: amountNum,
2396
+ currency: "USDC"
2397
+ }, null, 2));
2398
+ return;
2399
+ }
2400
+ const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
2401
+ if (!credentialMatch) {
2402
+ return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
2403
+ }
2404
+ let mppCredential;
2405
+ try {
2406
+ const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
2407
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
2408
+ mppCredential = JSON.parse(decoded);
2409
+ } catch (err) {
2410
+ console.error("[MoltsPay] /proxy MPP: Failed to parse credential:", err);
2411
+ return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
2412
+ }
2413
+ let txHash;
2414
+ if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
2415
+ txHash = mppCredential.payload.hash;
2416
+ } else {
2417
+ return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
2418
+ }
2419
+ console.log(`[MoltsPay] /proxy MPP: Verifying tx ${txHash} on Tempo...`);
2420
+ const requirements = this.buildPaymentRequirements(config, "eip155:42431", wallet, "USDC");
2421
+ const paymentPayload = {
2422
+ x402Version: X402_VERSION2,
2423
+ scheme: "exact",
2424
+ network: "eip155:42431",
2425
+ payload: { txHash, chainId: 42431 }
2426
+ };
2427
+ const verification = await this.registry.verify(paymentPayload, requirements);
2428
+ if (!verification.valid) {
2429
+ return this.sendJson(res, 402, {
2430
+ error: `Payment verification failed: ${verification.error}`
2431
+ });
2432
+ }
2433
+ console.log(`[MoltsPay] /proxy MPP: Payment verified by ${verification.facilitator}`);
2434
+ const { execute, service, params } = body;
2435
+ if (execute && service) {
2436
+ console.log(`[MoltsPay] /proxy MPP: Executing skill: ${service}`);
2437
+ const skill = this.skills.get(service);
2438
+ if (!skill) {
2439
+ return this.sendJson(res, 404, {
2440
+ success: false,
2441
+ paymentSettled: true,
2442
+ // Payment already happened on Tempo
2443
+ error: `Service not found: ${service}`
2444
+ });
2445
+ }
2446
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
2447
+ let result;
2448
+ try {
2449
+ result = await Promise.race([
2450
+ skill.handler(params || {}),
2451
+ new Promise(
2452
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
2453
+ )
2454
+ ]);
2455
+ } catch (err) {
2456
+ console.error(`[MoltsPay] /proxy MPP: Skill failed: ${err.message}`);
2457
+ return this.sendJson(res, 500, {
2458
+ success: false,
2459
+ paymentSettled: true,
2460
+ error: `Service execution failed: ${err.message}`
2461
+ });
2462
+ }
2463
+ return this.sendJson(res, 200, {
2464
+ success: true,
2465
+ verified: true,
2466
+ txHash,
2467
+ chain: "tempo_moderato",
2468
+ paidTo: wallet,
2469
+ amount: amountNum,
2470
+ currency: "USDC",
2471
+ facilitator: verification.facilitator,
2472
+ memo,
2473
+ result
2474
+ });
2475
+ }
2476
+ this.sendJson(res, 200, {
2477
+ success: true,
2478
+ verified: true,
2479
+ txHash,
2480
+ chain: "tempo_moderato",
2481
+ paidTo: wallet,
2482
+ amount: amountNum,
2483
+ currency: "USDC",
2484
+ facilitator: verification.facilitator,
2485
+ memo
2486
+ });
2487
+ }
1208
2488
  /**
1209
2489
  * Build payment requirements for proxy endpoint (uses provided wallet)
1210
2490
  */
@@ -1216,7 +2496,7 @@ var MoltsPayServer = class {
1216
2496
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
1217
2497
  const tokenAddress = tokenAddresses[selectedToken];
1218
2498
  const tokenDomain = getTokenDomain(networkId, selectedToken);
1219
- return {
2499
+ const requirements = {
1220
2500
  scheme: "exact",
1221
2501
  network: networkId,
1222
2502
  asset: tokenAddress,
@@ -1226,6 +2506,17 @@ var MoltsPayServer = class {
1226
2506
  maxTimeoutSeconds: 300,
1227
2507
  extra: tokenDomain
1228
2508
  };
2509
+ if (networkId === "eip155:56" || networkId === "eip155:97") {
2510
+ const bnbFacilitator = this.registry.get("bnb");
2511
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
2512
+ if (spenderAddress) {
2513
+ requirements.extra = {
2514
+ ...requirements.extra || {},
2515
+ bnbSpender: spenderAddress
2516
+ };
2517
+ }
2518
+ }
2519
+ return requirements;
1229
2520
  }
1230
2521
  /**
1231
2522
  * Return 402 with x402 payment requirements for proxy endpoint