moltspay 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +89 -14
  2. package/dist/cdp/index.d.mts +1 -1
  3. package/dist/cdp/index.d.ts +1 -1
  4. package/dist/cdp/index.js +53 -30368
  5. package/dist/cdp/index.js.map +1 -1
  6. package/dist/cdp/index.mjs +37 -30360
  7. package/dist/cdp/index.mjs.map +1 -1
  8. package/dist/cdp-DeohBe1o.d.ts +66 -0
  9. package/dist/cdp-p_eHuQpb.d.mts +66 -0
  10. package/dist/chains/index.d.mts +1 -1
  11. package/dist/chains/index.d.ts +1 -1
  12. package/dist/chains/index.js +29 -0
  13. package/dist/chains/index.js.map +1 -1
  14. package/dist/chains/index.mjs +29 -0
  15. package/dist/chains/index.mjs.map +1 -1
  16. package/dist/cli/index.js +863 -109
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/cli/index.mjs +867 -109
  19. package/dist/cli/index.mjs.map +1 -1
  20. package/dist/client/index.d.mts +25 -2
  21. package/dist/client/index.d.ts +25 -2
  22. package/dist/client/index.js +195 -12
  23. package/dist/client/index.js.map +1 -1
  24. package/dist/client/index.mjs +185 -12
  25. package/dist/client/index.mjs.map +1 -1
  26. package/dist/facilitators/index.d.mts +15 -53
  27. package/dist/facilitators/index.d.ts +15 -53
  28. package/dist/facilitators/index.js +234 -1
  29. package/dist/facilitators/index.js.map +1 -1
  30. package/dist/facilitators/index.mjs +233 -1
  31. package/dist/facilitators/index.mjs.map +1 -1
  32. package/dist/{index-DgJPZMBG.d.mts → index-On9ZaGDW.d.mts} +1 -1
  33. package/dist/{index-DgJPZMBG.d.ts → index-On9ZaGDW.d.ts} +1 -1
  34. package/dist/index.d.mts +2 -2
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.js +718 -30584
  37. package/dist/index.js.map +1 -1
  38. package/dist/index.mjs +711 -30587
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/server/index.d.mts +17 -0
  41. package/dist/server/index.d.ts +17 -0
  42. package/dist/server/index.js +443 -5
  43. package/dist/server/index.js.map +1 -1
  44. package/dist/server/index.mjs +443 -5
  45. package/dist/server/index.mjs.map +1 -1
  46. package/dist/verify/index.d.mts +1 -1
  47. package/dist/verify/index.d.ts +1 -1
  48. package/dist/verify/index.js +29 -0
  49. package/dist/verify/index.js.map +1 -1
  50. package/dist/verify/index.mjs +29 -0
  51. package/dist/verify/index.mjs.map +1 -1
  52. package/dist/wallet/index.d.mts +1 -1
  53. package/dist/wallet/index.d.ts +1 -1
  54. package/dist/wallet/index.js +29 -0
  55. package/dist/wallet/index.js.map +1 -1
  56. package/dist/wallet/index.mjs +29 -0
  57. package/dist/wallet/index.mjs.map +1 -1
  58. package/package.json +5 -2
package/dist/cli/index.js CHANGED
@@ -35,6 +35,7 @@ var init_cjs_shims = __esm({
35
35
 
36
36
  // src/cli/index.ts
37
37
  init_cjs_shims();
38
+ var import_crypto = require("crypto");
38
39
  var import_commander = require("commander");
39
40
  var import_os2 = require("os");
40
41
  var import_path2 = require("path");
@@ -127,6 +128,35 @@ var CHAINS = {
127
128
  explorer: "https://sepolia.basescan.org/address/",
128
129
  explorerTx: "https://sepolia.basescan.org/tx/",
129
130
  avgBlockTime: 2
131
+ },
132
+ // ============ Tempo Testnet (Moderato) ============
133
+ tempo_moderato: {
134
+ name: "Tempo Moderato",
135
+ chainId: 42431,
136
+ rpc: "https://rpc.moderato.tempo.xyz",
137
+ tokens: {
138
+ // TIP-20 stablecoins on Tempo testnet (from mppx SDK)
139
+ // Note: Tempo uses USD as native gas token, not ETH
140
+ USDC: {
141
+ address: "0x20c0000000000000000000000000000000000000",
142
+ // pathUSD - primary testnet stablecoin
143
+ decimals: 6,
144
+ symbol: "USDC",
145
+ eip712Name: "pathUSD"
146
+ },
147
+ USDT: {
148
+ address: "0x20c0000000000000000000000000000000000001",
149
+ // alphaUSD
150
+ decimals: 6,
151
+ symbol: "USDT",
152
+ eip712Name: "alphaUSD"
153
+ }
154
+ },
155
+ usdc: "0x20c0000000000000000000000000000000000000",
156
+ explorer: "https://explore.testnet.tempo.xyz/address/",
157
+ explorerTx: "https://explore.testnet.tempo.xyz/tx/",
158
+ avgBlockTime: 0.5
159
+ // ~500ms finality
130
160
  }
131
161
  };
132
162
  function getChain(name) {
@@ -565,30 +595,59 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
565
595
  };
566
596
  }
567
597
  /**
568
- * Get wallet balances on all supported chains (Base + Polygon)
598
+ * Get wallet balances on all supported chains (Base + Polygon + Tempo)
569
599
  */
570
600
  async getAllBalances() {
571
601
  if (!this.wallet) {
572
602
  throw new Error("Client not initialized");
573
603
  }
574
- const supportedChains = ["base", "polygon", "base_sepolia"];
604
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
575
605
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
576
606
  const results = {};
607
+ const tempoTokens = {
608
+ pathUSD: "0x20c0000000000000000000000000000000000000",
609
+ alphaUSD: "0x20c0000000000000000000000000000000000001",
610
+ betaUSD: "0x20c0000000000000000000000000000000000002",
611
+ thetaUSD: "0x20c0000000000000000000000000000000000003"
612
+ };
577
613
  await Promise.all(
578
614
  supportedChains.map(async (chainName) => {
579
615
  try {
580
616
  const chain = getChain(chainName);
581
617
  const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
582
- const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
583
- provider.getBalance(this.wallet.address),
584
- new import_ethers.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
585
- new import_ethers.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
586
- ]);
587
- results[chainName] = {
588
- usdc: parseFloat(import_ethers.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
589
- usdt: parseFloat(import_ethers.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
590
- native: parseFloat(import_ethers.ethers.formatEther(nativeBalance))
591
- };
618
+ if (chainName === "tempo_moderato") {
619
+ const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
620
+ provider.getBalance(this.wallet.address),
621
+ new import_ethers.ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
622
+ new import_ethers.ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
623
+ new import_ethers.ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
624
+ new import_ethers.ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
625
+ ]);
626
+ results[chainName] = {
627
+ usdc: parseFloat(import_ethers.ethers.formatUnits(pathUSD, 6)),
628
+ // pathUSD as default USDC
629
+ usdt: parseFloat(import_ethers.ethers.formatUnits(alphaUSD, 6)),
630
+ // alphaUSD as default USDT
631
+ native: parseFloat(import_ethers.ethers.formatEther(nativeBalance)),
632
+ tempo: {
633
+ pathUSD: parseFloat(import_ethers.ethers.formatUnits(pathUSD, 6)),
634
+ alphaUSD: parseFloat(import_ethers.ethers.formatUnits(alphaUSD, 6)),
635
+ betaUSD: parseFloat(import_ethers.ethers.formatUnits(betaUSD, 6)),
636
+ thetaUSD: parseFloat(import_ethers.ethers.formatUnits(thetaUSD, 6))
637
+ }
638
+ };
639
+ } else {
640
+ const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
641
+ provider.getBalance(this.wallet.address),
642
+ new import_ethers.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
643
+ new import_ethers.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
644
+ ]);
645
+ results[chainName] = {
646
+ usdc: parseFloat(import_ethers.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
647
+ usdt: parseFloat(import_ethers.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
648
+ native: parseFloat(import_ethers.ethers.formatEther(nativeBalance))
649
+ };
650
+ }
592
651
  } catch (err) {
593
652
  results[chainName] = { usdc: 0, usdt: 0, native: 0 };
594
653
  }
@@ -596,6 +655,121 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
596
655
  );
597
656
  return results;
598
657
  }
