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
@@ -229,7 +229,825 @@ var CDPFacilitator = class extends BaseFacilitator {
229
229
  }
230
230
  };
231
231
 
232
+ // src/chains/index.ts
233
+ var CHAINS = {
234
+ // ============ Mainnet ============
235
+ base: {
236
+ name: "Base",
237
+ chainId: 8453,
238
+ rpc: "https://mainnet.base.org",
239
+ tokens: {
240
+ USDC: {
241
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
242
+ decimals: 6,
243
+ symbol: "USDC",
244
+ eip712Name: "USD Coin"
245
+ // EIP-712 domain name
246
+ },
247
+ USDT: {
248
+ address: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
249
+ decimals: 6,
250
+ symbol: "USDT",
251
+ eip712Name: "Tether USD"
252
+ }
253
+ },
254
+ usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
255
+ // deprecated, for backward compat
256
+ explorer: "https://basescan.org/address/",
257
+ explorerTx: "https://basescan.org/tx/",
258
+ avgBlockTime: 2
259
+ },
260
+ polygon: {
261
+ name: "Polygon",
262
+ chainId: 137,
263
+ rpc: "https://polygon-bor-rpc.publicnode.com",
264
+ tokens: {
265
+ USDC: {
266
+ address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
267
+ decimals: 6,
268
+ symbol: "USDC",
269
+ eip712Name: "USD Coin"
270
+ },
271
+ USDT: {
272
+ address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
273
+ decimals: 6,
274
+ symbol: "USDT",
275
+ eip712Name: "(PoS) Tether USD"
276
+ // Polygon uses this name
277
+ }
278
+ },
279
+ usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
280
+ explorer: "https://polygonscan.com/address/",
281
+ explorerTx: "https://polygonscan.com/tx/",
282
+ avgBlockTime: 2
283
+ },
284
+ // ============ Testnet ============
285
+ base_sepolia: {
286
+ name: "Base Sepolia",
287
+ chainId: 84532,
288
+ rpc: "https://sepolia.base.org",
289
+ tokens: {
290
+ USDC: {
291
+ address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
292
+ decimals: 6,
293
+ symbol: "USDC",
294
+ eip712Name: "USDC"
295
+ // Testnet USDC uses 'USDC' not 'USD Coin'
296
+ },
297
+ USDT: {
298
+ address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
299
+ // Same as USDC on testnet (no official USDT)
300
+ decimals: 6,
301
+ symbol: "USDT",
302
+ eip712Name: "USDC"
303
+ // Uses same contract as USDC
304
+ }
305
+ },
306
+ usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
307
+ explorer: "https://sepolia.basescan.org/address/",
308
+ explorerTx: "https://sepolia.basescan.org/tx/",
309
+ avgBlockTime: 2
310
+ },
311
+ // ============ Tempo Testnet (Moderato) ============
312
+ tempo_moderato: {
313
+ name: "Tempo Moderato",
314
+ chainId: 42431,
315
+ rpc: "https://rpc.moderato.tempo.xyz",
316
+ tokens: {
317
+ // TIP-20 stablecoins on Tempo testnet (from mppx SDK)
318
+ // Note: Tempo uses USD as native gas token, not ETH
319
+ USDC: {
320
+ address: "0x20c0000000000000000000000000000000000000",
321
+ // pathUSD - primary testnet stablecoin
322
+ decimals: 6,
323
+ symbol: "USDC",
324
+ eip712Name: "pathUSD"
325
+ },
326
+ USDT: {
327
+ address: "0x20c0000000000000000000000000000000000001",
328
+ // alphaUSD
329
+ decimals: 6,
330
+ symbol: "USDT",
331
+ eip712Name: "alphaUSD"
332
+ }
333
+ },
334
+ usdc: "0x20c0000000000000000000000000000000000000",
335
+ explorer: "https://explore.testnet.tempo.xyz/address/",
336
+ explorerTx: "https://explore.testnet.tempo.xyz/tx/",
337
+ avgBlockTime: 0.5
338
+ // ~500ms finality
339
+ },
340
+ // ============ BNB Chain Testnet ============
341
+ bnb_testnet: {
342
+ name: "BNB Testnet",
343
+ chainId: 97,
344
+ rpc: "https://data-seed-prebsc-1-s1.binance.org:8545",
345
+ tokens: {
346
+ // Note: BNB uses 18 decimals for stablecoins (unlike Base/Polygon which use 6)
347
+ // Using official Binance-Peg testnet tokens
348
+ USDC: {
349
+ address: "0x64544969ed7EBf5f083679233325356EbE738930",
350
+ // Testnet USDC
351
+ decimals: 18,
352
+ symbol: "USDC",
353
+ eip712Name: "USD Coin"
354
+ },
355
+ USDT: {
356
+ address: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd",
357
+ // Testnet USDT
358
+ decimals: 18,
359
+ symbol: "USDT",
360
+ eip712Name: "Tether USD"
361
+ }
362
+ },
363
+ usdc: "0x64544969ed7EBf5f083679233325356EbE738930",
364
+ explorer: "https://testnet.bscscan.com/address/",
365
+ explorerTx: "https://testnet.bscscan.com/tx/",
366
+ avgBlockTime: 3,
367
+ // BNB-specific: requires approval for pay-for-success flow
368
+ requiresApproval: true
369
+ },
370
+ // ============ BNB Chain Mainnet ============
371
+ bnb: {
372
+ name: "BNB Smart Chain",
373
+ chainId: 56,
374
+ rpc: "https://bsc-dataseed.binance.org",
375
+ tokens: {
376
+ // Note: BNB uses 18 decimals for stablecoins
377
+ USDC: {
378
+ address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
379
+ decimals: 18,
380
+ symbol: "USDC",
381
+ eip712Name: "USD Coin"
382
+ },
383
+ USDT: {
384
+ address: "0x55d398326f99059fF775485246999027B3197955",
385
+ decimals: 18,
386
+ symbol: "USDT",
387
+ eip712Name: "Tether USD"
388
+ }
389
+ },
390
+ usdc: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
391
+ explorer: "https://bscscan.com/address/",
392
+ explorerTx: "https://bscscan.com/tx/",
393
+ avgBlockTime: 3,
394
+ // BNB-specific: requires approval for pay-for-success flow
395
+ requiresApproval: true
396
+ }
397
+ };
398
+
399
+ // src/facilitators/tempo.ts
400
+ var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
401
+ var TempoFacilitator = class extends BaseFacilitator {
402
+ name = "tempo";
403
+ displayName = "Tempo Testnet";
404
+ supportedNetworks = ["eip155:42431"];
405
+ // Tempo Moderato
406
+ rpcUrl;
407
+ constructor() {
408
+ super();
409
+ this.rpcUrl = CHAINS.tempo_moderato.rpc;
410
+ }
411
+ async healthCheck() {
412
+ const start = Date.now();
413
+ try {
414
+ const response = await fetch(this.rpcUrl, {
415
+ method: "POST",
416
+ headers: { "Content-Type": "application/json" },
417
+ body: JSON.stringify({
418
+ jsonrpc: "2.0",
419
+ method: "eth_chainId",
420
+ params: [],
421
+ id: 1
422
+ })
423
+ });
424
+ const data = await response.json();
425
+ const chainId = parseInt(data.result, 16);
426
+ if (chainId !== 42431) {
427
+ return { healthy: false, error: `Wrong chainId: ${chainId}` };
428
+ }
429
+ return { healthy: true, latencyMs: Date.now() - start };
430
+ } catch (error) {
431
+ return { healthy: false, error: String(error) };
432
+ }
433
+ }
434
+ async verify(paymentPayload, requirements) {
435
+ try {
436
+ const tempoPayload = paymentPayload.payload;
437
+ if (!tempoPayload?.txHash) {
438
+ return { valid: false, error: "Missing txHash in payment payload" };
439
+ }
440
+ const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
441
+ if (!receipt) {
442
+ return { valid: false, error: "Transaction not found" };
443
+ }
444
+ if (receipt.status !== "0x1") {
445
+ return { valid: false, error: "Transaction failed" };
446
+ }
447
+ const transferLog = receipt.logs.find(
448
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC
449
+ );
450
+ if (!transferLog) {
451
+ return { valid: false, error: "No Transfer event found" };
452
+ }
453
+ const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
454
+ const expectedTo = requirements.payTo.toLowerCase();
455
+ if (toAddress !== expectedTo) {
456
+ return {
457
+ valid: false,
458
+ error: `Wrong recipient: ${toAddress}, expected ${expectedTo}`
459
+ };
460
+ }
461
+ const amount = BigInt(transferLog.data);
462
+ const expectedAmount = BigInt(requirements.amount);
463
+ if (amount < expectedAmount) {
464
+ return {
465
+ valid: false,
466
+ error: `Insufficient amount: ${amount}, expected ${expectedAmount}`
467
+ };
468
+ }
469
+ const tokenAddress = transferLog.address.toLowerCase();
470
+ const expectedToken = requirements.asset.toLowerCase();
471
+ if (tokenAddress !== expectedToken) {
472
+ return {
473
+ valid: false,
474
+ error: `Wrong token: ${tokenAddress}, expected ${expectedToken}`
475
+ };
476
+ }
477
+ return {
478
+ valid: true,
479
+ details: {
480
+ txHash: tempoPayload.txHash,
481
+ from: "0x" + transferLog.topics[1].slice(26),
482
+ to: toAddress,
483
+ amount: amount.toString(),
484
+ token: tokenAddress
485
+ }
486
+ };
487
+ } catch (error) {
488
+ return { valid: false, error: `Verification failed: ${error}` };
489
+ }
490
+ }
491
+ async settle(paymentPayload, requirements) {
492
+ const verifyResult = await this.verify(paymentPayload, requirements);
493
+ if (!verifyResult.valid) {
494
+ return { success: false, error: verifyResult.error };
495
+ }
496
+ const tempoPayload = paymentPayload.payload;
497
+ return {
498
+ success: true,
499
+ transaction: tempoPayload.txHash,
500
+ status: "settled"
501
+ };
502
+ }
503
+ async getTransactionReceipt(txHash) {
504
+ const response = await fetch(this.rpcUrl, {
505
+ method: "POST",
506
+ headers: { "Content-Type": "application/json" },
507
+ body: JSON.stringify({
508
+ jsonrpc: "2.0",
509
+ method: "eth_getTransactionReceipt",
510
+ params: [txHash],
511
+ id: 1
512
+ })
513
+ });
514
+ const data = await response.json();
515
+ return data.result;
516
+ }
517
+ };
518
+
519
+ // src/facilitators/bnb.ts
520
+ import { privateKeyToAccount } from "viem/accounts";
521
+ var TRANSFER_EVENT_TOPIC2 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
522
+ var EIP712_DOMAIN = {
523
+ name: "MoltsPay",
524
+ version: "1"
525
+ };
526
+ var INTENT_TYPES = {
527
+ PaymentIntent: [
528
+ { name: "from", type: "address" },
529
+ { name: "to", type: "address" },
530
+ { name: "amount", type: "uint256" },
531
+ { name: "token", type: "address" },
532
+ { name: "service", type: "string" },
533
+ { name: "nonce", type: "uint256" },
534
+ { name: "deadline", type: "uint256" }
535
+ ]
536
+ };
537
+ var BNBFacilitator = class extends BaseFacilitator {
538
+ name = "bnb";
539
+ displayName = "BNB Smart Chain";
540
+ supportedNetworks = ["eip155:56", "eip155:97"];
541
+ // Mainnet + Testnet
542
+ serverPrivateKey;
543
+ spenderAddress = null;
544
+ chainConfigs;
545
+ constructor(serverPrivateKey) {
546
+ super();
547
+ this.serverPrivateKey = serverPrivateKey || process.env.BNB_SERVER_PRIVATE_KEY || "";
548
+ if (this.serverPrivateKey) {
549
+ const key = this.serverPrivateKey.startsWith("0x") ? this.serverPrivateKey : `0x${this.serverPrivateKey}`;
550
+ const account = privateKeyToAccount(key);
551
+ this.spenderAddress = account.address;
552
+ }
553
+ this.chainConfigs = {
554
+ 56: { rpc: CHAINS.bnb.rpc, chain: CHAINS.bnb },
555
+ 97: { rpc: CHAINS.bnb_testnet.rpc, chain: CHAINS.bnb_testnet }
556
+ };
557
+ }
558
+ async healthCheck() {
559
+ const start = Date.now();
560
+ try {
561
+ const response = await fetch(this.chainConfigs[56].rpc, {
562
+ method: "POST",
563
+ headers: { "Content-Type": "application/json" },
564
+ body: JSON.stringify({
565
+ jsonrpc: "2.0",
566
+ method: "eth_chainId",
567
+ params: [],
568
+ id: 1
569
+ })
570
+ });
571
+ const data = await response.json();
572
+ const chainId = parseInt(data.result, 16);
573
+ if (chainId !== 56) {
574
+ return { healthy: false, error: `Wrong chainId: ${chainId}` };
575
+ }
576
+ return { healthy: true, latencyMs: Date.now() - start };
577
+ } catch (error) {
578
+ return { healthy: false, error: String(error) };
579
+ }
580
+ }
581
+ /**
582
+ * Verify a payment intent signature (before service execution)
583
+ *
584
+ * This verifies:
585
+ * 1. Signature is valid for the intent
586
+ * 2. Client has approved server wallet
587
+ * 3. Client has sufficient balance
588
+ * 4. Intent hasn't expired
589
+ */
590
+ async verify(paymentPayload, requirements) {
591
+ try {
592
+ const bnbPayload = paymentPayload.payload;
593
+ if (!bnbPayload?.intent) {
594
+ return { valid: false, error: "Missing intent in payment payload" };
595
+ }
596
+ const { intent, chainId } = bnbPayload;
597
+ const config = this.chainConfigs[chainId];
598
+ if (!config) {
599
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
600
+ }
601
+ if (intent.deadline < Date.now()) {
602
+ return { valid: false, error: "Intent expired" };
603
+ }
604
+ const recoveredAddress = await this.recoverIntentSigner(intent, chainId);
605
+ if (recoveredAddress.toLowerCase() !== intent.from.toLowerCase()) {
606
+ return { valid: false, error: "Invalid signature" };
607
+ }
608
+ if (intent.to.toLowerCase() !== requirements.payTo.toLowerCase()) {
609
+ return { valid: false, error: `Wrong recipient: ${intent.to}` };
610
+ }
611
+ if (BigInt(intent.amount) < BigInt(requirements.amount)) {
612
+ return { valid: false, error: `Insufficient amount: ${intent.amount}` };
613
+ }
614
+ if (intent.token.toLowerCase() !== requirements.asset.toLowerCase()) {
615
+ return { valid: false, error: `Wrong token: ${intent.token}` };
616
+ }
617
+ const serverAddress = await this.getServerAddress();
618
+ const allowance = await this.getAllowance(intent.from, serverAddress, intent.token, config.rpc);
619
+ if (BigInt(allowance) < BigInt(intent.amount)) {
620
+ return { valid: false, error: "Insufficient allowance. Run: npx moltspay init --chain bnb" };
621
+ }
622
+ const balance = await this.getBalance(intent.from, intent.token, config.rpc);
623
+ if (BigInt(balance) < BigInt(intent.amount)) {
624
+ return { valid: false, error: "Insufficient balance" };
625
+ }
626
+ return {
627
+ valid: true,
628
+ details: {
629
+ from: intent.from,
630
+ to: intent.to,
631
+ amount: intent.amount,
632
+ token: intent.token,
633
+ service: intent.service,
634
+ nonce: intent.nonce,
635
+ deadline: intent.deadline
636
+ }
637
+ };
638
+ } catch (error) {
639
+ return { valid: false, error: `Verification failed: ${error}` };
640
+ }
641
+ }
642
+ /**
643
+ * Settle a payment by executing transferFrom
644
+ *
645
+ * This is called AFTER the service has been successfully delivered.
646
+ * Server pays gas, transfers tokens from client to provider.
647
+ */
648
+ async settle(paymentPayload, requirements) {
649
+ if (!this.serverPrivateKey) {
650
+ return { success: false, error: "Server wallet not configured (BNB_SERVER_PRIVATE_KEY)" };
651
+ }
652
+ try {
653
+ const verifyResult = await this.verify(paymentPayload, requirements);
654
+ if (!verifyResult.valid) {
655
+ return { success: false, error: verifyResult.error };
656
+ }
657
+ const bnbPayload = paymentPayload.payload;
658
+ const { intent, chainId } = bnbPayload;
659
+ const config = this.chainConfigs[chainId];
660
+ const txHash = await this.executeTransferFrom(
661
+ intent.from,
662
+ intent.to,
663
+ intent.amount,
664
+ intent.token,
665
+ config.rpc
666
+ );
667
+ return {
668
+ success: true,
669
+ transaction: txHash,
670
+ status: "settled"
671
+ };
672
+ } catch (error) {
673
+ return { success: false, error: `Settlement failed: ${error}` };
674
+ }
675
+ }
676
+ /**
677
+ * Check if client has approved the server wallet
678
+ */
679
+ async checkApproval(clientAddress, token, chainId) {
680
+ const config = this.chainConfigs[chainId];
681
+ if (!config) {
682
+ throw new Error(`Unsupported chainId: ${chainId}`);
683
+ }
684
+ const serverAddress = await this.getServerAddress();
685
+ const allowance = await this.getAllowance(clientAddress, serverAddress, token, config.rpc);
686
+ const minAllowance = BigInt("1000000000000000000000");
687
+ return {
688
+ approved: BigInt(allowance) >= minAllowance,
689
+ allowance
690
+ };
691
+ }
692
+ /**
693
+ * Verify a completed transaction (for checking past payments)
694
+ */
695
+ async verifyTransaction(txHash, expected, chainId) {
696
+ const config = this.chainConfigs[chainId];
697
+ if (!config) {
698
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
699
+ }
700
+ try {
701
+ const receipt = await this.getTransactionReceipt(txHash, config.rpc);
702
+ if (!receipt) {
703
+ return { valid: false, error: "Transaction not found" };
704
+ }
705
+ if (receipt.status !== "0x1") {
706
+ return { valid: false, error: "Transaction failed" };
707
+ }
708
+ const transferLog = receipt.logs.find(
709
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC2 && log.address.toLowerCase() === expected.token.toLowerCase()
710
+ );
711
+ if (!transferLog) {
712
+ return { valid: false, error: "No Transfer event found" };
713
+ }
714
+ const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
715
+ if (toAddress !== expected.to.toLowerCase()) {
716
+ return { valid: false, error: `Wrong recipient: ${toAddress}` };
717
+ }
718
+ const amount = BigInt(transferLog.data);
719
+ if (amount < BigInt(expected.amount)) {
720
+ return { valid: false, error: `Insufficient amount: ${amount}` };
721
+ }
722
+ return {
723
+ valid: true,
724
+ details: {
725
+ txHash,
726
+ from: "0x" + transferLog.topics[1].slice(26),
727
+ to: toAddress,
728
+ amount: amount.toString(),
729
+ token: transferLog.address
730
+ }
731
+ };
732
+ } catch (error) {
733
+ return { valid: false, error: `Verification failed: ${error}` };
734
+ }
735
+ }
736
+ // ==================== Private Methods ====================
737
+ /**
738
+ * Get the server's spender address (public, for 402 responses)
739
+ * Returns cached value computed at construction time.
740
+ */
741
+ getSpenderAddress() {
742
+ return this.spenderAddress;
743
+ }
744
+ async getServerAddress() {
745
+ const { ethers } = await import("ethers");
746
+ const wallet = new ethers.Wallet(this.serverPrivateKey);
747
+ return wallet.address;
748
+ }
749
+ async recoverIntentSigner(intent, chainId) {
750
+ const { ethers } = await import("ethers");
751
+ const domain = {
752
+ ...EIP712_DOMAIN,
753
+ chainId
754
+ };
755
+ const message = {
756
+ from: intent.from,
757
+ to: intent.to,
758
+ amount: intent.amount,
759
+ token: intent.token,
760
+ service: intent.service,
761
+ nonce: intent.nonce,
762
+ deadline: intent.deadline
763
+ };
764
+ const recoveredAddress = ethers.verifyTypedData(
765
+ domain,
766
+ INTENT_TYPES,
767
+ message,
768
+ intent.signature
769
+ );
770
+ return recoveredAddress;
771
+ }
772
+ async getAllowance(owner, spender, token, rpcUrl) {
773
+ const selector = "0xdd62ed3e";
774
+ const ownerPadded = owner.toLowerCase().replace("0x", "").padStart(64, "0");
775
+ const spenderPadded = spender.toLowerCase().replace("0x", "").padStart(64, "0");
776
+ const data = selector + ownerPadded + spenderPadded;
777
+ const response = await fetch(rpcUrl, {
778
+ method: "POST",
779
+ headers: { "Content-Type": "application/json" },
780
+ body: JSON.stringify({
781
+ jsonrpc: "2.0",
782
+ method: "eth_call",
783
+ params: [{ to: token, data }, "latest"],
784
+ id: 1
785
+ })
786
+ });
787
+ const result = await response.json();
788
+ return result.result || "0x0";
789
+ }
790
+ async getBalance(account, token, rpcUrl) {
791
+ const selector = "0x70a08231";
792
+ const accountPadded = account.toLowerCase().replace("0x", "").padStart(64, "0");
793
+ const data = selector + accountPadded;
794
+ const response = await fetch(rpcUrl, {
795
+ method: "POST",
796
+ headers: { "Content-Type": "application/json" },
797
+ body: JSON.stringify({
798
+ jsonrpc: "2.0",
799
+ method: "eth_call",
800
+ params: [{ to: token, data }, "latest"],
801
+ id: 1
802
+ })
803
+ });
804
+ const result = await response.json();
805
+ return result.result || "0x0";
806
+ }
807
+ async executeTransferFrom(from, to, amount, token, rpcUrl) {
808
+ const { ethers } = await import("ethers");
809
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
810
+ const wallet = new ethers.Wallet(this.serverPrivateKey, provider);
811
+ const tokenContract = new ethers.Contract(token, [
812
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)"
813
+ ], wallet);
814
+ const tx = await tokenContract.transferFrom(from, to, amount);
815
+ const receipt = await tx.wait();
816
+ return receipt.hash;
817
+ }
818
+ async getTransactionReceipt(txHash, rpcUrl) {
819
+ const response = await fetch(rpcUrl, {
820
+ method: "POST",
821
+ headers: { "Content-Type": "application/json" },
822
+ body: JSON.stringify({
823
+ jsonrpc: "2.0",
824
+ method: "eth_getTransactionReceipt",
825
+ params: [txHash],
826
+ id: 1
827
+ })
828
+ });
829
+ const data = await response.json();
830
+ return data.result;
831
+ }
832
+ };
833
+
834
+ // src/facilitators/solana.ts
835
+ import {
836
+ Connection as Connection2,
837
+ PublicKey as PublicKey2,
838
+ Transaction,
839
+ VersionedTransaction
840
+ } from "@solana/web3.js";
841
+ import {
842
+ getAssociatedTokenAddress,
843
+ createTransferCheckedInstruction,
844
+ getAccount,
845
+ createAssociatedTokenAccountInstruction
846
+ } from "@solana/spl-token";
847
+
848
+ // src/chains/solana.ts
849
+ import { Connection, PublicKey } from "@solana/web3.js";
850
+ var SOLANA_CHAINS = {
851
+ solana: {
852
+ name: "Solana Mainnet",
853
+ cluster: "mainnet-beta",
854
+ rpc: "https://api.mainnet-beta.solana.com",
855
+ explorer: "https://solscan.io/account/",
856
+ explorerTx: "https://solscan.io/tx/",
857
+ tokens: {
858
+ USDC: {
859
+ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
860
+ // Circle official USDC
861
+ decimals: 6
862
+ }
863
+ }
864
+ },
865
+ solana_devnet: {
866
+ name: "Solana Devnet",
867
+ cluster: "devnet",
868
+ rpc: "https://api.devnet.solana.com",
869
+ explorer: "https://solscan.io/account/",
870
+ explorerTx: "https://solscan.io/tx/",
871
+ tokens: {
872
+ USDC: {
873
+ // Circle's devnet USDC (if not available, we'll deploy our own test token)
874
+ mint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
875
+ decimals: 6
876
+ }
877
+ }
878
+ }
879
+ };
880
+
881
+ // src/facilitators/solana.ts
882
+ var SolanaFacilitator = class extends BaseFacilitator {
883
+ name = "solana";
884
+ displayName = "Solana Direct";
885
+ supportedNetworks = ["solana:mainnet", "solana:devnet"];
886
+ connections = /* @__PURE__ */ new Map();
887
+ feePayerKeypair;
888
+ constructor(config) {
889
+ super();
890
+ this.feePayerKeypair = config?.feePayerKeypair;
891
+ for (const [chain, config2] of Object.entries(SOLANA_CHAINS)) {
892
+ this.connections.set(
893
+ chain,
894
+ new Connection2(config2.rpc, "confirmed")
895
+ );
896
+ }
897
+ if (this.feePayerKeypair) {
898
+ console.log(`[SolanaFacilitator] Gasless mode enabled. Fee payer: ${this.feePayerKeypair.publicKey.toBase58()}`);
899
+ }
900
+ }
901
+ /**
902
+ * Get fee payer public key (for gasless transactions)
903
+ */
904
+ getFeePayerPubkey() {
905
+ return this.feePayerKeypair?.publicKey.toBase58() || null;
906
+ }
907
+ getConnection(chain) {
908
+ const conn = this.connections.get(chain);
909
+ if (!conn) {
910
+ throw new Error(`No connection for chain: ${chain}`);
911
+ }
912
+ return conn;
913
+ }
914
+ /**
915
+ * Convert our chain name to network identifier
916
+ */
917
+ static chainToNetwork(chain) {
918
+ return chain === "solana" ? "solana:mainnet" : "solana:devnet";
919
+ }
920
+ /**
921
+ * Convert network identifier to chain name
922
+ */
923
+ static networkToChain(network) {
924
+ if (network === "solana:mainnet") return "solana";
925
+ if (network === "solana:devnet") return "solana_devnet";
926
+ return null;
927
+ }
928
+ async healthCheck() {
929
+ const start = Date.now();
930
+ try {
931
+ const conn = this.getConnection("solana_devnet");
932
+ await conn.getSlot();
933
+ return {
934
+ healthy: true,
935
+ latencyMs: Date.now() - start
936
+ };
937
+ } catch (error) {
938
+ return {
939
+ healthy: false,
940
+ error: error.message
941
+ };
942
+ }
943
+ }
944
+ /**
945
+ * Verify a Solana payment
946
+ *
947
+ * Checks:
948
+ * 1. Transaction is valid and properly signed
949
+ * 2. Transfer instruction matches expected amount and recipient
950
+ */
951
+ async verify(paymentPayload, requirements) {
952
+ try {
953
+ const solanaPayload = paymentPayload.payload;
954
+ if (!solanaPayload || !solanaPayload.signedTransaction) {
955
+ return { valid: false, error: "Missing signed transaction" };
956
+ }
957
+ const chain = solanaPayload.chain || "solana_devnet";
958
+ const chainConfig = SOLANA_CHAINS[chain];
959
+ if (!chainConfig) {
960
+ return { valid: false, error: `Invalid chain: ${chain}` };
961
+ }
962
+ const txBuffer = Buffer.from(solanaPayload.signedTransaction, "base64");
963
+ let tx;
964
+ try {
965
+ tx = Transaction.from(txBuffer);
966
+ } catch {
967
+ tx = VersionedTransaction.deserialize(txBuffer);
968
+ }
969
+ if (tx instanceof Transaction) {
970
+ const hasAnySignature = tx.signatures.some(
971
+ (sig) => sig.signature && !sig.signature.every((b) => b === 0)
972
+ );
973
+ if (!hasAnySignature) {
974
+ return { valid: false, error: "Transaction not signed" };
975
+ }
976
+ }
977
+ const expectedAmount = BigInt(requirements.amount);
978
+ const expectedRecipient = new PublicKey2(requirements.payTo);
979
+ return {
980
+ valid: true,
981
+ details: {
982
+ chain,
983
+ sender: solanaPayload.sender,
984
+ recipient: requirements.payTo,
985
+ amount: requirements.amount
986
+ }
987
+ };
988
+ } catch (error) {
989
+ return { valid: false, error: error.message };
990
+ }
991
+ }
992
+ /**
993
+ * Settle a Solana payment
994
+ *
995
+ * Submits the signed transaction to the network.
996
+ * In gasless mode, adds fee payer signature before submitting.
997
+ */
998
+ async settle(paymentPayload, requirements) {
999
+ try {
1000
+ const solanaPayload = paymentPayload.payload;
1001
+ if (!solanaPayload || !solanaPayload.signedTransaction) {
1002
+ return { success: false, error: "Missing signed transaction" };
1003
+ }
1004
+ const chain = solanaPayload.chain || "solana_devnet";
1005
+ const connection = this.getConnection(chain);
1006
+ const txBuffer = Buffer.from(solanaPayload.signedTransaction, "base64");
1007
+ let txToSend;
1008
+ try {
1009
+ const tx = Transaction.from(txBuffer);
1010
+ if (this.feePayerKeypair && tx.feePayer) {
1011
+ const feePayerPubkey = this.feePayerKeypair.publicKey.toBase58();
1012
+ const txFeePayer = tx.feePayer.toBase58();
1013
+ if (txFeePayer === feePayerPubkey) {
1014
+ console.log(`[SolanaFacilitator] Gasless mode: adding fee payer signature`);
1015
+ tx.partialSign(this.feePayerKeypair);
1016
+ }
1017
+ }
1018
+ txToSend = tx.serialize();
1019
+ } catch (e) {
1020
+ txToSend = txBuffer;
1021
+ }
1022
+ const signature = await connection.sendRawTransaction(txToSend, {
1023
+ skipPreflight: false,
1024
+ preflightCommitment: "confirmed"
1025
+ });
1026
+ const confirmation = await connection.confirmTransaction(signature, "confirmed");
1027
+ if (confirmation.value.err) {
1028
+ return {
1029
+ success: false,
1030
+ error: `Transaction failed: ${JSON.stringify(confirmation.value.err)}`,
1031
+ transaction: signature
1032
+ };
1033
+ }
1034
+ return {
1035
+ success: true,
1036
+ transaction: signature,
1037
+ status: "confirmed"
1038
+ };
1039
+ } catch (error) {
1040
+ return { success: false, error: error.message };
1041
+ }
1042
+ }
1043
+ supportsNetwork(network) {
1044
+ return this.supportedNetworks.includes(network);
1045
+ }
1046
+ };
1047
+
232
1048
  // src/facilitators/registry.ts
