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.mjs CHANGED
@@ -343,6 +343,63 @@ var CHAINS = {
343
343
  explorerTx: "https://explore.testnet.tempo.xyz/tx/",
344
344
  avgBlockTime: 0.5
345
345
  // ~500ms finality
346
+ },
347
+ // ============ BNB Chain Testnet ============
348
+ bnb_testnet: {
349
+ name: "BNB Testnet",
350
+ chainId: 97,
351
+ rpc: "https://data-seed-prebsc-1-s1.binance.org:8545",
352
+ tokens: {
353
+ // Note: BNB uses 18 decimals for stablecoins (unlike Base/Polygon which use 6)
354
+ // Using official Binance-Peg testnet tokens
355
+ USDC: {
356
+ address: "0x64544969ed7EBf5f083679233325356EbE738930",
357
+ // Testnet USDC
358
+ decimals: 18,
359
+ symbol: "USDC",
360
+ eip712Name: "USD Coin"
361
+ },
362
+ USDT: {
363
+ address: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd",
364
+ // Testnet USDT
365
+ decimals: 18,
366
+ symbol: "USDT",
367
+ eip712Name: "Tether USD"
368
+ }
369
+ },
370
+ usdc: "0x64544969ed7EBf5f083679233325356EbE738930",
371
+ explorer: "https://testnet.bscscan.com/address/",
372
+ explorerTx: "https://testnet.bscscan.com/tx/",
373
+ avgBlockTime: 3,
374
+ // BNB-specific: requires approval for pay-for-success flow
375
+ requiresApproval: true
376
+ },
377
+ // ============ BNB Chain Mainnet ============
378
+ bnb: {
379
+ name: "BNB Smart Chain",
380
+ chainId: 56,
381
+ rpc: "https://bsc-dataseed.binance.org",
382
+ tokens: {
383
+ // Note: BNB uses 18 decimals for stablecoins
384
+ USDC: {
385
+ address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
386
+ decimals: 18,
387
+ symbol: "USDC",
388
+ eip712Name: "USD Coin"
389
+ },
390
+ USDT: {
391
+ address: "0x55d398326f99059fF775485246999027B3197955",
392
+ decimals: 18,
393
+ symbol: "USDT",
394
+ eip712Name: "Tether USD"
395
+ }
396
+ },
397
+ usdc: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
398
+ explorer: "https://bscscan.com/address/",
399
+ explorerTx: "https://bscscan.com/tx/",
400
+ avgBlockTime: 3,
401
+ // BNB-specific: requires approval for pay-for-success flow
402
+ requiresApproval: true
346
403
  }
347
404
  };
348
405
  function getChain(name) {
@@ -492,7 +549,583 @@ var TempoFacilitator = class extends BaseFacilitator {
492
549
  }
493
550
  };
494
551
 
