moltspay 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +221 -38
  2. package/dist/cdp/index.d.mts +4 -4
  3. package/dist/cdp/index.d.ts +4 -4
  4. package/dist/cdp/index.js +57 -0
  5. package/dist/cdp/index.js.map +1 -1
  6. package/dist/cdp/index.mjs +57 -0
  7. package/dist/cdp/index.mjs.map +1 -1
  8. package/dist/chains/index.d.mts +9 -8
  9. package/dist/chains/index.d.ts +9 -8
  10. package/dist/chains/index.js +57 -0
  11. package/dist/chains/index.js.map +1 -1
  12. package/dist/chains/index.mjs +57 -0
  13. package/dist/chains/index.mjs.map +1 -1
  14. package/dist/cli/index.js +1975 -273
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/cli/index.mjs +1977 -265
  17. package/dist/cli/index.mjs.map +1 -1
  18. package/dist/client/index.d.mts +36 -3
  19. package/dist/client/index.d.ts +36 -3
  20. package/dist/client/index.js +540 -32
  21. package/dist/client/index.js.map +1 -1
  22. package/dist/client/index.mjs +548 -30
  23. package/dist/client/index.mjs.map +1 -1
  24. package/dist/facilitators/index.d.mts +220 -1
  25. package/dist/facilitators/index.d.ts +220 -1
  26. package/dist/facilitators/index.js +664 -1
  27. package/dist/facilitators/index.js.map +1 -1
  28. package/dist/facilitators/index.mjs +670 -1
  29. package/dist/facilitators/index.mjs.map +1 -1
  30. package/dist/{index-On9ZaGDW.d.mts → index-D_2FkLwV.d.mts} +6 -2
  31. package/dist/{index-On9ZaGDW.d.ts → index-D_2FkLwV.d.ts} +6 -2
  32. package/dist/index.d.mts +2 -1
  33. package/dist/index.d.ts +2 -1
  34. package/dist/index.js +1413 -146
  35. package/dist/index.js.map +1 -1
  36. package/dist/index.mjs +1421 -144
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/server/index.d.mts +13 -3
  39. package/dist/server/index.d.ts +13 -3
  40. package/dist/server/index.js +905 -52
  41. package/dist/server/index.js.map +1 -1
  42. package/dist/server/index.mjs +915 -52
  43. package/dist/server/index.mjs.map +1 -1
  44. package/dist/verify/index.d.mts +1 -1
  45. package/dist/verify/index.d.ts +1 -1
  46. package/dist/verify/index.js +57 -0
  47. package/dist/verify/index.js.map +1 -1
  48. package/dist/verify/index.mjs +57 -0
  49. package/dist/verify/index.mjs.map +1 -1
  50. package/dist/wallet/index.d.mts +3 -3
  51. package/dist/wallet/index.d.ts +3 -3
  52. package/dist/wallet/index.js +57 -0
  53. package/dist/wallet/index.js.map +1 -1
  54. package/dist/wallet/index.mjs +57 -0
  55. package/dist/wallet/index.mjs.map +1 -1
  56. package/package.json +4 -1
  57. package/schemas/moltspay.services.schema.json +27 -132
package/dist/index.js CHANGED
@@ -395,6 +395,63 @@ var CHAINS = {
395
395
  explorerTx: "https://explore.testnet.tempo.xyz/tx/",
396
396
  avgBlockTime: 0.5
397
397
  // ~500ms finality
398
+ },
399
+ // ============ BNB Chain Testnet ============
400
+ bnb_testnet: {
401
+ name: "BNB Testnet",
402
+ chainId: 97,
403
+ rpc: "https://data-seed-prebsc-1-s1.binance.org:8545",
404
+ tokens: {
405
+ // Note: BNB uses 18 decimals for stablecoins (unlike Base/Polygon which use 6)
406
+ // Using official Binance-Peg testnet tokens
407
+ USDC: {
408
+ address: "0x64544969ed7EBf5f083679233325356EbE738930",
409
+ // Testnet USDC
410
+ decimals: 18,
411
+ symbol: "USDC",
412
+ eip712Name: "USD Coin"
413
+ },
414
+ USDT: {
415
+ address: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd",
416
+ // Testnet USDT
417
+ decimals: 18,
418
+ symbol: "USDT",
419
+ eip712Name: "Tether USD"
420
+ }
421
+ },
422
+ usdc: "0x64544969ed7EBf5f083679233325356EbE738930",
423
+ explorer: "https://testnet.bscscan.com/address/",
424
+ explorerTx: "https://testnet.bscscan.com/tx/",
425
+ avgBlockTime: 3,
426
+ // BNB-specific: requires approval for pay-for-success flow
427
+ requiresApproval: true
428
+ },
429
+ // ============ BNB Chain Mainnet ============
430
+ bnb: {
431
+ name: "BNB Smart Chain",
432
+ chainId: 56,
433
+ rpc: "https://bsc-dataseed.binance.org",
434
+ tokens: {
435
+ // Note: BNB uses 18 decimals for stablecoins
436
+ USDC: {
437
+ address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
438
+ decimals: 18,
439
+ symbol: "USDC",
440
+ eip712Name: "USD Coin"
441
+ },
442
+ USDT: {
443
+ address: "0x55d398326f99059fF775485246999027B3197955",
444
+ decimals: 18,
445
+ symbol: "USDT",
446
+ eip712Name: "Tether USD"
447
+ }
448
+ },
449
+ usdc: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
450
+ explorer: "https://bscscan.com/address/",
451
+ explorerTx: "https://bscscan.com/tx/",
452
+ avgBlockTime: 3,
453
+ // BNB-specific: requires approval for pay-for-success flow
454
+ requiresApproval: true
398
455
  }
399
456
  };
400
457
  function getChain(name) {
@@ -544,7 +601,573 @@ var TempoFacilitator = class extends BaseFacilitator {
544
601
  }
545
602
  };
546
603
 