658
+ /**
659
+ * Pay for a service using MPP (Machine Payments Protocol)
660
+ *
661
+ * This implements the MPP flow manually for EOA wallets:
662
+ * 1. Request service → get 402 with WWW-Authenticate
663
+ * 2. Parse payment requirements
664
+ * 3. Execute transfer on Tempo chain
665
+ * 4. Retry with transaction hash as credential
666
+ *
667
+ * @param url - Full URL of the MPP-enabled endpoint
668
+ * @param options - Request options (body, headers)
669
+ * @returns Response from the service
670
+ */
671
+ async payWithMPP(url, options = {}) {
672
+ if (!this.wallet || !this.walletData) {
673
+ throw new Error("Client not initialized. Run: npx moltspay init");
674
+ }
675
+ const { privateKeyToAccount } = await import("viem/accounts");
676
+ const { createWalletClient, createPublicClient, http } = await import("viem");
677
+ const { tempoModerato } = await import("viem/chains");
678
+ const { Actions } = await import("viem/tempo");
679
+ const privateKey = this.walletData.privateKey;
680
+ const account = privateKeyToAccount(privateKey);
681
+ console.log(`[MoltsPay] Making MPP request to: ${url}`);
682
+ console.log(`[MoltsPay] Using account: ${account.address}`);
683
+ const initResponse = await fetch(url, {
684
+ method: "POST",
685
+ headers: {
686
+ "Content-Type": "application/json",
687
+ ...options.headers
688
+ },
689
+ body: options.body ? JSON.stringify(options.body) : void 0
690
+ });
691
+ if (initResponse.status !== 402) {
692
+ if (initResponse.ok) {
693
+ return initResponse.json();
694
+ }
695
+ const errorText = await initResponse.text();
696
+ throw new Error(`Request failed (${initResponse.status}): ${errorText}`);
697
+ }
698
+ const wwwAuth = initResponse.headers.get("www-authenticate");
699
+ if (!wwwAuth || !wwwAuth.toLowerCase().includes("payment")) {
700
+ throw new Error("No WWW-Authenticate Payment challenge in 402 response");
701
+ }
702
+ console.log(`[MoltsPay] Got 402, parsing payment challenge...`);
703
+ const parseAuthParam = (header, key) => {
704
+ const match = header.match(new RegExp(`${key}="([^"]+)"`, "i"));
705
+ return match ? match[1] : null;
706
+ };
707
+ const challengeId = parseAuthParam(wwwAuth, "id");
708
+ const method = parseAuthParam(wwwAuth, "method");
709
+ const realm = parseAuthParam(wwwAuth, "realm");
710
+ const requestB64 = parseAuthParam(wwwAuth, "request");
711
+ if (method !== "tempo") {
712
+ throw new Error(`Unsupported payment method: ${method}`);
713
+ }
714
+ if (!requestB64) {
715
+ throw new Error("Missing request in WWW-Authenticate");
716
+ }
717
+ const requestJson = Buffer.from(requestB64, "base64").toString("utf-8");
718
+ const paymentRequest = JSON.parse(requestJson);
719
+ console.log(`[MoltsPay] Payment request:`, paymentRequest);
720
+ const { amount, currency, recipient, methodDetails } = paymentRequest;
721
+ const chainId = methodDetails?.chainId || 42431;
722
+ console.log(`[MoltsPay] Executing transfer on Tempo (chainId: ${chainId})...`);
723
+ console.log(`[MoltsPay] Amount: ${amount}, To: ${recipient}`);
724
+ const tempoChain = { ...tempoModerato, feeToken: currency };
725
+ const publicClient = createPublicClient({
726
+ chain: tempoChain,
727
+ transport: http("https://rpc.moderato.tempo.xyz")
728
+ });
729
+ const walletClient = createWalletClient({
730
+ account,
731
+ chain: tempoChain,
732
+ transport: http("https://rpc.moderato.tempo.xyz")
733
+ });
734
+ const txHash = await Actions.token.transfer(walletClient, {
735
+ to: recipient,
736
+ amount: BigInt(amount),
737
+ token: currency
738
+ });
739
+ console.log(`[MoltsPay] Transaction sent: ${txHash}`);
740
+ console.log(`[MoltsPay] Waiting for confirmation...`);
741
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
742
+ console.log(`[MoltsPay] Transaction confirmed!`);
743
+ const challenge = {
744
+ id: challengeId,
745
+ realm,
746
+ method: "tempo",
747
+ intent: "charge",
748
+ request: paymentRequest
749
+ };
750
+ const credential = {
751
+ challenge,
752
+ payload: { hash: txHash, type: "hash" },
753
+ source: `did:pkh:eip155:${chainId}:${account.address}`
754
+ };
755
+ const credentialB64 = Buffer.from(JSON.stringify(credential)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
756
+ console.log(`[MoltsPay] Retrying with payment credential...`);
757
+ const paidResponse = await fetch(url, {
758
+ method: "POST",
759
+ headers: {
760
+ "Content-Type": "application/json",
761
+ "Authorization": `Payment ${credentialB64}`,
762
+ ...options.headers
763
+ },
764
+ body: options.body ? JSON.stringify(options.body) : void 0
765
+ });
766
+ if (!paidResponse.ok) {
767
+ const errorText = await paidResponse.text();
768
+ throw new Error(`Payment verification failed (${paidResponse.status}): ${errorText}`);
769
+ }
770
+ console.log(`[MoltsPay] Payment verified! Service completed.`);
771
+ return paidResponse.json();
772
+ }
599
773
  };
600
774
 
601
775
  // src/server/index.ts
@@ -835,6 +1009,127 @@ var CDPFacilitator = class extends BaseFacilitator {
835
1009
  }
836
1010
  };
837
1011
 