552
+ // src/facilitators/bnb.ts
553
+ import { privateKeyToAccount } from "viem/accounts";
554
+ var TRANSFER_EVENT_TOPIC2 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
555
+ var EIP712_DOMAIN = {
556
+ name: "MoltsPay",
557
+ version: "1"
558
+ };
559
+ var INTENT_TYPES = {
560
+ PaymentIntent: [
561
+ { name: "from", type: "address" },
562
+ { name: "to", type: "address" },
563
+ { name: "amount", type: "uint256" },
564
+ { name: "token", type: "address" },
565
+ { name: "service", type: "string" },
566
+ { name: "nonce", type: "uint256" },
567
+ { name: "deadline", type: "uint256" }
568
+ ]
569
+ };
570
+ var BNBFacilitator = class extends BaseFacilitator {
571
+ name = "bnb";
572
+ displayName = "BNB Smart Chain";
573
+ supportedNetworks = ["eip155:56", "eip155:97"];
574
+ // Mainnet + Testnet
575
+ serverPrivateKey;
576
+ spenderAddress = null;
577
+ chainConfigs;
578
+ constructor(serverPrivateKey) {
579
+ super();
580
+ this.serverPrivateKey = serverPrivateKey || process.env.BNB_SERVER_PRIVATE_KEY || "";
581
+ if (this.serverPrivateKey) {
582
+ const key = this.serverPrivateKey.startsWith("0x") ? this.serverPrivateKey : `0x${this.serverPrivateKey}`;
583
+ const account = privateKeyToAccount(key);
584
+ this.spenderAddress = account.address;
585
+ }
586
+ this.chainConfigs = {
587
+ 56: { rpc: CHAINS.bnb.rpc, chain: CHAINS.bnb },
588
+ 97: { rpc: CHAINS.bnb_testnet.rpc, chain: CHAINS.bnb_testnet }
589
+ };
590
+ }
591
+ async healthCheck() {
592
+ const start = Date.now();
593
+ try {
594
+ const response = await fetch(this.chainConfigs[56].rpc, {
595
+ method: "POST",
596
+ headers: { "Content-Type": "application/json" },
597
+ body: JSON.stringify({
598
+ jsonrpc: "2.0",
599
+ method: "eth_chainId",
600
+ params: [],
601
+ id: 1
602
+ })
603
+ });
604
+ const data = await response.json();
605
+ const chainId = parseInt(data.result, 16);
606
+ if (chainId !== 56) {
607
+ return { healthy: false, error: `Wrong chainId: ${chainId}` };
608
+ }
609
+ return { healthy: true, latencyMs: Date.now() - start };
610
+ } catch (error) {
611
+ return { healthy: false, error: String(error) };
612
+ }
613
+ }
614
+ /**
615
+ * Verify a payment intent signature (before service execution)
616
+ *
617
+ * This verifies:
618
+ * 1. Signature is valid for the intent
619
+ * 2. Client has approved server wallet
620
+ * 3. Client has sufficient balance
621
+ * 4. Intent hasn't expired
622
+ */
623
+ async verify(paymentPayload, requirements) {
624
+ try {
625
+ const bnbPayload = paymentPayload.payload;
626
+ if (!bnbPayload?.intent) {
627
+ return { valid: false, error: "Missing intent in payment payload" };
628
+ }
629
+ const { intent, chainId } = bnbPayload;
630
+ const config = this.chainConfigs[chainId];
631
+ if (!config) {
632
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
633
+ }
634
+ if (intent.deadline < Date.now()) {
635
+ return { valid: false, error: "Intent expired" };
636
+ }
637
+ const recoveredAddress = await this.recoverIntentSigner(intent, chainId);
638
+ if (recoveredAddress.toLowerCase() !== intent.from.toLowerCase()) {
639
+ return { valid: false, error: "Invalid signature" };
640
+ }
641
+ if (intent.to.toLowerCase() !== requirements.payTo.toLowerCase()) {
642
+ return { valid: false, error: `Wrong recipient: ${intent.to}` };
643
+ }
644
+ if (BigInt(intent.amount) < BigInt(requirements.amount)) {
645
+ return { valid: false, error: `Insufficient amount: ${intent.amount}` };
646
+ }
647
+ if (intent.token.toLowerCase() !== requirements.asset.toLowerCase()) {
648
+ return { valid: false, error: `Wrong token: ${intent.token}` };
649
+ }
650
+ const serverAddress = await this.getServerAddress();
651
+ const allowance = await this.getAllowance(intent.from, serverAddress, intent.token, config.rpc);
652
+ if (BigInt(allowance) < BigInt(intent.amount)) {
653
+ return { valid: false, error: "Insufficient allowance. Run: npx moltspay init --chain bnb" };
654
+ }
655
+ const balance = await this.getBalance(intent.from, intent.token, config.rpc);
656
+ if (BigInt(balance) < BigInt(intent.amount)) {
657
+ return { valid: false, error: "Insufficient balance" };
658
+ }
659
+ return {
660
+ valid: true,
661
+ details: {
662
+ from: intent.from,
663
+ to: intent.to,
664
+ amount: intent.amount,
665
+ token: intent.token,
666
+ service: intent.service,
667
+ nonce: intent.nonce,
668
+ deadline: intent.deadline
669
+ }
670
+ };
671
+ } catch (error) {
672
+ return { valid: false, error: `Verification failed: ${error}` };
673
+ }
674
+ }
675
+ /**
676
+ * Settle a payment by executing transferFrom
677
+ *
678
+ * This is called AFTER the service has been successfully delivered.
679
+ * Server pays gas, transfers tokens from client to provider.
680
+ */
681
+ async settle(paymentPayload, requirements) {
682
+ if (!this.serverPrivateKey) {
683
+ return { success: false, error: "Server wallet not configured (BNB_SERVER_PRIVATE_KEY)" };
684
+ }
685
+ try {
686
+ const verifyResult = await this.verify(paymentPayload, requirements);
687
+ if (!verifyResult.valid) {
688
+ return { success: false, error: verifyResult.error };
689
+ }
690
+ const bnbPayload = paymentPayload.payload;
691
+ const { intent, chainId } = bnbPayload;
692
+ const config = this.chainConfigs[chainId];
693
+ const txHash = await this.executeTransferFrom(
694
+ intent.from,
695
+ intent.to,
696
+ intent.amount,
697
+ intent.token,
698
+ config.rpc
699
+ );
700
+ return {
701
+ success: true,
702
+ transaction: txHash,
703
+ status: "settled"
704
+ };
705
+ } catch (error) {
706
+ return { success: false, error: `Settlement failed: ${error}` };
707
+ }
708
+ }
709
+ /**
710
+ * Check if client has approved the server wallet
711
+ */
712
+ async checkApproval(clientAddress, token, chainId) {
713
+ const config = this.chainConfigs[chainId];
714
+ if (!config) {
715
+ throw new Error(`Unsupported chainId: ${chainId}`);
716
+ }
717
+ const serverAddress = await this.getServerAddress();
718
+ const allowance = await this.getAllowance(clientAddress, serverAddress, token, config.rpc);
719
+ const minAllowance = BigInt("1000000000000000000000");
720
+ return {
721
+ approved: BigInt(allowance) >= minAllowance,
722
+ allowance
723
+ };
724
+ }
725
+ /**
726
+ * Verify a completed transaction (for checking past payments)
727
+ */
728
+ async verifyTransaction(txHash, expected, chainId) {
729
+ const config = this.chainConfigs[chainId];
730
+ if (!config) {
731
+ return { valid: false, error: `Unsupported chainId: ${chainId}` };
732
+ }
733
+ try {
734
+ const receipt = await this.getTransactionReceipt(txHash, config.rpc);
735
+ if (!receipt) {
736
+ return { valid: false, error: "Transaction not found" };
737
+ }
738
+ if (receipt.status !== "0x1") {
739
+ return { valid: false, error: "Transaction failed" };
740
+ }
741
+ const transferLog = receipt.logs.find(
742
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC2 && log.address.toLowerCase() === expected.token.toLowerCase()
743
+ );
744
+ if (!transferLog) {
745
+ return { valid: false, error: "No Transfer event found" };
746
+ }
747
+ const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
748
+ if (toAddress !== expected.to.toLowerCase()) {
749
+ return { valid: false, error: `Wrong recipient: ${toAddress}` };
750
+ }
751
+ const amount = BigInt(transferLog.data);
752
+ if (amount < BigInt(expected.amount)) {
753
+ return { valid: false, error: `Insufficient amount: ${amount}` };
754
+ }
755
+ return {
756
+ valid: true,
757
+ details: {
758
+ txHash,
759
+ from: "0x" + transferLog.topics[1].slice(26),
760
+ to: toAddress,
761
+ amount: amount.toString(),
762
+ token: transferLog.address
763
+ }
764
+ };
765
+ } catch (error) {
766
+ return { valid: false, error: `Verification failed: ${error}` };
767
+ }
768
+ }
769
+ // ==================== Private Methods ====================
770
+ /**
771
+ * Get the server's spender address (public, for 402 responses)
772
+ * Returns cached value computed at construction time.
773
+ */
774
+ getSpenderAddress() {
775
+ return this.spenderAddress;
776
+ }
777
+ async getServerAddress() {
778
+ const { ethers: ethers5 } = await import("ethers");
779
+ const wallet = new ethers5.Wallet(this.serverPrivateKey);
780
+ return wallet.address;
781
+ }
782
+ async recoverIntentSigner(intent, chainId) {
783
+ const { ethers: ethers5 } = await import("ethers");
784
+ const domain = {
785
+ ...EIP712_DOMAIN,
786
+ chainId
787
+ };
788
+ const message = {
789
+ from: intent.from,
790
+ to: intent.to,
791
+ amount: intent.amount,
792
+ token: intent.token,
793
+ service: intent.service,
794
+ nonce: intent.nonce,
795
+ deadline: intent.deadline
796
+ };
797
+ const recoveredAddress = ethers5.verifyTypedData(
798
+ domain,
799
+ INTENT_TYPES,
800
+ message,
801
+ intent.signature
802
+ );
803
+ return recoveredAddress;
804
+ }
805
+ async getAllowance(owner, spender, token, rpcUrl) {
806
+ const selector = "0xdd62ed3e";
807
+ const ownerPadded = owner.toLowerCase().replace("0x", "").padStart(64, "0");
808
+ const spenderPadded = spender.toLowerCase().replace("0x", "").padStart(64, "0");
809
+ const data = selector + ownerPadded + spenderPadded;
810
+ const response = await fetch(rpcUrl, {
811
+ method: "POST",
812
+ headers: { "Content-Type": "application/json" },
813
+ body: JSON.stringify({
814
+ jsonrpc: "2.0",
815
+ method: "eth_call",
816
+ params: [{ to: token, data }, "latest"],
817
+ id: 1
818
+ })
819
+ });
820
+ const result = await response.json();
821
+ return result.result || "0x0";
822
+ }
823
+ async getBalance(account, token, rpcUrl) {
824
+ const selector = "0x70a08231";
825
+ const accountPadded = account.toLowerCase().replace("0x", "").padStart(64, "0");
826
+ const data = selector + accountPadded;
827
+ const response = await fetch(rpcUrl, {
828
+ method: "POST",
829
+ headers: { "Content-Type": "application/json" },
830
+ body: JSON.stringify({
831
+ jsonrpc: "2.0",
832
+ method: "eth_call",
833
+ params: [{ to: token, data }, "latest"],
834
+ id: 1
835
+ })
836
+ });
837
+ const result = await response.json();
838
+ return result.result || "0x0";
839
+ }
840
+ async executeTransferFrom(from, to, amount, token, rpcUrl) {
841
+ const { ethers: ethers5 } = await import("ethers");
842
+ const provider = new ethers5.JsonRpcProvider(rpcUrl);
843
+ const wallet = new ethers5.Wallet(this.serverPrivateKey, provider);
844
+ const tokenContract = new ethers5.Contract(token, [
845
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)"
846
+ ], wallet);
847
+ const tx = await tokenContract.transferFrom(from, to, amount);
848
+ const receipt = await tx.wait();
849
+ return receipt.hash;
850
+ }
851
+ async getTransactionReceipt(txHash, rpcUrl) {
852
+ const response = await fetch(rpcUrl, {
853
+ method: "POST",
854
+ headers: { "Content-Type": "application/json" },
855
+ body: JSON.stringify({
856
+ jsonrpc: "2.0",
857
+ method: "eth_getTransactionReceipt",
858
+ params: [txHash],
859
+ id: 1
860
+ })
861
+ });
862
+ const data = await response.json();
863
+ return data.result;
864
+ }
865
+ };
866
+
867
+ // src/facilitators/solana.ts
868
+ import {
869
+ Connection as Connection2,
870
+ PublicKey as PublicKey2,
871
+ Transaction,
872
+ VersionedTransaction
873
+ } from "@solana/web3.js";
874
+ import {
875
+ getAssociatedTokenAddress,
876
+ createTransferCheckedInstruction,
877
+ getAccount,
878
+ createAssociatedTokenAccountInstruction
879
+ } from "@solana/spl-token";
880
+
881
+ // src/chains/solana.ts
882
+ import { Connection, PublicKey } from "@solana/web3.js";
883
+ var SOLANA_CHAINS = {
884
+ solana: {
885
+ name: "Solana Mainnet",
886
+ cluster: "mainnet-beta",
887
+ rpc: "https://api.mainnet-beta.solana.com",
888
+ explorer: "https://solscan.io/account/",
889
+ explorerTx: "https://solscan.io/tx/",
890
+ tokens: {
891
+ USDC: {
892
+ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
893
+ // Circle official USDC
894
+ decimals: 6
895
+ }
896
+ }
897
+ },
898
+ solana_devnet: {
899
+ name: "Solana Devnet",
900
+ cluster: "devnet",
901
+ rpc: "https://api.devnet.solana.com",
902
+ explorer: "https://solscan.io/account/",
903
+ explorerTx: "https://solscan.io/tx/",
904
+ tokens: {
905
+ USDC: {
906
+ // Circle's devnet USDC (if not available, we'll deploy our own test token)
907
+ mint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
908
+ decimals: 6
909
+ }
910
+ }
911
+ }
912
+ };
913
+
914
+ // src/facilitators/solana.ts
915
+ var SolanaFacilitator = class extends BaseFacilitator {
916
+ name = "solana";
917
+ displayName = "Solana Direct";
918
+ supportedNetworks = ["solana:mainnet", "solana:devnet"];
919
+ connections = /* @__PURE__ */ new Map();
920
+ feePayerKeypair;
921
+ constructor(config) {
922
+ super();
923
+ this.feePayerKeypair = config?.feePayerKeypair;
924
+ for (const [chain, config2] of Object.entries(SOLANA_CHAINS)) {
925
+ this.connections.set(
926
+ chain,
927
+ new Connection2(config2.rpc, "confirmed")
928
+ );
929
+ }
930
+ if (this.feePayerKeypair) {
931
+ console.log(`[SolanaFacilitator] Gasless mode enabled. Fee payer: ${this.feePayerKeypair.publicKey.toBase58()}`);
932
+ }
933
+ }
934
+ /**
935
+ * Get fee payer public key (for gasless transactions)
936
+ */
937
+ getFeePayerPubkey() {
938
+ return this.feePayerKeypair?.publicKey.toBase58() || null;
939
+ }
940
+ getConnection(chain) {
941
+ const conn = this.connections.get(chain);
942
+ if (!conn) {
943
+ throw new Error(`No connection for chain: ${chain}`);
944
+ }
945
+ return conn;
946
+ }
947
+ /**
948
+ * Convert our chain name to network identifier
949
+ */
950
+ static chainToNetwork(chain) {
951
+ return chain === "solana" ? "solana:mainnet" : "solana:devnet";
952
+ }
953
+ /**
954
+ * Convert network identifier to chain name
955
+ */
956
+ static networkToChain(network) {
957
+ if (network === "solana:mainnet") return "solana";
958
+ if (network === "solana:devnet") return "solana_devnet";
959
+ return null;
960
+ }
961
+ async healthCheck() {
962
+ const start = Date.now();
963
+ try {
964
+ const conn = this.getConnection("solana_devnet");
965
+ await conn.getSlot();
966
+ return {
967
+ healthy: true,
968
+ latencyMs: Date.now() - start
969
+ };
970
+ } catch (error) {
971
+ return {
972
+ healthy: false,
973
+ error: error.message
974
+ };
975
+ }
976
+ }
977
+ /**
978
+ * Verify a Solana payment
979
+ *
980
+ * Checks:
981
+ * 1. Transaction is valid and properly signed
982
+ * 2. Transfer instruction matches expected amount and recipient
983
+ */
984
+ async verify(paymentPayload, requirements) {
985
+ try {
986
+ const solanaPayload = paymentPayload.payload;
987
+ if (!solanaPayload || !solanaPayload.signedTransaction) {
988
+ return { valid: false, error: "Missing signed transaction" };
989
+ }
990
+ const chain = solanaPayload.chain || "solana_devnet";
991
+ const chainConfig = SOLANA_CHAINS[chain];
992
+ if (!chainConfig) {
993
+ return { valid: false, error: `Invalid chain: ${chain}` };
994
+ }
995
+ const txBuffer = Buffer.from(solanaPayload.signedTransaction, "base64");
996
+ let tx;
997
+ try {
998
+ tx = Transaction.from(txBuffer);
999
+ } catch {
1000
+ tx = VersionedTransaction.deserialize(txBuffer);
1001
+ }
1002
+ if (tx instanceof Transaction) {
1003
+ const hasAnySignature = tx.signatures.some(
1004
+ (sig) => sig.signature && !sig.signature.every((b) => b === 0)
1005
+ );
1006
+ if (!hasAnySignature) {
1007
+ return { valid: false, error: "Transaction not signed" };
1008
+ }
1009
+ }
1010
+ const expectedAmount = BigInt(requirements.amount);
1011
+ const expectedRecipient = new PublicKey2(requirements.payTo);
1012
+ return {
1013
+ valid: true,
1014
+ details: {
1015
+ chain,
1016
+ sender: solanaPayload.sender,
1017
+ recipient: requirements.payTo,
1018
+ amount: requirements.amount
1019
+ }
1020
+ };
1021
+ } catch (error) {
1022
+ return { valid: false, error: error.message };
1023
+ }
1024
+ }
1025
+ /**
1026
+ * Settle a Solana payment
1027
+ *
1028
+ * Submits the signed transaction to the network.
1029
+ * In gasless mode, adds fee payer signature before submitting.
1030
+ */
1031
+ async settle(paymentPayload, requirements) {
1032
+ try {
1033
+ const solanaPayload = paymentPayload.payload;
1034
+ if (!solanaPayload || !solanaPayload.signedTransaction) {
1035
+ return { success: false, error: "Missing signed transaction" };
1036
+ }
1037
+ const chain = solanaPayload.chain || "solana_devnet";
1038
+ const connection = this.getConnection(chain);
1039
+ const txBuffer = Buffer.from(solanaPayload.signedTransaction, "base64");
1040
+ let txToSend;
1041
+ try {
1042
+ const tx = Transaction.from(txBuffer);
1043
+ if (this.feePayerKeypair && tx.feePayer) {
1044
+ const feePayerPubkey = this.feePayerKeypair.publicKey.toBase58();
1045
+ const txFeePayer = tx.feePayer.toBase58();
1046
+ if (txFeePayer === feePayerPubkey) {
1047
+ console.log(`[SolanaFacilitator] Gasless mode: adding fee payer signature`);
1048
+ tx.partialSign(this.feePayerKeypair);
1049
+ }
1050
+ }
1051
+ txToSend = tx.serialize();
1052
+ } catch (e) {
1053
+ txToSend = txBuffer;
1054
+ }
1055
+ const signature = await connection.sendRawTransaction(txToSend, {
1056
+ skipPreflight: false,
1057
+ preflightCommitment: "confirmed"
1058
+ });
1059
+ const confirmation = await connection.confirmTransaction(signature, "confirmed");
1060
+ if (confirmation.value.err) {
1061
+ return {
1062
+ success: false,
1063
+ error: `Transaction failed: ${JSON.stringify(confirmation.value.err)}`,
1064
+ transaction: signature
1065
+ };
1066
+ }
1067
+ return {
1068
+ success: true,
1069
+ transaction: signature,
1070
+ status: "confirmed"
1071
+ };
1072
+ } catch (error) {
1073
+ return { success: false, error: error.message };
1074
+ }
1075
+ }
1076
+ supportsNetwork(network) {
1077
+ return this.supportedNetworks.includes(network);
1078
+ }
1079
+ };
1080
+ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
1081
+ const chainConfig = SOLANA_CHAINS[chain];
1082
+ const connection = new Connection2(chainConfig.rpc, "confirmed");
1083
+ const mint = new PublicKey2(chainConfig.tokens.USDC.mint);
1084
+ const actualFeePayer = feePayerPubkey || senderPubkey;
1085
+ const senderATA = await getAssociatedTokenAddress(mint, senderPubkey);
1086
+ const recipientATA = await getAssociatedTokenAddress(mint, recipientPubkey);
1087
+ const transaction = new Transaction();
1088
+ try {
1089
+ await getAccount(connection, recipientATA);
1090
+ } catch {
1091
+ transaction.add(
1092
+ createAssociatedTokenAccountInstruction(
1093
+ actualFeePayer,
1094
+ // payer (fee payer in gasless mode)
1095
+ recipientATA,
1096
+ // ata to create
1097
+ recipientPubkey,
1098
+ // owner
1099
+ mint
1100
+ // mint
1101
+ )
1102
+ );
1103
+ }
1104
+ transaction.add(
1105
+ createTransferCheckedInstruction(
1106
+ senderATA,
1107
+ // source
1108
+ mint,
1109
+ // mint
1110
+ recipientATA,
1111
+ // destination
1112
+ senderPubkey,
1113
+ // owner (sender still authorizes the transfer)
1114
+ amount,
1115
+ // amount
1116
+ chainConfig.tokens.USDC.decimals
1117
+ // decimals
1118
+ )
1119
+ );
1120
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
1121
+ transaction.recentBlockhash = blockhash;
1122
+ transaction.feePayer = actualFeePayer;
1123
+ return transaction;
1124
+ }
1125
+
495
1126
  // src/facilitators/registry.ts