1049
+ import { Keypair as Keypair2 } from "@solana/web3.js";
1050
+ import bs58 from "bs58";
233
1051
  var FacilitatorRegistry = class {
234
1052
  factories = /* @__PURE__ */ new Map();
235
1053
  instances = /* @__PURE__ */ new Map();
@@ -237,7 +1055,21 @@ var FacilitatorRegistry = class {
237
1055
  roundRobinIndex = 0;
238
1056
  constructor(selection) {
239
1057
  this.registerFactory("cdp", (config) => new CDPFacilitator(config));
240
- this.selection = selection || { primary: "cdp", strategy: "failover" };
1058
+ this.registerFactory("tempo", () => new TempoFacilitator());
1059
+ this.registerFactory("bnb", (config) => new BNBFacilitator(config?.serverPrivateKey));
1060
+ this.registerFactory("solana", (config) => {
1061
+ let feePayerKeypair;
1062
+ const feePayerKey = config?.feePayerPrivateKey || process.env.SOLANA_FEE_PAYER_KEY;
1063
+ if (feePayerKey) {
1064
+ try {
1065
+ feePayerKeypair = Keypair2.fromSecretKey(bs58.decode(feePayerKey));
1066
+ } catch (e) {
1067
+ console.warn(`[SolanaFacilitator] Invalid fee payer key: ${e.message}`);
1068
+ }
1069
+ }
1070
+ return new SolanaFacilitator({ feePayerKeypair });
1071
+ });
1072
+ this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
241
1073
  }
242
1074
  /**
243
1075
  * Register a new facilitator factory
@@ -458,6 +1290,9 @@ var X402_VERSION2 = 2;
458
1290
  var PAYMENT_REQUIRED_HEADER = "x-payment-required";
459
1291
  var PAYMENT_HEADER = "x-payment";
460
1292
  var PAYMENT_RESPONSE_HEADER = "x-payment-response";
1293
+ var MPP_AUTH_HEADER = "authorization";
1294
+ var MPP_WWW_AUTH_HEADER = "www-authenticate";
1295
+ var MPP_RECEIPT_HEADER = "payment-receipt";
461
1296
  var TOKEN_ADDRESSES = {
462
1297
  "eip155:8453": {
463
1298
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -471,13 +1306,47 @@ var TOKEN_ADDRESSES = {
471
1306
  "eip155:137": {
472
1307
  USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
473
1308
  USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
1309
+ },
1310
+ "eip155:42431": {
1311
+ // Tempo Moderato testnet - TIP-20 stablecoins
1312
+ USDC: "0x20c0000000000000000000000000000000000000",
1313
+ // pathUSD
1314
+ USDT: "0x20c0000000000000000000000000000000000001"
1315
+ // alphaUSD
1316
+ },
1317
+ // BNB Smart Chain mainnet
1318
+ "eip155:56": {
1319
+ USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
1320
+ USDT: "0x55d398326f99059fF775485246999027B3197955"
1321
+ },
1322
+ // BNB Smart Chain testnet
1323
+ "eip155:97": {
1324
+ USDC: "0x64544969ed7EBf5f083679233325356EbE738930",
1325
+ USDT: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd"
1326
+ },
1327
+ // Solana networks use mint addresses (SPL tokens)
1328
+ "solana:mainnet": {
1329
+ USDC: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
1330
+ // Circle USDC
1331
+ },
1332
+ "solana:devnet": {
1333
+ USDC: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
1334
+ // Devnet USDC
474
1335
  }
475
1336
  };
476
1337
  var CHAIN_TO_NETWORK = {
477
1338
  "base": "eip155:8453",
478
1339
  "base_sepolia": "eip155:84532",
479
- "polygon": "eip155:137"
1340
+ "polygon": "eip155:137",
1341
+ "tempo_moderato": "eip155:42431",
1342
+ "bnb": "eip155:56",
1343
+ "bnb_testnet": "eip155:97",
1344
+ "solana": "solana:mainnet",
1345
+ "solana_devnet": "solana:devnet"
480
1346
  };
1347
+ function isSolanaNetwork(network) {
1348
+ return network.startsWith("solana:");
1349
+ }
481
1350
  var TOKEN_DOMAINS = {
482
1351
  // Base mainnet
483
1352
  "eip155:8453": {
@@ -494,6 +1363,21 @@ var TOKEN_DOMAINS = {
494
1363
  "eip155:137": {
495
1364
  USDC: { name: "USD Coin", version: "2" },
496
1365
  USDT: { name: "(PoS) Tether USD", version: "2" }
1366
+ },
1367
+ // Tempo Moderato testnet - TIP-20 stablecoins
1368
+ "eip155:42431": {
1369
+ USDC: { name: "pathUSD", version: "1" },
1370
+ USDT: { name: "alphaUSD", version: "1" }
1371
+ },
1372
+ // BNB Smart Chain mainnet
1373
+ "eip155:56": {
1374
+ USDC: { name: "USD Coin", version: "1" },
1375
+ USDT: { name: "Tether USD", version: "1" }
1376
+ },
1377
+ // BNB Smart Chain testnet
1378
+ "eip155:97": {
1379
+ USDC: { name: "USD Coin", version: "1" },
1380
+ USDT: { name: "Tether USD", version: "1" }
497
1381
  }
498
1382
  };
499
1383
  function getTokenDomain(network, token) {
@@ -551,9 +1435,11 @@ var MoltsPayServer = class {
551
1435
  };
552
1436
  this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
553
1437
  this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
1438
+ const defaultFallback = ["tempo", "bnb", "solana"];
1439
+ const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
554
1440
  const facilitatorConfig = options.facilitators || {
555
1441
  primary: process.env.FACILITATOR_PRIMARY || "cdp",
556
- fallback: process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean),
1442
+ fallback: envFallback || defaultFallback,
557
1443
  strategy: process.env.FACILITATOR_STRATEGY || "failover",
558
1444
  config: {
559
1445
  cdp: { useMainnet: this.useMainnet }
@@ -592,12 +1478,20 @@ var MoltsPayServer = class {
592
1478
  */
593
1479
  getProviderChains() {
594
1480
  const provider = this.manifest.provider;
1481
+ const getWalletForChain = (chainName, explicitWallet) => {
1482
+ if (explicitWallet) return explicitWallet;
1483
+ if ((chainName === "solana" || chainName === "solana_devnet") && provider.solana_wallet) {
1484
+ return provider.solana_wallet;
1485
+ }
1486
+ return provider.wallet;
1487
+ };
595
1488
  if (provider.chains && provider.chains.length > 0) {
596
1489
  return provider.chains.map((c) => {
597
1490
  const chainName = typeof c === "string" ? c : c.chain;
1491
+ const explicitWallet = typeof c === "object" ? c.wallet : null;
598
1492
  return {
599
1493
  network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
600
- wallet: (typeof c === "object" ? c.wallet : null) || provider.wallet,
1494
+ wallet: getWalletForChain(chainName, explicitWallet || void 0),
601
1495
  tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
602
1496
  };
603
1497
  });
@@ -606,7 +1500,7 @@ var MoltsPayServer = class {
606
1500
  const network = CHAIN_TO_NETWORK[chain] || this.networkId;
607
1501
  return [{
608
1502
  network,
609
- wallet: provider.wallet,
1503
+ wallet: getWalletForChain(chain),
610
1504
  tokens: ["USDC"]
611
1505
  }];
612
1506
  }
@@ -647,8 +1541,8 @@ var MoltsPayServer = class {
647
1541
  async handleRequest(req, res) {
648
1542
  res.setHeader("Access-Control-Allow-Origin", "*");
649
1543
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
650
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
651
- res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
1544
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
1545
+ res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
652
1546
  if (req.method === "OPTIONS") {
653
1547
  res.writeHead(204);
654
1548
  res.end();
@@ -677,7 +1571,16 @@ var MoltsPayServer = class {
677
1571
  }
678
1572
  const body = await this.readBody(req);
679
1573
  const paymentHeader = req.headers[PAYMENT_HEADER];
680
- return await this.handleProxy(body, paymentHeader, res);
1574
+ const authHeader = req.headers[MPP_AUTH_HEADER];
1575
+ return await this.handleProxy(body, paymentHeader, authHeader, res);
1576
+ }
1577
+ const servicePath = url.pathname.replace(/^\//, "");
1578
+ const skill = this.skills.get(servicePath);
1579
+ if (skill && (req.method === "POST" || req.method === "GET")) {
1580
+ const body = req.method === "POST" ? await this.readBody(req) : {};
1581
+ const authHeader = req.headers[MPP_AUTH_HEADER];
1582
+ const x402Header = req.headers[PAYMENT_HEADER];
1583
+ return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
681
1584
  }
682
1585
  this.sendJson(res, 404, { error: "Not found" });
683
1586
  } catch (err) {
@@ -706,7 +1609,9 @@ var MoltsPayServer = class {
706
1609
  name: this.manifest.provider.name,
707
1610
  description: this.manifest.provider.description,
708
1611
  wallet: this.manifest.provider.wallet,
709
- chain: this.manifest.provider.chain || "base"
1612
+ chain: this.manifest.provider.chain || "base",
1613
+ solana_wallet: this.manifest.provider.solana_wallet,
1614
+ chains: this.manifest.provider.chains
710
1615
  },
711
1616
  services,
712
1617
  endpoints: {
@@ -819,6 +1724,21 @@ var MoltsPayServer = class {
819
1724
  });
820
1725
  }
821
1726
  console.log(`[MoltsPay] Verified by ${verifyResult.facilitator}`);
1727
+ const isSolana = isSolanaNetwork(paymentNetwork);
1728
+ let settlement = null;
1729
+ if (isSolana) {
1730
+ console.log(`[MoltsPay] Solana detected - settling payment FIRST (blockhash expiry protection)`);
1731
+ try {
1732
+ settlement = await this.registry.settle(payment, requirements);
1733
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1734
+ } catch (err) {
1735
+ console.error("[MoltsPay] Solana settlement failed:", err.message);
1736
+ return this.sendJson(res, 402, {
1737
+ error: "Payment settlement failed",
1738
+ message: err.message
1739
+ });
1740
+ }
1741
+ }
822
1742
  const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
823
1743
  console.log(`[MoltsPay] Executing skill: ${service} (timeout: ${timeoutSeconds}s)`);
824
1744
  let result;
@@ -833,16 +1753,19 @@ var MoltsPayServer = class {
833
1753
  console.error("[MoltsPay] Skill execution failed:", err.message);
834
1754
  return this.sendJson(res, 500, {
835
1755
  error: "Service execution failed",
836
- message: err.message
1756
+ message: err.message,
1757
+ paymentSettled: isSolana ? true : false,
1758
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
837
1759
  });
838
1760
  }
839
- console.log(`[MoltsPay] Skill succeeded, settling payment...`);
840
- let settlement = null;
841
- try {
842
- settlement = await this.registry.settle(payment, requirements);
843
- console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
844
- } catch (err) {
845
- console.error("[MoltsPay] Settlement failed:", err.message);
1761
+ if (!isSolana) {
1762
+ console.log(`[MoltsPay] Skill succeeded, settling payment...`);
1763
+ try {
1764
+ settlement = await this.registry.settle(payment, requirements);
1765
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1766
+ } catch (err) {
1767
+ console.error("[MoltsPay] Settlement failed:", err.message);
1768
+ }
846
1769
  }
847
1770
  const responseHeaders = {};
848
1771
  if (settlement?.success) {
@@ -862,6 +1785,187 @@ var MoltsPayServer = class {
862
1785
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
863
1786
  }, responseHeaders);
864
1787
  }
1788
+ /**
1789
+ * Handle MPP (Machine Payments Protocol) request
1790
+ * Supports both x402 and MPP protocols on service endpoints
1791
+ */
1792
+ async handleMPPRequest(skill, body, authHeader, x402Header, res) {
1793
+ const config = skill.config;
1794
+ const params = body || {};
1795
+ if (x402Header) {
1796
+ return await this.handleExecute({ service: config.id, params }, x402Header, res);
1797
+ }
1798
+ if (authHeader && authHeader.toLowerCase().startsWith("payment ")) {
1799
+ return await this.handleMPPPayment(skill, params, authHeader, res);
1800
+ }
1801
+ return this.sendMPPPaymentRequired(config, res);
1802
+ }
1803
+ /**
1804
+ * Handle MPP payment verification and service execution
1805
+ */
1806
+ async handleMPPPayment(skill, params, authHeader, res) {
1807
+ const config = skill.config;
1808
+ const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
1809
+ if (!credentialMatch) {
1810
+ return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
1811
+ }
1812
+ let mppCredential;
1813
+ try {
1814
+ const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
1815
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
1816
+ mppCredential = JSON.parse(decoded);
1817
+ } catch (err) {
1818
+ console.error("[MoltsPay] Failed to parse MPP credential:", err);
1819
+ return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
1820
+ }
1821
+ let txHash;
1822
+ if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
1823
+ txHash = mppCredential.payload.hash;
1824
+ } else if (mppCredential.payload?.type === "transaction") {
1825
+ return this.sendJson(res, 400, {
1826
+ error: "Transaction type not supported. Please use push mode (hash type)."
1827
+ });
1828
+ }
1829
+ if (!txHash) {
1830
+ return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
1831
+ }
1832
+ let chainId = mppCredential.challenge?.request?.methodDetails?.chainId;
1833
+ if (!chainId && mppCredential.source) {
1834
+ const chainMatch = mppCredential.source.match(/eip155:(\d+)/);
1835
+ if (chainMatch) chainId = parseInt(chainMatch[1], 10);
1836
+ }
1837
+ chainId = chainId || 42431;
1838
+ const network = `eip155:${chainId}`;
1839
+ if (!this.isNetworkAccepted(network)) {
1840
+ return this.sendJson(res, 402, {
1841
+ error: `Network not accepted: ${network}`
1842
+ });
1843
+ }
1844
+ const requirements = this.buildPaymentRequirements(
1845
+ config,
1846
+ network,
1847
+ this.getWalletForNetwork(network),
1848
+ "USDC"
1849
+ );
1850
+ const paymentPayload = {
1851
+ x402Version: X402_VERSION2,
1852
+ scheme: "exact",
1853
+ network,
1854
+ payload: {
1855
+ txHash,
1856
+ chainId
1857
+ }
1858
+ };
1859
+ console.log(`[MoltsPay] Verifying MPP payment: txHash=${txHash}, chainId=${chainId}`);
1860
+ const verification = await this.registry.verify(paymentPayload, requirements);
1861
+ if (!verification.valid) {
1862
+ return this.sendJson(res, 402, {
1863
+ error: `Payment verification failed: ${verification.error}`
1864
+ });
1865
+ }
1866
+ console.log(`[MoltsPay] Payment verified! Executing service: ${config.id}`);
1867
+ let result;
1868
+ try {
1869
+ result = await skill.handler(params);
1870
+ } catch (err) {
1871
+ console.error(`[MoltsPay] Skill execution error:`, err);
1872
+ return this.sendJson(res, 500, {
1873
+ error: `Service execution failed: ${err.message}`
1874
+ });
1875
+ }
1876
+ const receipt = {
1877
+ success: true,
1878
+ txHash,
1879
+ network,
1880
+ facilitator: verification.facilitator
1881
+ };
1882
+ const receiptEncoded = Buffer.from(JSON.stringify(receipt)).toString("base64");
1883
+ res.writeHead(200, {
1884
+ "Content-Type": "application/json",
1885
+ [MPP_RECEIPT_HEADER]: receiptEncoded
1886
+ });
1887
+ res.end(JSON.stringify({
1888
+ success: true,
1889
+ result,
1890
+ payment: {
1891
+ txHash,
1892
+ status: "verified",
1893
+ facilitator: verification.facilitator
1894
+ }
1895
+ }, null, 2));
1896
+ }
1897
+ /**
1898
+ * Return 402 with both x402 and MPP payment requirements
1899
+ */
1900
+ sendMPPPaymentRequired(config, res) {
1901
+ const acceptedTokens = getAcceptedCurrencies(config);
1902
+ const providerChains = this.getProviderChains();
1903
+ const accepts = [];
1904
+ for (const chainConfig of providerChains) {
1905
+ for (const token of acceptedTokens) {
1906
+ if (chainConfig.tokens.includes(token)) {
1907
+ accepts.push(this.buildPaymentRequirements(config, chainConfig.network, chainConfig.wallet, token));
1908
+ }
1909
+ }
1910
+ }
1911
+ const x402PaymentRequired = {
1912
+ x402Version: X402_VERSION2,
1913
+ accepts,
1914
+ acceptedCurrencies: acceptedTokens,
1915
+ resource: {
1916
+ url: `/${config.id}`,
1917
+ description: `${config.name} - $${config.price} ${config.currency}`
1918
+ }
1919
+ };
1920
+ const x402Encoded = Buffer.from(JSON.stringify(x402PaymentRequired)).toString("base64");
1921
+ const tempoChain = providerChains.find((c) => c.network === "eip155:42431");
1922
+ let mppWwwAuth = "";
1923
+ if (tempoChain) {
1924
+ const challengeId = this.generateChallengeId();
1925
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
1926
+ const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
1927
+ const mppRequest = {
1928
+ amount: amountInUnits,
1929
+ currency: tokenAddress,
1930
+ methodDetails: {
1931
+ chainId: 42431,
1932
+ feePayer: true
1933
+ },
1934
+ recipient: tempoChain.wallet
1935
+ };
1936
+ const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
1937
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
1938
+ mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
1939
+ }
1940
+ const headers = {
1941
+ "Content-Type": "application/problem+json",
1942
+ [PAYMENT_REQUIRED_HEADER]: x402Encoded
1943
+ };
1944
+ if (mppWwwAuth) {
1945
+ headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
1946
+ }
1947
+ res.writeHead(402, headers);
1948
+ res.end(JSON.stringify({
1949
+ type: "https://paymentauth.org/problems/payment-required",
1950
+ title: "Payment Required",
1951
+ status: 402,
1952
+ detail: `Payment is required (${config.name}).`,
1953
+ service: config.id,
1954
+ price: config.price,
1955
+ currency: config.currency,
1956
+ acceptedCurrencies: acceptedTokens
1957
+ }, null, 2));
1958
+ }
1959
+ /**
1960
+ * Generate a unique challenge ID for MPP
1961
+ */
1962
+ generateChallengeId() {
1963
+ const bytes = new Uint8Array(24);
1964
+ for (let i = 0; i < bytes.length; i++) {
1965
+ bytes[i] = Math.floor(Math.random() * 256);
1966
+ }
1967
+ return Buffer.from(bytes).toString("base64url");
1968
+ }
865
1969
  /**
866
1970
  * Return 402 with x402 payment requirements (v2 format)
867
1971
  * Includes requirements for all chains and all accepted currencies
@@ -937,7 +2041,7 @@ var MoltsPayServer = class {
937
2041
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
938
2042
  const tokenAddress = tokenAddresses[selectedToken];
939
2043
  const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
940
- return {
2044
+ const requirements = {
941
2045
  scheme: "exact",
942
2046
  network: selectedNetwork,
943
2047
  asset: tokenAddress,
@@ -946,6 +2050,27 @@ var MoltsPayServer = class {
946
2050
  maxTimeoutSeconds: 300,
947
2051
  extra: tokenDomain
948
2052
  };
2053
+ if (selectedNetwork === "solana:mainnet" || selectedNetwork === "solana:devnet") {
2054
+ const solanaFacilitator = this.registry.get("solana");
2055
+ const feePayerPubkey = solanaFacilitator?.getFeePayerPubkey?.();
2056
+ if (feePayerPubkey) {
2057
+ requirements.extra = {
2058
+ ...requirements.extra || {},
2059
+ solanaFeePayer: feePayerPubkey
2060
+ };
2061
+ }
2062
+ }
2063
+ if (selectedNetwork === "eip155:56" || selectedNetwork === "eip155:97") {
2064
+ const bnbFacilitator = this.registry.get("bnb");
2065
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
2066
+ if (spenderAddress) {
2067
+ requirements.extra = {
2068
+ ...requirements.extra || {},
2069
+ bnbSpender: spenderAddress
2070
+ };
2071
+ }
2072
+ }
2073
+ return requirements;
949
2074
  }
950
2075
  /**
951
2076
  * Detect which token is being used in the payment
@@ -1011,31 +2136,42 @@ var MoltsPayServer = class {
1011
2136
  /**
1012
2137
  * POST /proxy - Handle payment for external services (moltspay-creators)
1013
2138
  *
1014
- * This endpoint allows other services to delegate x402 payment handling.
2139
+ * This endpoint allows other services to delegate x402/MPP payment handling.
1015
2140
  * It does NOT execute any skill - just handles payment verification/settlement.
1016
2141
  *
1017
2142
  * Request body:
1018
2143
  * { wallet, amount, currency, chain, memo, serviceId, description }
1019
2144
  *
1020
- * Without X-Payment header: returns 402 with payment requirements
1021
- * With X-Payment header: verifies payment and returns result
2145
+ * For x402 (base, polygon, base_sepolia):
2146
+ * Without X-Payment header: returns 402 with X-Payment-Required
2147
+ * With X-Payment header: verifies payment via CDP
2148
+ *
2149
+ * For MPP (tempo_moderato):
2150
+ * Without Authorization header: returns 402 with WWW-Authenticate
2151
+ * With Authorization: Payment header: verifies tx on Tempo chain
1022
2152
  */
1023
- async handleProxy(body, paymentHeader, res) {
2153
+ async handleProxy(body, paymentHeader, authHeader, res) {
1024
2154
  const { wallet, amount, currency, chain, memo, serviceId, description } = body;
1025
2155
  if (!wallet || !amount) {
1026
2156
  return this.sendJson(res, 400, { error: "Missing required fields: wallet, amount" });
1027
2157
  }
1028
- if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
1029
- return this.sendJson(res, 400, { error: "Invalid wallet address format" });
2158
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet", "solana", "solana_devnet"];
2159
+ if (chain && !supportedChains.includes(chain)) {
2160
+ return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
2161
+ }
2162
+ const isSolanaChain = chain === "solana" || chain === "solana_devnet";
2163
+ const isValidEvmAddress = /^0x[a-fA-F0-9]{40}$/.test(wallet);
2164
+ const isValidSolanaAddress = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(wallet);
2165
+ if (isSolanaChain && !isValidSolanaAddress) {
2166
+ return this.sendJson(res, 400, { error: "Invalid Solana wallet address format" });
2167
+ }
2168
+ if (!isSolanaChain && !isValidEvmAddress) {
2169
+ return this.sendJson(res, 400, { error: "Invalid EVM wallet address format" });
1030
2170
  }
1031
2171
  const amountNum = parseFloat(amount);
1032
2172
  if (isNaN(amountNum) || amountNum <= 0) {
1033
2173
  return this.sendJson(res, 400, { error: "Invalid amount" });
1034
2174
  }
1035
- const supportedChains = ["base", "polygon", "base_sepolia"];
1036
- if (chain && !supportedChains.includes(chain)) {
1037
- return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
1038
- }
1039
2175
  const proxyConfig = {
1040
2176
  id: serviceId || "proxy",
1041
2177
  name: description || "Proxy Payment",
@@ -1047,6 +2183,9 @@ var MoltsPayServer = class {
1047
2183
  input: {},
1048
2184
  output: {}
1049
2185
  };
2186
+ if (chain === "tempo_moderato") {
2187
+ return await this.handleProxyMPP(body, proxyConfig, authHeader, res);
2188
+ }
1050
2189
  const requirements = this.buildProxyPaymentRequirements(proxyConfig, wallet, currency, chain);
1051
2190
  if (!paymentHeader) {
1052
2191
  return this.sendProxyPaymentRequired(proxyConfig, wallet, memo, chain, res);
@@ -1082,7 +2221,6 @@ var MoltsPayServer = class {
1082
2221
  console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
1083
2222
  const { execute, service, params } = body;
1084
2223
  if (execute && service) {
1085
- console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
1086
2224
  const skill = this.skills.get(service);
1087
2225
  if (!skill) {
1088
2226
  console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
@@ -1092,6 +2230,32 @@ var MoltsPayServer = class {
1092
2230
  error: `Service not found: ${service}`
1093
2231
  });
1094
2232
  }
2233
+ const isSolana = isSolanaNetwork(network);
2234
+ let settlement2 = null;
2235
+ if (isSolana) {
2236
+ console.log(`[MoltsPay] /proxy: Solana detected - settling payment FIRST`);
2237
+ try {
2238
+ settlement2 = await this.registry.settle(payment, requirements);
2239
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
2240
+ if (!settlement2.success) {
2241
+ console.error(`[MoltsPay] /proxy: Solana settlement failed: ${settlement2.error}`);
2242
+ return this.sendJson(res, 402, {
2243
+ success: false,
2244
+ paymentSettled: false,
2245
+ error: `Payment settlement failed: ${settlement2.error || "Unknown error"}`
2246
+ });
2247
+ }
2248
+ } catch (err) {
2249
+ console.error("[MoltsPay] /proxy: Solana settlement failed:", err.message);
2250
+ return this.sendJson(res, 402, {
2251
+ success: false,
2252
+ paymentSettled: false,
2253
+ error: `Payment settlement failed: ${err.message}`
2254
+ });
2255
+ }
2256
+ } else {
2257
+ console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
2258
+ }
1095
2259
  const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
1096
2260
  let result;
1097
2261
  try {
@@ -1101,34 +2265,36 @@ var MoltsPayServer = class {
1101
2265
  (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
1102
2266
  )
1103
2267
  ]);
1104
- console.log(`[MoltsPay] /proxy: Skill succeeded, now settling payment...`);
2268
+ console.log(`[MoltsPay] /proxy: Skill succeeded`);
1105
2269
  } catch (err) {
1106
- console.error(`[MoltsPay] /proxy: Skill failed: ${err.message} - NOT settling`);
2270
+ console.error(`[MoltsPay] /proxy: Skill failed: ${err.message}`);
1107
2271
  return this.sendJson(res, 500, {
1108
2272
  success: false,
1109
- paymentSettled: false,
1110
- error: `Service execution failed: ${err.message}`
2273
+ paymentSettled: isSolana ? true : false,
2274
+ error: `Service execution failed: ${err.message}`,
2275
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
1111
2276
  });
1112
2277
  }
1113
- let settlement2 = null;
1114
- try {
1115
- settlement2 = await this.registry.settle(payment, requirements);
1116
- console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
1117
- } catch (err) {
1118
- console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
1119
- return this.sendJson(res, 200, {
1120
- success: true,
1121
- verified: true,
1122
- settled: false,
1123
- settlementError: err.message,
1124
- from: payment.payload?.authorization?.from,
1125
- // Buyer's wallet address
1126
- paidTo: wallet,
1127
- amount: amountNum,
1128
- currency: currency || "USDC",
1129
- memo,
1130
- result
1131
- });
2278
+ if (!isSolana) {
2279
+ console.log(`[MoltsPay] /proxy: Settling payment...`);
2280
+ try {
2281
+ settlement2 = await this.registry.settle(payment, requirements);
2282
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
2283
+ } catch (err) {
2284
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
2285
+ return this.sendJson(res, 200, {
2286
+ success: true,
2287
+ verified: true,
2288
+ settled: false,
2289
+ settlementError: err.message,
2290
+ from: payment.payload?.authorization?.from,
2291
+ paidTo: wallet,
2292
+ amount: amountNum,
2293
+ currency: currency || "USDC",
2294
+ memo,
2295
+ result
2296
+ });
2297
+ }
1132
2298
  }
1133
2299
  return this.sendJson(res, 200, {
1134
2300
  success: true,
@@ -1136,7 +2302,6 @@ var MoltsPayServer = class {
1136
2302
  settled: settlement2?.success || false,
1137
2303
  txHash: settlement2?.transaction,
1138
2304
  from: payment.payload?.authorization?.from,
1139
- // Buyer's wallet address
1140
2305
  paidTo: wallet,
1141
2306
  amount: amountNum,
1142
2307
  currency: currency || "USDC",
@@ -1171,6 +2336,131 @@ var MoltsPayServer = class {
1171
2336
  memo
1172
2337
  });
1173
2338
  }
2339
+ /**
2340
+ * Handle MPP payment flow for /proxy endpoint (tempo_moderato chain)
2341
+ */
2342
+ async handleProxyMPP(body, config, authHeader, res) {
2343
+ const { wallet, amount, memo, serviceId } = body;
2344
+ const amountNum = parseFloat(amount);
2345
+ const amountInUnits = Math.floor(amountNum * 1e6).toString();
2346
+ if (!authHeader || !authHeader.toLowerCase().startsWith("payment ")) {
2347
+ const challengeId = this.generateChallengeId();
2348
+ const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
2349
+ const mppRequest = {
2350
+ amount: amountInUnits,
2351
+ currency: tokenAddress,
2352
+ methodDetails: {
2353
+ chainId: 42431,
2354
+ feePayer: true
2355
+ },
2356
+ recipient: wallet
2357
+ };
2358
+ const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
2359
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
2360
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
2361
+ res.writeHead(402, {
2362
+ "Content-Type": "application/problem+json",
2363
+ [MPP_WWW_AUTH_HEADER]: wwwAuth
2364
+ });
2365
+ res.end(JSON.stringify({
2366
+ type: "https://paymentauth.org/problems/payment-required",
2367
+ title: "Payment Required",
2368
+ status: 402,
2369
+ detail: `Payment is required (${config.name}).`,
2370
+ service: serviceId || "proxy",
2371
+ price: amountNum,
2372
+ currency: "USDC"
2373
+ }, null, 2));
2374
+ return;
2375
+ }
2376
+ const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
2377
+ if (!credentialMatch) {
2378
+ return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
2379
+ }
2380
+ let mppCredential;
2381
+ try {
2382
+ const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
2383
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
2384
+ mppCredential = JSON.parse(decoded);
2385
+ } catch (err) {
2386
+ console.error("[MoltsPay] /proxy MPP: Failed to parse credential:", err);
2387
+ return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
2388
+ }
2389
+ let txHash;
2390
+ if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
2391
+ txHash = mppCredential.payload.hash;
2392
+ } else {
2393
+ return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
2394
+ }
2395
+ console.log(`[MoltsPay] /proxy MPP: Verifying tx ${txHash} on Tempo...`);
2396
+ const requirements = this.buildPaymentRequirements(config, "eip155:42431", wallet, "USDC");
2397
+ const paymentPayload = {
2398
+ x402Version: X402_VERSION2,
2399
+ scheme: "exact",
2400
+ network: "eip155:42431",
2401
+ payload: { txHash, chainId: 42431 }
2402
+ };
2403
+ const verification = await this.registry.verify(paymentPayload, requirements);
2404
+ if (!verification.valid) {
2405
+ return this.sendJson(res, 402, {
2406
+ error: `Payment verification failed: ${verification.error}`
2407
+ });
2408
+ }
2409
+ console.log(`[MoltsPay] /proxy MPP: Payment verified by ${verification.facilitator}`);
2410
+ const { execute, service, params } = body;
2411
+ if (execute && service) {
2412
+ console.log(`[MoltsPay] /proxy MPP: Executing skill: ${service}`);
2413
+ const skill = this.skills.get(service);
2414
+ if (!skill) {
2415
+ return this.sendJson(res, 404, {
2416
+ success: false,
2417
+ paymentSettled: true,
2418
+ // Payment already happened on Tempo
2419
+ error: `Service not found: ${service}`
2420
+ });
2421
+ }
2422
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
2423
+ let result;
2424
+ try {
2425
+ result = await Promise.race([
2426
+ skill.handler(params || {}),
2427
+ new Promise(
2428
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
2429
+ )
2430
+ ]);
2431
+ } catch (err) {
2432
+ console.error(`[MoltsPay] /proxy MPP: Skill failed: ${err.message}`);
2433
+ return this.sendJson(res, 500, {
2434
+ success: false,
2435
+ paymentSettled: true,
2436
+ error: `Service execution failed: ${err.message}`
2437
+ });
2438
+ }
2439
+ return this.sendJson(res, 200, {
2440
+ success: true,
2441
+ verified: true,
2442
+ txHash,
2443
+ chain: "tempo_moderato",
2444
+ paidTo: wallet,
2445
+ amount: amountNum,
2446
+ currency: "USDC",
2447
+ facilitator: verification.facilitator,
2448
+ memo,
2449
+ result
2450
+ });
2451
+ }
2452
+ this.sendJson(res, 200, {
2453
+ success: true,
2454
+ verified: true,
2455
+ txHash,
2456
+ chain: "tempo_moderato",
2457
+ paidTo: wallet,
2458
+ amount: amountNum,
2459
+ currency: "USDC",
2460
+ facilitator: verification.facilitator,
2461
+ memo
2462
+ });
2463
+ }
1174
2464
  /**
1175
2465
  * Build payment requirements for proxy endpoint (uses provided wallet)
1176
2466
  */
@@ -1182,7 +2472,7 @@ var MoltsPayServer = class {
1182
2472
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
1183
2473
  const tokenAddress = tokenAddresses[selectedToken];
1184
2474
  const tokenDomain = getTokenDomain(networkId, selectedToken);
1185
- return {
2475
+ const requirements = {
1186
2476
  scheme: "exact",
1187
2477
  network: networkId,
1188
2478
  asset: tokenAddress,
@@ -1192,6 +2482,17 @@ var MoltsPayServer = class {
1192
2482
  maxTimeoutSeconds: 300,
1193
2483
  extra: tokenDomain
1194
2484
  };
2485
+ if (networkId === "eip155:56" || networkId === "eip155:97") {
2486
+ const bnbFacilitator = this.registry.get("bnb");
2487
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
2488
+ if (spenderAddress) {
2489
+ requirements.extra = {
2490
+ ...requirements.extra || {},
2491
+ bnbSpender: spenderAddress
2492
+ };
2493
+ }
2494
+ }
2495
+ return requirements;
1195
2496
  }
1196
2497
  /**
1197
2498
  * Return 402 with x402 payment requirements for proxy endpoint