604
+ // src/facilitators/bnb.ts
605
+ var import_accounts = require("viem/accounts");
606
+ var TRANSFER_EVENT_TOPIC2 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
607
+ var EIP712_DOMAIN = {
608
+ name: "MoltsPay",
609
+ version: "1"
610
+ };
611
+ var INTENT_TYPES = {
612
+ PaymentIntent: [
613
+ { name: "from", type: "address" },
614
+ { name: "to", type: "address" },
615
+ { name: "amount", type: "uint256" },
616
+ { name: "token", type: "address" },
617
+ { name: "service", type: "string" },
618
+ { name: "nonce", type: "uint256" },
619
+ { name: "deadline", type: "uint256" }
620
+ ]
621
+ };
622
+ var BNBFacilitator = class extends BaseFacilitator {
623
+ name = "bnb";
624
+ displayName = "BNB Smart Chain";
625
+ supportedNetworks = ["eip155:56", "eip155:97"];
626
+ // Mainnet + Testnet
627
+ serverPrivateKey;
628
+ spenderAddress = null;
629
+ chainConfigs;
630
+ constructor(serverPrivateKey) {
631
+ super();
632
+ this.serverPrivateKey = serverPrivateKey || process.env.BNB_SERVER_PRIVATE_KEY || "";
633
+ if (this.serverPrivateKey) {
634
+ const key = this.serverPrivateKey.startsWith("0x") ? this.serverPrivateKey : `0x${this.serverPrivateKey}`;
635
+ const account = (0, import_accounts.privateKeyToAccount)(key);
636
+ this.spenderAddress = account.address;
637
+ }
638
+ this.chainConfigs = {
639
+ 56: { rpc: CHAINS.bnb.rpc, chain: CHAINS.bnb },
640
+ 97: { rpc: CHAINS.bnb_testnet.rpc, chain: CHAINS.bnb_testnet }
641
+ };
642
+ }
643
+ async healthCheck() {
644
+ const start = Date.now();
645
+ try {
646
+ const response = await fetch(this.chainConfigs[56].rpc, {
647
+ method: "POST",
648
+ headers: { "Content-Type": "application/json" },
649
+ body: JSON.stringify({
650
+ jsonrpc: "2.0",
651
+ method: "eth_chainId",
652
+ params: [],
653
+ id: 1
654
+ })
655
+ });
656
+ const data = await response.json();
657
+ const chainId = parseInt(data.result, 16);
658
+ if (chainId !== 56) {
659
+ return { healthy: false, error: `Wrong chainId: ${chainId}` };
660
+ }
661
+ return { healthy: true, latencyMs: Date.now() - start };
662
+ } catch (error) {
663
+ return { healthy: false, error: String(error) };
664
+ }
665
+ }
666
+ /**
667
+ * Verify a payment intent signature (before service execution)
668
+ *
669
+ * This verifies:
670
+ * 1. Signature is valid for the intent
671
+ * 2. Client has approved server wallet
672
+ * 3. Client has sufficient balance
673
+ * 4. Intent hasn't expired
674
+ */
675
+ async verify(paymentPayload, requirements) {
676
+ try {
677
+ const bnbPayload = paymentPayload.payload;
678
+ if (!bnbPayload?.intent) {
679
+ return { valid: false, error: "Missing intent in payment payload" };
680
+ }
681
+ const { intent, chainId } = bnbPayload;
682
+ const config = this.chainConfigs[chainId];
683
+ if (!config) {
684
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
685
+ }
686
+ if (intent.deadline < Date.now()) {
687
+ return { valid: false, error: "Intent expired" };
688
+ }
689
+ const recoveredAddress = await this.recoverIntentSigner(intent, chainId);
690
+ if (recoveredAddress.toLowerCase() !== intent.from.toLowerCase()) {
691
+ return { valid: false, error: "Invalid signature" };
692
+ }
693
+ if (intent.to.toLowerCase() !== requirements.payTo.toLowerCase()) {
694
+ return { valid: false, error: `Wrong recipient: ${intent.to}` };
695
+ }
696
+ if (BigInt(intent.amount) < BigInt(requirements.amount)) {
697
+ return { valid: false, error: `Insufficient amount: ${intent.amount}` };
698
+ }
699
+ if (intent.token.toLowerCase() !== requirements.asset.toLowerCase()) {
700
+ return { valid: false, error: `Wrong token: ${intent.token}` };
701
+ }
702
+ const serverAddress = await this.getServerAddress();
703
+ const allowance = await this.getAllowance(intent.from, serverAddress, intent.token, config.rpc);
704
+ if (BigInt(allowance) < BigInt(intent.amount)) {
705
+ return { valid: false, error: "Insufficient allowance. Run: npx moltspay init --chain bnb" };
706
+ }
707
+ const balance = await this.getBalance(intent.from, intent.token, config.rpc);
708
+ if (BigInt(balance) < BigInt(intent.amount)) {
709
+ return { valid: false, error: "Insufficient balance" };
710
+ }
711
+ return {
712
+ valid: true,
713
+ details: {
714
+ from: intent.from,
715
+ to: intent.to,
716
+ amount: intent.amount,
717
+ token: intent.token,
718
+ service: intent.service,
719
+ nonce: intent.nonce,
720
+ deadline: intent.deadline
721
+ }
722
+ };
723
+ } catch (error) {
724
+ return { valid: false, error: `Verification failed: ${error}` };
725
+ }
726
+ }
727
+ /**
728
+ * Settle a payment by executing transferFrom
729
+ *
730
+ * This is called AFTER the service has been successfully delivered.
731
+ * Server pays gas, transfers tokens from client to provider.
732
+ */
733
+ async settle(paymentPayload, requirements) {
734
+ if (!this.serverPrivateKey) {
735
+ return { success: false, error: "Server wallet not configured (BNB_SERVER_PRIVATE_KEY)" };
736
+ }
737
+ try {
738
+ const verifyResult = await this.verify(paymentPayload, requirements);
739
+ if (!verifyResult.valid) {
740
+ return { success: false, error: verifyResult.error };
741
+ }
742
+ const bnbPayload = paymentPayload.payload;
743
+ const { intent, chainId } = bnbPayload;
744
+ const config = this.chainConfigs[chainId];
745
+ const txHash = await this.executeTransferFrom(
746
+ intent.from,
747
+ intent.to,
748
+ intent.amount,
749
+ intent.token,
750
+ config.rpc
751
+ );
752
+ return {
753
+ success: true,
754
+ transaction: txHash,
755
+ status: "settled"
756
+ };
757
+ } catch (error) {
758
+ return { success: false, error: `Settlement failed: ${error}` };
759
+ }
760
+ }
761
+ /**
762
+ * Check if client has approved the server wallet
763
+ */
764
+ async checkApproval(clientAddress, token, chainId) {
765
+ const config = this.chainConfigs[chainId];
766
+ if (!config) {
767
+ throw new Error(`Unsupported chainId: ${chainId}`);
768
+ }
769
+ const serverAddress = await this.getServerAddress();
770
+ const allowance = await this.getAllowance(clientAddress, serverAddress, token, config.rpc);
771
+ const minAllowance = BigInt("1000000000000000000000");
772
+ return {
773
+ approved: BigInt(allowance) >= minAllowance,
774
+ allowance
775
+ };
776
+ }
777
+ /**
778
+ * Verify a completed transaction (for checking past payments)
779
+ */
780
+ async verifyTransaction(txHash, expected, chainId) {
781
+ const config = this.chainConfigs[chainId];
782
+ if (!config) {
783
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
784
+ }
785
+ try {
786
+ const receipt = await this.getTransactionReceipt(txHash, config.rpc);
787
+ if (!receipt) {
788
+ return { valid: false, error: "Transaction not found" };
789
+ }
790
+ if (receipt.status !== "0x1") {
791
+ return { valid: false, error: "Transaction failed" };
792
+ }
793
+ const transferLog = receipt.logs.find(
794
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC2 && log.address.toLowerCase() === expected.token.toLowerCase()
795
+ );
796
+ if (!transferLog) {
797
+ return { valid: false, error: "No Transfer event found" };
798
+ }
799
+ const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
800
+ if (toAddress !== expected.to.toLowerCase()) {
801
+ return { valid: false, error: `Wrong recipient: ${toAddress}` };
802
+ }
803
+ const amount = BigInt(transferLog.data);
804
+ if (amount < BigInt(expected.amount)) {
805
+ return { valid: false, error: `Insufficient amount: ${amount}` };
806
+ }
807
+ return {
808
+ valid: true,
809
+ details: {
810
+ txHash,
811
+ from: "0x" + transferLog.topics[1].slice(26),
812
+ to: toAddress,
813
+ amount: amount.toString(),
814
+ token: transferLog.address
815
+ }
816
+ };
817
+ } catch (error) {
818
+ return { valid: false, error: `Verification failed: ${error}` };
819
+ }
820
+ }
821
+ // ==================== Private Methods ====================
822
+ /**
823
+ * Get the server's spender address (public, for 402 responses)
824
+ * Returns cached value computed at construction time.
825
+ */
826
+ getSpenderAddress() {
827
+ return this.spenderAddress;
828
+ }
829
+ async getServerAddress() {
830
+ const { ethers: ethers5 } = await import("ethers");
831
+ const wallet = new ethers5.Wallet(this.serverPrivateKey);
832
+ return wallet.address;
833
+ }
834
+ async recoverIntentSigner(intent, chainId) {
835
+ const { ethers: ethers5 } = await import("ethers");
836
+ const domain = {
837
+ ...EIP712_DOMAIN,
838
+ chainId
839
+ };
840
+ const message = {
841
+ from: intent.from,
842
+ to: intent.to,
843
+ amount: intent.amount,
844
+ token: intent.token,
845
+ service: intent.service,
846
+ nonce: intent.nonce,
847
+ deadline: intent.deadline
848
+ };
849
+ const recoveredAddress = ethers5.verifyTypedData(
850
+ domain,
851
+ INTENT_TYPES,
852
+ message,
853
+ intent.signature
854
+ );
855
+ return recoveredAddress;
856
+ }
857
+ async getAllowance(owner, spender, token, rpcUrl) {
858
+ const selector = "0xdd62ed3e";
859
+ const ownerPadded = owner.toLowerCase().replace("0x", "").padStart(64, "0");
860
+ const spenderPadded = spender.toLowerCase().replace("0x", "").padStart(64, "0");
861
+ const data = selector + ownerPadded + spenderPadded;
862
+ const response = await fetch(rpcUrl, {
863
+ method: "POST",
864
+ headers: { "Content-Type": "application/json" },
865
+ body: JSON.stringify({
866
+ jsonrpc: "2.0",
867
+ method: "eth_call",
868
+ params: [{ to: token, data }, "latest"],
869
+ id: 1
870
+ })
871
+ });
872
+ const result = await response.json();
873
+ return result.result || "0x0";
874
+ }
875
+ async getBalance(account, token, rpcUrl) {
876
+ const selector = "0x70a08231";
877
+ const accountPadded = account.toLowerCase().replace("0x", "").padStart(64, "0");
878
+ const data = selector + accountPadded;
879
+ const response = await fetch(rpcUrl, {
880
+ method: "POST",
881
+ headers: { "Content-Type": "application/json" },
882
+ body: JSON.stringify({
883
+ jsonrpc: "2.0",
884
+ method: "eth_call",
885
+ params: [{ to: token, data }, "latest"],
886
+ id: 1
887
+ })
888
+ });
889
+ const result = await response.json();
890
+ return result.result || "0x0";
891
+ }
892
+ async executeTransferFrom(from, to, amount, token, rpcUrl) {
893
+ const { ethers: ethers5 } = await import("ethers");
894
+ const provider = new ethers5.JsonRpcProvider(rpcUrl);
895
+ const wallet = new ethers5.Wallet(this.serverPrivateKey, provider);
896
+ const tokenContract = new ethers5.Contract(token, [
897
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)"
898
+ ], wallet);
899
+ const tx = await tokenContract.transferFrom(from, to, amount);
900
+ const receipt = await tx.wait();
901
+ return receipt.hash;
902
+ }
903
+ async getTransactionReceipt(txHash, rpcUrl) {
904
+ const response = await fetch(rpcUrl, {
905
+ method: "POST",
906
+ headers: { "Content-Type": "application/json" },
907
+ body: JSON.stringify({
908
+ jsonrpc: "2.0",
909
+ method: "eth_getTransactionReceipt",
910
+ params: [txHash],
911
+ id: 1
912
+ })
913
+ });
914
+ const data = await response.json();
915
+ return data.result;
916
+ }
917
+ };
918
+
919
+ // src/facilitators/solana.ts
920
+ var import_web32 = require("@solana/web3.js");
921
+ var import_spl_token = require("@solana/spl-token");
922
+
923
+ // src/chains/solana.ts
924
+ var import_web3 = require("@solana/web3.js");
925
+ var SOLANA_CHAINS = {
926
+ solana: {
927
+ name: "Solana Mainnet",
928
+ cluster: "mainnet-beta",
929
+ rpc: "https://api.mainnet-beta.solana.com",
930
+ explorer: "https://solscan.io/account/",
931
+ explorerTx: "https://solscan.io/tx/",
932
+ tokens: {
933
+ USDC: {
934
+ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
935
+ // Circle official USDC
936
+ decimals: 6
937
+ }
938
+ }
939
+ },
940
+ solana_devnet: {
941
+ name: "Solana Devnet",
942
+ cluster: "devnet",
943
+ rpc: "https://api.devnet.solana.com",
944
+ explorer: "https://solscan.io/account/",
945
+ explorerTx: "https://solscan.io/tx/",
946
+ tokens: {
947
+ USDC: {
948
+ // Circle's devnet USDC (if not available, we'll deploy our own test token)
949
+ mint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
950
+ decimals: 6
951
+ }
952
+ }
953
+ }
954
+ };
955
+
956
+ // src/facilitators/solana.ts
957
+ var SolanaFacilitator = class extends BaseFacilitator {
958
+ name = "solana";
959
+ displayName = "Solana Direct";
960
+ supportedNetworks = ["solana:mainnet", "solana:devnet"];
961
+ connections = /* @__PURE__ */ new Map();
962
+ feePayerKeypair;
963
+ constructor(config) {
964
+ super();
965
+ this.feePayerKeypair = config?.feePayerKeypair;
966
+ for (const [chain, config2] of Object.entries(SOLANA_CHAINS)) {
967
+ this.connections.set(
968
+ chain,
969
+ new import_web32.Connection(config2.rpc, "confirmed")
970
+ );
971
+ }
972
+ if (this.feePayerKeypair) {
973
+ console.log(`[SolanaFacilitator] Gasless mode enabled. Fee payer: ${this.feePayerKeypair.publicKey.toBase58()}`);
974
+ }
975
+ }
976
+ /**
977
+ * Get fee payer public key (for gasless transactions)
978
+ */
979
+ getFeePayerPubkey() {
980
+ return this.feePayerKeypair?.publicKey.toBase58() || null;
981
+ }
982
+ getConnection(chain) {
983
+ const conn = this.connections.get(chain);
984
+ if (!conn) {
985
+ throw new Error(`No connection for chain: ${chain}`);
986
+ }
987
+ return conn;
988
+ }
989
+ /**
990
+ * Convert our chain name to network identifier
991
+ */
992
+ static chainToNetwork(chain) {
993
+ return chain === "solana" ? "solana:mainnet" : "solana:devnet";
994
+ }
995
+ /**
996
+ * Convert network identifier to chain name
997
+ */
998
+ static networkToChain(network) {
999
+ if (network === "solana:mainnet") return "solana";
1000
+ if (network === "solana:devnet") return "solana_devnet";
1001
+ return null;
1002
+ }
1003
+ async healthCheck() {
1004
+ const start = Date.now();
1005
+ try {
1006
+ const conn = this.getConnection("solana_devnet");
1007
+ await conn.getSlot();
1008
+ return {
1009
+ healthy: true,
1010
+ latencyMs: Date.now() - start
1011
+ };
1012
+ } catch (error) {
1013
+ return {
1014
+ healthy: false,
1015
+ error: error.message
1016
+ };
1017
+ }
1018
+ }
1019
+ /**
1020
+ * Verify a Solana payment
1021
+ *
1022
+ * Checks:
1023
+ * 1. Transaction is valid and properly signed
1024
+ * 2. Transfer instruction matches expected amount and recipient
1025
+ */
1026
+ async verify(paymentPayload, requirements) {
1027
+ try {
1028
+ const solanaPayload = paymentPayload.payload;
1029
+ if (!solanaPayload || !solanaPayload.signedTransaction) {
1030
+ return { valid: false, error: "Missing signed transaction" };
1031
+ }
1032
+ const chain = solanaPayload.chain || "solana_devnet";
1033
+ const chainConfig = SOLANA_CHAINS[chain];
1034
+ if (!chainConfig) {
1035
+ return { valid: false, error: `Invalid chain: ${chain}` };
1036
+ }
1037
+ const txBuffer = Buffer.from(solanaPayload.signedTransaction, "base64");
1038
+ let tx;
1039
+ try {
1040
+ tx = import_web32.Transaction.from(txBuffer);
1041
+ } catch {
1042
+ tx = import_web32.VersionedTransaction.deserialize(txBuffer);
1043
+ }
1044
+ if (tx instanceof import_web32.Transaction) {
1045
+ const hasAnySignature = tx.signatures.some(
1046
+ (sig) => sig.signature && !sig.signature.every((b) => b === 0)
1047
+ );
1048
+ if (!hasAnySignature) {
1049
+ return { valid: false, error: "Transaction not signed" };
1050
+ }
1051
+ }
1052
+ const expectedAmount = BigInt(requirements.amount);
1053
+ const expectedRecipient = new import_web32.PublicKey(requirements.payTo);
1054
+ return {
1055
+ valid: true,
1056
+ details: {
1057
+ chain,
1058
+ sender: solanaPayload.sender,
1059
+ recipient: requirements.payTo,
1060
+ amount: requirements.amount
1061
+ }
1062
+ };
1063
+ } catch (error) {
1064
+ return { valid: false, error: error.message };
1065
+ }
1066
+ }
1067
+ /**
1068
+ * Settle a Solana payment
1069
+ *
1070
+ * Submits the signed transaction to the network.
1071
+ * In gasless mode, adds fee payer signature before submitting.
1072
+ */
1073
+ async settle(paymentPayload, requirements) {
1074
+ try {
1075
+ const solanaPayload = paymentPayload.payload;
1076
+ if (!solanaPayload || !solanaPayload.signedTransaction) {
1077
+ return { success: false, error: "Missing signed transaction" };
1078
+ }
1079
+ const chain = solanaPayload.chain || "solana_devnet";
1080
+ const connection = this.getConnection(chain);
1081
+ const txBuffer = Buffer.from(solanaPayload.signedTransaction, "base64");
1082
+ let txToSend;
1083
+ try {
1084
+ const tx = import_web32.Transaction.from(txBuffer);
1085
+ if (this.feePayerKeypair && tx.feePayer) {
1086
+ const feePayerPubkey = this.feePayerKeypair.publicKey.toBase58();
1087
+ const txFeePayer = tx.feePayer.toBase58();
1088
+ if (txFeePayer === feePayerPubkey) {
1089
+ console.log(`[SolanaFacilitator] Gasless mode: adding fee payer signature`);
1090
+ tx.partialSign(this.feePayerKeypair);
1091
+ }
1092
+ }
1093
+ txToSend = tx.serialize();
1094
+ } catch (e) {
1095
+ txToSend = txBuffer;
1096
+ }
1097
+ const signature = await connection.sendRawTransaction(txToSend, {
1098
+ skipPreflight: false,
1099
+ preflightCommitment: "confirmed"
1100
+ });
1101
+ const confirmation = await connection.confirmTransaction(signature, "confirmed");
1102
+ if (confirmation.value.err) {
1103
+ return {
1104
+ success: false,
1105
+ error: `Transaction failed: ${JSON.stringify(confirmation.value.err)}`,
1106
+ transaction: signature
1107
+ };
1108
+ }
1109
+ return {
1110
+ success: true,
1111
+ transaction: signature,
1112
+ status: "confirmed"
1113
+ };
1114
+ } catch (error) {
1115
+ return { success: false, error: error.message };
1116
+ }
1117
+ }
1118
+ supportsNetwork(network) {
1119
+ return this.supportedNetworks.includes(network);
1120
+ }
1121
+ };
1122
+ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
1123
+ const chainConfig = SOLANA_CHAINS[chain];
1124
+ const connection = new import_web32.Connection(chainConfig.rpc, "confirmed");
1125
+ const mint = new import_web32.PublicKey(chainConfig.tokens.USDC.mint);
1126
+ const actualFeePayer = feePayerPubkey || senderPubkey;
1127
+ const senderATA = await (0, import_spl_token.getAssociatedTokenAddress)(mint, senderPubkey);
1128
+ const recipientATA = await (0, import_spl_token.getAssociatedTokenAddress)(mint, recipientPubkey);
1129
+ const transaction = new import_web32.Transaction();
1130
+ try {
1131
+ await (0, import_spl_token.getAccount)(connection, recipientATA);
1132
+ } catch {
1133
+ transaction.add(
1134
+ (0, import_spl_token.createAssociatedTokenAccountInstruction)(
1135
+ actualFeePayer,
1136
+ // payer (fee payer in gasless mode)
1137
+ recipientATA,
1138
+ // ata to create
1139
+ recipientPubkey,
1140
+ // owner
1141
+ mint
1142
+ // mint
1143
+ )
1144
+ );
1145
+ }
1146
+ transaction.add(
1147
+ (0, import_spl_token.createTransferCheckedInstruction)(
1148
+ senderATA,
1149
+ // source
1150
+ mint,
1151
+ // mint
1152
+ recipientATA,
1153
+ // destination
1154
+ senderPubkey,
1155
+ // owner (sender still authorizes the transfer)
1156
+ amount,
1157
+ // amount
1158
+ chainConfig.tokens.USDC.decimals
1159
+ // decimals
1160
+ )
1161
+ );
1162
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
1163
+ transaction.recentBlockhash = blockhash;
1164
+ transaction.feePayer = actualFeePayer;
1165
+ return transaction;
1166
+ }
1167
+
547
1168
  // src/facilitators/registry.ts