1012
+ // src/facilitators/tempo.ts
1013
+ init_cjs_shims();
1014
+ var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
1015
+ var TempoFacilitator = class extends BaseFacilitator {
1016
+ name = "tempo";
1017
+ displayName = "Tempo Testnet";
1018
+ supportedNetworks = ["eip155:42431"];
1019
+ // Tempo Moderato
1020
+ rpcUrl;
1021
+ constructor() {
1022
+ super();
1023
+ this.rpcUrl = CHAINS.tempo_moderato.rpc;
1024
+ }
1025
+ async healthCheck() {
1026
+ const start = Date.now();
1027
+ try {
1028
+ const response = await fetch(this.rpcUrl, {
1029
+ method: "POST",
1030
+ headers: { "Content-Type": "application/json" },
1031
+ body: JSON.stringify({
1032
+ jsonrpc: "2.0",
1033
+ method: "eth_chainId",
1034
+ params: [],
1035
+ id: 1
1036
+ })
1037
+ });
1038
+ const data = await response.json();
1039
+ const chainId = parseInt(data.result, 16);
1040
+ if (chainId !== 42431) {
1041
+ return { healthy: false, error: `Wrong chainId: ${chainId}` };
1042
+ }
1043
+ return { healthy: true, latencyMs: Date.now() - start };
1044
+ } catch (error) {
1045
+ return { healthy: false, error: String(error) };
1046
+ }
1047
+ }
1048
+ async verify(paymentPayload, requirements) {
1049
+ try {
1050
+ const tempoPayload = paymentPayload.payload;
1051
+ if (!tempoPayload?.txHash) {
1052
+ return { valid: false, error: "Missing txHash in payment payload" };
1053
+ }
1054
+ const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
1055
+ if (!receipt) {
1056
+ return { valid: false, error: "Transaction not found" };
1057
+ }
1058
+ if (receipt.status !== "0x1") {
1059
+ return { valid: false, error: "Transaction failed" };
1060
+ }
1061
+ const transferLog = receipt.logs.find(
1062
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC
1063
+ );
1064
+ if (!transferLog) {
1065
+ return { valid: false, error: "No Transfer event found" };
1066
+ }
1067
+ const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
1068
+ const expectedTo = requirements.payTo.toLowerCase();
1069
+ if (toAddress !== expectedTo) {
1070
+ return {
1071
+ valid: false,
1072
+ error: `Wrong recipient: ${toAddress}, expected ${expectedTo}`
1073
+ };
1074
+ }
1075
+ const amount = BigInt(transferLog.data);
1076
+ const expectedAmount = BigInt(requirements.amount);
1077
+ if (amount < expectedAmount) {
1078
+ return {
1079
+ valid: false,
1080
+ error: `Insufficient amount: ${amount}, expected ${expectedAmount}`
1081
+ };
1082
+ }
1083
+ const tokenAddress = transferLog.address.toLowerCase();
1084
+ const expectedToken = requirements.asset.toLowerCase();
1085
+ if (tokenAddress !== expectedToken) {
1086
+ return {
1087
+ valid: false,
1088
+ error: `Wrong token: ${tokenAddress}, expected ${expectedToken}`
1089
+ };
1090
+ }
1091
+ return {
1092
+ valid: true,
1093
+ details: {
1094
+ txHash: tempoPayload.txHash,
1095
+ from: "0x" + transferLog.topics[1].slice(26),
1096
+ to: toAddress,
1097
+ amount: amount.toString(),
1098
+ token: tokenAddress
1099
+ }
1100
+ };
1101
+ } catch (error) {
1102
+ return { valid: false, error: `Verification failed: ${error}` };
1103
+ }
1104
+ }
1105
+ async settle(paymentPayload, requirements) {
1106
+ const verifyResult = await this.verify(paymentPayload, requirements);
1107
+ if (!verifyResult.valid) {
1108
+ return { success: false, error: verifyResult.error };
1109
+ }
1110
+ const tempoPayload = paymentPayload.payload;
1111
+ return {
1112
+ success: true,
1113
+ transaction: tempoPayload.txHash,
1114
+ status: "settled"
1115
+ };
1116
+ }
1117
+ async getTransactionReceipt(txHash) {
1118
+ const response = await fetch(this.rpcUrl, {
1119
+ method: "POST",
1120
+ headers: { "Content-Type": "application/json" },
1121
+ body: JSON.stringify({
1122
+ jsonrpc: "2.0",
1123
+ method: "eth_getTransactionReceipt",
1124
+ params: [txHash],
1125
+ id: 1
1126
+ })
1127
+ });
1128
+ const data = await response.json();
1129
+ return data.result;
1130
+ }
1131
+ };
1132
+
838
1133
  // src/facilitators/registry.ts
839
1134
  init_cjs_shims();
