moltspay 1.3.0 → 1.4.1

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 (58) hide show
  1. package/.env.example +14 -0
  2. package/README.md +319 -89
  3. package/dist/cdp/index.d.mts +4 -4
  4. package/dist/cdp/index.d.ts +4 -4
  5. package/dist/cdp/index.js +57 -0
  6. package/dist/cdp/index.js.map +1 -1
  7. package/dist/cdp/index.mjs +57 -0
  8. package/dist/cdp/index.mjs.map +1 -1
  9. package/dist/chains/index.d.mts +9 -8
  10. package/dist/chains/index.d.ts +9 -8
  11. package/dist/chains/index.js +57 -0
  12. package/dist/chains/index.js.map +1 -1
  13. package/dist/chains/index.mjs +57 -0
  14. package/dist/chains/index.mjs.map +1 -1
  15. package/dist/cli/index.js +2021 -285
  16. package/dist/cli/index.js.map +1 -1
  17. package/dist/cli/index.mjs +2023 -277
  18. package/dist/cli/index.mjs.map +1 -1
  19. package/dist/client/index.d.mts +39 -3
  20. package/dist/client/index.d.ts +39 -3
  21. package/dist/client/index.js +563 -37
  22. package/dist/client/index.js.map +1 -1
  23. package/dist/client/index.mjs +571 -35
  24. package/dist/client/index.mjs.map +1 -1
  25. package/dist/facilitators/index.d.mts +220 -1
  26. package/dist/facilitators/index.d.ts +220 -1
  27. package/dist/facilitators/index.js +664 -1
  28. package/dist/facilitators/index.js.map +1 -1
  29. package/dist/facilitators/index.mjs +670 -1
  30. package/dist/facilitators/index.mjs.map +1 -1
  31. package/dist/{index-On9ZaGDW.d.mts → index-D_2FkLwV.d.mts} +6 -2
  32. package/dist/{index-On9ZaGDW.d.ts → index-D_2FkLwV.d.ts} +6 -2
  33. package/dist/index.d.mts +2 -1
  34. package/dist/index.d.ts +2 -1
  35. package/dist/index.js +1440 -153
  36. package/dist/index.js.map +1 -1
  37. package/dist/index.mjs +1448 -151
  38. package/dist/index.mjs.map +1 -1
  39. package/dist/server/index.d.mts +13 -3
  40. package/dist/server/index.d.ts +13 -3
  41. package/dist/server/index.js +909 -54
  42. package/dist/server/index.js.map +1 -1
  43. package/dist/server/index.mjs +919 -54
  44. package/dist/server/index.mjs.map +1 -1
  45. package/dist/verify/index.d.mts +1 -1
  46. package/dist/verify/index.d.ts +1 -1
  47. package/dist/verify/index.js +57 -0
  48. package/dist/verify/index.js.map +1 -1
  49. package/dist/verify/index.mjs +57 -0
  50. package/dist/verify/index.mjs.map +1 -1
  51. package/dist/wallet/index.d.mts +3 -3
  52. package/dist/wallet/index.d.ts +3 -3
  53. package/dist/wallet/index.js +57 -0
  54. package/dist/wallet/index.js.map +1 -1
  55. package/dist/wallet/index.mjs +57 -0
  56. package/dist/wallet/index.mjs.map +1 -1
  57. package/package.json +5 -2
  58. 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