1169
+ var import_web33 = require("@solana/web3.js");
1170
+ var import_bs58 = __toESM(require("bs58"));
548
1171
  var FacilitatorRegistry = class {
549
1172
  factories = /* @__PURE__ */ new Map();
550
1173
  instances = /* @__PURE__ */ new Map();
@@ -553,7 +1176,20 @@ var FacilitatorRegistry = class {
553
1176
  constructor(selection) {
554
1177
  this.registerFactory("cdp", (config) => new CDPFacilitator(config));
555
1178
  this.registerFactory("tempo", () => new TempoFacilitator());
556
- this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
1179
+ this.registerFactory("bnb", (config) => new BNBFacilitator(config?.serverPrivateKey));
1180
+ this.registerFactory("solana", (config) => {
1181
+ let feePayerKeypair;
1182
+ const feePayerKey = config?.feePayerPrivateKey || process.env.SOLANA_FEE_PAYER_KEY;
1183
+ if (feePayerKey) {
1184
+ try {
1185
+ feePayerKeypair = import_web33.Keypair.fromSecretKey(import_bs58.default.decode(feePayerKey));
1186
+ } catch (e) {
1187
+ console.warn(`[SolanaFacilitator] Invalid fee payer key: ${e.message}`);
1188
+ }
1189
+ }
1190
+ return new SolanaFacilitator({ feePayerKeypair });
1191
+ });
1192
+ this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
557
1193
  }
558
1194
  /**
559
1195
  * Register a new facilitator factory
@@ -807,14 +1443,40 @@ var TOKEN_ADDRESSES = {
807
1443
  // pathUSD
808
1444
  USDT: "0x20c0000000000000000000000000000000000001"
809
1445
  // alphaUSD
1446
+ },
1447
+ // BNB Smart Chain mainnet
1448
+ "eip155:56": {
1449
+ USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
1450
+ USDT: "0x55d398326f99059fF775485246999027B3197955"
1451
+ },
1452
+ // BNB Smart Chain testnet
1453
+ "eip155:97": {
1454
+ USDC: "0x64544969ed7EBf5f083679233325356EbE738930",
1455
+ USDT: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd"
1456
+ },
1457
+ // Solana networks use mint addresses (SPL tokens)
1458
+ "solana:mainnet": {
1459
+ USDC: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
1460
+ // Circle USDC
1461
+ },
1462
+ "solana:devnet": {
1463
+ USDC: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
1464
+ // Devnet USDC
810
1465
  }
811
1466
  };
812
1467
  var CHAIN_TO_NETWORK = {
813
1468
  "base": "eip155:8453",
814
1469
  "base_sepolia": "eip155:84532",
815
1470
  "polygon": "eip155:137",
816
- "tempo_moderato": "eip155:42431"
1471
+ "tempo_moderato": "eip155:42431",
1472
+ "bnb": "eip155:56",
1473
+ "bnb_testnet": "eip155:97",
1474
+ "solana": "solana:mainnet",
1475
+ "solana_devnet": "solana:devnet"
817
1476
  };
1477
+ function isSolanaNetwork(network) {
1478
+ return network.startsWith("solana:");
1479
+ }
818
1480
  var TOKEN_DOMAINS = {
819
1481
  // Base mainnet
820
1482
  "eip155:8453": {
@@ -836,6 +1498,16 @@ var TOKEN_DOMAINS = {
836
1498
  "eip155:42431": {
837
1499
  USDC: { name: "pathUSD", version: "1" },
838
1500
  USDT: { name: "alphaUSD", version: "1" }
1501
+ },
1502
+ // BNB Smart Chain mainnet
1503
+ "eip155:56": {
1504
+ USDC: { name: "USD Coin", version: "1" },
1505
+ USDT: { name: "Tether USD", version: "1" }
1506
+ },
1507
+ // BNB Smart Chain testnet
1508
+ "eip155:97": {
1509
+ USDC: { name: "USD Coin", version: "1" },
1510
+ USDT: { name: "Tether USD", version: "1" }
839
1511
  }
840
1512
  };
841
1513
  function getTokenDomain(network, token) {
@@ -893,7 +1565,7 @@ var MoltsPayServer = class {
893
1565
  };
894
1566
  this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
895
1567
  this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
896
- const defaultFallback = ["tempo"];
1568
+ const defaultFallback = ["tempo", "bnb", "solana"];
897
1569
  const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
898
1570
  const facilitatorConfig = options.facilitators || {
899
1571
  primary: process.env.FACILITATOR_PRIMARY || "cdp",
@@ -936,12 +1608,20 @@ var MoltsPayServer = class {
936
1608
  */
937
1609
  getProviderChains() {
938
1610
  const provider = this.manifest.provider;
1611
+ const getWalletForChain = (chainName, explicitWallet) => {
1612
+ if (explicitWallet) return explicitWallet;
1613
+ if ((chainName === "solana" || chainName === "solana_devnet") && provider.solana_wallet) {
1614
+ return provider.solana_wallet;
1615
+ }
1616
+ return provider.wallet;
1617
+ };
939
1618
  if (provider.chains && provider.chains.length > 0) {
940
1619
  return provider.chains.map((c) => {
941
1620
  const chainName = typeof c === "string" ? c : c.chain;
1621
+ const explicitWallet = typeof c === "object" ? c.wallet : null;
942
1622
  return {
943
1623
  network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
944
- wallet: (typeof c === "object" ? c.wallet : null) || provider.wallet,
1624
+ wallet: getWalletForChain(chainName, explicitWallet || void 0),
945
1625
  tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
946
1626
  };
947
1627
  });
@@ -950,7 +1630,7 @@ var MoltsPayServer = class {
950
1630
  const network = CHAIN_TO_NETWORK[chain] || this.networkId;
951
1631
  return [{
952
1632
  network,
953
- wallet: provider.wallet,
1633
+ wallet: getWalletForChain(chain),
954
1634
  tokens: ["USDC"]
955
1635
  }];
956
1636
  }
@@ -1021,7 +1701,8 @@ var MoltsPayServer = class {
1021
1701
  }
1022
1702
  const body = await this.readBody(req);
1023
1703
  const paymentHeader = req.headers[PAYMENT_HEADER];
1024
- return await this.handleProxy(body, paymentHeader, res);
1704
+ const authHeader = req.headers[MPP_AUTH_HEADER];
1705
+ return await this.handleProxy(body, paymentHeader, authHeader, res);
1025
1706
  }
1026
1707
  const servicePath = url.pathname.replace(/^\//, "");
1027
1708
  const skill = this.skills.get(servicePath);
@@ -1058,7 +1739,9 @@ var MoltsPayServer = class {
1058
1739
  name: this.manifest.provider.name,
1059
1740
  description: this.manifest.provider.description,
1060
1741
  wallet: this.manifest.provider.wallet,
1061
- chain: this.manifest.provider.chain || "base"
1742
+ chain: this.manifest.provider.chain || "base",
1743
+ solana_wallet: this.manifest.provider.solana_wallet,
1744
+ chains: this.manifest.provider.chains
1062
1745
  },
1063
1746
  services,
1064
1747
  endpoints: {
@@ -1171,6 +1854,21 @@ var MoltsPayServer = class {
1171
1854
  });
1172
1855
  }
1173
1856
  console.log(`[MoltsPay] Verified by ${verifyResult.facilitator}`);
1857
+ const isSolana = isSolanaNetwork(paymentNetwork);
1858
+ let settlement = null;
1859
+ if (isSolana) {
1860
+ console.log(`[MoltsPay] Solana detected - settling payment FIRST (blockhash expiry protection)`);
1861
+ try {
1862
+ settlement = await this.registry.settle(payment, requirements);
1863
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1864
+ } catch (err) {
1865
+ console.error("[MoltsPay] Solana settlement failed:", err.message);
1866
+ return this.sendJson(res, 402, {
1867
+ error: "Payment settlement failed",
1868
+ message: err.message
1869
+ });
1870
+ }
1871
+ }
1174
1872
  const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
1175
1873
  console.log(`[MoltsPay] Executing skill: ${service} (timeout: ${timeoutSeconds}s)`);
1176
1874
  let result;
@@ -1185,16 +1883,19 @@ var MoltsPayServer = class {
1185
1883
  console.error("[MoltsPay] Skill execution failed:", err.message);
1186
1884
  return this.sendJson(res, 500, {
1187
1885
  error: "Service execution failed",
1188
- message: err.message
1886
+ message: err.message,
1887
+ paymentSettled: isSolana ? true : false,
1888
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
1189
1889
  });
1190
1890
  }
1191
- console.log(`[MoltsPay] Skill succeeded, settling payment...`);
1192
- let settlement = null;
1193
- try {
1194
- settlement = await this.registry.settle(payment, requirements);
1195
- console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1196
- } catch (err) {
1197
- console.error("[MoltsPay] Settlement failed:", err.message);
1891
+ if (!isSolana) {
1892
+ console.log(`[MoltsPay] Skill succeeded, settling payment...`);
1893
+ try {
1894
+ settlement = await this.registry.settle(payment, requirements);
1895
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1896
+ } catch (err) {
1897
+ console.error("[MoltsPay] Settlement failed:", err.message);
1898
+ }
1198
1899
  }
1199
1900
  const responseHeaders = {};
1200
1901
  if (settlement?.success) {
@@ -1470,7 +2171,7 @@ var MoltsPayServer = class {
1470
2171
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
1471
2172
  const tokenAddress = tokenAddresses[selectedToken];
1472
2173
  const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
1473
- return {
2174
+ const requirements = {
1474
2175
  scheme: "exact",
1475
2176
  network: selectedNetwork,
1476
2177
  asset: tokenAddress,
@@ -1479,6 +2180,27 @@ var MoltsPayServer = class {
1479
2180
  maxTimeoutSeconds: 300,
1480
2181
  extra: tokenDomain
1481
2182
  };
2183
+ if (selectedNetwork === "solana:mainnet" || selectedNetwork === "solana:devnet") {
2184
+ const solanaFacilitator = this.registry.get("solana");
2185
+ const feePayerPubkey = solanaFacilitator?.getFeePayerPubkey?.();
2186
+ if (feePayerPubkey) {
2187
+ requirements.extra = {
2188
+ ...requirements.extra || {},
2189
+ solanaFeePayer: feePayerPubkey
2190
+ };
2191
+ }
2192
+ }
2193
+ if (selectedNetwork === "eip155:56" || selectedNetwork === "eip155:97") {
2194
+ const bnbFacilitator = this.registry.get("bnb");
2195
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
2196
+ if (spenderAddress) {
2197
+ requirements.extra = {
2198
+ ...requirements.extra || {},
2199
+ bnbSpender: spenderAddress
2200
+ };
2201
+ }
2202
+ }
2203
+ return requirements;
1482
2204
  }
1483
2205
  /**
1484
2206
  * Detect which token is being used in the payment
@@ -1544,31 +2266,42 @@ var MoltsPayServer = class {
1544
2266
  /**
1545
2267
  * POST /proxy - Handle payment for external services (moltspay-creators)
1546
2268
  *
1547
- * This endpoint allows other services to delegate x402 payment handling.
2269
+ * This endpoint allows other services to delegate x402/MPP payment handling.
1548
2270
  * It does NOT execute any skill - just handles payment verification/settlement.
1549
2271
  *
1550
2272
  * Request body:
1551
2273
  * { wallet, amount, currency, chain, memo, serviceId, description }
1552
2274
  *
1553
- * Without X-Payment header: returns 402 with payment requirements
1554
- * With X-Payment header: verifies payment and returns result
2275
+ * For x402 (base, polygon, base_sepolia):
2276
+ * Without X-Payment header: returns 402 with X-Payment-Required
2277
+ * With X-Payment header: verifies payment via CDP
2278
+ *
2279
+ * For MPP (tempo_moderato):
2280
+ * Without Authorization header: returns 402 with WWW-Authenticate
2281
+ * With Authorization: Payment header: verifies tx on Tempo chain
1555
2282
  */
1556
- async handleProxy(body, paymentHeader, res) {
2283
+ async handleProxy(body, paymentHeader, authHeader, res) {
1557
2284
  const { wallet, amount, currency, chain, memo, serviceId, description } = body;
1558
2285
  if (!wallet || !amount) {
1559
2286
  return this.sendJson(res, 400, { error: "Missing required fields: wallet, amount" });
1560
2287
  }
1561
- if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
1562
- return this.sendJson(res, 400, { error: "Invalid wallet address format" });
2288
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet", "solana", "solana_devnet"];
2289
+ if (chain && !supportedChains.includes(chain)) {
2290
+ return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
2291
+ }
2292
+ const isSolanaChain = chain === "solana" || chain === "solana_devnet";
2293
+ const isValidEvmAddress = /^0x[a-fA-F0-9]{40}$/.test(wallet);
2294
+ const isValidSolanaAddress = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(wallet);
2295
+ if (isSolanaChain && !isValidSolanaAddress) {
2296
+ return this.sendJson(res, 400, { error: "Invalid Solana wallet address format" });
2297
+ }
2298
+ if (!isSolanaChain && !isValidEvmAddress) {
2299
+ return this.sendJson(res, 400, { error: "Invalid EVM wallet address format" });
1563
2300
  }
1564
2301
  const amountNum = parseFloat(amount);
1565
2302
  if (isNaN(amountNum) || amountNum <= 0) {
1566
2303
  return this.sendJson(res, 400, { error: "Invalid amount" });
1567
2304
  }
1568
- const supportedChains = ["base", "polygon", "base_sepolia"];
1569
- if (chain && !supportedChains.includes(chain)) {
1570
- return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
1571
- }
1572
2305
  const proxyConfig = {
1573
2306
  id: serviceId || "proxy",
1574
2307
  name: description || "Proxy Payment",
@@ -1580,6 +2313,9 @@ var MoltsPayServer = class {
1580
2313
  input: {},
1581
2314
  output: {}
1582
2315
  };
2316
+ if (chain === "tempo_moderato") {
2317
+ return await this.handleProxyMPP(body, proxyConfig, authHeader, res);
2318
+ }
1583
2319
  const requirements = this.buildProxyPaymentRequirements(proxyConfig, wallet, currency, chain);
1584
2320
  if (!paymentHeader) {
1585
2321
  return this.sendProxyPaymentRequired(proxyConfig, wallet, memo, chain, res);
@@ -1591,37 +2327,225 @@ var MoltsPayServer = class {
1591
2327
  } catch {
1592
2328
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
1593
2329
  }
1594
- if (payment.x402Version !== X402_VERSION2) {
1595
- return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
2330
+ if (payment.x402Version !== X402_VERSION2) {
2331
+ return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
2332
+ }
2333
+ const scheme = payment.accepted?.scheme || payment.scheme;
2334
+ const network = payment.accepted?.network || payment.network;
2335
+ if (scheme !== "exact") {
2336
+ return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
2337
+ }
2338
+ const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
2339
+ if (network !== expectedNetwork) {
2340
+ return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
2341
+ }
2342
+ console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
2343
+ const verifyResult = await this.registry.verify(payment, requirements);
2344
+ if (!verifyResult.valid) {
2345
+ return this.sendJson(res, 402, {
2346
+ success: false,
2347
+ error: `Payment verification failed: ${verifyResult.error}`,
2348
+ facilitator: verifyResult.facilitator
2349
+ });
2350
+ }
2351
+ console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
2352
+ const { execute, service, params } = body;
2353
+ if (execute && service) {
2354
+ const skill = this.skills.get(service);
2355
+ if (!skill) {
2356
+ console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
2357
+ return this.sendJson(res, 404, {
2358
+ success: false,
2359
+ paymentSettled: false,
2360
+ error: `Service not found: ${service}`
2361
+ });
2362
+ }
2363
+ const isSolana = isSolanaNetwork(network);
2364
+ let settlement2 = null;
2365
+ if (isSolana) {
2366
+ console.log(`[MoltsPay] /proxy: Solana detected - settling payment FIRST`);
2367
+ try {
2368
+ settlement2 = await this.registry.settle(payment, requirements);
2369
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
2370
+ if (!settlement2.success) {
2371
+ console.error(`[MoltsPay] /proxy: Solana settlement failed: ${settlement2.error}`);
2372
+ return this.sendJson(res, 402, {
2373
+ success: false,
2374
+ paymentSettled: false,
2375
+ error: `Payment settlement failed: ${settlement2.error || "Unknown error"}`
2376
+ });
2377
+ }
2378
+ } catch (err) {
2379
+ console.error("[MoltsPay] /proxy: Solana settlement failed:", err.message);
2380
+ return this.sendJson(res, 402, {
2381
+ success: false,
2382
+ paymentSettled: false,
2383
+ error: `Payment settlement failed: ${err.message}`
2384
+ });
2385
+ }
2386
+ } else {
2387
+ console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
2388
+ }
2389
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
2390
+ let result;
2391
+ try {
2392
+ result = await Promise.race([
2393
+ skill.handler(params || {}),
2394
+ new Promise(
2395
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
2396
+ )
2397
+ ]);
2398
+ console.log(`[MoltsPay] /proxy: Skill succeeded`);
2399
+ } catch (err) {
2400
+ console.error(`[MoltsPay] /proxy: Skill failed: ${err.message}`);
2401
+ return this.sendJson(res, 500, {
2402
+ success: false,
2403
+ paymentSettled: isSolana ? true : false,
2404
+ error: `Service execution failed: ${err.message}`,
2405
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
2406
+ });
2407
+ }
2408
+ if (!isSolana) {
2409
+ console.log(`[MoltsPay] /proxy: Settling payment...`);
2410
+ try {
2411
+ settlement2 = await this.registry.settle(payment, requirements);
2412
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
2413
+ } catch (err) {
2414
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
2415
+ return this.sendJson(res, 200, {
2416
+ success: true,
2417
+ verified: true,
2418
+ settled: false,
2419
+ settlementError: err.message,
2420
+ from: payment.payload?.authorization?.from,
2421
+ paidTo: wallet,
2422
+ amount: amountNum,
2423
+ currency: currency || "USDC",
2424
+ memo,
2425
+ result
2426
+ });
2427
+ }
2428
+ }
2429
+ return this.sendJson(res, 200, {
2430
+ success: true,
2431
+ verified: true,
2432
+ settled: settlement2?.success || false,
2433
+ txHash: settlement2?.transaction,
2434
+ from: payment.payload?.authorization?.from,
2435
+ paidTo: wallet,
2436
+ amount: amountNum,
2437
+ currency: currency || "USDC",
2438
+ facilitator: settlement2?.facilitator,
2439
+ memo,
2440
+ result
2441
+ });
2442
+ }
2443
+ console.log(`[MoltsPay] /proxy: Settling payment (no execution)...`);
2444
+ let settlement = null;
2445
+ try {
2446
+ settlement = await this.registry.settle(payment, requirements);
2447
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2448
+ } catch (err) {
2449
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
2450
+ return this.sendJson(res, 500, {
2451
+ success: false,
2452
+ error: `Settlement failed: ${err.message}`
2453
+ });
2454
+ }
2455
+ this.sendJson(res, 200, {
2456
+ success: true,
2457
+ verified: true,
2458
+ settled: settlement?.success || false,
2459
+ txHash: settlement?.transaction,
2460
+ from: payment.payload?.authorization?.from,
2461
+ // Buyer's wallet address
2462
+ paidTo: wallet,
2463
+ amount: amountNum,
2464
+ currency: currency || "USDC",
2465
+ facilitator: settlement?.facilitator,
2466
+ memo
2467
+ });
2468
+ }
2469
+ /**
2470
+ * Handle MPP payment flow for /proxy endpoint (tempo_moderato chain)
2471
+ */
2472
+ async handleProxyMPP(body, config, authHeader, res) {
2473
+ const { wallet, amount, memo, serviceId } = body;
2474
+ const amountNum = parseFloat(amount);
2475
+ const amountInUnits = Math.floor(amountNum * 1e6).toString();
2476
+ if (!authHeader || !authHeader.toLowerCase().startsWith("payment ")) {
2477
+ const challengeId = this.generateChallengeId();
2478
+ const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
2479
+ const mppRequest = {
2480
+ amount: amountInUnits,
2481
+ currency: tokenAddress,
2482
+ methodDetails: {
2483
+ chainId: 42431,
2484
+ feePayer: true
2485
+ },
2486
+ recipient: wallet
2487
+ };
2488
+ const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
2489
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
2490
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
2491
+ res.writeHead(402, {
2492
+ "Content-Type": "application/problem+json",
2493
+ [MPP_WWW_AUTH_HEADER]: wwwAuth
2494
+ });
2495
+ res.end(JSON.stringify({
2496
+ type: "https://paymentauth.org/problems/payment-required",
2497
+ title: "Payment Required",
2498
+ status: 402,
2499
+ detail: `Payment is required (${config.name}).`,
2500
+ service: serviceId || "proxy",
2501
+ price: amountNum,
2502
+ currency: "USDC"
2503
+ }, null, 2));
2504
+ return;
2505
+ }
2506
+ const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
2507
+ if (!credentialMatch) {
2508
+ return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
1596
2509
  }
1597
- const scheme = payment.accepted?.scheme || payment.scheme;
1598
- const network = payment.accepted?.network || payment.network;
1599
- if (scheme !== "exact") {
1600
- return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
2510
+ let mppCredential;
2511
+ try {
2512
+ const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
2513
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
2514
+ mppCredential = JSON.parse(decoded);
2515
+ } catch (err) {
2516
+ console.error("[MoltsPay] /proxy MPP: Failed to parse credential:", err);
2517
+ return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
1601
2518
  }
1602
- const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
1603
- if (network !== expectedNetwork) {
1604
- return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
2519
+ let txHash;
2520
+ if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
2521
+ txHash = mppCredential.payload.hash;
2522
+ } else {
2523
+ return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
1605
2524
  }
1606
- console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
1607
- const verifyResult = await this.registry.verify(payment, requirements);
1608
- if (!verifyResult.valid) {
2525
+ console.log(`[MoltsPay] /proxy MPP: Verifying tx ${txHash} on Tempo...`);
2526
+ const requirements = this.buildPaymentRequirements(config, "eip155:42431", wallet, "USDC");
2527
+ const paymentPayload = {
2528
+ x402Version: X402_VERSION2,
2529
+ scheme: "exact",
2530
+ network: "eip155:42431",
2531
+ payload: { txHash, chainId: 42431 }
2532
+ };
2533
+ const verification = await this.registry.verify(paymentPayload, requirements);
2534
+ if (!verification.valid) {
1609
2535
  return this.sendJson(res, 402, {
1610
- success: false,
1611
- error: `Payment verification failed: ${verifyResult.error}`,
1612
- facilitator: verifyResult.facilitator
2536
+ error: `Payment verification failed: ${verification.error}`
1613
2537
  });
1614
2538
  }
1615
- console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
2539
+ console.log(`[MoltsPay] /proxy MPP: Payment verified by ${verification.facilitator}`);
1616
2540
  const { execute, service, params } = body;
1617
2541
  if (execute && service) {
1618
- console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
2542
+ console.log(`[MoltsPay] /proxy MPP: Executing skill: ${service}`);
1619
2543
  const skill = this.skills.get(service);
1620
2544
  if (!skill) {
1621
- console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
1622
2545
  return this.sendJson(res, 404, {
1623
2546
  success: false,
1624
- paymentSettled: false,
2547
+ paymentSettled: true,
2548
+ // Payment already happened on Tempo
1625
2549
  error: `Service not found: ${service}`
1626
2550
  });
1627
2551
  }
@@ -1634,73 +2558,36 @@ var MoltsPayServer = class {
1634
2558
  (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
1635
2559
  )
1636
2560
  ]);
1637
- console.log(`[MoltsPay] /proxy: Skill succeeded, now settling payment...`);
1638
2561
  } catch (err) {
1639
- console.error(`[MoltsPay] /proxy: Skill failed: ${err.message} - NOT settling`);
2562
+ console.error(`[MoltsPay] /proxy MPP: Skill failed: ${err.message}`);
1640
2563
  return this.sendJson(res, 500, {
1641
2564
  success: false,
1642
- paymentSettled: false,
2565
+ paymentSettled: true,
1643
2566
  error: `Service execution failed: ${err.message}`
1644
2567
  });
1645
2568
  }
1646
- let settlement2 = null;
1647
- try {
1648
- settlement2 = await this.registry.settle(payment, requirements);
1649
- console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
1650
- } catch (err) {
1651
- console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
1652
- return this.sendJson(res, 200, {
1653
- success: true,
1654
- verified: true,
1655
- settled: false,
1656
- settlementError: err.message,
1657
- from: payment.payload?.authorization?.from,
1658
- // Buyer's wallet address
1659
- paidTo: wallet,
1660
- amount: amountNum,
1661
- currency: currency || "USDC",
1662
- memo,
1663
- result
1664
- });
1665
- }
1666
2569
  return this.sendJson(res, 200, {
1667
2570
  success: true,
1668
2571
  verified: true,
1669
- settled: settlement2?.success || false,
1670
- txHash: settlement2?.transaction,
1671
- from: payment.payload?.authorization?.from,
1672
- // Buyer's wallet address
2572
+ txHash,
2573
+ chain: "tempo_moderato",
1673
2574
  paidTo: wallet,
1674
2575
  amount: amountNum,
1675
- currency: currency || "USDC",
1676
- facilitator: settlement2?.facilitator,
2576
+ currency: "USDC",
2577
+ facilitator: verification.facilitator,
1677
2578
  memo,
1678
2579
  result
1679
2580
  });
1680
2581
  }
1681
- console.log(`[MoltsPay] /proxy: Settling payment (no execution)...`);
1682
- let settlement = null;
1683
- try {
1684
- settlement = await this.registry.settle(payment, requirements);
1685
- console.log(`[MoltsPay] /proxy: Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1686
- } catch (err) {
1687
- console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
1688
- return this.sendJson(res, 500, {
1689
- success: false,
1690
- error: `Settlement failed: ${err.message}`
1691
- });
1692
- }
1693
2582
  this.sendJson(res, 200, {
1694
2583
  success: true,
1695
2584
  verified: true,
1696
- settled: settlement?.success || false,
1697
- txHash: settlement?.transaction,
1698
- from: payment.payload?.authorization?.from,
1699
- // Buyer's wallet address
2585
+ txHash,
2586
+ chain: "tempo_moderato",
1700
2587
  paidTo: wallet,
1701
2588
  amount: amountNum,
1702
- currency: currency || "USDC",
1703
- facilitator: settlement?.facilitator,
2589
+ currency: "USDC",
2590
+ facilitator: verification.facilitator,
1704
2591
  memo
1705
2592
  });
1706
2593
  }
@@ -1715,7 +2602,7 @@ var MoltsPayServer = class {
1715
2602
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
1716
2603
  const tokenAddress = tokenAddresses[selectedToken];
1717
2604
  const tokenDomain = getTokenDomain(networkId, selectedToken);
1718
- return {
2605
+ const requirements = {
1719
2606
  scheme: "exact",
1720
2607
  network: networkId,
1721
2608
  asset: tokenAddress,
@@ -1725,6 +2612,17 @@ var MoltsPayServer = class {
1725
2612
  maxTimeoutSeconds: 300,
1726
2613
  extra: tokenDomain
1727
2614
  };
2615
+ if (networkId === "eip155:56" || networkId === "eip155:97") {
2616
+ const bnbFacilitator = this.registry.get("bnb");
2617
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
2618
+ if (spenderAddress) {
2619
+ requirements.extra = {
2620
+ ...requirements.extra || {},
2621
+ bnbSpender: spenderAddress
2622
+ };
2623
+ }
2624
+ }
2625
+ return requirements;
1728
2626
  }
1729
2627
  /**
1730
2628
  * Return 402 with x402 payment requirements for proxy endpoint
@@ -1755,10 +2653,40 @@ var MoltsPayServer = class {
1755
2653
  };
1756
2654
 
1757
2655
  // src/client/index.ts
2656
+ var import_fs4 = require("fs");
2657
+ var import_os2 = require("os");
2658
+ var import_path2 = require("path");
2659
+ var import_ethers = require("ethers");
2660
+
2661
+ // src/wallet/solana.ts
2662
+ var import_web34 = require("@solana/web3.js");
2663
+ var import_spl_token2 = require("@solana/spl-token");
1758
2664
  var import_fs3 = require("fs");
1759
- var import_os = require("os");
1760
2665
  var import_path = require("path");
1761
- var import_ethers = require("ethers");
2666
+ var import_os = require("os");
2667
+ var import_bs582 = __toESM(require("bs58"));
2668
+ var DEFAULT_CONFIG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".moltspay");
2669
+ var SOLANA_WALLET_FILE = "wallet-solana.json";
2670
+ function getSolanaWalletPath(configDir = DEFAULT_CONFIG_DIR) {
2671
+ return (0, import_path.join)(configDir, SOLANA_WALLET_FILE);
2672
+ }
2673
+ function loadSolanaWallet(configDir = DEFAULT_CONFIG_DIR) {
2674
+ const walletPath = getSolanaWalletPath(configDir);
2675
+ if (!(0, import_fs3.existsSync)(walletPath)) {
2676
+ return null;
2677
+ }
2678
+ try {
2679
+ const data = JSON.parse((0, import_fs3.readFileSync)(walletPath, "utf-8"));
2680
+ const secretKey = import_bs582.default.decode(data.secretKey);
2681
+ return import_web34.Keypair.fromSecretKey(secretKey);
2682
+ } catch (error) {
2683
+ console.error("Failed to load Solana wallet:", error);
2684
+ return null;
2685
+ }
2686
+ }
2687
+
2688
+ // src/client/index.ts
2689
+ var import_web35 = require("@solana/web3.js");
1762
2690
  var X402_VERSION3 = 2;
1763
2691
  var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
1764
2692
  var PAYMENT_HEADER2 = "x-payment";
@@ -1777,7 +2705,7 @@ var MoltsPayClient = class {
1777
2705
  todaySpending = 0;
1778
2706
  lastSpendingReset = 0;
1779
2707
  constructor(options = {}) {
1780
- this.configDir = options.configDir || (0, import_path.join)((0, import_os.homedir)(), ".moltspay");
2708
+ this.configDir = options.configDir || (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
1781
2709
  this.config = this.loadConfig();
1782
2710
  this.walletData = this.loadWallet();
1783
2711
  this.loadSpending();
@@ -1797,6 +2725,12 @@ var MoltsPayClient = class {
1797
2725
  get address() {
1798
2726
  return this.wallet?.address || null;
1799
2727
  }
2728
+ /**
2729
+ * Get wallet instance (for direct operations like approvals)
2730
+ */
2731
+ getWallet() {
2732
+ return this.wallet;
2733
+ }
1800
2734
  /**
1801
2735
  * Get current config
1802
2736
  */
@@ -1866,9 +2800,14 @@ var MoltsPayClient = class {
1866
2800
  }
1867
2801
  throw new Error(data.error || "Unexpected response");
1868
2802
  }
2803
+ const wwwAuthHeader = initialRes.headers.get("www-authenticate");
1869
2804
  const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER2);
2805
+ if (wwwAuthHeader && wwwAuthHeader.toLowerCase().includes("payment")) {
2806
+ console.log("[MoltsPay] Detected MPP protocol, using Tempo flow...");
2807
+ return await this.handleMPPPayment(serverUrl, service, params, wwwAuthHeader);
2808
+ }
1870
2809
  if (!paymentRequiredHeader) {
1871
- throw new Error("Missing x-payment-required header");
2810
+ throw new Error("Missing payment header (x-payment-required or www-authenticate)");
1872
2811
  }
1873
2812
  let requirements;
1874
2813
  try {
@@ -1885,17 +2824,22 @@ var MoltsPayClient = class {
1885
2824
  throw new Error("Invalid x-payment-required header");
1886
2825
  }
1887
2826
  const networkToChainName = (network2) => {
2827
+ if (network2 === "solana:mainnet") return "solana";
2828
+ if (network2 === "solana:devnet") return "solana_devnet";
1888
2829
  const match = network2.match(/^eip155:(\d+)$/);
1889
2830
  if (!match) return null;
1890
2831
  const chainId = parseInt(match[1]);
1891
2832
  if (chainId === 8453) return "base";
1892
2833
  if (chainId === 137) return "polygon";
1893
2834
  if (chainId === 84532) return "base_sepolia";
2835
+ if (chainId === 42431) return "tempo_moderato";
2836
+ if (chainId === 56) return "bnb";
2837
+ if (chainId === 97) return "bnb_testnet";
1894
2838
  return null;
1895
2839
  };
1896
2840
  const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
1897
- let chainName;
1898
2841
  const userSpecifiedChain = options.chain;
2842
+ let selectedChain;
1899
2843
  if (userSpecifiedChain) {
1900
2844
  if (!serverChains.includes(userSpecifiedChain)) {
1901
2845
  throw new Error(
@@ -1903,17 +2847,27 @@ var MoltsPayClient = class {
1903
2847
  Server accepts: ${serverChains.join(", ")}`
1904
2848
  );
1905
2849
  }
1906
- chainName = userSpecifiedChain;
2850
+ selectedChain = userSpecifiedChain;
1907
2851
  } else {
1908
2852
  if (serverChains.length === 1 && serverChains[0] === "base") {
1909
- chainName = "base";
2853
+ selectedChain = "base";
1910
2854
  } else {
1911
2855
  throw new Error(
1912
2856
  `Server accepts: ${serverChains.join(", ")}
1913
- Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2857
+ Please specify: --chain <chain_name>`
1914
2858
  );
1915
2859
  }
1916
2860
  }
2861
+ if (selectedChain === "solana" || selectedChain === "solana_devnet") {
2862
+ const solanaChain = selectedChain;
2863
+ const network2 = solanaChain === "solana" ? "solana:mainnet" : "solana:devnet";
2864
+ const req2 = requirements.find((r) => r.network === network2);
2865
+ if (!req2) {
2866
+ throw new Error(`Failed to find payment requirement for ${selectedChain}`);
2867
+ }
2868
+ return await this.handleSolanaPayment(serverUrl, service, params, req2, solanaChain);
2869
+ }
2870
+ const chainName = selectedChain;
1917
2871
  const chain = getChain(chainName);
1918
2872
  const network = `eip155:${chain.chainId}`;
1919
2873
  const req = requirements.find((r) => r.scheme === "exact" && r.network === network);
@@ -1948,6 +2902,25 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
1948
2902
  } else {
1949
2903
  console.log(`[MoltsPay] Signing payment: $${amount} ${token} (gasless)`);
1950
2904
  }
2905
+ if (chainName === "bnb" || chainName === "bnb_testnet") {
2906
+ console.log(`[MoltsPay] Using BNB intent-based payment flow...`);
2907
+ const payTo2 = req.payTo || req.resource;
2908
+ if (!payTo2) {
2909
+ throw new Error("Missing payTo address in payment requirements");
2910
+ }
2911
+ const bnbSpender = req.extra?.bnbSpender;
2912
+ if (!bnbSpender) {
2913
+ throw new Error("Server did not provide bnbSpender address. Server may not support BNB payments.");
2914
+ }
2915
+ return await this.handleBNBPayment(serverUrl, service, params, {
2916
+ to: payTo2,
2917
+ amount,
2918
+ token,
2919
+ chainName,
2920
+ chain,
2921
+ spender: bnbSpender
2922
+ });
2923
+ }
1951
2924
  const payTo = req.payTo || req.resource;
1952
2925
  if (!payTo) {
1953
2926
  throw new Error("Missing payTo address in payment requirements");
@@ -1997,6 +2970,300 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
1997
2970
  console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
1998
2971
  return result.result;
1999
2972
  }
2973
+ /**
2974
+ * Handle MPP (Machine Payments Protocol) payment flow
2975
+ * Called when pay() detects WWW-Authenticate header in 402 response
2976
+ */
2977
+ async handleMPPPayment(serverUrl, service, params, wwwAuthHeader) {
2978
+ const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
2979
+ const { createWalletClient, createPublicClient, http } = await import("viem");
2980
+ const { tempoModerato } = await import("viem/chains");
2981
+ const { Actions } = await import("viem/tempo");
2982
+ const privateKey = this.walletData.privateKey;
2983
+ const account = privateKeyToAccount2(privateKey);
2984
+ console.log(`[MoltsPay] Using MPP protocol on Tempo`);
2985
+ console.log(`[MoltsPay] Account: ${account.address}`);
2986
+ const parseAuthParam = (header, key) => {
2987
+ const match = header.match(new RegExp(`${key}="([^"]+)"`, "i"));
2988
+ return match ? match[1] : null;
2989
+ };
2990
+ const challengeId = parseAuthParam(wwwAuthHeader, "id");
2991
+ const method = parseAuthParam(wwwAuthHeader, "method");
2992
+ const realm = parseAuthParam(wwwAuthHeader, "realm");
2993
+ const requestB64 = parseAuthParam(wwwAuthHeader, "request");
2994
+ if (method !== "tempo") {
2995
+ throw new Error(`Unsupported payment method: ${method}`);
2996
+ }
2997
+ if (!requestB64) {
2998
+ throw new Error("Missing request in WWW-Authenticate");
2999
+ }
3000
+ const requestJson = Buffer.from(requestB64, "base64").toString("utf-8");
3001
+ const paymentRequest = JSON.parse(requestJson);
3002
+ const { amount, currency, recipient, methodDetails } = paymentRequest;
3003
+ const chainId = methodDetails?.chainId || 42431;
3004
+ const amountDisplay = Number(amount) / 1e6;
3005
+ console.log(`[MoltsPay] Payment: $${amountDisplay} to ${recipient}`);
3006
+ this.checkLimits(amountDisplay);
3007
+ console.log(`[MoltsPay] Sending transaction on Tempo...`);
3008
+ const tempoChain = { ...tempoModerato, feeToken: currency };
3009
+ const publicClient = createPublicClient({
3010
+ chain: tempoChain,
3011
+ transport: http("https://rpc.moderato.tempo.xyz")
3012
+ });
3013
+ const walletClient = createWalletClient({
3014
+ account,
3015
+ chain: tempoChain,
3016
+ transport: http("https://rpc.moderato.tempo.xyz")
3017
+ });
3018
+ const txHash = await Actions.token.transfer(walletClient, {
3019
+ to: recipient,
3020
+ amount: BigInt(amount),
3021
+ token: currency
3022
+ });
3023
+ console.log(`[MoltsPay] Transaction: ${txHash}`);
3024
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
3025
+ console.log(`[MoltsPay] Confirmed! Retrying with credential...`);
3026
+ const credential = {
3027
+ challenge: {
3028
+ id: challengeId,
3029
+ realm,
3030
+ method: "tempo",
3031
+ intent: "charge",
3032
+ request: paymentRequest
3033
+ },
3034
+ payload: { hash: txHash, type: "hash" },
3035
+ source: `did:pkh:eip155:${chainId}:${account.address}`
3036
+ };
3037
+ const credentialB64 = Buffer.from(JSON.stringify(credential)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3038
+ const paidRes = await fetch(`${serverUrl}/execute`, {
3039
+ method: "POST",
3040
+ headers: {
3041
+ "Content-Type": "application/json",
3042
+ "Authorization": `Payment ${credentialB64}`
3043
+ },
3044
+ body: JSON.stringify({ service, params, chain: "tempo_moderato" })
3045
+ });
3046
+ const result = await paidRes.json();
3047
+ if (!paidRes.ok) {
3048
+ throw new Error(result.error || "Payment verification failed");
3049
+ }
3050
+ this.recordSpending(amountDisplay);
3051
+ console.log(`[MoltsPay] Success!`);
3052
+ return result.result || result;
3053
+ }
3054
+ /**
3055
+ * Handle BNB Chain payment flow (pre-approval + intent signature)
3056
+ *
3057
+ * Flow:
3058
+ * 1. Check client has approved server wallet (done via `moltspay init`)
3059
+ * 2. Sign EIP-712 payment intent (no gas, just signature)
3060
+ * 3. Send intent to server
3061
+ * 4. Server executes service
3062
+ * 5. Server calls transferFrom if successful (pay-for-success)
3063
+ */
3064
+ async handleBNBPayment(serverUrl, service, params, paymentDetails) {
3065
+ const { to, amount, token, chainName, chain, spender } = paymentDetails;
3066
+ const tokenConfig = chain.tokens[token];
3067
+ const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
3068
+ const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
3069
+ const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
3070
+ if (allowance < amountWeiCheck) {
3071
+ const nativeBalance = await provider.getBalance(this.wallet.address);
3072
+ const minGasBalance = import_ethers.ethers.parseEther("0.0005");
3073
+ if (nativeBalance < minGasBalance) {
3074
+ const nativeBNB = parseFloat(import_ethers.ethers.formatEther(nativeBalance)).toFixed(4);
3075
+ const isTestnet = chainName === "bnb_testnet";
3076
+ if (isTestnet) {
3077
+ throw new Error(
3078
+ `\u274C Insufficient tBNB for approval transaction
3079
+
3080
+ Current tBNB: ${nativeBNB}
3081
+ Required: ~0.001 tBNB
3082
+
3083
+ Get testnet tokens: npx moltspay faucet --chain bnb_testnet
3084
+ (Gives USDC + tBNB for gas)`
3085
+ );
3086
+ } else {
3087
+ throw new Error(
3088
+ `\u274C Insufficient BNB for approval transaction
3089
+
3090
+ Current BNB: ${nativeBNB}
3091
+ Required: ~0.001 BNB (~$0.60)
3092
+
3093
+ To get BNB:
3094
+ \u2022 Withdraw from Binance/exchange to your wallet
3095
+ \u2022 Most exchanges include BNB dust with withdrawals
3096
+
3097
+ After funding, run:
3098
+ npx moltspay approve --chain ${chainName} --spender ${spender}`
3099
+ );
3100
+ }
3101
+ }
3102
+ throw new Error(
3103
+ `Insufficient allowance for ${spender.slice(0, 10)}...
3104
+ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
3105
+ );
3106
+ }
3107
+ const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
3108
+ const intent = {
3109
+ from: this.wallet.address,
3110
+ to,
3111
+ amount: amountWei,
3112
+ token: tokenConfig.address,
3113
+ service,
3114
+ nonce: Date.now(),
3115
+ // Use timestamp as nonce for simplicity
3116
+ deadline: Date.now() + 36e5
3117
+ // 1 hour
3118
+ };
3119
+ const domain = {
3120
+ name: "MoltsPay",
3121
+ version: "1",
3122
+ chainId: chain.chainId
3123
+ };
3124
+ const types = {
3125
+ PaymentIntent: [
3126
+ { name: "from", type: "address" },
3127
+ { name: "to", type: "address" },
3128
+ { name: "amount", type: "uint256" },
3129
+ { name: "token", type: "address" },
3130
+ { name: "service", type: "string" },
3131
+ { name: "nonce", type: "uint256" },
3132
+ { name: "deadline", type: "uint256" }
3133
+ ]
3134
+ };
3135
+ console.log(`[MoltsPay] Signing BNB payment intent...`);
3136
+ const signature = await this.wallet.signTypedData(domain, types, intent);
3137
+ const network = `eip155:${chain.chainId}`;
3138
+ const payload = {
3139
+ x402Version: 2,
3140
+ scheme: "exact",
3141
+ network,
3142
+ payload: {
3143
+ intent: {
3144
+ ...intent,
3145
+ signature
3146
+ },
3147
+ chainId: chain.chainId
3148
+ },
3149
+ accepted: {
3150
+ scheme: "exact",
3151
+ network,
3152
+ asset: tokenConfig.address,
3153
+ amount: amountWei,
3154
+ payTo: to,
3155
+ maxTimeoutSeconds: 300
3156
+ }
3157
+ };
3158
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
3159
+ console.log(`[MoltsPay] Sending BNB payment request...`);
3160
+ const paidRes = await fetch(`${serverUrl}/execute`, {
3161
+ method: "POST",
3162
+ headers: {
3163
+ "Content-Type": "application/json",
3164
+ "X-Payment": paymentHeader
3165
+ },
3166
+ body: JSON.stringify({ service, params, chain: chainName })
3167
+ });
3168
+ const result = await paidRes.json();
3169
+ if (!paidRes.ok) {
3170
+ throw new Error(result.error || "BNB payment failed");
3171
+ }
3172
+ this.recordSpending(amount);
3173
+ console.log(`[MoltsPay] Success! BNB payment settled.`);
3174
+ return result.result || result;
3175
+ }
3176
+ /**
3177
+ * Handle Solana payment flow
3178
+ *
3179
+ * Solana uses SPL token transfers with pay-for-success model:
3180
+ * 1. Client creates and signs a transfer transaction
3181
+ * 2. Server submits the transaction after service completes
3182
+ */
3183
+ async handleSolanaPayment(serverUrl, service, params, requirements, chain) {
3184
+ const solanaWallet = loadSolanaWallet(this.configDir);
3185
+ if (!solanaWallet) {
3186
+ throw new Error("No Solana wallet found. Run: npx moltspay init --chain solana_devnet");
3187
+ }
3188
+ const amount = Number(requirements.amount);
3189
+ const amountUSDC = amount / 1e6;
3190
+ this.checkLimits(amountUSDC);
3191
+ console.log(`[MoltsPay] Creating Solana payment: $${amountUSDC} USDC`);
3192
+ if (!requirements.payTo) {
3193
+ throw new Error("Missing payTo address in payment requirements");
3194
+ }
3195
+ const solanaFeePayer = requirements.extra?.solanaFeePayer;
3196
+ const feePayerPubkey = solanaFeePayer ? new import_web35.PublicKey(solanaFeePayer) : void 0;
3197
+ if (feePayerPubkey) {
3198
+ console.log(`[MoltsPay] Gasless mode: server pays fees`);
3199
+ }
3200
+ const recipientPubkey = new import_web35.PublicKey(requirements.payTo);
3201
+ const transaction = await createSolanaPaymentTransaction(
3202
+ solanaWallet.publicKey,
3203
+ recipientPubkey,
3204
+ BigInt(amount),
3205
+ chain,
3206
+ feePayerPubkey
3207
+ // Optional fee payer for gasless mode
3208
+ );
3209
+ if (feePayerPubkey) {
3210
+ transaction.partialSign(solanaWallet);
3211
+ } else {
3212
+ transaction.sign(solanaWallet);
3213
+ }
3214
+ const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
3215
+ console.log(`[MoltsPay] Transaction signed, sending to server...`);
3216
+ const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
3217
+ const payload = {
3218
+ x402Version: 2,
3219
+ scheme: "exact",
3220
+ network,
3221
+ payload: {
3222
+ signedTransaction: signedTx,
3223
+ sender: solanaWallet.publicKey.toBase58(),
3224
+ chain
3225
+ },
3226
+ accepted: {
3227
+ scheme: "exact",
3228
+ network,
3229
+ asset: requirements.asset,
3230
+ amount: requirements.amount,
3231
+ payTo: requirements.payTo,
3232
+ maxTimeoutSeconds: 300
3233
+ }
3234
+ };
3235
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
3236
+ const paidRes = await fetch(`${serverUrl}/execute`, {
3237
+ method: "POST",
3238
+ headers: {
3239
+ "Content-Type": "application/json",
3240
+ "X-Payment": paymentHeader
3241
+ },
3242
+ body: JSON.stringify({ service, params, chain })
3243
+ });
3244
+ const result = await paidRes.json();
3245
+ if (!paidRes.ok) {
3246
+ throw new Error(result.error || "Solana payment failed");
3247
+ }
3248
+ this.recordSpending(amountUSDC);
3249
+ console.log(`[MoltsPay] Success! Solana payment settled.`);
3250
+ if (result.payment?.transaction) {
3251
+ const explorerUrl = chain === "solana" ? `https://solscan.io/tx/${result.payment.transaction}` : `https://solscan.io/tx/${result.payment.transaction}?cluster=devnet`;
3252
+ console.log(`[MoltsPay] Transaction: ${explorerUrl}`);
3253
+ }
3254
+ return result.result || result;
3255
+ }
3256
+ /**
3257
+ * Check ERC20 allowance for a spender
3258
+ */
3259
+ async checkAllowance(tokenAddress, spender, provider) {
3260
+ const contract = new import_ethers.ethers.Contract(
3261
+ tokenAddress,
3262
+ ["function allowance(address owner, address spender) view returns (uint256)"],
3263
+ provider
3264
+ );
3265
+ return await contract.allowance(this.wallet.address, spender);
3266
+ }
2000
3267
  /**
2001
3268
  * Sign EIP-3009 transferWithAuthorization (GASLESS)
2002
3269
  * This only signs - no on-chain transaction, no gas needed.
@@ -2067,26 +3334,26 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2067
3334
  }
2068
3335
  // --- Config & Wallet Management ---
2069
3336
  loadConfig() {
2070
- const configPath = (0, import_path.join)(this.configDir, "config.json");
2071
- if ((0, import_fs3.existsSync)(configPath)) {
2072
- const content = (0, import_fs3.readFileSync)(configPath, "utf-8");
3337
+ const configPath = (0, import_path2.join)(this.configDir, "config.json");
3338
+ if ((0, import_fs4.existsSync)(configPath)) {
3339
+ const content = (0, import_fs4.readFileSync)(configPath, "utf-8");
2073
3340
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
2074
3341
  }
2075
3342
  return { ...DEFAULT_CONFIG };
2076
3343
  }
2077
3344
  saveConfig() {
2078
- (0, import_fs3.mkdirSync)(this.configDir, { recursive: true });
2079
- const configPath = (0, import_path.join)(this.configDir, "config.json");
2080
- (0, import_fs3.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
3345
+ (0, import_fs4.mkdirSync)(this.configDir, { recursive: true });
3346
+ const configPath = (0, import_path2.join)(this.configDir, "config.json");
3347
+ (0, import_fs4.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
2081
3348
  }
2082
3349
  /**
2083
3350
  * Load spending data from disk
2084
3351
  */
2085
3352
  loadSpending() {
2086
- const spendingPath = (0, import_path.join)(this.configDir, "spending.json");
2087
- if ((0, import_fs3.existsSync)(spendingPath)) {
3353
+ const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
3354
+ if ((0, import_fs4.existsSync)(spendingPath)) {
2088
3355
  try {
2089
- const data = JSON.parse((0, import_fs3.readFileSync)(spendingPath, "utf-8"));
3356
+ const data = JSON.parse((0, import_fs4.readFileSync)(spendingPath, "utf-8"));
2090
3357
  const today = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
2091
3358
  if (data.date && data.date === today) {
2092
3359
  this.todaySpending = data.amount || 0;
@@ -2105,29 +3372,29 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2105
3372
  * Save spending data to disk
2106
3373
  */
2107
3374
  saveSpending() {
2108
- (0, import_fs3.mkdirSync)(this.configDir, { recursive: true });
2109
- const spendingPath = (0, import_path.join)(this.configDir, "spending.json");
3375
+ (0, import_fs4.mkdirSync)(this.configDir, { recursive: true });
3376
+ const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
2110
3377
  const data = {
2111
3378
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
2112
3379
  amount: this.todaySpending,
2113
3380
  updatedAt: Date.now()
2114
3381
  };
2115
- (0, import_fs3.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
3382
+ (0, import_fs4.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
2116
3383
  }
2117
3384
  loadWallet() {
2118
- const walletPath = (0, import_path.join)(this.configDir, "wallet.json");
2119
- if ((0, import_fs3.existsSync)(walletPath)) {
3385
+ const walletPath = (0, import_path2.join)(this.configDir, "wallet.json");
3386
+ if ((0, import_fs4.existsSync)(walletPath)) {
2120
3387
  try {
2121
- const stats = (0, import_fs3.statSync)(walletPath);
3388
+ const stats = (0, import_fs4.statSync)(walletPath);
2122
3389
  const mode = stats.mode & 511;
2123
3390
  if (mode !== 384) {
2124
3391
  console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
2125
3392
  console.warn(`[MoltsPay] Fixing permissions to 0600...`);
2126
- (0, import_fs3.chmodSync)(walletPath, 384);
3393
+ (0, import_fs4.chmodSync)(walletPath, 384);
2127
3394
  }
2128
3395
  } catch (err) {
2129
3396
  }
2130
- const content = (0, import_fs3.readFileSync)(walletPath, "utf-8");
3397
+ const content = (0, import_fs4.readFileSync)(walletPath, "utf-8");
2131
3398
  return JSON.parse(content);
2132
3399
  }
2133
3400
  return null;
@@ -2136,15 +3403,15 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2136
3403
  * Initialize a new wallet (called by CLI)
2137
3404
  */
2138
3405
  static init(configDir, options) {
2139
- (0, import_fs3.mkdirSync)(configDir, { recursive: true });
3406
+ (0, import_fs4.mkdirSync)(configDir, { recursive: true });
2140
3407
  const wallet = import_ethers.Wallet.createRandom();
2141
3408
  const walletData = {
2142
3409
  address: wallet.address,
2143
3410
  privateKey: wallet.privateKey,
2144
3411
  createdAt: Date.now()
2145
3412
  };
2146
- const walletPath = (0, import_path.join)(configDir, "wallet.json");
2147
- (0, import_fs3.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
3413
+ const walletPath = (0, import_path2.join)(configDir, "wallet.json");
3414
+ (0, import_fs4.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
2148
3415
  const config = {
2149
3416
  chain: options.chain,
2150
3417
  limits: {
@@ -2152,8 +3419,8 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2152
3419
  maxPerDay: options.maxPerDay
2153
3420
  }
2154
3421
  };
2155
- const configPath = (0, import_path.join)(configDir, "config.json");
2156
- (0, import_fs3.writeFileSync)(configPath, JSON.stringify(config, null, 2));
3422
+ const configPath = (0, import_path2.join)(configDir, "config.json");
3423
+ (0, import_fs4.writeFileSync)(configPath, JSON.stringify(config, null, 2));
2157
3424
  return { address: wallet.address, configDir };
2158
3425
  }
2159
3426
  /**
@@ -2189,7 +3456,7 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2189
3456
  if (!this.wallet) {
2190
3457
  throw new Error("Client not initialized");
2191
3458
  }
2192
- const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
3459
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet"];
2193
3460
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
2194
3461
  const results = {};
2195
3462
  const tempoTokens = {
@@ -2260,12 +3527,12 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2260
3527
  if (!this.wallet || !this.walletData) {
2261
3528
  throw new Error("Client not initialized. Run: npx moltspay init");
2262
3529
  }
2263
- const { privateKeyToAccount } = await import("viem/accounts");
3530
+ const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
2264
3531
  const { createWalletClient, createPublicClient, http } = await import("viem");
2265
3532
  const { tempoModerato } = await import("viem/chains");
2266
3533
  const { Actions } = await import("viem/tempo");
2267
3534
  const privateKey = this.walletData.privateKey;
2268
- const account = privateKeyToAccount(privateKey);
3535
+ const account = privateKeyToAccount2(privateKey);
2269
3536
  console.log(`[MoltsPay] Making MPP request to: ${url}`);
2270
3537
  console.log(`[MoltsPay] Using account: ${account.address}`);
2271
3538
  const initResponse = await fetch(url, {
@@ -2365,10 +3632,10 @@ var import_ethers2 = require("ethers");
2365
3632
 
2366
3633
  // src/wallet/createWallet.ts
2367
3634
  var import_ethers3 = require("ethers");
2368
- var import_fs4 = require("fs");
2369
- var import_path2 = require("path");
3635
+ var import_fs5 = require("fs");
3636
+ var import_path3 = require("path");
2370
3637
  var import_crypto = require("crypto");
2371
- var DEFAULT_STORAGE_DIR = (0, import_path2.join)(process.env.HOME || "~", ".moltspay");
3638
+ var DEFAULT_STORAGE_DIR = (0, import_path3.join)(process.env.HOME || "~", ".moltspay");
2372
3639
  var DEFAULT_STORAGE_FILE = "wallet.json";
2373
3640
  function encryptPrivateKey(privateKey, password) {
2374
3641
  const salt = (0, import_crypto.randomBytes)(16);
@@ -2391,10 +3658,10 @@ function decryptPrivateKey(encrypted, password, iv, salt) {
2391
3658
  return decrypted;
2392
3659
  }
2393
3660
  function createWallet(options = {}) {
2394
- const storagePath = options.storagePath || (0, import_path2.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
2395
- if ((0, import_fs4.existsSync)(storagePath) && !options.overwrite) {
3661
+ const storagePath = options.storagePath || (0, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
3662
+ if ((0, import_fs5.existsSync)(storagePath) && !options.overwrite) {
2396
3663
  try {
2397
- const existing = JSON.parse((0, import_fs4.readFileSync)(storagePath, "utf8"));
3664
+ const existing = JSON.parse((0, import_fs5.readFileSync)(storagePath, "utf8"));
2398
3665
  return {
2399
3666
  success: true,
2400
3667
  address: existing.address,
@@ -2425,11 +3692,11 @@ function createWallet(options = {}) {
2425
3692
  } else {
2426
3693
  walletData.privateKey = wallet.privateKey;
2427
3694
  }
2428
- const dir = (0, import_path2.dirname)(storagePath);
2429
- if (!(0, import_fs4.existsSync)(dir)) {
2430
- (0, import_fs4.mkdirSync)(dir, { recursive: true });
3695
+ const dir = (0, import_path3.dirname)(storagePath);
3696
+ if (!(0, import_fs5.existsSync)(dir)) {
3697
+ (0, import_fs5.mkdirSync)(dir, { recursive: true });
2431
3698
  }
2432
- (0, import_fs4.writeFileSync)(storagePath, JSON.stringify(walletData, null, 2), { mode: 384 });
3699
+ (0, import_fs5.writeFileSync)(storagePath, JSON.stringify(walletData, null, 2), { mode: 384 });
2433
3700
  return {
2434
3701
  success: true,
2435
3702
  address: wallet.address,
@@ -2444,12 +3711,12 @@ function createWallet(options = {}) {
2444
3711
  }
2445
3712
  }
2446
3713
  function loadWallet(options = {}) {
2447
- const storagePath = options.storagePath || (0, import_path2.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
2448
- if (!(0, import_fs4.existsSync)(storagePath)) {
3714
+ const storagePath = options.storagePath || (0, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
3715
+ if (!(0, import_fs5.existsSync)(storagePath)) {
2449
3716
  return { success: false, error: "Wallet not found. Run createWallet() first." };
2450
3717
  }
2451
3718
  try {
2452
- const data = JSON.parse((0, import_fs4.readFileSync)(storagePath, "utf8"));
3719
+ const data = JSON.parse((0, import_fs5.readFileSync)(storagePath, "utf8"));
2453
3720
  if (data.encrypted) {
2454
3721
  if (!options.password) {
2455
3722
  return { success: false, error: "Wallet is encrypted. Password required." };
@@ -2464,25 +3731,25 @@ function loadWallet(options = {}) {
2464
3731
  }
2465
3732
  }
2466
3733
  function getWalletAddress(storagePath) {
2467
- const path4 = storagePath || (0, import_path2.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
2468
- if (!(0, import_fs4.existsSync)(path4)) {
3734
+ const path4 = storagePath || (0, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
3735
+ if (!(0, import_fs5.existsSync)(path4)) {
2469
3736
  return null;
2470
3737
  }
2471
3738
  try {
2472
- const data = JSON.parse((0, import_fs4.readFileSync)(path4, "utf8"));
3739
+ const data = JSON.parse((0, import_fs5.readFileSync)(path4, "utf8"));
2473
3740
  return data.address;
2474
3741
  } catch {
2475
3742
  return null;
2476
3743
  }
2477
3744
  }
2478
3745
  function walletExists(storagePath) {
2479
- const path4 = storagePath || (0, import_path2.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
2480
- return (0, import_fs4.existsSync)(path4);
3746
+ const path4 = storagePath || (0, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
3747
+ return (0, import_fs5.existsSync)(path4);
2481
3748
  }
2482
3749
 
2483
3750
  // src/verify/index.ts
2484
3751
  var import_ethers4 = require("ethers");
2485
- var TRANSFER_EVENT_TOPIC2 = import_ethers4.ethers.id("Transfer(address,address,uint256)");
3752
+ var TRANSFER_EVENT_TOPIC3 = import_ethers4.ethers.id("Transfer(address,address,uint256)");
2486
3753
  async function verifyPayment(params) {
2487
3754
  const { txHash, expectedAmount, expectedTo, expectedToken } = params;
2488
3755
  let chain;
@@ -2523,7 +3790,7 @@ async function verifyPayment(params) {
2523
3790
  if (!detectedToken) {
2524
3791
  continue;
2525
3792
  }
2526
- if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC2) {
3793
+ if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC3) {
2527
3794
  continue;
2528
3795
  }
2529
3796
  const from = "0x" + log.topics[1].slice(-40);