840
1135
  var FacilitatorRegistry = class {
@@ -844,7 +1139,8 @@ var FacilitatorRegistry = class {
844
1139
  roundRobinIndex = 0;
845
1140
  constructor(selection) {
846
1141
  this.registerFactory("cdp", (config) => new CDPFacilitator(config));
847
- this.selection = selection || { primary: "cdp", strategy: "failover" };
1142
+ this.registerFactory("tempo", () => new TempoFacilitator());
1143
+ this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
848
1144
  }
849
1145
  /**
850
1146
  * Register a new facilitator factory
@@ -1068,6 +1364,9 @@ var X402_VERSION3 = 2;
1068
1364
  var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
1069
1365
  var PAYMENT_HEADER2 = "x-payment";
1070
1366
  var PAYMENT_RESPONSE_HEADER = "x-payment-response";
1367
+ var MPP_AUTH_HEADER = "authorization";
1368
+ var MPP_WWW_AUTH_HEADER = "www-authenticate";
1369
+ var MPP_RECEIPT_HEADER = "payment-receipt";
1071
1370
  var TOKEN_ADDRESSES = {
1072
1371
  "eip155:8453": {
1073
1372
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -1081,12 +1380,20 @@ var TOKEN_ADDRESSES = {
1081
1380
  "eip155:137": {
1082
1381
  USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
1083
1382
  USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
1383
+ },
1384
+ "eip155:42431": {
1385
+ // Tempo Moderato testnet - TIP-20 stablecoins
1386
+ USDC: "0x20c0000000000000000000000000000000000000",
1387
+ // pathUSD
1388
+ USDT: "0x20c0000000000000000000000000000000000001"
1389
+ // alphaUSD
1084
1390
  }
1085
1391
  };
1086
1392
  var CHAIN_TO_NETWORK = {
1087
1393
  "base": "eip155:8453",
1088
1394
  "base_sepolia": "eip155:84532",
1089
- "polygon": "eip155:137"
1395
+ "polygon": "eip155:137",
1396
+ "tempo_moderato": "eip155:42431"
1090
1397
  };
1091
1398
  var TOKEN_DOMAINS = {
1092
1399
  // Base mainnet
@@ -1104,6 +1411,11 @@ var TOKEN_DOMAINS = {
1104
1411
  "eip155:137": {
1105
1412
  USDC: { name: "USD Coin", version: "2" },
1106
1413
  USDT: { name: "(PoS) Tether USD", version: "2" }
1414
+ },
1415
+ // Tempo Moderato testnet - TIP-20 stablecoins
1416
+ "eip155:42431": {
1417
+ USDC: { name: "pathUSD", version: "1" },
1418
+ USDT: { name: "alphaUSD", version: "1" }
1107
1419
  }
1108
1420
  };
1109
1421
  function getTokenDomain(network, token) {
@@ -1161,9 +1473,11 @@ var MoltsPayServer = class {
1161
1473
  };
1162
1474
  this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
1163
1475
  this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
1476
+ const defaultFallback = ["tempo"];
1477
+ const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
1164
1478
  const facilitatorConfig = options.facilitators || {
1165
1479
  primary: process.env.FACILITATOR_PRIMARY || "cdp",
1166
- fallback: process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean),
1480
+ fallback: envFallback || defaultFallback,
1167
1481
  strategy: process.env.FACILITATOR_STRATEGY || "failover",
1168
1482
  config: {
1169
1483
  cdp: { useMainnet: this.useMainnet }
@@ -1257,8 +1571,8 @@ var MoltsPayServer = class {
1257
1571
  async handleRequest(req, res) {
1258
1572
  res.setHeader("Access-Control-Allow-Origin", "*");
1259
1573
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1260
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
1261
- res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
1574
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
1575
+ res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
1262
1576
  if (req.method === "OPTIONS") {
1263
1577
  res.writeHead(204);
1264
1578
  res.end();
@@ -1289,6 +1603,14 @@ var MoltsPayServer = class {
1289
1603
  const paymentHeader = req.headers[PAYMENT_HEADER2];
1290
1604
  return await this.handleProxy(body, paymentHeader, res);
1291
1605
  }
1606
+ const servicePath = url.pathname.replace(/^\//, "");
1607
+ const skill = this.skills.get(servicePath);
1608
+ if (skill && (req.method === "POST" || req.method === "GET")) {
1609
+ const body = req.method === "POST" ? await this.readBody(req) : {};
1610
+ const authHeader = req.headers[MPP_AUTH_HEADER];
1611
+ const x402Header = req.headers[PAYMENT_HEADER2];
1612
+ return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
1613
+ }
1292
1614
  this.sendJson(res, 404, { error: "Not found" });
1293
1615
  } catch (err) {
1294
1616
  console.error("[MoltsPay] Error:", err);
@@ -1472,6 +1794,187 @@ var MoltsPayServer = class {
1472
1794
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
1473
1795
  }, responseHeaders);
1474
1796
  }
1797
+ /**
1798
+ * Handle MPP (Machine Payments Protocol) request
1799
+ * Supports both x402 and MPP protocols on service endpoints
1800
+ */
1801
+ async handleMPPRequest(skill, body, authHeader, x402Header, res) {
1802
+ const config = skill.config;
1803
+ const params = body || {};
1804
+ if (x402Header) {
1805
+ return await this.handleExecute({ service: config.id, params }, x402Header, res);
1806
+ }
1807
+ if (authHeader && authHeader.toLowerCase().startsWith("payment ")) {
1808
+ return await this.handleMPPPayment(skill, params, authHeader, res);
1809
+ }
1810
+ return this.sendMPPPaymentRequired(config, res);
1811
+ }
1812
+ /**
1813
+ * Handle MPP payment verification and service execution
1814
+ */
1815
+ async handleMPPPayment(skill, params, authHeader, res) {
1816
+ const config = skill.config;
1817
+ const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
1818
+ if (!credentialMatch) {
1819
+ return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
1820
+ }
1821
+ let mppCredential;
1822
+ try {
1823
+ const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
1824
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
1825
+ mppCredential = JSON.parse(decoded);
1826
+ } catch (err) {
1827
+ console.error("[MoltsPay] Failed to parse MPP credential:", err);
1828
+ return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
1829
+ }
1830
+ let txHash;
1831
+ if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
1832
+ txHash = mppCredential.payload.hash;
1833
+ } else if (mppCredential.payload?.type === "transaction") {
1834
+ return this.sendJson(res, 400, {
1835
+ error: "Transaction type not supported. Please use push mode (hash type)."
1836
+ });
1837
+ }
1838
+ if (!txHash) {
1839
+ return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
1840
+ }
1841
+ let chainId = mppCredential.challenge?.request?.methodDetails?.chainId;
1842
+ if (!chainId && mppCredential.source) {
1843
+ const chainMatch = mppCredential.source.match(/eip155:(\d+)/);
1844
+ if (chainMatch) chainId = parseInt(chainMatch[1], 10);
1845
+ }
1846
+ chainId = chainId || 42431;
1847
+ const network = `eip155:${chainId}`;
1848
+ if (!this.isNetworkAccepted(network)) {
1849
+ return this.sendJson(res, 402, {
1850
+ error: `Network not accepted: ${network}`
1851
+ });
1852
+ }
1853
+ const requirements = this.buildPaymentRequirements(
1854
+ config,
1855
+ network,
1856
+ this.getWalletForNetwork(network),
1857
+ "USDC"
1858
+ );
1859
+ const paymentPayload = {
1860
+ x402Version: X402_VERSION3,
1861
+ scheme: "exact",
1862
+ network,
1863
+ payload: {
1864
+ txHash,
1865
+ chainId
1866
+ }
1867
+ };
1868
+ console.log(`[MoltsPay] Verifying MPP payment: txHash=${txHash}, chainId=${chainId}`);
1869
+ const verification = await this.registry.verify(paymentPayload, requirements);
1870
+ if (!verification.valid) {
1871
+ return this.sendJson(res, 402, {
1872
+ error: `Payment verification failed: ${verification.error}`
1873
+ });
1874
+ }
1875
+ console.log(`[MoltsPay] Payment verified! Executing service: ${config.id}`);
1876
+ let result;
1877
+ try {
1878
+ result = await skill.handler(params);
1879
+ } catch (err) {
1880
+ console.error(`[MoltsPay] Skill execution error:`, err);
1881
+ return this.sendJson(res, 500, {
1882
+ error: `Service execution failed: ${err.message}`
1883
+ });
1884
+ }
1885
+ const receipt = {
1886
+ success: true,
1887
+ txHash,
1888
+ network,
1889
+ facilitator: verification.facilitator
1890
+ };
1891
+ const receiptEncoded = Buffer.from(JSON.stringify(receipt)).toString("base64");
1892
+ res.writeHead(200, {
1893
+ "Content-Type": "application/json",
1894
+ [MPP_RECEIPT_HEADER]: receiptEncoded
1895
+ });
1896
+ res.end(JSON.stringify({
1897
+ success: true,
1898
+ result,
1899
+ payment: {
1900
+ txHash,
1901
+ status: "verified",
1902
+ facilitator: verification.facilitator
1903
+ }
1904
+ }, null, 2));
1905
+ }
1906
+ /**
1907
+ * Return 402 with both x402 and MPP payment requirements
1908
+ */
1909
+ sendMPPPaymentRequired(config, res) {
1910
+ const acceptedTokens = getAcceptedCurrencies(config);
1911
+ const providerChains = this.getProviderChains();
1912
+ const accepts = [];
1913
+ for (const chainConfig of providerChains) {
1914
+ for (const token of acceptedTokens) {
1915
+ if (chainConfig.tokens.includes(token)) {
1916
+ accepts.push(this.buildPaymentRequirements(config, chainConfig.network, chainConfig.wallet, token));
1917
+ }
1918
+ }
1919
+ }
1920
+ const x402PaymentRequired = {
1921
+ x402Version: X402_VERSION3,
1922
+ accepts,
1923
+ acceptedCurrencies: acceptedTokens,
1924
+ resource: {
1925
+ url: `/${config.id}`,
1926
+ description: `${config.name} - $${config.price} ${config.currency}`
1927
+ }
1928
+ };
1929
+ const x402Encoded = Buffer.from(JSON.stringify(x402PaymentRequired)).toString("base64");
1930
+ const tempoChain = providerChains.find((c) => c.network === "eip155:42431");
1931
+ let mppWwwAuth = "";
1932
+ if (tempoChain) {
1933
+ const challengeId = this.generateChallengeId();
1934
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
1935
+ const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
1936
+ const mppRequest = {
1937
+ amount: amountInUnits,
1938
+ currency: tokenAddress,
1939
+ methodDetails: {
1940
+ chainId: 42431,
1941
+ feePayer: true
1942
+ },
1943
+ recipient: tempoChain.wallet
1944
+ };
1945
+ const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
1946
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
1947
+ mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
1948
+ }
1949
+ const headers = {
1950
+ "Content-Type": "application/problem+json",
1951
+ [PAYMENT_REQUIRED_HEADER2]: x402Encoded
1952
+ };
1953
+ if (mppWwwAuth) {
1954
+ headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
1955
+ }
1956
+ res.writeHead(402, headers);
1957
+ res.end(JSON.stringify({
1958
+ type: "https://paymentauth.org/problems/payment-required",
1959
+ title: "Payment Required",
1960
+ status: 402,
1961
+ detail: `Payment is required (${config.name}).`,
1962
+ service: config.id,
1963
+ price: config.price,
1964
+ currency: config.currency,
1965
+ acceptedCurrencies: acceptedTokens
1966
+ }, null, 2));
1967
+ }
1968
+ /**
1969
+ * Generate a unique challenge ID for MPP
1970
+ */
1971
+ generateChallengeId() {
1972
+ const bytes = new Uint8Array(24);
1973
+ for (let i = 0; i < bytes.length; i++) {
1974
+ bytes[i] = Math.floor(Math.random() * 256);
1975
+ }
1976
+ return Buffer.from(bytes).toString("base64url");
1977
+ }
1475
1978
  /**
1476
1979
  * Return 402 with x402 payment requirements (v2 format)
1477
1980
  * Includes requirements for all chains and all accepted currencies
@@ -1846,6 +2349,26 @@ async function printQRCode(url) {
1846
2349
 
1847
2350
  // src/cli/index.ts
1848
2351
  var readline = __toESM(require("readline"));
2352
+ if (!globalThis.crypto) {
2353
+ globalThis.crypto = import_crypto.webcrypto;
2354
+ }
2355
+ function getVersion() {
2356
+ const locations = [
2357
+ (0, import_path2.join)(__dirname, "../../package.json"),
2358
+ (0, import_path2.join)(__dirname, "../package.json"),
2359
+ (0, import_path2.join)(process.cwd(), "node_modules/moltspay/package.json")
2360
+ ];
2361
+ for (const loc of locations) {
2362
+ try {
2363
+ if ((0, import_fs4.existsSync)(loc)) {
2364
+ const pkg = JSON.parse((0, import_fs4.readFileSync)(loc, "utf-8"));
2365
+ if (pkg.name === "moltspay") return pkg.version;
2366
+ }
2367
+ } catch {
2368
+ }
2369
+ }
2370
+ return "0.0.0";
2371
+ }
1849
2372
  var program = new import_commander.Command();
1850
2373
  var DEFAULT_CONFIG_DIR = (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
1851
2374
  var PID_FILE = (0, import_path2.join)(DEFAULT_CONFIG_DIR, "server.pid");
@@ -1864,7 +2387,7 @@ function prompt(question) {
1864
2387
  });
1865
2388
  });
1866
2389
  }
1867
- program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version("1.0.0");
2390
+ program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version(getVersion());
1868
2391
  program.command("init").description("Initialize MoltsPay client (create wallet, set limits)").option("--chain <chain>", "Blockchain to use", "base").option("--max-per-tx <amount>", "Max amount per transaction").option("--max-per-day <amount>", "Max amount per day").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
1869
2392
  console.log("\n\u{1F510} MoltsPay Client Setup\n");
1870
2393
  if ((0, import_fs4.existsSync)((0, import_path2.join)(options.configDir, "wallet.json"))) {
@@ -1873,7 +2396,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
1873
2396
  return;
1874
2397
  }
1875
2398
  let chain = options.chain;
1876
- const supportedChains = ["base", "polygon", "base_sepolia"];
2399
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
1877
2400
  if (!supportedChains.includes(chain)) {
1878
2401
  console.error(`\u274C Unknown chain: ${chain}. Supported: ${supportedChains.join(", ")}`);
1879
2402
  process.exit(1);
@@ -1961,11 +2484,9 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
1961
2484
  console.log(` Wallet: ${client.address}`);
1962
2485
  console.log(` Chain: Base Sepolia (testnet)
1963
2486
  `);
1964
- console.log("\u{1F4DD} Get testnet USDC from these faucets:");
1965
- console.log(" \u2022 Circle Faucet: https://faucet.circle.com/");
1966
- console.log(" \u2022 Base Sepolia: https://www.coinbase.com/faucets/base-ethereum-sepolia-faucet\n");
1967
- console.log(`\u{1F4A1} Send USDC to: ${client.address}
1968
- `);
2487
+ console.log("\u{1F4A1} Use the MoltsPay faucet to get free testnet USDC:\n");
2488
+ console.log(" npx moltspay faucet\n");
2489
+ console.log(" Or get from Circle Faucet: https://faucet.circle.com/\n");
1969
2490
  return;
1970
2491
  }
1971
2492
  console.log("\n\u{1F4B3} Fund your agent wallet\n");
@@ -1997,8 +2518,13 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
1997
2518
  console.log(`\u274C ${error.message}`);
1998
2519
  }
1999
2520
  });
2000
- program.command("faucet").description("Request testnet USDC from MoltsPay faucet (Base Sepolia)").option("--address <address>", "Wallet address (defaults to your wallet)").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
2521
+ program.command("faucet").description("Request testnet tokens from faucet (Base Sepolia or Tempo Moderato)").option("--chain <chain>", "Chain to get tokens on (base_sepolia or tempo_moderato)", "base_sepolia").option("--address <address>", "Wallet address (defaults to your wallet)").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
2001
2522
  let address = options.address;
2523
+ const chain = options.chain?.toLowerCase() || "base_sepolia";
2524
+ if (!["base_sepolia", "tempo_moderato"].includes(chain)) {
2525
+ console.log("\u274C Invalid chain. Use: base_sepolia or tempo_moderato");
2526
+ return;
2527
+ }
2002
2528
  if (!address) {
2003
2529
  const client = new MoltsPayClient({ configDir: options.configDir });
2004
2530
  if (client.isInitialized) {
@@ -2013,34 +2539,67 @@ program.command("faucet").description("Request testnet USDC from MoltsPay faucet
2013
2539
  return;
2014
2540
  }
2015
2541
  console.log("\n\u{1F6B0} MoltsPay Testnet Faucet\n");
2016
- console.log(` Requesting 1 USDC on Base Sepolia...`);
2017
- console.log(` Address: ${address}
2542
+ if (chain === "tempo_moderato") {
2543
+ console.log(` Requesting testnet tokens on Tempo Moderato...`);
2544
+ console.log(` Address: ${address}
2018
2545
  `);
2019
- try {
2020
- const FAUCET_API = process.env.MOLTSPAY_FAUCET_API || "https://moltspay.com/api/v1/faucet";
2021
- const response = await fetch(FAUCET_API, {
2022
- method: "POST",
2023
- headers: { "Content-Type": "application/json" },
2024
- body: JSON.stringify({ address })
2025
- });
2026
- const result = await response.json();
2027
- if (!response.ok) {
2028
- console.log(`\u274C ${result.error || "Request failed"}`);
2029
- if (result.hint) console.log(` ${result.hint}`);
2030
- if (result.retry_after) console.log(` Retry after: ${result.retry_after}`);
2031
- return;
2546
+ try {
2547
+ const TEMPO_FAUCET_API = "https://docs.tempo.xyz/api/faucet";
2548
+ const response = await fetch(TEMPO_FAUCET_API, {
2549
+ method: "POST",
2550
+ headers: { "Content-Type": "application/json" },
2551
+ body: JSON.stringify({ address })
2552
+ });
2553
+ const result = await response.json();
2554
+ if (response.ok && result.data && result.data.length > 0) {
2555
+ console.log(`\u2705 Received testnet tokens!
2556
+ `);
2557
+ console.log(` Tokens: pathUSD, AlphaUSD, BetaUSD, ThetaUSD (1M each)`);
2558
+ console.log(` Transactions:`);
2559
+ for (const tx of result.data) {
2560
+ console.log(` https://explore.testnet.tempo.xyz/tx/${tx.hash}`);
2561
+ }
2562
+ console.log("\n\u{1F4A1} Use these tokens to test MPP payments:");
2563
+ console.log(` npx moltspay pay <service-url> <service-id> --chain tempo_moderato
2564
+ `);
2565
+ } else {
2566
+ console.log(`\u274C ${result.error || "Faucet request failed"}`);
2567
+ console.log("\n Try again later or use Tempo Wallet: https://wallet.tempo.xyz\n");
2568
+ }
2569
+ } catch (error) {
2570
+ console.log(`\u274C ${error.message}`);
2571
+ console.log("\n Try Tempo Wallet instead: https://wallet.tempo.xyz\n");
2032
2572
  }
2033
- console.log(`\u2705 Received ${result.amount} USDC!
2573
+ } else {
2574
+ console.log(` Requesting 1 USDC on Base Sepolia...`);
2575
+ console.log(` Address: ${address}
2034
2576
  `);
2035
- console.log(` Transaction: ${result.transaction}`);
2036
- console.log(` Explorer: ${result.explorer}`);
2037
- console.log(` Faucet balance: ${result.faucet_balance} USDC remaining
2577
+ try {
2578
+ const FAUCET_API = process.env.MOLTSPAY_FAUCET_API || "https://moltspay.com/api/v1/faucet";
2579
+ const response = await fetch(FAUCET_API, {
2580
+ method: "POST",
2581
+ headers: { "Content-Type": "application/json" },
2582
+ body: JSON.stringify({ address })
2583
+ });
2584
+ const result = await response.json();
2585
+ if (!response.ok) {
2586
+ console.log(`\u274C ${result.error || "Request failed"}`);
2587
+ if (result.hint) console.log(` ${result.hint}`);
2588
+ if (result.retry_after) console.log(` Retry after: ${result.retry_after}`);
2589
+ return;
2590
+ }
2591
+ console.log(`\u2705 Received ${result.amount} USDC!
2038
2592
  `);
2039
- console.log("\u{1F4A1} Use this USDC to test x402 payments:");
2040
- console.log(` npx moltspay pay <service-url> <service-id> --chain base_sepolia
2593
+ console.log(` Transaction: ${result.transaction}`);
2594
+ console.log(` Explorer: ${result.explorer}`);
2595
+ console.log(` Faucet balance: ${result.faucet_balance} USDC remaining
2041
2596
  `);
2042
- } catch (error) {
2043
- console.log(`\u274C ${error.message}`);
2597
+ console.log("\u{1F4A1} Use this USDC to test x402 payments:");
2598
+ console.log(` npx moltspay pay <service-url> <service-id> --chain base_sepolia
2599
+ `);
2600
+ } catch (error) {
2601
+ console.log(`\u274C ${error.message}`);
2602
+ }
2044
2603
  }
2045
2604
  });
2046
2605
  program.command("status").description("Show wallet status and balance").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).option("--json", "Output as JSON").action(async (options) => {
@@ -2072,8 +2631,26 @@ program.command("status").description("Show wallet status and balance").option("
2072
2631
  console.log("");
2073
2632
  console.log(" Balances:");
2074
2633
  for (const [chainName, balance] of Object.entries(allBalances)) {
2075
- const chainLabel = chainName.charAt(0).toUpperCase() + chainName.slice(1);
2076
- console.log(` ${chainLabel.padEnd(10)} ${balance.usdc.toFixed(2)} USDC | ${balance.usdt.toFixed(2)} USDT`);
2634
+ let chainLabel;
2635
+ if (chainName === "base_sepolia") {
2636
+ chainLabel = "Base Sepolia";
2637
+ } else if (chainName === "tempo_moderato") {
2638
+ chainLabel = "Tempo Moderato";
2639
+ } else {
2640
+ chainLabel = chainName.charAt(0).toUpperCase() + chainName.slice(1);
2641
+ }
2642
+ if (chainName === "tempo_moderato" && balance.tempo) {
2643
+ const tempo = balance.tempo;
2644
+ const nativeStr = balance.native > 1e12 ? balance.native.toExponential(2) : balance.native.toFixed(2);
2645
+ console.log(` ${chainLabel}:`);
2646
+ console.log(` Native: ${nativeStr} TEMPO (for gas)`);
2647
+ console.log(` pathUSD: ${tempo.pathUSD.toFixed(2)}`);
2648
+ console.log(` alphaUSD: ${tempo.alphaUSD.toFixed(2)}`);
2649
+ console.log(` betaUSD: ${tempo.betaUSD.toFixed(2)}`);
2650
+ console.log(` thetaUSD: ${tempo.thetaUSD.toFixed(2)}`);
2651
+ } else {
2652
+ console.log(` ${chainLabel.padEnd(14)} ${balance.usdc.toFixed(2)} USDC | ${balance.usdt.toFixed(2)} USDT`);
2653
+ }
2077
2654
  }
2078
2655
  console.log("");
2079
2656
  console.log(" Spending Limits:");
@@ -2091,8 +2668,8 @@ program.command("list").description("List recent transactions").option("--days <
2091
2668
  const days = parseInt(options.days) || 7;
2092
2669
  const limit = parseInt(options.limit) || 20;
2093
2670
  const chain = options.chain?.toLowerCase() || "all";
2094
- if (!["base", "polygon", "base_sepolia", "all"].includes(chain)) {
2095
- console.log("\u274C Invalid chain. Use: base, polygon, base_sepolia, or all");
2671
+ if (!["base", "polygon", "base_sepolia", "tempo_moderato", "all"].includes(chain)) {
2672
+ console.log("\u274C Invalid chain. Use: base, polygon, base_sepolia, tempo_moderato, or all");
2096
2673
  return;
2097
2674
  }
2098
2675
  const wallet = client.address;
@@ -2112,9 +2689,16 @@ program.command("list").description("List recent transactions").option("--days <
2112
2689
  api: "https://base-sepolia.blockscout.com/api/v2",
2113
2690
  usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
2114
2691
  name: "Base Sepolia"
2692
+ },
2693
+ // Tempo explorer doesn't have public API yet
2694
+ tempo_moderato: {
2695
+ api: "",
2696
+ // No API available
2697
+ usdc: "0x20c0000000000000000000000000000000000000",
2698
+ name: "Tempo Moderato"
2115
2699
  }
2116
2700
  };
2117
- const chainsToQuery = chain === "all" ? ["base", "polygon", "base_sepolia"] : [chain];
2701
+ const chainsToQuery = chain === "all" ? ["base", "polygon", "base_sepolia", "tempo_moderato"] : [chain];
2118
2702
  console.log(`
2119
2703
  \u{1F4DC} Transactions (last ${days} day${days > 1 ? "s" : ""})
2120
2704
  `);
@@ -2122,27 +2706,136 @@ program.command("list").description("List recent transactions").option("--days <
2122
2706
  for (const c of chainsToQuery) {
2123
2707
  const explorer = explorers[c];
2124
2708
  try {
2125
- const url = `${explorer.api}/addresses/${wallet}/token-transfers?type=ERC-20&token=${explorer.usdc}`;
2126
- const response = await fetch(url);
2127
- const data = await response.json();
2128
- if (data.items && Array.isArray(data.items)) {
2129
- for (const tx of data.items) {
2130
- const timestamp = new Date(tx.timestamp).getTime();
2131
- if (timestamp < cutoffTime) continue;
2132
- const isIncoming = tx.to.hash.toLowerCase() === wallet.toLowerCase();
2133
- const decimals = parseInt(tx.total.decimals) || 6;
2134
- allTxns.push({
2135
- chain: c,
2136
- timestamp,
2137
- type: isIncoming ? "IN" : "OUT",
2138
- amount: parseInt(tx.total.value) / Math.pow(10, decimals),
2139
- other: isIncoming ? tx.from.hash : tx.to.hash,
2140
- hash: tx.transaction_hash
2141
- });
2709
+ if (c === "tempo_moderato") {
2710
+ const tempoTokens = [
2711
+ { address: "0x20c0000000000000000000000000000000000000", name: "pathUSD" },
2712
+ { address: "0x20c0000000000000000000000000000000000001", name: "alphaUSD" },
2713
+ { address: "0x20c0000000000000000000000000000000000002", name: "betaUSD" },
2714
+ { address: "0x20c0000000000000000000000000000000000003", name: "thetaUSD" }
2715
+ ];
2716
+ const transferTopic = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
2717
+ const walletTopic = "0x000000000000000000000000" + wallet.toLowerCase().slice(2);
2718
+ let latestBlock = 0;
2719
+ for (let attempt = 0; attempt < 3; attempt++) {
2720
+ try {
2721
+ const blockRes = await fetch("https://rpc.moderato.tempo.xyz", {
2722
+ method: "POST",
2723
+ headers: { "Content-Type": "application/json" },
2724
+ body: JSON.stringify({ jsonrpc: "2.0", method: "eth_blockNumber", params: [], id: 1 })
2725
+ });
2726
+ const blockData = await blockRes.json();
2727
+ if (blockData.result) {
2728
+ latestBlock = parseInt(blockData.result, 16);
2729
+ break;
2730
+ }
2731
+ } catch (e) {
2732
+ if (attempt === 2) throw e;
2733
+ await new Promise((r) => setTimeout(r, 500));
2734
+ }
2735
+ }
2736
+ if (latestBlock === 0) {
2737
+ console.log(" \u26A0\uFE0F Tempo Moderato: Could not get latest block");
2738
+ continue;
2739
+ }
2740
+ const maxBlocks = 1e5;
2741
+ const blocksPerDay = 172800;
2742
+ const requestedBlocks = blocksPerDay * days;
2743
+ const actualBlocks = Math.min(requestedBlocks, maxBlocks);
2744
+ const fromBlock = "0x" + Math.max(0, latestBlock - actualBlocks).toString(16);
2745
+ const toBlock = "0x" + latestBlock.toString(16);
2746
+ if (requestedBlocks > maxBlocks) {
2747
+ console.log(` \u2139\uFE0F Tempo: querying last ~14 hours (RPC limit: 100k blocks)`);
2748
+ }
2749
+ for (const token of tempoTokens) {
2750
+ try {
2751
+ const inRes = await fetch("https://rpc.moderato.tempo.xyz", {
2752
+ method: "POST",
2753
+ headers: { "Content-Type": "application/json" },
2754
+ body: JSON.stringify({
2755
+ jsonrpc: "2.0",
2756
+ method: "eth_getLogs",
2757
+ params: [{ fromBlock, toBlock, address: token.address, topics: [transferTopic, null, walletTopic] }],
2758
+ id: 1
2759
+ })
2760
+ });
2761
+ const inData = await inRes.json();
2762
+ if (inData.error) {
2763
+ console.log(` \u26A0\uFE0F ${token.name}: ${inData.error.message}`);
2764
+ continue;
2765
+ }
2766
+ if (inData.result && Array.isArray(inData.result)) {
2767
+ for (const log of inData.result) {
2768
+ const timestamp = parseInt(log.blockTimestamp, 16) * 1e3;
2769
+ if (timestamp < cutoffTime) continue;
2770
+ const amount = parseInt(log.data, 16) / 1e6;
2771
+ const from = "0x" + log.topics[1].slice(26);
2772
+ allTxns.push({
2773
+ chain: c,
2774
+ timestamp,
2775
+ type: "IN",
2776
+ amount,
2777
+ other: from,
2778
+ hash: log.transactionHash,
2779
+ token: token.name
2780
+ });
2781
+ }
2782
+ }
2783
+ const outRes = await fetch("https://rpc.moderato.tempo.xyz", {
2784
+ method: "POST",
2785
+ headers: { "Content-Type": "application/json" },
2786
+ body: JSON.stringify({
2787
+ jsonrpc: "2.0",
2788
+ method: "eth_getLogs",
2789
+ params: [{ fromBlock, toBlock, address: token.address, topics: [transferTopic, walletTopic, null] }],
2790
+ id: 1
2791
+ })
2792
+ });
2793
+ const outData = await outRes.json();
2794
+ if (outData.result && Array.isArray(outData.result)) {
2795
+ for (const log of outData.result) {
2796
+ const timestamp = parseInt(log.blockTimestamp, 16) * 1e3;
2797
+ if (timestamp < cutoffTime) continue;
2798
+ const amount = parseInt(log.data, 16) / 1e6;
2799
+ const to = "0x" + log.topics[2].slice(26);
2800
+ allTxns.push({
2801
+ chain: c,
2802
+ timestamp,
2803
+ type: "OUT",
2804
+ amount,
2805
+ other: to,
2806
+ hash: log.transactionHash,
2807
+ token: token.name
2808
+ });
2809
+ }
2810
+ }
2811
+ } catch (tokenError) {
2812
+ continue;
2813
+ }
2814
+ }
2815
+ } else {
2816
+ const url = `${explorer.api}/addresses/${wallet}/token-transfers?type=ERC-20&token=${explorer.usdc}`;
2817
+ const response = await fetch(url);
2818
+ const data = await response.json();
2819
+ if (data.items && Array.isArray(data.items)) {
2820
+ for (const tx of data.items) {
2821
+ const timestamp = new Date(tx.timestamp).getTime();
2822
+ if (timestamp < cutoffTime) continue;
2823
+ const isIncoming = tx.to.hash.toLowerCase() === wallet.toLowerCase();
2824
+ const decimals = parseInt(tx.total.decimals) || 6;
2825
+ allTxns.push({
2826
+ chain: c,
2827
+ timestamp,
2828
+ type: isIncoming ? "IN" : "OUT",
2829
+ amount: parseInt(tx.total.value) / Math.pow(10, decimals),
2830
+ other: isIncoming ? tx.from.hash : tx.to.hash,
2831
+ hash: tx.transaction_hash
2832
+ });
2833
+ }
2142
2834
  }
2143
2835
  }
2144
2836
  } catch (error) {
2145
- console.log(` \u26A0\uFE0F ${explorer.name}: API error`);
2837
+ const errMsg = error instanceof Error ? error.message : String(error);
2838
+ console.log(` \u26A0\uFE0F ${explorer.name}: ${errMsg}`);
2146
2839
  }
2147
2840
  }
2148
2841
  allTxns.sort((a, b) => b.timestamp - a.timestamp);
@@ -2155,8 +2848,12 @@ program.command("list").description("List recent transactions").option("--days <
2155
2848
  const color = tx.type === "IN" ? "\x1B[32m" : "\x1B[31m";
2156
2849
  const reset = "\x1B[0m";
2157
2850
  const date = new Date(tx.timestamp).toISOString().slice(5, 16).replace("T", " ");
2158
- const chainTag = chain === "all" ? `[${tx.chain.toUpperCase()}] ` : "";
2159
- console.log(` ${color}${sign}${tx.amount.toFixed(2)} USDC${reset} | ${chainTag}${tx.type === "IN" ? "from" : "to"} ${tx.other.slice(0, 10)}...${tx.other.slice(-4)} | ${date}`);
2851
+ let chainLabel = tx.chain.toUpperCase();
2852
+ if (tx.chain === "tempo_moderato") chainLabel = "TEMPO";
2853
+ else if (tx.chain === "base_sepolia") chainLabel = "BASE_SEPOLIA";
2854
+ const chainTag = chain === "all" ? `[${chainLabel}] ` : "";
2855
+ const tokenName = tx.token || "USDC";
2856
+ console.log(` ${color}${sign}${tx.amount.toFixed(2)} ${tokenName}${reset} | ${chainTag}${tx.type === "IN" ? "from" : "to"} ${tx.other.slice(0, 10)}...${tx.other.slice(-4)} | ${date}`);
2160
2857
  }
2161
2858
  const inTotal = allTxns.filter((t) => t.type === "IN").reduce((s, t) => s + t.amount, 0);
2162
2859
  const outTotal = allTxns.filter((t) => t.type === "OUT").reduce((s, t) => s + t.amount, 0);
@@ -2165,39 +2862,88 @@ program.command("list").description("List recent transactions").option("--days <
2165
2862
  `);
2166
2863
  }
2167
2864
  });
2168
- program.command("services <url>").description("List services from a provider").option("--json", "Output as JSON").action(async (url, options) => {
2865
+ program.command("services [url]").description("List services from registry or a specific provider").option("-q, --query <keyword>", "Search by keyword (name, description, tags)").option("--max-price <price>", "Maximum price in USD").option("--type <type>", "Filter by type: api_service | file_download").option("--tag <tag>", "Filter by tag").option("--json", "Output as JSON").action(async (url, options) => {
2866
+ const MOLTSPAY_REGISTRY = "https://moltspay.com";
2169
2867
  try {
2170
- const client = new MoltsPayClient();
2171
- const services = await client.getServices(url);
2868
+ let services;
2869
+ let isRegistry = false;
2870
+ if (url) {
2871
+ const client = new MoltsPayClient();
2872
+ services = await client.getServices(url);
2873
+ } else {
2874
+ isRegistry = true;
2875
+ const params = new URLSearchParams();
2876
+ if (options.query) params.set("q", options.query);
2877
+ if (options.maxPrice) params.set("maxPrice", options.maxPrice);
2878
+ if (options.type) params.set("type", options.type);
2879
+ if (options.tag) params.set("tag", options.tag);
2880
+ const queryString = params.toString();
2881
+ const registryUrl = `${MOLTSPAY_REGISTRY}/registry/services${queryString ? "?" + queryString : ""}`;
2882
+ const res = await fetch(registryUrl);
2883
+ if (!res.ok) {
2884
+ throw new Error(`Registry request failed: ${res.status}`);
2885
+ }
2886
+ services = await res.json();
2887
+ }
2172
2888
  if (options.json) {
2173
2889
  console.log(JSON.stringify(services, null, 2));
2174
2890
  } else {
2175
- if (services.provider) {
2176
- console.log(`
2177
- \u{1F3EA} ${services.provider.name}
2891
+ const serviceList = services.services || [];
2892
+ if (isRegistry) {
2893
+ if (options.query) {
2894
+ console.log(`
2895
+ \u{1F50D} Search: "${options.query}" (${serviceList.length} results)
2178
2896
  `);
2179
- console.log(` ${services.provider.description || ""}`);
2180
- console.log(` Wallet: ${services.provider.wallet}`);
2181
- const chains = services.provider.chains ? Array.isArray(services.provider.chains) ? services.provider.chains.map((c) => typeof c === "string" ? c : c.chain).join(", ") : services.provider.chains : services.provider.chain || "base";
2182
- console.log(` Chains: ${chains}`);
2897
+ } else {
2898
+ const filters = [];
2899
+ if (options.maxPrice) filters.push(`max $${options.maxPrice}`);
2900
+ if (options.type) filters.push(options.type);
2901
+ if (options.tag) filters.push(`#${options.tag}`);
2902
+ const filterStr = filters.length > 0 ? ` (${filters.join(", ")})` : "";
2903
+ console.log(`
2904
+ \u{1F50D} MoltsPay Registry${filterStr} - ${serviceList.length} services
2905
+ `);
2906
+ }
2907
+ for (const svc of serviceList) {
2908
+ const name = (svc.name || svc.id).slice(0, 30).padEnd(30);
2909
+ const price = `$${svc.price}`.padEnd(8);
2910
+ const type = (svc.type || "unknown").padEnd(14);
2911
+ const provider = `@${svc.provider?.username || "unknown"}`;
2912
+ console.log(` ${name} ${price} ${type} ${provider}`);
2913
+ }
2914
+ if (serviceList.length > 0) {
2915
+ console.log(`
2916
+ \u{1F4A1} Use: moltspay pay <provider-url> <service-id>
2917
+ `);
2918
+ }
2183
2919
  } else {
2184
- console.log(`
2185
- \u{1F3EA} MoltsPay Service Registry
2920
+ if (services.provider) {
2921
+ console.log(`
2922
+ \u{1F3EA} ${services.provider.name}
2186
2923
  `);
2187
- console.log(` ${services.services?.length || 0} services available`);
2188
- }
2189
- console.log("\n\u{1F4E6} Services:\n");
2190
- for (const svc of services.services) {
2191
- const status = svc.available !== false ? "\u2705" : "\u274C";
2192
- console.log(` ${status} ${svc.id || svc.name}`);
2193
- console.log(` ${svc.name} - $${svc.price} ${svc.currency}`);
2194
- if (svc.description) {
2195
- console.log(` ${svc.description}`);
2924
+ console.log(` ${services.provider.description || ""}`);
2925
+ console.log(` Wallet: ${services.provider.wallet}`);
2926
+ const chains = services.provider.chains ? Array.isArray(services.provider.chains) ? services.provider.chains.map((c) => typeof c === "string" ? c : c.chain).join(", ") : services.provider.chains : services.provider.chain || "base";
2927
+ console.log(` Chains: ${chains}`);
2928
+ } else {
2929
+ console.log(`
2930
+ \u{1F3EA} Provider Services
2931
+ `);
2932
+ console.log(` ${serviceList.length} services available`);
2196
2933
  }
2197
- if (svc.provider && !services.provider) {
2198
- console.log(` Provider: ${svc.provider.name || svc.provider.username}`);
2934
+ console.log("\n\u{1F4E6} Services:\n");
2935
+ for (const svc of serviceList) {
2936
+ const status = svc.available !== false ? "\u2705" : "\u274C";
2937
+ console.log(` ${status} ${svc.id || svc.name}`);
2938
+ console.log(` ${svc.name} - $${svc.price} ${svc.currency}`);
2939
+ if (svc.description) {
2940
+ console.log(` ${svc.description}`);
2941
+ }
2942
+ if (svc.provider && !services.provider) {
2943
+ console.log(` Provider: ${svc.provider.name || svc.provider.username}`);
2944
+ }
2945
+ console.log("");
2199
2946
  }
2200
- console.log("");
2201
2947
  }
2202
2948
  }
2203
2949
  } catch (err) {
@@ -2411,8 +3157,8 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
2411
3157
  process.exit(1);
2412
3158
  }
2413
3159
  });
2414
- program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, or base_sepolia). Required if server accepts multiple chains.").option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
2415
- const client = new MoltsPayClient();
3160
+ program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, base_sepolia, or tempo_moderato).").option("--config-dir <dir>", "Config directory with wallet.json", DEFAULT_CONFIG_DIR).option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
3161
+ const client = new MoltsPayClient({ configDir: options.configDir });
2416
3162
  if (!client.isInitialized) {
2417
3163
  console.error("\u274C Wallet not initialized. Run: npx moltspay init");
2418
3164
  process.exit(1);
@@ -2441,13 +3187,9 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
2441
3187
  params.image_base64 = imageData.toString("base64");
2442
3188
  }
2443
3189
  }
2444
- if (!params.prompt) {
2445
- console.error("\u274C Missing prompt. Use --prompt or pass JSON params");
2446
- process.exit(1);
2447
- }
2448
3190
  const chain = options.chain?.toLowerCase();
2449
- if (chain && !["base", "polygon", "base_sepolia"].includes(chain)) {
2450
- console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon, base_sepolia`);
3191
+ if (chain && !["base", "polygon", "base_sepolia", "tempo_moderato"].includes(chain)) {
3192
+ console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon, base_sepolia, tempo_moderato`);
2451
3193
  process.exit(1);
2452
3194
  }
2453
3195
  const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);
@@ -2478,10 +3220,22 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
2478
3220
  console.log("");
2479
3221
  }
2480
3222
  try {
2481
- const result = await client.pay(server, service, params, {
2482
- token,
2483
- chain
2484
- });
3223
+ let result;
3224
+ if (chain === "tempo_moderato") {
3225
+ if (!options.json) {
3226
+ console.log(" Protocol: MPP (Machine Payments Protocol)");
3227
+ console.log("");
3228
+ }
3229
+ const mppUrl = server.includes(service) ? server : `${server}/${service}`;
3230
+ result = await client.payWithMPP(mppUrl, {
3231
+ body: params
3232
+ });
3233
+ } else {
3234
+ result = await client.pay(server, service, params, {
3235
+ token,
3236
+ chain
3237
+ });
3238
+ }
2485
3239
  if (options.json) {
2486
3240
  console.log(JSON.stringify(result));
2487
3241
  } else {