@@ -1531,8 +2253,10 @@ var MoltsPayServer = class {
1531
2253
  isProxyAllowed(clientIP) {
1532
2254
  const allowedIPs = process.env.PROXY_ALLOWED_IPS?.split(",").map((ip) => ip.trim()) || [];
1533
2255
  if (allowedIPs.length === 0) {
1534
- console.log(`[MoltsPay] /proxy denied: no PROXY_ALLOWED_IPS configured`);
1535
- return false;
2256
+ return true;
2257
+ }
2258
+ if (allowedIPs.includes("*")) {
2259
+ return true;
1536
2260
  }
1537
2261
  const normalizedIP = clientIP === "::1" ? "127.0.0.1" : clientIP.replace("::ffff:", "");
1538
2262
  const allowed = allowedIPs.includes(normalizedIP) || allowedIPs.includes(clientIP);
@@ -1544,31 +2268,42 @@ var MoltsPayServer = class {
1544
2268
  /**
1545
2269
  * POST /proxy - Handle payment for external services (moltspay-creators)
1546
2270
  *
1547
- * This endpoint allows other services to delegate x402 payment handling.
2271
+ * This endpoint allows other services to delegate x402/MPP payment handling.
1548
2272
  * It does NOT execute any skill - just handles payment verification/settlement.
1549
2273
  *
1550
2274
  * Request body:
1551
2275
  * { wallet, amount, currency, chain, memo, serviceId, description }
1552
2276
  *
1553
- * Without X-Payment header: returns 402 with payment requirements
1554
- * With X-Payment header: verifies payment and returns result
2277
+ * For x402 (base, polygon, base_sepolia):
2278
+ * Without X-Payment header: returns 402 with X-Payment-Required
2279
+ * With X-Payment header: verifies payment via CDP
2280
+ *
2281
+ * For MPP (tempo_moderato):
2282
+ * Without Authorization header: returns 402 with WWW-Authenticate
2283
+ * With Authorization: Payment header: verifies tx on Tempo chain
1555
2284
  */
1556
- async handleProxy(body, paymentHeader, res) {
2285
+ async handleProxy(body, paymentHeader, authHeader, res) {
1557
2286
  const { wallet, amount, currency, chain, memo, serviceId, description } = body;
1558
2287
  if (!wallet || !amount) {
1559
2288
  return this.sendJson(res, 400, { error: "Missing required fields: wallet, amount" });
1560
2289
  }
1561
- if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
1562
- return this.sendJson(res, 400, { error: "Invalid wallet address format" });
2290
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet", "solana", "solana_devnet"];
2291
+ if (chain && !supportedChains.includes(chain)) {
2292
+ return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
2293
+ }
2294
+ const isSolanaChain = chain === "solana" || chain === "solana_devnet";
2295
+ const isValidEvmAddress = /^0x[a-fA-F0-9]{40}$/.test(wallet);
2296
+ const isValidSolanaAddress = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(wallet);
2297
+ if (isSolanaChain && !isValidSolanaAddress) {
2298
+ return this.sendJson(res, 400, { error: "Invalid Solana wallet address format" });
2299
+ }
2300
+ if (!isSolanaChain && !isValidEvmAddress) {
2301
+ return this.sendJson(res, 400, { error: "Invalid EVM wallet address format" });
1563
2302
  }
1564
2303
  const amountNum = parseFloat(amount);
1565
2304
  if (isNaN(amountNum) || amountNum <= 0) {
1566
2305
  return this.sendJson(res, 400, { error: "Invalid amount" });
1567
2306
  }
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
2307
  const proxyConfig = {
1573
2308
  id: serviceId || "proxy",
1574
2309
  name: description || "Proxy Payment",
@@ -1580,6 +2315,9 @@ var MoltsPayServer = class {
1580
2315
  input: {},
1581
2316
  output: {}
1582
2317
  };
2318
+ if (chain === "tempo_moderato") {
2319
+ return await this.handleProxyMPP(body, proxyConfig, authHeader, res);
2320
+ }
1583
2321
  const requirements = this.buildProxyPaymentRequirements(proxyConfig, wallet, currency, chain);
1584
2322
  if (!paymentHeader) {
1585
2323
  return this.sendProxyPaymentRequired(proxyConfig, wallet, memo, chain, res);
@@ -1591,37 +2329,225 @@ var MoltsPayServer = class {
1591
2329
  } catch {
1592
2330
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
1593
2331
  }
1594
- if (payment.x402Version !== X402_VERSION2) {
1595
- return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
2332
+ if (payment.x402Version !== X402_VERSION2) {
2333
+ return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
2334
+ }
2335
+ const scheme = payment.accepted?.scheme || payment.scheme;
2336
+ const network = payment.accepted?.network || payment.network;
2337
+ if (scheme !== "exact") {
2338
+ return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
2339
+ }
2340
+ const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
2341
+ if (network !== expectedNetwork) {
2342
+ return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
2343
+ }
2344
+ console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
2345
+ const verifyResult = await this.registry.verify(payment, requirements);
2346
+ if (!verifyResult.valid) {
2347
+ return this.sendJson(res, 402, {
2348
+ success: false,
2349
+ error: `Payment verification failed: ${verifyResult.error}`,
2350
+ facilitator: verifyResult.facilitator
2351
+ });
2352
+ }
2353
+ console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
2354
+ const { execute, service, params } = body;
2355
+ if (execute && service) {
2356
+ const skill = this.skills.get(service);
2357
+ if (!skill) {
2358
+ console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
2359
+ return this.sendJson(res, 404, {
2360
+ success: false,
2361
+ paymentSettled: false,
2362
+ error: `Service not found: ${service}`
2363
+ });
2364
+ }
2365
+ const isSolana = isSolanaNetwork(network);
2366
+ let settlement2 = null;
2367
+ if (isSolana) {
2368
+ console.log(`[MoltsPay] /proxy: Solana detected - settling payment FIRST`);
2369
+ try {
2370
+ settlement2 = await this.registry.settle(payment, requirements);
2371
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
2372
+ if (!settlement2.success) {
2373
+ console.error(`[MoltsPay] /proxy: Solana settlement failed: ${settlement2.error}`);
2374
+ return this.sendJson(res, 402, {
2375
+ success: false,
2376
+ paymentSettled: false,
2377
+ error: `Payment settlement failed: ${settlement2.error || "Unknown error"}`
2378
+ });
2379
+ }
2380
+ } catch (err) {
2381
+ console.error("[MoltsPay] /proxy: Solana settlement failed:", err.message);
2382
+ return this.sendJson(res, 402, {
2383
+ success: false,
2384
+ paymentSettled: false,
2385
+ error: `Payment settlement failed: ${err.message}`
2386
+ });
2387
+ }
2388
+ } else {
2389
+ console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
2390
+ }
2391
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
2392
+ let result;
2393
+ try {
2394
+ result = await Promise.race([
2395
+ skill.handler(params || {}),
2396
+ new Promise(
2397
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
2398
+ )
2399
+ ]);
2400
+ console.log(`[MoltsPay] /proxy: Skill succeeded`);
2401
+ } catch (err) {
2402
+ console.error(`[MoltsPay] /proxy: Skill failed: ${err.message}`);
2403
+ return this.sendJson(res, 500, {
2404
+ success: false,
2405
+ paymentSettled: isSolana ? true : false,
2406
+ error: `Service execution failed: ${err.message}`,
2407
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
2408
+ });
2409
+ }
2410
+ if (!isSolana) {
2411
+ console.log(`[MoltsPay] /proxy: Settling payment...`);
2412
+ try {
2413
+ settlement2 = await this.registry.settle(payment, requirements);
2414
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
2415
+ } catch (err) {
2416
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
2417
+ return this.sendJson(res, 200, {
2418
+ success: true,
2419
+ verified: true,
2420
+ settled: false,
2421
+ settlementError: err.message,
2422
+ from: payment.payload?.authorization?.from,
2423
+ paidTo: wallet,
2424
+ amount: amountNum,
2425
+ currency: currency || "USDC",
2426
+ memo,
2427
+ result
2428
+ });
2429
+ }
2430
+ }
2431
+ return this.sendJson(res, 200, {
2432
+ success: true,
2433
+ verified: true,
2434
+ settled: settlement2?.success || false,
2435
+ txHash: settlement2?.transaction,
2436
+ from: payment.payload?.authorization?.from,
2437
+ paidTo: wallet,
2438
+ amount: amountNum,
2439
+ currency: currency || "USDC",
2440
+ facilitator: settlement2?.facilitator,
2441
+ memo,
2442
+ result
2443
+ });
2444
+ }
2445
+ console.log(`[MoltsPay] /proxy: Settling payment (no execution)...`);
2446
+ let settlement = null;
2447
+ try {
2448
+ settlement = await this.registry.settle(payment, requirements);
2449
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2450
+ } catch (err) {
2451
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
2452
+ return this.sendJson(res, 500, {
2453
+ success: false,
2454
+ error: `Settlement failed: ${err.message}`
2455
+ });
2456
+ }
2457
+ this.sendJson(res, 200, {
2458
+ success: true,
2459
+ verified: true,
2460
+ settled: settlement?.success || false,
2461
+ txHash: settlement?.transaction,
2462
+ from: payment.payload?.authorization?.from,
2463
+ // Buyer's wallet address
2464
+ paidTo: wallet,
2465
+ amount: amountNum,
2466
+ currency: currency || "USDC",
2467
+ facilitator: settlement?.facilitator,
2468
+ memo
2469
+ });
2470
+ }
2471
+ /**
2472
+ * Handle MPP payment flow for /proxy endpoint (tempo_moderato chain)
2473
+ */
2474
+ async handleProxyMPP(body, config, authHeader, res) {
2475
+ const { wallet, amount, memo, serviceId } = body;
2476
+ const amountNum = parseFloat(amount);
2477
+ const amountInUnits = Math.floor(amountNum * 1e6).toString();
2478
+ if (!authHeader || !authHeader.toLowerCase().startsWith("payment ")) {
2479
+ const challengeId = this.generateChallengeId();
2480
+ const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
2481
+ const mppRequest = {
2482
+ amount: amountInUnits,
2483
+ currency: tokenAddress,
2484
+ methodDetails: {
2485
+ chainId: 42431,
2486
+ feePayer: true
2487
+ },
2488
+ recipient: wallet
2489
+ };
2490
+ const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
2491
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
2492
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
2493
+ res.writeHead(402, {
2494
+ "Content-Type": "application/problem+json",
2495
+ [MPP_WWW_AUTH_HEADER]: wwwAuth
2496
+ });
2497
+ res.end(JSON.stringify({
2498
+ type: "https://paymentauth.org/problems/payment-required",
2499
+ title: "Payment Required",
2500
+ status: 402,
2501
+ detail: `Payment is required (${config.name}).`,
2502
+ service: serviceId || "proxy",
2503
+ price: amountNum,
2504
+ currency: "USDC"
2505
+ }, null, 2));
2506
+ return;
2507
+ }
2508
+ const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
2509
+ if (!credentialMatch) {
2510
+ return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
1596
2511
  }
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}` });
2512
+ let mppCredential;
2513
+ try {
2514
+ const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
2515
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
2516
+ mppCredential = JSON.parse(decoded);
2517
+ } catch (err) {
2518
+ console.error("[MoltsPay] /proxy MPP: Failed to parse credential:", err);
2519
+ return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
1601
2520
  }
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}` });
2521
+ let txHash;
2522
+ if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
2523
+ txHash = mppCredential.payload.hash;
2524
+ } else {
2525
+ return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
1605
2526
  }
1606
- console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
1607
- const verifyResult = await this.registry.verify(payment, requirements);
1608
- if (!verifyResult.valid) {
2527
+ console.log(`[MoltsPay] /proxy MPP: Verifying tx ${txHash} on Tempo...`);
2528
+ const requirements = this.buildPaymentRequirements(config, "eip155:42431", wallet, "USDC");
2529
+ const paymentPayload = {
2530
+ x402Version: X402_VERSION2,
2531
+ scheme: "exact",
2532
+ network: "eip155:42431",
2533
+ payload: { txHash, chainId: 42431 }
2534
+ };
2535
+ const verification = await this.registry.verify(paymentPayload, requirements);
2536
+ if (!verification.valid) {
1609
2537
  return this.sendJson(res, 402, {
1610
- success: false,
1611
- error: `Payment verification failed: ${verifyResult.error}`,
1612
- facilitator: verifyResult.facilitator
2538
+ error: `Payment verification failed: ${verification.error}`
1613
2539
  });
1614
2540
  }
1615
- console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
2541
+ console.log(`[MoltsPay] /proxy MPP: Payment verified by ${verification.facilitator}`);
1616
2542
  const { execute, service, params } = body;
1617
2543
  if (execute && service) {
1618
- console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
2544
+ console.log(`[MoltsPay] /proxy MPP: Executing skill: ${service}`);
1619
2545
  const skill = this.skills.get(service);
1620
2546
  if (!skill) {
1621
- console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
1622
2547
  return this.sendJson(res, 404, {
1623
2548
  success: false,
1624
- paymentSettled: false,
2549
+ paymentSettled: true,
2550
+ // Payment already happened on Tempo
1625
2551
  error: `Service not found: ${service}`
1626
2552
  });
1627
2553
  }
@@ -1634,73 +2560,36 @@ var MoltsPayServer = class {
1634
2560
  (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
1635
2561
  )
1636
2562
  ]);
1637
- console.log(`[MoltsPay] /proxy: Skill succeeded, now settling payment...`);
1638
2563
  } catch (err) {
1639
- console.error(`[MoltsPay] /proxy: Skill failed: ${err.message} - NOT settling`);
2564
+ console.error(`[MoltsPay] /proxy MPP: Skill failed: ${err.message}`);
1640
2565
  return this.sendJson(res, 500, {
1641
2566
  success: false,
1642
- paymentSettled: false,
2567
+ paymentSettled: true,
1643
2568
  error: `Service execution failed: ${err.message}`
1644
2569
  });
1645
2570
  }
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
2571
  return this.sendJson(res, 200, {
1667
2572
  success: true,
1668
2573
  verified: true,
1669
- settled: settlement2?.success || false,
1670
- txHash: settlement2?.transaction,
1671
- from: payment.payload?.authorization?.from,
1672
- // Buyer's wallet address
2574
+ txHash,
2575
+ chain: "tempo_moderato",
1673
2576
  paidTo: wallet,
1674
2577
  amount: amountNum,
1675
- currency: currency || "USDC",
1676
- facilitator: settlement2?.facilitator,
2578
+ currency: "USDC",
2579
+ facilitator: verification.facilitator,
1677
2580
  memo,
1678
2581
  result
1679
2582
  });
1680
2583
  }
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
2584
  this.sendJson(res, 200, {
1694
2585
  success: true,
1695
2586
  verified: true,
1696
- settled: settlement?.success || false,
1697
- txHash: settlement?.transaction,
1698
- from: payment.payload?.authorization?.from,
1699
- // Buyer's wallet address
2587
+ txHash,
2588
+ chain: "tempo_moderato",
1700
2589
  paidTo: wallet,
1701
2590
  amount: amountNum,
1702
- currency: currency || "USDC",
1703
- facilitator: settlement?.facilitator,
2591
+ currency: "USDC",
2592
+ facilitator: verification.facilitator,
1704
2593
  memo
1705
2594
  });
1706
2595
  }
@@ -1715,7 +2604,7 @@ var MoltsPayServer = class {
1715
2604
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
1716
2605
  const tokenAddress = tokenAddresses[selectedToken];
1717
2606
  const tokenDomain = getTokenDomain(networkId, selectedToken);
1718
- return {
2607
+ const requirements = {
1719
2608
  scheme: "exact",
1720
2609
  network: networkId,
1721
2610
  asset: tokenAddress,
@@ -1725,6 +2614,17 @@ var MoltsPayServer = class {
1725
2614
  maxTimeoutSeconds: 300,
1726
2615
  extra: tokenDomain
1727
2616
  };
2617
+ if (networkId === "eip155:56" || networkId === "eip155:97") {
2618
+ const bnbFacilitator = this.registry.get("bnb");
2619
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
2620
+ if (spenderAddress) {
2621
+ requirements.extra = {
2622
+ ...requirements.extra || {},
2623
+ bnbSpender: spenderAddress
2624
+ };
2625
+ }
2626
+ }
2627
+ return requirements;
1728
2628
  }
1729
2629
  /**
1730
2630
  * Return 402 with x402 payment requirements for proxy endpoint
@@ -1755,10 +2655,40 @@ var MoltsPayServer = class {
1755
2655
  };
1756
2656
 
1757
2657
  // src/client/index.ts
2658
+ var import_fs4 = require("fs");
2659
+ var import_os2 = require("os");
2660
+ var import_path2 = require("path");
2661
+ var import_ethers = require("ethers");
2662
+
2663
+ // src/wallet/solana.ts
2664
+ var import_web34 = require("@solana/web3.js");
2665
+ var import_spl_token2 = require("@solana/spl-token");
1758
2666
  var import_fs3 = require("fs");
1759
- var import_os = require("os");
1760
2667
  var import_path = require("path");
1761
- var import_ethers = require("ethers");
2668
+ var import_os = require("os");
2669
+ var import_bs582 = __toESM(require("bs58"));
2670
+ var DEFAULT_CONFIG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".moltspay");
2671
+ var SOLANA_WALLET_FILE = "wallet-solana.json";
2672
+ function getSolanaWalletPath(configDir = DEFAULT_CONFIG_DIR) {
2673
+ return (0, import_path.join)(configDir, SOLANA_WALLET_FILE);
2674
+ }
2675
+ function loadSolanaWallet(configDir = DEFAULT_CONFIG_DIR) {
2676
+ const walletPath = getSolanaWalletPath(configDir);
2677
+ if (!(0, import_fs3.existsSync)(walletPath)) {
2678
+ return null;
2679
+ }
2680
+ try {
2681
+ const data = JSON.parse((0, import_fs3.readFileSync)(walletPath, "utf-8"));
2682
+ const secretKey = import_bs582.default.decode(data.secretKey);
2683
+ return import_web34.Keypair.fromSecretKey(secretKey);
2684
+ } catch (error) {
2685
+ console.error("Failed to load Solana wallet:", error);
2686
+ return null;
2687
+ }
2688
+ }
2689
+
2690
+ // src/client/index.ts
2691
+ var import_web35 = require("@solana/web3.js");
1762
2692
  var X402_VERSION3 = 2;
1763
2693
  var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
1764
2694
  var PAYMENT_HEADER2 = "x-payment";
@@ -1777,7 +2707,7 @@ var MoltsPayClient = class {
1777
2707
  todaySpending = 0;
1778
2708
  lastSpendingReset = 0;
1779
2709
  constructor(options = {}) {
1780
- this.configDir = options.configDir || (0, import_path.join)((0, import_os.homedir)(), ".moltspay");
2710
+ this.configDir = options.configDir || (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
1781
2711
  this.config = this.loadConfig();
1782
2712
  this.walletData = this.loadWallet();
1783
2713
  this.loadSpending();
@@ -1797,6 +2727,12 @@ var MoltsPayClient = class {
1797
2727
  get address() {
1798
2728
  return this.wallet?.address || null;
1799
2729
  }
2730
+ /**
2731
+ * Get wallet instance (for direct operations like approvals)
2732
+ */
2733
+ getWallet() {
2734
+ return this.wallet;
2735
+ }
1800
2736
  /**
1801
2737
  * Get current config
1802
2738
  */
@@ -1850,11 +2786,26 @@ var MoltsPayClient = class {
1850
2786
  throw new Error("Client not initialized. Run: npx moltspay init");
1851
2787
  }
1852
2788
  console.log(`[MoltsPay] Requesting service: ${service}`);
1853
- const requestBody = { service, params };
2789
+ let executeUrl = `${serverUrl}/execute`;
2790
+ try {
2791
+ const services = await this.getServices(serverUrl);
2792
+ const svc = services.services?.find((s) => s.id === service);
2793
+ if (svc?.endpoint) {
2794
+ executeUrl = `${serverUrl}${svc.endpoint}`;
2795
+ console.log(`[MoltsPay] Using service endpoint: ${svc.endpoint}`);
2796
+ }
2797
+ } catch {
2798
+ }
2799
+ let requestBody;
2800
+ if (options.rawData) {
2801
+ requestBody = { service, ...params };
2802
+ } else {
2803
+ requestBody = { service, params };
2804
+ }
1854
2805
  if (options.chain) {
1855
2806
  requestBody.chain = options.chain;
1856
2807
  }
1857
- const initialRes = await fetch(`${serverUrl}/execute`, {
2808
+ const initialRes = await fetch(executeUrl, {
1858
2809
  method: "POST",
1859
2810
  headers: { "Content-Type": "application/json" },
1860
2811
  body: JSON.stringify(requestBody)
@@ -1866,9 +2817,14 @@ var MoltsPayClient = class {
1866
2817
  }
1867
2818
  throw new Error(data.error || "Unexpected response");
1868
2819
  }
2820
+ const wwwAuthHeader = initialRes.headers.get("www-authenticate");
1869
2821
  const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER2);
2822
+ if (wwwAuthHeader && wwwAuthHeader.toLowerCase().includes("payment")) {
2823
+ console.log("[MoltsPay] Detected MPP protocol, using Tempo flow...");
2824
+ return await this.handleMPPPayment(executeUrl, service, params, wwwAuthHeader, options);
2825
+ }
1870
2826
  if (!paymentRequiredHeader) {
1871
- throw new Error("Missing x-payment-required header");
2827
+ throw new Error("Missing payment header (x-payment-required or www-authenticate)");
1872
2828
  }
1873
2829
  let requirements;
1874
2830
  try {
@@ -1885,17 +2841,22 @@ var MoltsPayClient = class {
1885
2841
  throw new Error("Invalid x-payment-required header");
1886
2842
  }
1887
2843
  const networkToChainName = (network2) => {
2844
+ if (network2 === "solana:mainnet") return "solana";
2845
+ if (network2 === "solana:devnet") return "solana_devnet";
1888
2846
  const match = network2.match(/^eip155:(\d+)$/);
1889
2847
  if (!match) return null;
1890
2848
  const chainId = parseInt(match[1]);
1891
2849
  if (chainId === 8453) return "base";
1892
2850
  if (chainId === 137) return "polygon";
1893
2851
  if (chainId === 84532) return "base_sepolia";
2852
+ if (chainId === 42431) return "tempo_moderato";
2853
+ if (chainId === 56) return "bnb";
2854
+ if (chainId === 97) return "bnb_testnet";
1894
2855
  return null;
1895
2856
  };
1896
2857
  const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
1897
- let chainName;
1898
2858
  const userSpecifiedChain = options.chain;
2859
+ let selectedChain;
1899
2860
  if (userSpecifiedChain) {
1900
2861
  if (!serverChains.includes(userSpecifiedChain)) {
1901
2862
  throw new Error(
@@ -1903,17 +2864,27 @@ var MoltsPayClient = class {
1903
2864
  Server accepts: ${serverChains.join(", ")}`
1904
2865
  );
1905
2866
  }
1906
- chainName = userSpecifiedChain;
2867
+ selectedChain = userSpecifiedChain;
1907
2868
  } else {
1908
2869
  if (serverChains.length === 1 && serverChains[0] === "base") {
1909
- chainName = "base";
2870
+ selectedChain = "base";
1910
2871
  } else {
1911
2872
  throw new Error(
1912
2873
  `Server accepts: ${serverChains.join(", ")}
1913
- Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2874
+ Please specify: --chain <chain_name>`
1914
2875
  );
1915
2876
  }
1916
2877
  }
2878
+ if (selectedChain === "solana" || selectedChain === "solana_devnet") {
2879
+ const solanaChain = selectedChain;
2880
+ const network2 = solanaChain === "solana" ? "solana:mainnet" : "solana:devnet";
2881
+ const req2 = requirements.find((r) => r.network === network2);
2882
+ if (!req2) {
2883
+ throw new Error(`Failed to find payment requirement for ${selectedChain}`);
2884
+ }
2885
+ return await this.handleSolanaPayment(executeUrl, service, params, req2, solanaChain, options);
2886
+ }
2887
+ const chainName = selectedChain;
1917
2888
  const chain = getChain(chainName);
1918
2889
  const network = `eip155:${chain.chainId}`;
1919
2890
  const req = requirements.find((r) => r.scheme === "exact" && r.network === network);
@@ -1948,6 +2919,25 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
1948
2919
  } else {
1949
2920
  console.log(`[MoltsPay] Signing payment: $${amount} ${token} (gasless)`);
1950
2921
  }
2922
+ if (chainName === "bnb" || chainName === "bnb_testnet") {
2923
+ console.log(`[MoltsPay] Using BNB intent-based payment flow...`);
2924
+ const payTo2 = req.payTo || req.resource;
2925
+ if (!payTo2) {
2926
+ throw new Error("Missing payTo address in payment requirements");
2927
+ }
2928
+ const bnbSpender = req.extra?.bnbSpender;
2929
+ if (!bnbSpender) {
2930
+ throw new Error("Server did not provide bnbSpender address. Server may not support BNB payments.");
2931
+ }
2932
+ return await this.handleBNBPayment(executeUrl, service, params, {
2933
+ to: payTo2,
2934
+ amount,
2935
+ token,
2936
+ chainName,
2937
+ chain,
2938
+ spender: bnbSpender
2939
+ }, options);
2940
+ }
1951
2941
  const payTo = req.payTo || req.resource;
1952
2942
  if (!payTo) {
1953
2943
  throw new Error("Missing payTo address in payment requirements");
@@ -1977,11 +2967,11 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
1977
2967
  };
1978
2968
  const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
1979
2969
  console.log(`[MoltsPay] Sending request with payment...`);
1980
- const paidRequestBody = { service, params };
2970
+ const paidRequestBody = options.rawData ? { service, ...params } : { service, params };
1981
2971
  if (options.chain) {
1982
2972
  paidRequestBody.chain = options.chain;
1983
2973
  }
1984
- const paidRes = await fetch(`${serverUrl}/execute`, {
2974
+ const paidRes = await fetch(executeUrl, {
1985
2975
  method: "POST",
1986
2976
  headers: {
1987
2977
  "Content-Type": "application/json",
@@ -1995,7 +2985,304 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
1995
2985
  }
1996
2986
  this.recordSpending(amount);
1997
2987
  console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
1998
- return result.result;
2988
+ return result.result || result;
2989
+ }
2990
+ /**
2991
+ * Handle MPP (Machine Payments Protocol) payment flow
2992
+ * Called when pay() detects WWW-Authenticate header in 402 response
2993
+ */
2994
+ async handleMPPPayment(executeUrl, service, params, wwwAuthHeader, options = {}) {
2995
+ const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
2996
+ const { createWalletClient, createPublicClient, http } = await import("viem");
2997
+ const { tempoModerato } = await import("viem/chains");
2998
+ const { Actions } = await import("viem/tempo");
2999
+ const privateKey = this.walletData.privateKey;
3000
+ const account = privateKeyToAccount2(privateKey);
3001
+ console.log(`[MoltsPay] Using MPP protocol on Tempo`);
3002
+ console.log(`[MoltsPay] Account: ${account.address}`);
3003
+ const parseAuthParam = (header, key) => {
3004
+ const match = header.match(new RegExp(`${key}="([^"]+)"`, "i"));
3005
+ return match ? match[1] : null;
3006
+ };
3007
+ const challengeId = parseAuthParam(wwwAuthHeader, "id");
3008
+ const method = parseAuthParam(wwwAuthHeader, "method");
3009
+ const realm = parseAuthParam(wwwAuthHeader, "realm");
3010
+ const requestB64 = parseAuthParam(wwwAuthHeader, "request");
3011
+ if (method !== "tempo") {
3012
+ throw new Error(`Unsupported payment method: ${method}`);
3013
+ }
3014
+ if (!requestB64) {
3015
+ throw new Error("Missing request in WWW-Authenticate");
3016
+ }
3017
+ const requestJson = Buffer.from(requestB64, "base64").toString("utf-8");
3018
+ const paymentRequest = JSON.parse(requestJson);
3019
+ const { amount, currency, recipient, methodDetails } = paymentRequest;
3020
+ const chainId = methodDetails?.chainId || 42431;
3021
+ const amountDisplay = Number(amount) / 1e6;
3022
+ console.log(`[MoltsPay] Payment: $${amountDisplay} to ${recipient}`);
3023
+ this.checkLimits(amountDisplay);
3024
+ console.log(`[MoltsPay] Sending transaction on Tempo...`);
3025
+ const tempoChain = { ...tempoModerato, feeToken: currency };
3026
+ const publicClient = createPublicClient({
3027
+ chain: tempoChain,
3028
+ transport: http("https://rpc.moderato.tempo.xyz")
3029
+ });
3030
+ const walletClient = createWalletClient({
3031
+ account,
3032
+ chain: tempoChain,
3033
+ transport: http("https://rpc.moderato.tempo.xyz")
3034
+ });
3035
+ const txHash = await Actions.token.transfer(walletClient, {
3036
+ to: recipient,
3037
+ amount: BigInt(amount),
3038
+ token: currency
3039
+ });
3040
+ console.log(`[MoltsPay] Transaction: ${txHash}`);
3041
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
3042
+ console.log(`[MoltsPay] Confirmed! Retrying with credential...`);
3043
+ const credential = {
3044
+ challenge: {
3045
+ id: challengeId,
3046
+ realm,
3047
+ method: "tempo",
3048
+ intent: "charge",
3049
+ request: paymentRequest
3050
+ },
3051
+ payload: { hash: txHash, type: "hash" },
3052
+ source: `did:pkh:eip155:${chainId}:${account.address}`
3053
+ };
3054
+ const credentialB64 = Buffer.from(JSON.stringify(credential)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3055
+ const retryBody = options.rawData ? { service, ...params, chain: "tempo_moderato" } : { service, params, chain: "tempo_moderato" };
3056
+ const paidRes = await fetch(executeUrl, {
3057
+ method: "POST",
3058
+ headers: {
3059
+ "Content-Type": "application/json",
3060
+ "Authorization": `Payment ${credentialB64}`
3061
+ },
3062
+ body: JSON.stringify(retryBody)
3063
+ });
3064
+ const result = await paidRes.json();
3065
+ if (!paidRes.ok) {
3066
+ throw new Error(result.error || "Payment verification failed");
3067
+ }
3068
+ this.recordSpending(amountDisplay);
3069
+ console.log(`[MoltsPay] Success!`);
3070
+ return result.result || result;
3071
+ }
3072
+ /**
3073
+ * Handle BNB Chain payment flow (pre-approval + intent signature)
3074
+ *
3075
+ * Flow:
3076
+ * 1. Check client has approved server wallet (done via `moltspay init`)
3077
+ * 2. Sign EIP-712 payment intent (no gas, just signature)
3078
+ * 3. Send intent to server
3079
+ * 4. Server executes service
3080
+ * 5. Server calls transferFrom if successful (pay-for-success)
3081
+ */
3082
+ async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
3083
+ const { to, amount, token, chainName, chain, spender } = paymentDetails;
3084
+ const tokenConfig = chain.tokens[token];
3085
+ const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
3086
+ const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
3087
+ const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
3088
+ if (allowance < amountWeiCheck) {
3089
+ const nativeBalance = await provider.getBalance(this.wallet.address);
3090
+ const minGasBalance = import_ethers.ethers.parseEther("0.0005");
3091
+ if (nativeBalance < minGasBalance) {
3092
+ const nativeBNB = parseFloat(import_ethers.ethers.formatEther(nativeBalance)).toFixed(4);
3093
+ const isTestnet = chainName === "bnb_testnet";
3094
+ if (isTestnet) {
3095
+ throw new Error(
3096
+ `\u274C Insufficient tBNB for approval transaction
3097
+
3098
+ Current tBNB: ${nativeBNB}
3099
+ Required: ~0.001 tBNB
3100
+
3101
+ Get testnet tokens: npx moltspay faucet --chain bnb_testnet
3102
+ (Gives USDC + tBNB for gas)`
3103
+ );
3104
+ } else {
3105
+ throw new Error(
3106
+ `\u274C Insufficient BNB for approval transaction
3107
+
3108
+ Current BNB: ${nativeBNB}
3109
+ Required: ~0.001 BNB (~$0.60)
3110
+
3111
+ To get BNB:
3112
+ \u2022 Withdraw from Binance/exchange to your wallet
3113
+ \u2022 Most exchanges include BNB dust with withdrawals
3114
+
3115
+ After funding, run:
3116
+ npx moltspay approve --chain ${chainName} --spender ${spender}`
3117
+ );
3118
+ }
3119
+ }
3120
+ throw new Error(
3121
+ `Insufficient allowance for ${spender.slice(0, 10)}...
3122
+ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
3123
+ );
3124
+ }
3125
+ const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
3126
+ const intent = {
3127
+ from: this.wallet.address,
3128
+ to,
3129
+ amount: amountWei,
3130
+ token: tokenConfig.address,
3131
+ service,
3132
+ nonce: Date.now(),
3133
+ // Use timestamp as nonce for simplicity
3134
+ deadline: Date.now() + 36e5
3135
+ // 1 hour
3136
+ };
3137
+ const domain = {
3138
+ name: "MoltsPay",
3139
+ version: "1",
3140
+ chainId: chain.chainId
3141
+ };
3142
+ const types = {
3143
+ PaymentIntent: [
3144
+ { name: "from", type: "address" },
3145
+ { name: "to", type: "address" },
3146
+ { name: "amount", type: "uint256" },
3147
+ { name: "token", type: "address" },
3148
+ { name: "service", type: "string" },
3149
+ { name: "nonce", type: "uint256" },
3150
+ { name: "deadline", type: "uint256" }
3151
+ ]
3152
+ };
3153
+ console.log(`[MoltsPay] Signing BNB payment intent...`);
3154
+ const signature = await this.wallet.signTypedData(domain, types, intent);
3155
+ const network = `eip155:${chain.chainId}`;
3156
+ const payload = {
3157
+ x402Version: 2,
3158
+ scheme: "exact",
3159
+ network,
3160
+ payload: {
3161
+ intent: {
3162
+ ...intent,
3163
+ signature
3164
+ },
3165
+ chainId: chain.chainId
3166
+ },
3167
+ accepted: {
3168
+ scheme: "exact",
3169
+ network,
3170
+ asset: tokenConfig.address,
3171
+ amount: amountWei,
3172
+ payTo: to,
3173
+ maxTimeoutSeconds: 300
3174
+ }
3175
+ };
3176
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
3177
+ console.log(`[MoltsPay] Sending BNB payment request...`);
3178
+ const bnbRequestBody = options.rawData ? { service, ...params, chain: chainName } : { service, params, chain: chainName };
3179
+ const paidRes = await fetch(executeUrl, {
3180
+ method: "POST",
3181
+ headers: {
3182
+ "Content-Type": "application/json",
3183
+ "X-Payment": paymentHeader
3184
+ },
3185
+ body: JSON.stringify(bnbRequestBody)
3186
+ });
3187
+ const result = await paidRes.json();
3188
+ if (!paidRes.ok) {
3189
+ throw new Error(result.error || "BNB payment failed");
3190
+ }
3191
+ this.recordSpending(amount);
3192
+ console.log(`[MoltsPay] Success! BNB payment settled.`);
3193
+ return result.result || result;
3194
+ }
3195
+ /**
3196
+ * Handle Solana payment flow
3197
+ *
3198
+ * Solana uses SPL token transfers with pay-for-success model:
3199
+ * 1. Client creates and signs a transfer transaction
3200
+ * 2. Server submits the transaction after service completes
3201
+ */
3202
+ async handleSolanaPayment(executeUrl, service, params, requirements, chain, options = {}) {
3203
+ const solanaWallet = loadSolanaWallet(this.configDir);
3204
+ if (!solanaWallet) {
3205
+ throw new Error("No Solana wallet found. Run: npx moltspay init --chain solana_devnet");
3206
+ }
3207
+ const amount = Number(requirements.amount);
3208
+ const amountUSDC = amount / 1e6;
3209
+ this.checkLimits(amountUSDC);
3210
+ console.log(`[MoltsPay] Creating Solana payment: $${amountUSDC} USDC`);
3211
+ if (!requirements.payTo) {
3212
+ throw new Error("Missing payTo address in payment requirements");
3213
+ }
3214
+ const solanaFeePayer = requirements.extra?.solanaFeePayer;
3215
+ const feePayerPubkey = solanaFeePayer ? new import_web35.PublicKey(solanaFeePayer) : void 0;
3216
+ if (feePayerPubkey) {
3217
+ console.log(`[MoltsPay] Gasless mode: server pays fees`);
3218
+ }
3219
+ const recipientPubkey = new import_web35.PublicKey(requirements.payTo);
3220
+ const transaction = await createSolanaPaymentTransaction(
3221
+ solanaWallet.publicKey,
3222
+ recipientPubkey,
3223
+ BigInt(amount),
3224
+ chain,
3225
+ feePayerPubkey
3226
+ // Optional fee payer for gasless mode
3227
+ );
3228
+ if (feePayerPubkey) {
3229
+ transaction.partialSign(solanaWallet);
3230
+ } else {
3231
+ transaction.sign(solanaWallet);
3232
+ }
3233
+ const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
3234
+ console.log(`[MoltsPay] Transaction signed, sending to server...`);
3235
+ const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
3236
+ const payload = {
3237
+ x402Version: 2,
3238
+ scheme: "exact",
3239
+ network,
3240
+ payload: {
3241
+ signedTransaction: signedTx,
3242
+ sender: solanaWallet.publicKey.toBase58(),
3243
+ chain
3244
+ },
3245
+ accepted: {
3246
+ scheme: "exact",
3247
+ network,
3248
+ asset: requirements.asset,
3249
+ amount: requirements.amount,
3250
+ payTo: requirements.payTo,
3251
+ maxTimeoutSeconds: 300
3252
+ }
3253
+ };
3254
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
3255
+ const solanaRequestBody = options.rawData ? { service, ...params, chain } : { service, params, chain };
3256
+ const paidRes = await fetch(executeUrl, {
3257
+ method: "POST",
3258
+ headers: {
3259
+ "Content-Type": "application/json",
3260
+ "X-Payment": paymentHeader
3261
+ },
3262
+ body: JSON.stringify(solanaRequestBody)
3263
+ });
3264
+ const result = await paidRes.json();
3265
+ if (!paidRes.ok) {
3266
+ throw new Error(result.error || "Solana payment failed");
3267
+ }
3268
+ this.recordSpending(amountUSDC);
3269
+ console.log(`[MoltsPay] Success! Solana payment settled.`);
3270
+ if (result.payment?.transaction) {
3271
+ const explorerUrl = chain === "solana" ? `https://solscan.io/tx/${result.payment.transaction}` : `https://solscan.io/tx/${result.payment.transaction}?cluster=devnet`;
3272
+ console.log(`[MoltsPay] Transaction: ${explorerUrl}`);
3273
+ }
3274
+ return result.result || result;
3275
+ }
3276
+ /**
3277
+ * Check ERC20 allowance for a spender
3278
+ */
3279
+ async checkAllowance(tokenAddress, spender, provider) {
3280
+ const contract = new import_ethers.ethers.Contract(
3281
+ tokenAddress,
3282
+ ["function allowance(address owner, address spender) view returns (uint256)"],
3283
+ provider
3284
+ );
3285
+ return await contract.allowance(this.wallet.address, spender);
1999
3286
  }
2000
3287
  /**
2001
3288
  * Sign EIP-3009 transferWithAuthorization (GASLESS)
@@ -2067,26 +3354,26 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2067
3354
  }
2068
3355
  // --- Config & Wallet Management ---
2069
3356
  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");
3357
+ const configPath = (0, import_path2.join)(this.configDir, "config.json");
3358
+ if ((0, import_fs4.existsSync)(configPath)) {
3359
+ const content = (0, import_fs4.readFileSync)(configPath, "utf-8");
2073
3360
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
2074
3361
  }
2075
3362
  return { ...DEFAULT_CONFIG };
2076
3363
  }
2077
3364
  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));
3365
+ (0, import_fs4.mkdirSync)(this.configDir, { recursive: true });
3366
+ const configPath = (0, import_path2.join)(this.configDir, "config.json");
3367
+ (0, import_fs4.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
2081
3368
  }
2082
3369
  /**
2083
3370
  * Load spending data from disk
2084
3371
  */
2085
3372
  loadSpending() {
2086
- const spendingPath = (0, import_path.join)(this.configDir, "spending.json");
2087
- if ((0, import_fs3.existsSync)(spendingPath)) {
3373
+ const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
3374
+ if ((0, import_fs4.existsSync)(spendingPath)) {
2088
3375
  try {
2089
- const data = JSON.parse((0, import_fs3.readFileSync)(spendingPath, "utf-8"));
3376
+ const data = JSON.parse((0, import_fs4.readFileSync)(spendingPath, "utf-8"));
2090
3377
  const today = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
2091
3378
  if (data.date && data.date === today) {
2092
3379
  this.todaySpending = data.amount || 0;
@@ -2105,29 +3392,29 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2105
3392
  * Save spending data to disk
2106
3393
  */
2107
3394
  saveSpending() {
2108
- (0, import_fs3.mkdirSync)(this.configDir, { recursive: true });
2109
- const spendingPath = (0, import_path.join)(this.configDir, "spending.json");
3395
+ (0, import_fs4.mkdirSync)(this.configDir, { recursive: true });
3396
+ const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
2110
3397
  const data = {
2111
3398
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
2112
3399
  amount: this.todaySpending,
2113
3400
  updatedAt: Date.now()
2114
3401
  };
2115
- (0, import_fs3.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
3402
+ (0, import_fs4.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
2116
3403
  }
2117
3404
  loadWallet() {
2118
- const walletPath = (0, import_path.join)(this.configDir, "wallet.json");
2119
- if ((0, import_fs3.existsSync)(walletPath)) {
3405
+ const walletPath = (0, import_path2.join)(this.configDir, "wallet.json");
3406
+ if ((0, import_fs4.existsSync)(walletPath)) {
2120
3407
  try {
2121
- const stats = (0, import_fs3.statSync)(walletPath);
3408
+ const stats = (0, import_fs4.statSync)(walletPath);
2122
3409
  const mode = stats.mode & 511;
2123
3410
  if (mode !== 384) {
2124
3411
  console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
2125
3412
  console.warn(`[MoltsPay] Fixing permissions to 0600...`);
2126
- (0, import_fs3.chmodSync)(walletPath, 384);
3413
+ (0, import_fs4.chmodSync)(walletPath, 384);
2127
3414
  }
2128
3415
  } catch (err) {
2129
3416
  }
2130
- const content = (0, import_fs3.readFileSync)(walletPath, "utf-8");
3417
+ const content = (0, import_fs4.readFileSync)(walletPath, "utf-8");
2131
3418
  return JSON.parse(content);
2132
3419
  }
2133
3420
  return null;
@@ -2136,15 +3423,15 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2136
3423
  * Initialize a new wallet (called by CLI)
2137
3424
  */
2138
3425
  static init(configDir, options) {
2139
- (0, import_fs3.mkdirSync)(configDir, { recursive: true });
3426
+ (0, import_fs4.mkdirSync)(configDir, { recursive: true });
2140
3427
  const wallet = import_ethers.Wallet.createRandom();
2141
3428
  const walletData = {
2142
3429
  address: wallet.address,
2143
3430
  privateKey: wallet.privateKey,
2144
3431
  createdAt: Date.now()
2145
3432
  };
2146
- const walletPath = (0, import_path.join)(configDir, "wallet.json");
2147
- (0, import_fs3.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
3433
+ const walletPath = (0, import_path2.join)(configDir, "wallet.json");
3434
+ (0, import_fs4.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
2148
3435
  const config = {
2149
3436
  chain: options.chain,
2150
3437
  limits: {
@@ -2152,8 +3439,8 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2152
3439
  maxPerDay: options.maxPerDay
2153
3440
  }
2154
3441
  };
2155
- const configPath = (0, import_path.join)(configDir, "config.json");
2156
- (0, import_fs3.writeFileSync)(configPath, JSON.stringify(config, null, 2));
3442
+ const configPath = (0, import_path2.join)(configDir, "config.json");
3443
+ (0, import_fs4.writeFileSync)(configPath, JSON.stringify(config, null, 2));
2157
3444
  return { address: wallet.address, configDir };
2158
3445
  }
2159
3446
  /**
@@ -2189,7 +3476,7 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2189
3476
  if (!this.wallet) {
2190
3477
  throw new Error("Client not initialized");
2191
3478
  }
2192
- const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
3479
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet"];
2193
3480
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
2194
3481
  const results = {};
2195
3482
  const tempoTokens = {
@@ -2260,12 +3547,12 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2260
3547
  if (!this.wallet || !this.walletData) {
2261
3548
  throw new Error("Client not initialized. Run: npx moltspay init");
2262
3549
  }
2263
- const { privateKeyToAccount } = await import("viem/accounts");
3550
+ const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
2264
3551
  const { createWalletClient, createPublicClient, http } = await import("viem");
2265
3552
  const { tempoModerato } = await import("viem/chains");
2266
3553
  const { Actions } = await import("viem/tempo");
2267
3554
  const privateKey = this.walletData.privateKey;
2268
- const account = privateKeyToAccount(privateKey);
3555
+ const account = privateKeyToAccount2(privateKey);
2269
3556
  console.log(`[MoltsPay] Making MPP request to: ${url}`);
2270
3557
  console.log(`[MoltsPay] Using account: ${account.address}`);
2271
3558
  const initResponse = await fetch(url, {
@@ -2365,10 +3652,10 @@ var import_ethers2 = require("ethers");
2365
3652
 
2366
3653
  // src/wallet/createWallet.ts
2367
3654
  var import_ethers3 = require("ethers");
2368
- var import_fs4 = require("fs");
2369
- var import_path2 = require("path");
3655
+ var import_fs5 = require("fs");
3656
+ var import_path3 = require("path");
2370
3657
  var import_crypto = require("crypto");
2371
- var DEFAULT_STORAGE_DIR = (0, import_path2.join)(process.env.HOME || "~", ".moltspay");
3658
+ var DEFAULT_STORAGE_DIR = (0, import_path3.join)(process.env.HOME || "~", ".moltspay");
2372
3659
  var DEFAULT_STORAGE_FILE = "wallet.json";
2373
3660
  function encryptPrivateKey(privateKey, password) {
2374
3661
  const salt = (0, import_crypto.randomBytes)(16);
@@ -2391,10 +3678,10 @@ function decryptPrivateKey(encrypted, password, iv, salt) {
2391
3678
  return decrypted;
2392
3679
  }
2393
3680
  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) {
3681
+ const storagePath = options.storagePath || (0, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
3682
+ if ((0, import_fs5.existsSync)(storagePath) && !options.overwrite) {
2396
3683
  try {
2397
- const existing = JSON.parse((0, import_fs4.readFileSync)(storagePath, "utf8"));
3684
+ const existing = JSON.parse((0, import_fs5.readFileSync)(storagePath, "utf8"));
2398
3685
  return {
2399
3686
  success: true,
2400
3687
  address: existing.address,
@@ -2425,11 +3712,11 @@ function createWallet(options = {}) {
2425
3712
  } else {
2426
3713
  walletData.privateKey = wallet.privateKey;
2427
3714
  }
2428
- const dir = (0, import_path2.dirname)(storagePath);
2429
- if (!(0, import_fs4.existsSync)(dir)) {
2430
- (0, import_fs4.mkdirSync)(dir, { recursive: true });
3715
+ const dir = (0, import_path3.dirname)(storagePath);
3716
+ if (!(0, import_fs5.existsSync)(dir)) {
3717
+ (0, import_fs5.mkdirSync)(dir, { recursive: true });
2431
3718
  }
2432
- (0, import_fs4.writeFileSync)(storagePath, JSON.stringify(walletData, null, 2), { mode: 384 });
3719
+ (0, import_fs5.writeFileSync)(storagePath, JSON.stringify(walletData, null, 2), { mode: 384 });
2433
3720
  return {
2434
3721
  success: true,
2435
3722
  address: wallet.address,
@@ -2444,12 +3731,12 @@ function createWallet(options = {}) {
2444
3731
  }
2445
3732
  }
2446
3733
  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)) {
3734
+ const storagePath = options.storagePath || (0, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
3735
+ if (!(0, import_fs5.existsSync)(storagePath)) {
2449
3736
  return { success: false, error: "Wallet not found. Run createWallet() first." };
2450
3737
  }
2451
3738
  try {
2452
- const data = JSON.parse((0, import_fs4.readFileSync)(storagePath, "utf8"));
3739
+ const data = JSON.parse((0, import_fs5.readFileSync)(storagePath, "utf8"));
2453
3740
  if (data.encrypted) {
2454
3741
  if (!options.password) {
2455
3742
  return { success: false, error: "Wallet is encrypted. Password required." };
@@ -2464,25 +3751,25 @@ function loadWallet(options = {}) {
2464
3751
  }
2465
3752
  }
2466
3753
  function getWalletAddress(storagePath) {
2467
- const path4 = storagePath || (0, import_path2.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
2468
- if (!(0, import_fs4.existsSync)(path4)) {
3754
+ const path4 = storagePath || (0, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
3755
+ if (!(0, import_fs5.existsSync)(path4)) {
2469
3756
  return null;
2470
3757
  }
2471
3758
  try {
2472
- const data = JSON.parse((0, import_fs4.readFileSync)(path4, "utf8"));
3759
+ const data = JSON.parse((0, import_fs5.readFileSync)(path4, "utf8"));
2473
3760
  return data.address;
2474
3761
  } catch {
2475
3762
  return null;
2476
3763
  }
2477
3764
  }
2478
3765
  function walletExists(storagePath) {
2479
- const path4 = storagePath || (0, import_path2.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
2480
- return (0, import_fs4.existsSync)(path4);
3766
+ const path4 = storagePath || (0, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
3767
+ return (0, import_fs5.existsSync)(path4);
2481
3768
  }
2482
3769
 
2483
3770
  // src/verify/index.ts
2484
3771
  var import_ethers4 = require("ethers");
2485
- var TRANSFER_EVENT_TOPIC2 = import_ethers4.ethers.id("Transfer(address,address,uint256)");
3772
+ var TRANSFER_EVENT_TOPIC3 = import_ethers4.ethers.id("Transfer(address,address,uint256)");
2486
3773
  async function verifyPayment(params) {
2487
3774
  const { txHash, expectedAmount, expectedTo, expectedToken } = params;
2488
3775
  let chain;
@@ -2523,7 +3810,7 @@ async function verifyPayment(params) {
2523
3810
  if (!detectedToken) {
2524
3811
  continue;
2525
3812
  }
2526
- if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC2) {
3813
+ if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC3) {
2527
3814
  continue;
2528
3815
  }
2529
3816
  const from = "0x" + log.topics[1].slice(-40);