1127
+ import { Keypair as Keypair2 } from "@solana/web3.js";
1128
+ import bs58 from "bs58";
496
1129
  var FacilitatorRegistry = class {
497
1130
  factories = /* @__PURE__ */ new Map();
498
1131
  instances = /* @__PURE__ */ new Map();
@@ -501,7 +1134,20 @@ var FacilitatorRegistry = class {
501
1134
  constructor(selection) {
502
1135
  this.registerFactory("cdp", (config) => new CDPFacilitator(config));
503
1136
  this.registerFactory("tempo", () => new TempoFacilitator());
504
- this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
1137
+ this.registerFactory("bnb", (config) => new BNBFacilitator(config?.serverPrivateKey));
1138
+ this.registerFactory("solana", (config) => {
1139
+ let feePayerKeypair;
1140
+ const feePayerKey = config?.feePayerPrivateKey || process.env.SOLANA_FEE_PAYER_KEY;
1141
+ if (feePayerKey) {
1142
+ try {
1143
+ feePayerKeypair = Keypair2.fromSecretKey(bs58.decode(feePayerKey));
1144
+ } catch (e) {
1145
+ console.warn(`[SolanaFacilitator] Invalid fee payer key: ${e.message}`);
1146
+ }
1147
+ }
1148
+ return new SolanaFacilitator({ feePayerKeypair });
1149
+ });
1150
+ this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
505
1151
  }
506
1152
  /**
507
1153
  * Register a new facilitator factory
@@ -755,14 +1401,40 @@ var TOKEN_ADDRESSES = {
755
1401
  // pathUSD
756
1402
  USDT: "0x20c0000000000000000000000000000000000001"
757
1403
  // alphaUSD
1404
+ },
1405
+ // BNB Smart Chain mainnet
1406
+ "eip155:56": {
1407
+ USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
1408
+ USDT: "0x55d398326f99059fF775485246999027B3197955"
1409
+ },
1410
+ // BNB Smart Chain testnet
1411
+ "eip155:97": {
1412
+ USDC: "0x64544969ed7EBf5f083679233325356EbE738930",
1413
+ USDT: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd"
1414
+ },
1415
+ // Solana networks use mint addresses (SPL tokens)
1416
+ "solana:mainnet": {
1417
+ USDC: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
1418
+ // Circle USDC
1419
+ },
1420
+ "solana:devnet": {
1421
+ USDC: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
1422
+ // Devnet USDC
758
1423
  }
759
1424
  };
760
1425
  var CHAIN_TO_NETWORK = {
761
1426
  "base": "eip155:8453",
762
1427
  "base_sepolia": "eip155:84532",
763
1428
  "polygon": "eip155:137",
764
- "tempo_moderato": "eip155:42431"
1429
+ "tempo_moderato": "eip155:42431",
1430
+ "bnb": "eip155:56",
1431
+ "bnb_testnet": "eip155:97",
1432
+ "solana": "solana:mainnet",
1433
+ "solana_devnet": "solana:devnet"
765
1434
  };
1435
+ function isSolanaNetwork(network) {
1436
+ return network.startsWith("solana:");
1437
+ }
766
1438
  var TOKEN_DOMAINS = {
767
1439
  // Base mainnet
768
1440
  "eip155:8453": {
@@ -784,6 +1456,16 @@ var TOKEN_DOMAINS = {
784
1456
  "eip155:42431": {
785
1457
  USDC: { name: "pathUSD", version: "1" },
786
1458
  USDT: { name: "alphaUSD", version: "1" }
1459
+ },
1460
+ // BNB Smart Chain mainnet
1461
+ "eip155:56": {
1462
+ USDC: { name: "USD Coin", version: "1" },
1463
+ USDT: { name: "Tether USD", version: "1" }
1464
+ },
1465
+ // BNB Smart Chain testnet
1466
+ "eip155:97": {
1467
+ USDC: { name: "USD Coin", version: "1" },
1468
+ USDT: { name: "Tether USD", version: "1" }
787
1469
  }
788
1470
  };
789
1471
  function getTokenDomain(network, token) {
@@ -841,7 +1523,7 @@ var MoltsPayServer = class {
841
1523
  };
842
1524
  this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
843
1525
  this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
844
- const defaultFallback = ["tempo"];
1526
+ const defaultFallback = ["tempo", "bnb", "solana"];
845
1527
  const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
846
1528
  const facilitatorConfig = options.facilitators || {
847
1529
  primary: process.env.FACILITATOR_PRIMARY || "cdp",
@@ -884,12 +1566,20 @@ var MoltsPayServer = class {
884
1566
  */
885
1567
  getProviderChains() {
886
1568
  const provider = this.manifest.provider;
1569
+ const getWalletForChain = (chainName, explicitWallet) => {
1570
+ if (explicitWallet) return explicitWallet;
1571
+ if ((chainName === "solana" || chainName === "solana_devnet") && provider.solana_wallet) {
1572
+ return provider.solana_wallet;
1573
+ }
1574
+ return provider.wallet;
1575
+ };
887
1576
  if (provider.chains && provider.chains.length > 0) {
888
1577
  return provider.chains.map((c) => {
889
1578
  const chainName = typeof c === "string" ? c : c.chain;
1579
+ const explicitWallet = typeof c === "object" ? c.wallet : null;
890
1580
  return {
891
1581
  network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
892
- wallet: (typeof c === "object" ? c.wallet : null) || provider.wallet,
1582
+ wallet: getWalletForChain(chainName, explicitWallet || void 0),
893
1583
  tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
894
1584
  };
895
1585
  });
@@ -898,7 +1588,7 @@ var MoltsPayServer = class {
898
1588
  const network = CHAIN_TO_NETWORK[chain] || this.networkId;
899
1589
  return [{
900
1590
  network,
901
- wallet: provider.wallet,
1591
+ wallet: getWalletForChain(chain),
902
1592
  tokens: ["USDC"]
903
1593
  }];
904
1594
  }
@@ -969,7 +1659,8 @@ var MoltsPayServer = class {
969
1659
  }
970
1660
  const body = await this.readBody(req);
971
1661
  const paymentHeader = req.headers[PAYMENT_HEADER];
972
- return await this.handleProxy(body, paymentHeader, res);
1662
+ const authHeader = req.headers[MPP_AUTH_HEADER];
1663
+ return await this.handleProxy(body, paymentHeader, authHeader, res);
973
1664
  }
974
1665
  const servicePath = url.pathname.replace(/^\//, "");
975
1666
  const skill = this.skills.get(servicePath);
@@ -1006,7 +1697,9 @@ var MoltsPayServer = class {
1006
1697
  name: this.manifest.provider.name,
1007
1698
  description: this.manifest.provider.description,
1008
1699
  wallet: this.manifest.provider.wallet,
1009
- chain: this.manifest.provider.chain || "base"
1700
+ chain: this.manifest.provider.chain || "base",
1701
+ solana_wallet: this.manifest.provider.solana_wallet,
1702
+ chains: this.manifest.provider.chains
1010
1703
  },
1011
1704
  services,
1012
1705
  endpoints: {
@@ -1119,6 +1812,21 @@ var MoltsPayServer = class {
1119
1812
  });
1120
1813
  }
1121
1814
  console.log(`[MoltsPay] Verified by ${verifyResult.facilitator}`);
1815
+ const isSolana = isSolanaNetwork(paymentNetwork);
1816
+ let settlement = null;
1817
+ if (isSolana) {
1818
+ console.log(`[MoltsPay] Solana detected - settling payment FIRST (blockhash expiry protection)`);
1819
+ try {
1820
+ settlement = await this.registry.settle(payment, requirements);
1821
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1822
+ } catch (err) {
1823
+ console.error("[MoltsPay] Solana settlement failed:", err.message);
1824
+ return this.sendJson(res, 402, {
1825
+ error: "Payment settlement failed",
1826
+ message: err.message
1827
+ });
1828
+ }
1829
+ }
1122
1830
  const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
1123
1831
  console.log(`[MoltsPay] Executing skill: ${service} (timeout: ${timeoutSeconds}s)`);
1124
1832
  let result;
@@ -1133,16 +1841,19 @@ var MoltsPayServer = class {
1133
1841
  console.error("[MoltsPay] Skill execution failed:", err.message);
1134
1842
  return this.sendJson(res, 500, {
1135
1843
  error: "Service execution failed",
1136
- message: err.message
1844
+ message: err.message,
1845
+ paymentSettled: isSolana ? true : false,
1846
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
1137
1847
  });
1138
1848
  }
1139
- console.log(`[MoltsPay] Skill succeeded, settling payment...`);
1140
- let settlement = null;
1141
- try {
1142
- settlement = await this.registry.settle(payment, requirements);
1143
- console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1144
- } catch (err) {
1145
- console.error("[MoltsPay] Settlement failed:", err.message);
1849
+ if (!isSolana) {
1850
+ console.log(`[MoltsPay] Skill succeeded, settling payment...`);
1851
+ try {
1852
+ settlement = await this.registry.settle(payment, requirements);
1853
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1854
+ } catch (err) {
1855
+ console.error("[MoltsPay] Settlement failed:", err.message);
1856
+ }
1146
1857
  }
1147
1858
  const responseHeaders = {};
1148
1859
  if (settlement?.success) {
@@ -1418,7 +2129,7 @@ var MoltsPayServer = class {
1418
2129
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
1419
2130
  const tokenAddress = tokenAddresses[selectedToken];
1420
2131
  const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
1421
- return {
2132
+ const requirements = {
1422
2133
  scheme: "exact",
1423
2134
  network: selectedNetwork,
1424
2135
  asset: tokenAddress,
@@ -1427,6 +2138,27 @@ var MoltsPayServer = class {
1427
2138
  maxTimeoutSeconds: 300,
1428
2139
  extra: tokenDomain
1429
2140
  };
2141
+ if (selectedNetwork === "solana:mainnet" || selectedNetwork === "solana:devnet") {
2142
+ const solanaFacilitator = this.registry.get("solana");
2143
+ const feePayerPubkey = solanaFacilitator?.getFeePayerPubkey?.();
2144
+ if (feePayerPubkey) {
2145
+ requirements.extra = {
2146
+ ...requirements.extra || {},
2147
+ solanaFeePayer: feePayerPubkey
2148
+ };
2149
+ }
2150
+ }
2151
+ if (selectedNetwork === "eip155:56" || selectedNetwork === "eip155:97") {
2152
+ const bnbFacilitator = this.registry.get("bnb");
2153
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
2154
+ if (spenderAddress) {
2155
+ requirements.extra = {
2156
+ ...requirements.extra || {},
2157
+ bnbSpender: spenderAddress
2158
+ };
2159
+ }
2160
+ }
2161
+ return requirements;
1430
2162
  }
1431
2163
  /**
1432
2164
  * Detect which token is being used in the payment
@@ -1479,8 +2211,10 @@ var MoltsPayServer = class {
1479
2211
  isProxyAllowed(clientIP) {
1480
2212
  const allowedIPs = process.env.PROXY_ALLOWED_IPS?.split(",").map((ip) => ip.trim()) || [];
1481
2213
  if (allowedIPs.length === 0) {
1482
- console.log(`[MoltsPay] /proxy denied: no PROXY_ALLOWED_IPS configured`);
1483
- return false;
2214
+ return true;
2215
+ }
2216
+ if (allowedIPs.includes("*")) {
2217
+ return true;
1484
2218
  }
1485
2219
  const normalizedIP = clientIP === "::1" ? "127.0.0.1" : clientIP.replace("::ffff:", "");
1486
2220
  const allowed = allowedIPs.includes(normalizedIP) || allowedIPs.includes(clientIP);
@@ -1492,31 +2226,42 @@ var MoltsPayServer = class {
1492
2226
  /**
1493
2227
  * POST /proxy - Handle payment for external services (moltspay-creators)
1494
2228
  *
1495
- * This endpoint allows other services to delegate x402 payment handling.
2229
+ * This endpoint allows other services to delegate x402/MPP payment handling.
1496
2230
  * It does NOT execute any skill - just handles payment verification/settlement.
1497
2231
  *
1498
2232
  * Request body:
1499
2233
  * { wallet, amount, currency, chain, memo, serviceId, description }
1500
2234
  *
1501
- * Without X-Payment header: returns 402 with payment requirements
1502
- * With X-Payment header: verifies payment and returns result
2235
+ * For x402 (base, polygon, base_sepolia):
2236
+ * Without X-Payment header: returns 402 with X-Payment-Required
2237
+ * With X-Payment header: verifies payment via CDP
2238
+ *
2239
+ * For MPP (tempo_moderato):
2240
+ * Without Authorization header: returns 402 with WWW-Authenticate
2241
+ * With Authorization: Payment header: verifies tx on Tempo chain
1503
2242
  */
1504
- async handleProxy(body, paymentHeader, res) {
2243
+ async handleProxy(body, paymentHeader, authHeader, res) {
1505
2244
  const { wallet, amount, currency, chain, memo, serviceId, description } = body;
1506
2245
  if (!wallet || !amount) {
1507
2246
  return this.sendJson(res, 400, { error: "Missing required fields: wallet, amount" });
1508
2247
  }
1509
- if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
1510
- return this.sendJson(res, 400, { error: "Invalid wallet address format" });
2248
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet", "solana", "solana_devnet"];
2249
+ if (chain && !supportedChains.includes(chain)) {
2250
+ return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
2251
+ }
2252
+ const isSolanaChain = chain === "solana" || chain === "solana_devnet";
2253
+ const isValidEvmAddress = /^0x[a-fA-F0-9]{40}$/.test(wallet);
2254
+ const isValidSolanaAddress = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(wallet);
2255
+ if (isSolanaChain && !isValidSolanaAddress) {
2256
+ return this.sendJson(res, 400, { error: "Invalid Solana wallet address format" });
2257
+ }
2258
+ if (!isSolanaChain && !isValidEvmAddress) {
2259
+ return this.sendJson(res, 400, { error: "Invalid EVM wallet address format" });
1511
2260
  }
1512
2261
  const amountNum = parseFloat(amount);
1513
2262
  if (isNaN(amountNum) || amountNum <= 0) {
1514
2263
  return this.sendJson(res, 400, { error: "Invalid amount" });
1515
2264
  }
1516
- const supportedChains = ["base", "polygon", "base_sepolia"];
1517
- if (chain && !supportedChains.includes(chain)) {
1518
- return this.sendJson(res, 400, { error: `Unsupported chain: ${chain}. Supported: ${supportedChains.join(", ")}` });
1519
- }
1520
2265
  const proxyConfig = {
1521
2266
  id: serviceId || "proxy",
1522
2267
  name: description || "Proxy Payment",
@@ -1528,6 +2273,9 @@ var MoltsPayServer = class {
1528
2273
  input: {},
1529
2274
  output: {}
1530
2275
  };
2276
+ if (chain === "tempo_moderato") {
2277
+ return await this.handleProxyMPP(body, proxyConfig, authHeader, res);
2278
+ }
1531
2279
  const requirements = this.buildProxyPaymentRequirements(proxyConfig, wallet, currency, chain);
1532
2280
  if (!paymentHeader) {
1533
2281
  return this.sendProxyPaymentRequired(proxyConfig, wallet, memo, chain, res);
@@ -1539,37 +2287,225 @@ var MoltsPayServer = class {
1539
2287
  } catch {
1540
2288
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
1541
2289
  }
1542
- if (payment.x402Version !== X402_VERSION2) {
1543
- return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
2290
+ if (payment.x402Version !== X402_VERSION2) {
2291
+ return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
2292
+ }
2293
+ const scheme = payment.accepted?.scheme || payment.scheme;
2294
+ const network = payment.accepted?.network || payment.network;
2295
+ if (scheme !== "exact") {
2296
+ return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
2297
+ }
2298
+ const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
2299
+ if (network !== expectedNetwork) {
2300
+ return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
2301
+ }
2302
+ console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
2303
+ const verifyResult = await this.registry.verify(payment, requirements);
2304
+ if (!verifyResult.valid) {
2305
+ return this.sendJson(res, 402, {
2306
+ success: false,
2307
+ error: `Payment verification failed: ${verifyResult.error}`,
2308
+ facilitator: verifyResult.facilitator
2309
+ });
2310
+ }
2311
+ console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
2312
+ const { execute, service, params } = body;
2313
+ if (execute && service) {
2314
+ const skill = this.skills.get(service);
2315
+ if (!skill) {
2316
+ console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
2317
+ return this.sendJson(res, 404, {
2318
+ success: false,
2319
+ paymentSettled: false,
2320
+ error: `Service not found: ${service}`
2321
+ });
2322
+ }
2323
+ const isSolana = isSolanaNetwork(network);
2324
+ let settlement2 = null;
2325
+ if (isSolana) {
2326
+ console.log(`[MoltsPay] /proxy: Solana detected - settling payment FIRST`);
2327
+ try {
2328
+ settlement2 = await this.registry.settle(payment, requirements);
2329
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
2330
+ if (!settlement2.success) {
2331
+ console.error(`[MoltsPay] /proxy: Solana settlement failed: ${settlement2.error}`);
2332
+ return this.sendJson(res, 402, {
2333
+ success: false,
2334
+ paymentSettled: false,
2335
+ error: `Payment settlement failed: ${settlement2.error || "Unknown error"}`
2336
+ });
2337
+ }
2338
+ } catch (err) {
2339
+ console.error("[MoltsPay] /proxy: Solana settlement failed:", err.message);
2340
+ return this.sendJson(res, 402, {
2341
+ success: false,
2342
+ paymentSettled: false,
2343
+ error: `Payment settlement failed: ${err.message}`
2344
+ });
2345
+ }
2346
+ } else {
2347
+ console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
2348
+ }
2349
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
2350
+ let result;
2351
+ try {
2352
+ result = await Promise.race([
2353
+ skill.handler(params || {}),
2354
+ new Promise(
2355
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
2356
+ )
2357
+ ]);
2358
+ console.log(`[MoltsPay] /proxy: Skill succeeded`);
2359
+ } catch (err) {
2360
+ console.error(`[MoltsPay] /proxy: Skill failed: ${err.message}`);
2361
+ return this.sendJson(res, 500, {
2362
+ success: false,
2363
+ paymentSettled: isSolana ? true : false,
2364
+ error: `Service execution failed: ${err.message}`,
2365
+ note: isSolana ? "Payment was settled before execution. Contact support for refund." : void 0
2366
+ });
2367
+ }
2368
+ if (!isSolana) {
2369
+ console.log(`[MoltsPay] /proxy: Settling payment...`);
2370
+ try {
2371
+ settlement2 = await this.registry.settle(payment, requirements);
2372
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
2373
+ } catch (err) {
2374
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
2375
+ return this.sendJson(res, 200, {
2376
+ success: true,
2377
+ verified: true,
2378
+ settled: false,
2379
+ settlementError: err.message,
2380
+ from: payment.payload?.authorization?.from,
2381
+ paidTo: wallet,
2382
+ amount: amountNum,
2383
+ currency: currency || "USDC",
2384
+ memo,
2385
+ result
2386
+ });
2387
+ }
2388
+ }
2389
+ return this.sendJson(res, 200, {
2390
+ success: true,
2391
+ verified: true,
2392
+ settled: settlement2?.success || false,
2393
+ txHash: settlement2?.transaction,
2394
+ from: payment.payload?.authorization?.from,
2395
+ paidTo: wallet,
2396
+ amount: amountNum,
2397
+ currency: currency || "USDC",
2398
+ facilitator: settlement2?.facilitator,
2399
+ memo,
2400
+ result
2401
+ });
2402
+ }
2403
+ console.log(`[MoltsPay] /proxy: Settling payment (no execution)...`);
2404
+ let settlement = null;
2405
+ try {
2406
+ settlement = await this.registry.settle(payment, requirements);
2407
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2408
+ } catch (err) {
2409
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
2410
+ return this.sendJson(res, 500, {
2411
+ success: false,
2412
+ error: `Settlement failed: ${err.message}`
2413
+ });
2414
+ }
2415
+ this.sendJson(res, 200, {
2416
+ success: true,
2417
+ verified: true,
2418
+ settled: settlement?.success || false,
2419
+ txHash: settlement?.transaction,
2420
+ from: payment.payload?.authorization?.from,
2421
+ // Buyer's wallet address
2422
+ paidTo: wallet,
2423
+ amount: amountNum,
2424
+ currency: currency || "USDC",
2425
+ facilitator: settlement?.facilitator,
2426
+ memo
2427
+ });
2428
+ }
2429
+ /**
2430
+ * Handle MPP payment flow for /proxy endpoint (tempo_moderato chain)
2431
+ */
2432
+ async handleProxyMPP(body, config, authHeader, res) {
2433
+ const { wallet, amount, memo, serviceId } = body;
2434
+ const amountNum = parseFloat(amount);
2435
+ const amountInUnits = Math.floor(amountNum * 1e6).toString();
2436
+ if (!authHeader || !authHeader.toLowerCase().startsWith("payment ")) {
2437
+ const challengeId = this.generateChallengeId();
2438
+ const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
2439
+ const mppRequest = {
2440
+ amount: amountInUnits,
2441
+ currency: tokenAddress,
2442
+ methodDetails: {
2443
+ chainId: 42431,
2444
+ feePayer: true
2445
+ },
2446
+ recipient: wallet
2447
+ };
2448
+ const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
2449
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
2450
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
2451
+ res.writeHead(402, {
2452
+ "Content-Type": "application/problem+json",
2453
+ [MPP_WWW_AUTH_HEADER]: wwwAuth
2454
+ });
2455
+ res.end(JSON.stringify({
2456
+ type: "https://paymentauth.org/problems/payment-required",
2457
+ title: "Payment Required",
2458
+ status: 402,
2459
+ detail: `Payment is required (${config.name}).`,
2460
+ service: serviceId || "proxy",
2461
+ price: amountNum,
2462
+ currency: "USDC"
2463
+ }, null, 2));
2464
+ return;
2465
+ }
2466
+ const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
2467
+ if (!credentialMatch) {
2468
+ return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
1544
2469
  }
1545
- const scheme = payment.accepted?.scheme || payment.scheme;
1546
- const network = payment.accepted?.network || payment.network;
1547
- if (scheme !== "exact") {
1548
- return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
2470
+ let mppCredential;
2471
+ try {
2472
+ const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
2473
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
2474
+ mppCredential = JSON.parse(decoded);
2475
+ } catch (err) {
2476
+ console.error("[MoltsPay] /proxy MPP: Failed to parse credential:", err);
2477
+ return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
1549
2478
  }
1550
- const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
1551
- if (network !== expectedNetwork) {
1552
- return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
2479
+ let txHash;
2480
+ if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
2481
+ txHash = mppCredential.payload.hash;
2482
+ } else {
2483
+ return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
1553
2484
  }
1554
- console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
1555
- const verifyResult = await this.registry.verify(payment, requirements);
1556
- if (!verifyResult.valid) {
2485
+ console.log(`[MoltsPay] /proxy MPP: Verifying tx ${txHash} on Tempo...`);
2486
+ const requirements = this.buildPaymentRequirements(config, "eip155:42431", wallet, "USDC");
2487
+ const paymentPayload = {
2488
+ x402Version: X402_VERSION2,
2489
+ scheme: "exact",
2490
+ network: "eip155:42431",
2491
+ payload: { txHash, chainId: 42431 }
2492
+ };
2493
+ const verification = await this.registry.verify(paymentPayload, requirements);
2494
+ if (!verification.valid) {
1557
2495
  return this.sendJson(res, 402, {
1558
- success: false,
1559
- error: `Payment verification failed: ${verifyResult.error}`,
1560
- facilitator: verifyResult.facilitator
2496
+ error: `Payment verification failed: ${verification.error}`
1561
2497
  });
1562
2498
  }
1563
- console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
2499
+ console.log(`[MoltsPay] /proxy MPP: Payment verified by ${verification.facilitator}`);
1564
2500
  const { execute, service, params } = body;
1565
2501
  if (execute && service) {
1566
- console.log(`[MoltsPay] /proxy: Executing skill first (pay on success): ${service}`);
2502
+ console.log(`[MoltsPay] /proxy MPP: Executing skill: ${service}`);
1567
2503
  const skill = this.skills.get(service);
1568
2504
  if (!skill) {
1569
- console.log(`[MoltsPay] /proxy: Service not found: ${service} - NOT settling`);
1570
2505
  return this.sendJson(res, 404, {
1571
2506
  success: false,
1572
- paymentSettled: false,
2507
+ paymentSettled: true,
2508
+ // Payment already happened on Tempo
1573
2509
  error: `Service not found: ${service}`
1574
2510
  });
1575
2511
  }
@@ -1582,73 +2518,36 @@ var MoltsPayServer = class {
1582
2518
  (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
1583
2519
  )
1584
2520
  ]);
1585
- console.log(`[MoltsPay] /proxy: Skill succeeded, now settling payment...`);
1586
2521
  } catch (err) {
1587
- console.error(`[MoltsPay] /proxy: Skill failed: ${err.message} - NOT settling`);
2522
+ console.error(`[MoltsPay] /proxy MPP: Skill failed: ${err.message}`);
1588
2523
  return this.sendJson(res, 500, {
1589
2524
  success: false,
1590
- paymentSettled: false,
2525
+ paymentSettled: true,
1591
2526
  error: `Service execution failed: ${err.message}`
1592
2527
  });
1593
2528
  }
1594
- let settlement2 = null;
1595
- try {
1596
- settlement2 = await this.registry.settle(payment, requirements);
1597
- console.log(`[MoltsPay] /proxy: Payment settled by ${settlement2.facilitator}: ${settlement2.transaction || "pending"}`);
1598
- } catch (err) {
1599
- console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
1600
- return this.sendJson(res, 200, {
1601
- success: true,
1602
- verified: true,
1603
- settled: false,
1604
- settlementError: err.message,
1605
- from: payment.payload?.authorization?.from,
1606
- // Buyer's wallet address
1607
- paidTo: wallet,
1608
- amount: amountNum,
1609
- currency: currency || "USDC",
1610
- memo,
1611
- result
1612
- });
1613
- }
1614
2529
  return this.sendJson(res, 200, {
1615
2530
  success: true,
1616
2531
  verified: true,
1617
- settled: settlement2?.success || false,
1618
- txHash: settlement2?.transaction,
1619
- from: payment.payload?.authorization?.from,
1620
- // Buyer's wallet address
2532
+ txHash,
2533
+ chain: "tempo_moderato",
1621
2534
  paidTo: wallet,
1622
2535
  amount: amountNum,
1623
- currency: currency || "USDC",
1624
- facilitator: settlement2?.facilitator,
2536
+ currency: "USDC",
2537
+ facilitator: verification.facilitator,
1625
2538
  memo,
1626
2539
  result
1627
2540
  });
1628
2541
  }
1629
- console.log(`[MoltsPay] /proxy: Settling payment (no execution)...`);
1630
- let settlement = null;
1631
- try {
1632
- settlement = await this.registry.settle(payment, requirements);
1633
- console.log(`[MoltsPay] /proxy: Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1634
- } catch (err) {
1635
- console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
1636
- return this.sendJson(res, 500, {
1637
- success: false,
1638
- error: `Settlement failed: ${err.message}`
1639
- });
1640
- }
1641
2542
  this.sendJson(res, 200, {
1642
2543
  success: true,
1643
2544
  verified: true,
1644
- settled: settlement?.success || false,
1645
- txHash: settlement?.transaction,
1646
- from: payment.payload?.authorization?.from,
1647
- // Buyer's wallet address
2545
+ txHash,
2546
+ chain: "tempo_moderato",
1648
2547
  paidTo: wallet,
1649
2548
  amount: amountNum,
1650
- currency: currency || "USDC",
1651
- facilitator: settlement?.facilitator,
2549
+ currency: "USDC",
2550
+ facilitator: verification.facilitator,
1652
2551
  memo
1653
2552
  });
1654
2553
  }
@@ -1663,7 +2562,7 @@ var MoltsPayServer = class {
1663
2562
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
1664
2563
  const tokenAddress = tokenAddresses[selectedToken];
1665
2564
  const tokenDomain = getTokenDomain(networkId, selectedToken);
1666
- return {
2565
+ const requirements = {
1667
2566
  scheme: "exact",
1668
2567
  network: networkId,
1669
2568
  asset: tokenAddress,
@@ -1673,6 +2572,17 @@ var MoltsPayServer = class {
1673
2572
  maxTimeoutSeconds: 300,
1674
2573
  extra: tokenDomain
1675
2574
  };
2575
+ if (networkId === "eip155:56" || networkId === "eip155:97") {
2576
+ const bnbFacilitator = this.registry.get("bnb");
2577
+ const spenderAddress = bnbFacilitator?.getSpenderAddress?.();
2578
+ if (spenderAddress) {
2579
+ requirements.extra = {
2580
+ ...requirements.extra || {},
2581
+ bnbSpender: spenderAddress
2582
+ };
2583
+ }
2584
+ }
2585
+ return requirements;
1676
2586
  }
1677
2587
  /**
1678
2588
  * Return 402 with x402 payment requirements for proxy endpoint
@@ -1703,10 +2613,40 @@ var MoltsPayServer = class {
1703
2613
  };
1704
2614
 
1705
2615
  // src/client/index.ts
1706
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync, mkdirSync, statSync, chmodSync } from "fs";
1707
- import { homedir } from "os";
1708
- import { join as join3 } from "path";
2616
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync, chmodSync } from "fs";
2617
+ import { homedir as homedir2 } from "os";
2618
+ import { join as join4 } from "path";
1709
2619
  import { Wallet, ethers } from "ethers";
2620
+
2621
+ // src/wallet/solana.ts
2622
+ import { Keypair as Keypair3, PublicKey as PublicKey3, LAMPORTS_PER_SOL } from "@solana/web3.js";
2623
+ import { getAssociatedTokenAddress as getAssociatedTokenAddress2, getAccount as getAccount2 } from "@solana/spl-token";
2624
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3, mkdirSync } from "fs";
2625
+ import { join as join3 } from "path";
2626
+ import { homedir } from "os";
2627
+ import bs582 from "bs58";
2628
+ var DEFAULT_CONFIG_DIR = join3(homedir(), ".moltspay");
2629
+ var SOLANA_WALLET_FILE = "wallet-solana.json";
2630
+ function getSolanaWalletPath(configDir = DEFAULT_CONFIG_DIR) {
2631
+ return join3(configDir, SOLANA_WALLET_FILE);
2632
+ }
2633
+ function loadSolanaWallet(configDir = DEFAULT_CONFIG_DIR) {
2634
+ const walletPath = getSolanaWalletPath(configDir);
2635
+ if (!existsSync3(walletPath)) {
2636
+ return null;
2637
+ }
2638
+ try {
2639
+ const data = JSON.parse(readFileSync3(walletPath, "utf-8"));
2640
+ const secretKey = bs582.decode(data.secretKey);
2641
+ return Keypair3.fromSecretKey(secretKey);
2642
+ } catch (error) {
2643
+ console.error("Failed to load Solana wallet:", error);
2644
+ return null;
2645
+ }
2646
+ }
2647
+
2648
+ // src/client/index.ts
2649
+ import { PublicKey as PublicKey4 } from "@solana/web3.js";
1710
2650
  var X402_VERSION3 = 2;
1711
2651
  var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
1712
2652
  var PAYMENT_HEADER2 = "x-payment";
@@ -1725,7 +2665,7 @@ var MoltsPayClient = class {
1725
2665
  todaySpending = 0;
1726
2666
  lastSpendingReset = 0;
1727
2667
  constructor(options = {}) {
1728
- this.configDir = options.configDir || join3(homedir(), ".moltspay");
2668
+ this.configDir = options.configDir || join4(homedir2(), ".moltspay");
1729
2669
  this.config = this.loadConfig();
1730
2670
  this.walletData = this.loadWallet();
1731
2671
  this.loadSpending();
@@ -1745,6 +2685,12 @@ var MoltsPayClient = class {
1745
2685
  get address() {
1746
2686
  return this.wallet?.address || null;
1747
2687
  }
2688
+ /**
2689
+ * Get wallet instance (for direct operations like approvals)
2690
+ */
2691
+ getWallet() {
2692
+ return this.wallet;
2693
+ }
1748
2694
  /**
1749
2695
  * Get current config
1750
2696
  */
@@ -1798,11 +2744,26 @@ var MoltsPayClient = class {
1798
2744
  throw new Error("Client not initialized. Run: npx moltspay init");
1799
2745
  }
1800
2746
  console.log(`[MoltsPay] Requesting service: ${service}`);
1801
- const requestBody = { service, params };
2747
+ let executeUrl = `${serverUrl}/execute`;
2748
+ try {
2749
+ const services = await this.getServices(serverUrl);
2750
+ const svc = services.services?.find((s) => s.id === service);
2751
+ if (svc?.endpoint) {
2752
+ executeUrl = `${serverUrl}${svc.endpoint}`;
2753
+ console.log(`[MoltsPay] Using service endpoint: ${svc.endpoint}`);
2754
+ }
2755
+ } catch {
2756
+ }
2757
+ let requestBody;
2758
+ if (options.rawData) {
2759
+ requestBody = { service, ...params };
2760
+ } else {
2761
+ requestBody = { service, params };
2762
+ }
1802
2763
  if (options.chain) {
1803
2764
  requestBody.chain = options.chain;
1804
2765
  }
1805
- const initialRes = await fetch(`${serverUrl}/execute`, {
2766
+ const initialRes = await fetch(executeUrl, {
1806
2767
  method: "POST",
1807
2768
  headers: { "Content-Type": "application/json" },
1808
2769
  body: JSON.stringify(requestBody)
@@ -1814,9 +2775,14 @@ var MoltsPayClient = class {
1814
2775
  }
1815
2776
  throw new Error(data.error || "Unexpected response");
1816
2777
  }
2778
+ const wwwAuthHeader = initialRes.headers.get("www-authenticate");
1817
2779
  const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER2);
2780
+ if (wwwAuthHeader && wwwAuthHeader.toLowerCase().includes("payment")) {
2781
+ console.log("[MoltsPay] Detected MPP protocol, using Tempo flow...");
2782
+ return await this.handleMPPPayment(executeUrl, service, params, wwwAuthHeader, options);
2783
+ }
1818
2784
  if (!paymentRequiredHeader) {
1819
- throw new Error("Missing x-payment-required header");
2785
+ throw new Error("Missing payment header (x-payment-required or www-authenticate)");
1820
2786
  }
1821
2787
  let requirements;
1822
2788
  try {
@@ -1833,17 +2799,22 @@ var MoltsPayClient = class {
1833
2799
  throw new Error("Invalid x-payment-required header");
1834
2800
  }
1835
2801
  const networkToChainName = (network2) => {
2802
+ if (network2 === "solana:mainnet") return "solana";
2803
+ if (network2 === "solana:devnet") return "solana_devnet";
1836
2804
  const match = network2.match(/^eip155:(\d+)$/);
1837
2805
  if (!match) return null;
1838
2806
  const chainId = parseInt(match[1]);
1839
2807
  if (chainId === 8453) return "base";
1840
2808
  if (chainId === 137) return "polygon";
1841
2809
  if (chainId === 84532) return "base_sepolia";
2810
+ if (chainId === 42431) return "tempo_moderato";
2811
+ if (chainId === 56) return "bnb";
2812
+ if (chainId === 97) return "bnb_testnet";
1842
2813
  return null;
1843
2814
  };
1844
2815
  const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
1845
- let chainName;
1846
2816
  const userSpecifiedChain = options.chain;
2817
+ let selectedChain;
1847
2818
  if (userSpecifiedChain) {
1848
2819
  if (!serverChains.includes(userSpecifiedChain)) {
1849
2820
  throw new Error(
@@ -1851,17 +2822,27 @@ var MoltsPayClient = class {
1851
2822
  Server accepts: ${serverChains.join(", ")}`
1852
2823
  );
1853
2824
  }
1854
- chainName = userSpecifiedChain;
2825
+ selectedChain = userSpecifiedChain;
1855
2826
  } else {
1856
2827
  if (serverChains.length === 1 && serverChains[0] === "base") {
1857
- chainName = "base";
2828
+ selectedChain = "base";
1858
2829
  } else {
1859
2830
  throw new Error(
1860
2831
  `Server accepts: ${serverChains.join(", ")}
1861
- Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2832
+ Please specify: --chain <chain_name>`
1862
2833
  );
1863
2834
  }
1864
2835
  }
2836
+ if (selectedChain === "solana" || selectedChain === "solana_devnet") {
2837
+ const solanaChain = selectedChain;
2838
+ const network2 = solanaChain === "solana" ? "solana:mainnet" : "solana:devnet";
2839
+ const req2 = requirements.find((r) => r.network === network2);
2840
+ if (!req2) {
2841
+ throw new Error(`Failed to find payment requirement for ${selectedChain}`);
2842
+ }
2843
+ return await this.handleSolanaPayment(executeUrl, service, params, req2, solanaChain, options);
2844
+ }
2845
+ const chainName = selectedChain;
1865
2846
  const chain = getChain(chainName);
1866
2847
  const network = `eip155:${chain.chainId}`;
1867
2848
  const req = requirements.find((r) => r.scheme === "exact" && r.network === network);
@@ -1896,6 +2877,25 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
1896
2877
  } else {
1897
2878
  console.log(`[MoltsPay] Signing payment: $${amount} ${token} (gasless)`);
1898
2879
  }
2880
+ if (chainName === "bnb" || chainName === "bnb_testnet") {
2881
+ console.log(`[MoltsPay] Using BNB intent-based payment flow...`);
2882
+ const payTo2 = req.payTo || req.resource;
2883
+ if (!payTo2) {
2884
+ throw new Error("Missing payTo address in payment requirements");
2885
+ }
2886
+ const bnbSpender = req.extra?.bnbSpender;
2887
+ if (!bnbSpender) {
2888
+ throw new Error("Server did not provide bnbSpender address. Server may not support BNB payments.");
2889
+ }
2890
+ return await this.handleBNBPayment(executeUrl, service, params, {
2891
+ to: payTo2,
2892
+ amount,
2893
+ token,
2894
+ chainName,
2895
+ chain,
2896
+ spender: bnbSpender
2897
+ }, options);
2898
+ }
1899
2899
  const payTo = req.payTo || req.resource;
1900
2900
  if (!payTo) {
1901
2901
  throw new Error("Missing payTo address in payment requirements");
@@ -1925,11 +2925,11 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
1925
2925
  };
1926
2926
  const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
1927
2927
  console.log(`[MoltsPay] Sending request with payment...`);
1928
- const paidRequestBody = { service, params };
2928
+ const paidRequestBody = options.rawData ? { service, ...params } : { service, params };
1929
2929
  if (options.chain) {
1930
2930
  paidRequestBody.chain = options.chain;
1931
2931
  }
1932
- const paidRes = await fetch(`${serverUrl}/execute`, {
2932
+ const paidRes = await fetch(executeUrl, {
1933
2933
  method: "POST",
1934
2934
  headers: {
1935
2935
  "Content-Type": "application/json",
@@ -1943,7 +2943,304 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
1943
2943
  }
1944
2944
  this.recordSpending(amount);
1945
2945
  console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
1946
- return result.result;
2946
+ return result.result || result;
2947
+ }
2948
+ /**
2949
+ * Handle MPP (Machine Payments Protocol) payment flow
2950
+ * Called when pay() detects WWW-Authenticate header in 402 response
2951
+ */
2952
+ async handleMPPPayment(executeUrl, service, params, wwwAuthHeader, options = {}) {
2953
+ const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
2954
+ const { createWalletClient, createPublicClient, http } = await import("viem");
2955
+ const { tempoModerato } = await import("viem/chains");
2956
+ const { Actions } = await import("viem/tempo");
2957
+ const privateKey = this.walletData.privateKey;
2958
+ const account = privateKeyToAccount2(privateKey);
2959
+ console.log(`[MoltsPay] Using MPP protocol on Tempo`);
2960
+ console.log(`[MoltsPay] Account: ${account.address}`);
2961
+ const parseAuthParam = (header, key) => {
2962
+ const match = header.match(new RegExp(`${key}="([^"]+)"`, "i"));
2963
+ return match ? match[1] : null;
2964
+ };
2965
+ const challengeId = parseAuthParam(wwwAuthHeader, "id");
2966
+ const method = parseAuthParam(wwwAuthHeader, "method");
2967
+ const realm = parseAuthParam(wwwAuthHeader, "realm");
2968
+ const requestB64 = parseAuthParam(wwwAuthHeader, "request");
2969
+ if (method !== "tempo") {
2970
+ throw new Error(`Unsupported payment method: ${method}`);
2971
+ }
2972
+ if (!requestB64) {
2973
+ throw new Error("Missing request in WWW-Authenticate");
2974
+ }
2975
+ const requestJson = Buffer.from(requestB64, "base64").toString("utf-8");
2976
+ const paymentRequest = JSON.parse(requestJson);
2977
+ const { amount, currency, recipient, methodDetails } = paymentRequest;
2978
+ const chainId = methodDetails?.chainId || 42431;
2979
+ const amountDisplay = Number(amount) / 1e6;
2980
+ console.log(`[MoltsPay] Payment: $${amountDisplay} to ${recipient}`);
2981
+ this.checkLimits(amountDisplay);
2982
+ console.log(`[MoltsPay] Sending transaction on Tempo...`);
2983
+ const tempoChain = { ...tempoModerato, feeToken: currency };
2984
+ const publicClient = createPublicClient({
2985
+ chain: tempoChain,
2986
+ transport: http("https://rpc.moderato.tempo.xyz")
2987
+ });
2988
+ const walletClient = createWalletClient({
2989
+ account,
2990
+ chain: tempoChain,
2991
+ transport: http("https://rpc.moderato.tempo.xyz")
2992
+ });
2993
+ const txHash = await Actions.token.transfer(walletClient, {
2994
+ to: recipient,
2995
+ amount: BigInt(amount),
2996
+ token: currency
2997
+ });
2998
+ console.log(`[MoltsPay] Transaction: ${txHash}`);
2999
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
3000
+ console.log(`[MoltsPay] Confirmed! Retrying with credential...`);
3001
+ const credential = {
3002
+ challenge: {
3003
+ id: challengeId,
3004
+ realm,
3005
+ method: "tempo",
3006
+ intent: "charge",
3007
+ request: paymentRequest
3008
+ },
3009
+ payload: { hash: txHash, type: "hash" },
3010
+ source: `did:pkh:eip155:${chainId}:${account.address}`
3011
+ };
3012
+ const credentialB64 = Buffer.from(JSON.stringify(credential)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3013
+ const retryBody = options.rawData ? { service, ...params, chain: "tempo_moderato" } : { service, params, chain: "tempo_moderato" };
3014
+ const paidRes = await fetch(executeUrl, {
3015
+ method: "POST",
3016
+ headers: {
3017
+ "Content-Type": "application/json",
3018
+ "Authorization": `Payment ${credentialB64}`
3019
+ },
3020
+ body: JSON.stringify(retryBody)
3021
+ });
3022
+ const result = await paidRes.json();
3023
+ if (!paidRes.ok) {
3024
+ throw new Error(result.error || "Payment verification failed");
3025
+ }
3026
+ this.recordSpending(amountDisplay);
3027
+ console.log(`[MoltsPay] Success!`);
3028
+ return result.result || result;
3029
+ }
3030
+ /**
3031
+ * Handle BNB Chain payment flow (pre-approval + intent signature)
3032
+ *
3033
+ * Flow:
3034
+ * 1. Check client has approved server wallet (done via `moltspay init`)
3035
+ * 2. Sign EIP-712 payment intent (no gas, just signature)
3036
+ * 3. Send intent to server
3037
+ * 4. Server executes service
3038
+ * 5. Server calls transferFrom if successful (pay-for-success)
3039
+ */
3040
+ async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
3041
+ const { to, amount, token, chainName, chain, spender } = paymentDetails;
3042
+ const tokenConfig = chain.tokens[token];
3043
+ const provider = new ethers.JsonRpcProvider(chain.rpc);
3044
+ const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
3045
+ const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
3046
+ if (allowance < amountWeiCheck) {
3047
+ const nativeBalance = await provider.getBalance(this.wallet.address);
3048
+ const minGasBalance = ethers.parseEther("0.0005");
3049
+ if (nativeBalance < minGasBalance) {
3050
+ const nativeBNB = parseFloat(ethers.formatEther(nativeBalance)).toFixed(4);
3051
+ const isTestnet = chainName === "bnb_testnet";
3052
+ if (isTestnet) {
3053
+ throw new Error(
3054
+ `\u274C Insufficient tBNB for approval transaction
3055
+
3056
+ Current tBNB: ${nativeBNB}
3057
+ Required: ~0.001 tBNB
3058
+
3059
+ Get testnet tokens: npx moltspay faucet --chain bnb_testnet
3060
+ (Gives USDC + tBNB for gas)`
3061
+ );
3062
+ } else {
3063
+ throw new Error(
3064
+ `\u274C Insufficient BNB for approval transaction
3065
+
3066
+ Current BNB: ${nativeBNB}
3067
+ Required: ~0.001 BNB (~$0.60)
3068
+
3069
+ To get BNB:
3070
+ \u2022 Withdraw from Binance/exchange to your wallet
3071
+ \u2022 Most exchanges include BNB dust with withdrawals
3072
+
3073
+ After funding, run:
3074
+ npx moltspay approve --chain ${chainName} --spender ${spender}`
3075
+ );
3076
+ }
3077
+ }
3078
+ throw new Error(
3079
+ `Insufficient allowance for ${spender.slice(0, 10)}...
3080
+ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
3081
+ );
3082
+ }
3083
+ const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
3084
+ const intent = {
3085
+ from: this.wallet.address,
3086
+ to,
3087
+ amount: amountWei,
3088
+ token: tokenConfig.address,
3089
+ service,
3090
+ nonce: Date.now(),
3091
+ // Use timestamp as nonce for simplicity
3092
+ deadline: Date.now() + 36e5
3093
+ // 1 hour
3094
+ };
3095
+ const domain = {
3096
+ name: "MoltsPay",
3097
+ version: "1",
3098
+ chainId: chain.chainId
3099
+ };
3100
+ const types = {
3101
+ PaymentIntent: [
3102
+ { name: "from", type: "address" },
3103
+ { name: "to", type: "address" },
3104
+ { name: "amount", type: "uint256" },
3105
+ { name: "token", type: "address" },
3106
+ { name: "service", type: "string" },
3107
+ { name: "nonce", type: "uint256" },
3108
+ { name: "deadline", type: "uint256" }
3109
+ ]
3110
+ };
3111
+ console.log(`[MoltsPay] Signing BNB payment intent...`);
3112
+ const signature = await this.wallet.signTypedData(domain, types, intent);
3113
+ const network = `eip155:${chain.chainId}`;
3114
+ const payload = {
3115
+ x402Version: 2,
3116
+ scheme: "exact",
3117
+ network,
3118
+ payload: {
3119
+ intent: {
3120
+ ...intent,
3121
+ signature
3122
+ },
3123
+ chainId: chain.chainId
3124
+ },
3125
+ accepted: {
3126
+ scheme: "exact",
3127
+ network,
3128
+ asset: tokenConfig.address,
3129
+ amount: amountWei,
3130
+ payTo: to,
3131
+ maxTimeoutSeconds: 300
3132
+ }
3133
+ };
3134
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
3135
+ console.log(`[MoltsPay] Sending BNB payment request...`);
3136
+ const bnbRequestBody = options.rawData ? { service, ...params, chain: chainName } : { service, params, chain: chainName };
3137
+ const paidRes = await fetch(executeUrl, {
3138
+ method: "POST",
3139
+ headers: {
3140
+ "Content-Type": "application/json",
3141
+ "X-Payment": paymentHeader
3142
+ },
3143
+ body: JSON.stringify(bnbRequestBody)
3144
+ });
3145
+ const result = await paidRes.json();
3146
+ if (!paidRes.ok) {
3147
+ throw new Error(result.error || "BNB payment failed");
3148
+ }
3149
+ this.recordSpending(amount);
3150
+ console.log(`[MoltsPay] Success! BNB payment settled.`);
3151
+ return result.result || result;
3152
+ }
3153
+ /**
3154
+ * Handle Solana payment flow
3155
+ *
3156
+ * Solana uses SPL token transfers with pay-for-success model:
3157
+ * 1. Client creates and signs a transfer transaction
3158
+ * 2. Server submits the transaction after service completes
3159
+ */
3160
+ async handleSolanaPayment(executeUrl, service, params, requirements, chain, options = {}) {
3161
+ const solanaWallet = loadSolanaWallet(this.configDir);
3162
+ if (!solanaWallet) {
3163
+ throw new Error("No Solana wallet found. Run: npx moltspay init --chain solana_devnet");
3164
+ }
3165
+ const amount = Number(requirements.amount);
3166
+ const amountUSDC = amount / 1e6;
3167
+ this.checkLimits(amountUSDC);
3168
+ console.log(`[MoltsPay] Creating Solana payment: $${amountUSDC} USDC`);
3169
+ if (!requirements.payTo) {
3170
+ throw new Error("Missing payTo address in payment requirements");
3171
+ }
3172
+ const solanaFeePayer = requirements.extra?.solanaFeePayer;
3173
+ const feePayerPubkey = solanaFeePayer ? new PublicKey4(solanaFeePayer) : void 0;
3174
+ if (feePayerPubkey) {
3175
+ console.log(`[MoltsPay] Gasless mode: server pays fees`);
3176
+ }
3177
+ const recipientPubkey = new PublicKey4(requirements.payTo);
3178
+ const transaction = await createSolanaPaymentTransaction(
3179
+ solanaWallet.publicKey,
3180
+ recipientPubkey,
3181
+ BigInt(amount),
3182
+ chain,
3183
+ feePayerPubkey
3184
+ // Optional fee payer for gasless mode
3185
+ );
3186
+ if (feePayerPubkey) {
3187
+ transaction.partialSign(solanaWallet);
3188
+ } else {
3189
+ transaction.sign(solanaWallet);
3190
+ }
3191
+ const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
3192
+ console.log(`[MoltsPay] Transaction signed, sending to server...`);
3193
+ const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
3194
+ const payload = {
3195
+ x402Version: 2,
3196
+ scheme: "exact",
3197
+ network,
3198
+ payload: {
3199
+ signedTransaction: signedTx,
3200
+ sender: solanaWallet.publicKey.toBase58(),
3201
+ chain
3202
+ },
3203
+ accepted: {
3204
+ scheme: "exact",
3205
+ network,
3206
+ asset: requirements.asset,
3207
+ amount: requirements.amount,
3208
+ payTo: requirements.payTo,
3209
+ maxTimeoutSeconds: 300
3210
+ }
3211
+ };
3212
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
3213
+ const solanaRequestBody = options.rawData ? { service, ...params, chain } : { service, params, chain };
3214
+ const paidRes = await fetch(executeUrl, {
3215
+ method: "POST",
3216
+ headers: {
3217
+ "Content-Type": "application/json",
3218
+ "X-Payment": paymentHeader
3219
+ },
3220
+ body: JSON.stringify(solanaRequestBody)
3221
+ });
3222
+ const result = await paidRes.json();
3223
+ if (!paidRes.ok) {
3224
+ throw new Error(result.error || "Solana payment failed");
3225
+ }
3226
+ this.recordSpending(amountUSDC);
3227
+ console.log(`[MoltsPay] Success! Solana payment settled.`);
3228
+ if (result.payment?.transaction) {
3229
+ const explorerUrl = chain === "solana" ? `https://solscan.io/tx/${result.payment.transaction}` : `https://solscan.io/tx/${result.payment.transaction}?cluster=devnet`;
3230
+ console.log(`[MoltsPay] Transaction: ${explorerUrl}`);
3231
+ }
3232
+ return result.result || result;
3233
+ }
3234
+ /**
3235
+ * Check ERC20 allowance for a spender
3236
+ */
3237
+ async checkAllowance(tokenAddress, spender, provider) {
3238
+ const contract = new ethers.Contract(
3239
+ tokenAddress,
3240
+ ["function allowance(address owner, address spender) view returns (uint256)"],
3241
+ provider
3242
+ );
3243
+ return await contract.allowance(this.wallet.address, spender);
1947
3244
  }
1948
3245
  /**
1949
3246
  * Sign EIP-3009 transferWithAuthorization (GASLESS)
@@ -2015,26 +3312,26 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2015
3312
  }
2016
3313
  // --- Config & Wallet Management ---
2017
3314
  loadConfig() {
2018
- const configPath = join3(this.configDir, "config.json");
2019
- if (existsSync3(configPath)) {
2020
- const content = readFileSync3(configPath, "utf-8");
3315
+ const configPath = join4(this.configDir, "config.json");
3316
+ if (existsSync4(configPath)) {
3317
+ const content = readFileSync4(configPath, "utf-8");
2021
3318
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
2022
3319
  }
2023
3320
  return { ...DEFAULT_CONFIG };
2024
3321
  }
2025
3322
  saveConfig() {
2026
- mkdirSync(this.configDir, { recursive: true });
2027
- const configPath = join3(this.configDir, "config.json");
2028
- writeFileSync(configPath, JSON.stringify(this.config, null, 2));
3323
+ mkdirSync2(this.configDir, { recursive: true });
3324
+ const configPath = join4(this.configDir, "config.json");
3325
+ writeFileSync2(configPath, JSON.stringify(this.config, null, 2));
2029
3326
  }
2030
3327
  /**
2031
3328
  * Load spending data from disk
2032
3329
  */
2033
3330
  loadSpending() {
2034
- const spendingPath = join3(this.configDir, "spending.json");
2035
- if (existsSync3(spendingPath)) {
3331
+ const spendingPath = join4(this.configDir, "spending.json");
3332
+ if (existsSync4(spendingPath)) {
2036
3333
  try {
2037
- const data = JSON.parse(readFileSync3(spendingPath, "utf-8"));
3334
+ const data = JSON.parse(readFileSync4(spendingPath, "utf-8"));
2038
3335
  const today = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
2039
3336
  if (data.date && data.date === today) {
2040
3337
  this.todaySpending = data.amount || 0;
@@ -2053,18 +3350,18 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2053
3350
  * Save spending data to disk
2054
3351
  */
2055
3352
  saveSpending() {
2056
- mkdirSync(this.configDir, { recursive: true });
2057
- const spendingPath = join3(this.configDir, "spending.json");
3353
+ mkdirSync2(this.configDir, { recursive: true });
3354
+ const spendingPath = join4(this.configDir, "spending.json");
2058
3355
  const data = {
2059
3356
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
2060
3357
  amount: this.todaySpending,
2061
3358
  updatedAt: Date.now()
2062
3359
  };
2063
- writeFileSync(spendingPath, JSON.stringify(data, null, 2));
3360
+ writeFileSync2(spendingPath, JSON.stringify(data, null, 2));
2064
3361
  }
2065
3362
  loadWallet() {
2066
- const walletPath = join3(this.configDir, "wallet.json");
2067
- if (existsSync3(walletPath)) {
3363
+ const walletPath = join4(this.configDir, "wallet.json");
3364
+ if (existsSync4(walletPath)) {
2068
3365
  try {
2069
3366
  const stats = statSync(walletPath);
2070
3367
  const mode = stats.mode & 511;
@@ -2075,7 +3372,7 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2075
3372
  }
2076
3373
  } catch (err) {
2077
3374
  }
2078
- const content = readFileSync3(walletPath, "utf-8");
3375
+ const content = readFileSync4(walletPath, "utf-8");
2079
3376
  return JSON.parse(content);
2080
3377
  }
2081
3378
  return null;
@@ -2084,15 +3381,15 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2084
3381
  * Initialize a new wallet (called by CLI)
2085
3382
  */
2086
3383
  static init(configDir, options) {
2087
- mkdirSync(configDir, { recursive: true });
3384
+ mkdirSync2(configDir, { recursive: true });
2088
3385
  const wallet = Wallet.createRandom();
2089
3386
  const walletData = {
2090
3387
  address: wallet.address,
2091
3388
  privateKey: wallet.privateKey,
2092
3389
  createdAt: Date.now()
2093
3390
  };
2094
- const walletPath = join3(configDir, "wallet.json");
2095
- writeFileSync(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
3391
+ const walletPath = join4(configDir, "wallet.json");
3392
+ writeFileSync2(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
2096
3393
  const config = {
2097
3394
  chain: options.chain,
2098
3395
  limits: {
@@ -2100,8 +3397,8 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2100
3397
  maxPerDay: options.maxPerDay
2101
3398
  }
2102
3399
  };
2103
- const configPath = join3(configDir, "config.json");
2104
- writeFileSync(configPath, JSON.stringify(config, null, 2));
3400
+ const configPath = join4(configDir, "config.json");
3401
+ writeFileSync2(configPath, JSON.stringify(config, null, 2));
2105
3402
  return { address: wallet.address, configDir };
2106
3403
  }
2107
3404
  /**
@@ -2137,7 +3434,7 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2137
3434
  if (!this.wallet) {
2138
3435
  throw new Error("Client not initialized");
2139
3436
  }
2140
- const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
3437
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet"];
2141
3438
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
2142
3439
  const results = {};
2143
3440
  const tempoTokens = {
@@ -2208,12 +3505,12 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
2208
3505
  if (!this.wallet || !this.walletData) {
2209
3506
  throw new Error("Client not initialized. Run: npx moltspay init");
2210
3507
  }
2211
- const { privateKeyToAccount } = await import("viem/accounts");
3508
+ const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
2212
3509
  const { createWalletClient, createPublicClient, http } = await import("viem");
2213
3510
  const { tempoModerato } = await import("viem/chains");
2214
3511
  const { Actions } = await import("viem/tempo");
2215
3512
  const privateKey = this.walletData.privateKey;
2216
- const account = privateKeyToAccount(privateKey);
3513
+ const account = privateKeyToAccount2(privateKey);
2217
3514
  console.log(`[MoltsPay] Making MPP request to: ${url}`);
2218
3515
  console.log(`[MoltsPay] Using account: ${account.address}`);
2219
3516
  const initResponse = await fetch(url, {
@@ -2313,10 +3610,10 @@ import { ethers as ethers2 } from "ethers";
2313
3610
 
2314
3611
  // src/wallet/createWallet.ts
2315
3612
  import { ethers as ethers3 } from "ethers";
2316
- import { writeFileSync as writeFileSync2, readFileSync as readFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
2317
- import { join as join4, dirname } from "path";
3613
+ import { writeFileSync as writeFileSync3, readFileSync as readFileSync5, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
3614
+ import { join as join5, dirname } from "path";
2318
3615
  import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
2319
- var DEFAULT_STORAGE_DIR = join4(process.env.HOME || "~", ".moltspay");
3616
+ var DEFAULT_STORAGE_DIR = join5(process.env.HOME || "~", ".moltspay");
2320
3617
  var DEFAULT_STORAGE_FILE = "wallet.json";
2321
3618
  function encryptPrivateKey(privateKey, password) {
2322
3619
  const salt = randomBytes(16);
@@ -2339,10 +3636,10 @@ function decryptPrivateKey(encrypted, password, iv, salt) {
2339
3636
  return decrypted;
2340
3637
  }
2341
3638
  function createWallet(options = {}) {
2342
- const storagePath = options.storagePath || join4(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
2343
- if (existsSync4(storagePath) && !options.overwrite) {
3639
+ const storagePath = options.storagePath || join5(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
3640
+ if (existsSync5(storagePath) && !options.overwrite) {
2344
3641
  try {
2345
- const existing = JSON.parse(readFileSync4(storagePath, "utf8"));
3642
+ const existing = JSON.parse(readFileSync5(storagePath, "utf8"));
2346
3643
  return {
2347
3644
  success: true,
2348
3645
  address: existing.address,
@@ -2374,10 +3671,10 @@ function createWallet(options = {}) {
2374
3671
  walletData.privateKey = wallet.privateKey;
2375
3672
  }
2376
3673
  const dir = dirname(storagePath);
2377
- if (!existsSync4(dir)) {
2378
- mkdirSync2(dir, { recursive: true });
3674
+ if (!existsSync5(dir)) {
3675
+ mkdirSync3(dir, { recursive: true });
2379
3676
  }
2380
- writeFileSync2(storagePath, JSON.stringify(walletData, null, 2), { mode: 384 });
3677
+ writeFileSync3(storagePath, JSON.stringify(walletData, null, 2), { mode: 384 });
2381
3678
  return {
2382
3679
  success: true,
2383
3680
  address: wallet.address,
@@ -2392,12 +3689,12 @@ function createWallet(options = {}) {
2392
3689
  }
2393
3690
  }
2394
3691
  function loadWallet(options = {}) {
2395
- const storagePath = options.storagePath || join4(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
2396
- if (!existsSync4(storagePath)) {
3692
+ const storagePath = options.storagePath || join5(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
3693
+ if (!existsSync5(storagePath)) {
2397
3694
  return { success: false, error: "Wallet not found. Run createWallet() first." };
2398
3695
  }
2399
3696
  try {
2400
- const data = JSON.parse(readFileSync4(storagePath, "utf8"));
3697
+ const data = JSON.parse(readFileSync5(storagePath, "utf8"));
2401
3698
  if (data.encrypted) {
2402
3699
  if (!options.password) {
2403
3700
  return { success: false, error: "Wallet is encrypted. Password required." };
@@ -2412,25 +3709,25 @@ function loadWallet(options = {}) {
2412
3709
  }
2413
3710
  }
2414
3711
  function getWalletAddress(storagePath) {
2415
- const path4 = storagePath || join4(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
2416
- if (!existsSync4(path4)) {
3712
+ const path4 = storagePath || join5(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
3713
+ if (!existsSync5(path4)) {
2417
3714
  return null;
2418
3715
  }
2419
3716
  try {
2420
- const data = JSON.parse(readFileSync4(path4, "utf8"));
3717
+ const data = JSON.parse(readFileSync5(path4, "utf8"));
2421
3718
  return data.address;
2422
3719
  } catch {
2423
3720
  return null;
2424
3721
  }
2425
3722
  }
2426
3723
  function walletExists(storagePath) {
2427
- const path4 = storagePath || join4(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
2428
- return existsSync4(path4);
3724
+ const path4 = storagePath || join5(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
3725
+ return existsSync5(path4);
2429
3726
  }
2430
3727
 
2431
3728
  // src/verify/index.ts
2432
3729
  import { ethers as ethers4 } from "ethers";
2433
- var TRANSFER_EVENT_TOPIC2 = ethers4.id("Transfer(address,address,uint256)");
3730
+ var TRANSFER_EVENT_TOPIC3 = ethers4.id("Transfer(address,address,uint256)");
2434
3731
  async function verifyPayment(params) {
2435
3732
  const { txHash, expectedAmount, expectedTo, expectedToken } = params;
2436
3733
  let chain;
@@ -2471,7 +3768,7 @@ async function verifyPayment(params) {
2471
3768
  if (!detectedToken) {
2472
3769
  continue;
2473
3770
  }
2474
- if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC2) {
3771
+ if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC3) {
2475
3772
  continue;
2476
3773
  }
2477
3774
  const from = "0x" + log.topics[1].slice(-40);