moltspay 1.2.0 → 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 +161 -17
  2. package/dist/cdp/index.d.mts +1 -1
  3. package/dist/cdp/index.d.ts +1 -1
  4. package/dist/cdp/index.js +60 -30408
  5. package/dist/cdp/index.js.map +1 -1
  6. package/dist/cdp/index.mjs +44 -30400
  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 +36 -40
  13. package/dist/chains/index.js.map +1 -1
  14. package/dist/chains/index.mjs +36 -40
  15. package/dist/chains/index.mjs.map +1 -1
  16. package/dist/cli/index.js +997 -174
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/cli/index.mjs +1001 -174
  19. package/dist/cli/index.mjs.map +1 -1
  20. package/dist/client/index.d.mts +27 -4
  21. package/dist/client/index.d.ts +27 -4
  22. package/dist/client/index.js +217 -60
  23. package/dist/client/index.js.map +1 -1
  24. package/dist/client/index.mjs +207 -60
  25. package/dist/client/index.mjs.map +1 -1
  26. package/dist/facilitators/index.d.mts +15 -47
  27. package/dist/facilitators/index.d.ts +15 -47
  28. package/dist/facilitators/index.js +273 -34
  29. package/dist/facilitators/index.js.map +1 -1
  30. package/dist/facilitators/index.mjs +272 -34
  31. package/dist/facilitators/index.mjs.map +1 -1
  32. package/dist/{index-B3v8IWjM.d.mts → index-On9ZaGDW.d.mts} +2 -1
  33. package/dist/{index-B3v8IWjM.d.ts → index-On9ZaGDW.d.ts} +2 -1
  34. package/dist/index.d.mts +2 -2
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.js +792 -30657
  37. package/dist/index.js.map +1 -1
  38. package/dist/index.mjs +782 -30657
  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 +513 -48
  43. package/dist/server/index.js.map +1 -1
  44. package/dist/server/index.mjs +513 -48
  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 +36 -40
  49. package/dist/verify/index.js.map +1 -1
  50. package/dist/verify/index.mjs +36 -40
  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 +36 -40
  55. package/dist/wallet/index.js.map +1 -1
  56. package/dist/wallet/index.mjs +36 -40
  57. package/dist/wallet/index.mjs.map +1 -1
  58. package/package.json +5 -2
@@ -48,8 +48,8 @@ var BaseFacilitator = class {
48
48
  var import_fs = require("fs");
49
49
  var path = __toESM(require("path"));
50
50
  var X402_VERSION = 2;
51
- var CDP_MAINNET_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
52
- var CDP_TESTNET_URL = "https://www.x402.org/facilitator";
51
+ var CDP_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
52
+ var TESTNET_CHAIN_IDS = [84532];
53
53
  function loadEnvFile() {
54
54
  const envPaths = [
55
55
  path.join(process.cwd(), ".env"),
@@ -84,31 +84,33 @@ var CDPFacilitator = class extends BaseFacilitator {
84
84
  displayName = "Coinbase CDP";
85
85
  supportedNetworks;
86
86
  endpoint;
87
- useMainnet;
88
87
  apiKeyId;
89
88
  apiKeySecret;
90
89
  constructor(config = {}) {
91
90
  super();
92
91
  loadEnvFile();
93
- this.useMainnet = config.useMainnet ?? process.env.USE_MAINNET?.toLowerCase() === "true";
94
92
  this.apiKeyId = config.apiKeyId || process.env.CDP_API_KEY_ID;
95
93
  this.apiKeySecret = config.apiKeySecret || process.env.CDP_API_KEY_SECRET;
96
- this.endpoint = this.useMainnet ? CDP_MAINNET_URL : CDP_TESTNET_URL;
97
- this.supportedNetworks = this.useMainnet ? ["eip155:8453", "eip155:137"] : ["eip155:8453", "eip155:84532", "eip155:137"];
98
- if (this.useMainnet && (!this.apiKeyId || !this.apiKeySecret)) {
99
- console.warn("[CDPFacilitator] WARNING: Mainnet mode but missing CDP credentials!");
100
- console.warn("[CDPFacilitator] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET");
94
+ this.endpoint = CDP_URL;
95
+ this.supportedNetworks = [
96
+ "eip155:8453",
97
+ // Base mainnet
98
+ "eip155:137",
99
+ // Polygon mainnet
100
+ "eip155:84532"
101
+ // Base Sepolia (testnet)
102
+ ];
103
+ if (!this.apiKeyId || !this.apiKeySecret) {
104
+ console.warn("[CDPFacilitator] WARNING: Missing CDP credentials!");
105
+ console.warn("[CDPFacilitator] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in ~/.moltspay/.env");
101
106
  }
102
107
  }
103
108
  /**
104
109
  * Get auth headers for CDP API requests
105
110
  */
106
111
  async getAuthHeaders(method, urlPath, body) {
107
- if (!this.useMainnet) {
108
- return {};
109
- }
110
112
  if (!this.apiKeyId || !this.apiKeySecret) {
111
- throw new Error("CDP credentials required for mainnet");
113
+ throw new Error("CDP credentials required. Set CDP_API_KEY_ID and CDP_API_KEY_SECRET");
112
114
  }
113
115
  try {
114
116
  const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
@@ -160,23 +162,23 @@ var CDPFacilitator = class extends BaseFacilitator {
160
162
  paymentPayload,
161
163
  paymentRequirements: requirements
162
164
  };
165
+ console.log("[CDP Verify] Payload:", JSON.stringify(paymentPayload, null, 2));
166
+ const authHeaders = await this.getAuthHeaders(
167
+ "POST",
168
+ "/platform/v2/x402/verify",
169
+ requestBody
170
+ );
163
171
  const headers = {
164
- "Content-Type": "application/json"
172
+ "Content-Type": "application/json",
173
+ ...authHeaders
165
174
  };
166
- if (this.useMainnet) {
167
- const authHeaders = await this.getAuthHeaders(
168
- "POST",
169
- "/platform/v2/x402/verify",
170
- requestBody
171
- );
172
- Object.assign(headers, authHeaders);
173
- }
174
175
  const response = await fetch(`${this.endpoint}/verify`, {
175
176
  method: "POST",
176
177
  headers,
177
178
  body: JSON.stringify(requestBody)
178
179
  });
179
180
  const result = await response.json();
181
+ console.log("[CDP Verify] Response:", response.status, JSON.stringify(result));
180
182
  if (!response.ok || !result.isValid) {
181
183
  return {
182
184
  valid: false,
@@ -202,17 +204,15 @@ var CDPFacilitator = class extends BaseFacilitator {
202
204
  paymentPayload,
203
205
  paymentRequirements: requirements
204
206
  };
207
+ const authHeaders = await this.getAuthHeaders(
208
+ "POST",
209
+ "/platform/v2/x402/settle",
210
+ requestBody
211
+ );
205
212
  const headers = {
206
- "Content-Type": "application/json"
213
+ "Content-Type": "application/json",
214
+ ...authHeaders
207
215
  };
208
- if (this.useMainnet) {
209
- const authHeaders = await this.getAuthHeaders(
210
- "POST",
211
- "/platform/v2/x402/settle",
212
- requestBody
213
- );
214
- Object.assign(headers, authHeaders);
215
- }
216
216
  const response = await fetch(`${this.endpoint}/settle`, {
217
217
  method: "POST",
218
218
  headers,
@@ -247,13 +247,249 @@ var CDPFacilitator = class extends BaseFacilitator {
247
247
  freeQuota: 1e3
248
248
  };
249
249
  }
250
+ /**
251
+ * Check if a chain ID is testnet
252
+ */
253
+ static isTestnet(chainId) {
254
+ return TESTNET_CHAIN_IDS.includes(chainId);
255
+ }
250
256
  /**
251
257
  * Get configuration summary (for logging)
252
258
  */
253
259
  getConfigSummary() {
254
- const mode = this.useMainnet ? "mainnet" : "testnet";
255
260
  const hasCredentials = !!(this.apiKeyId && this.apiKeySecret);
256
- return `CDP Facilitator (${mode}, credentials: ${hasCredentials ? "yes" : "no"})`;
261
+ const networks = this.supportedNetworks.join(", ");
262
+ return `CDP Facilitator (networks: ${networks}, credentials: ${hasCredentials ? "yes" : "no"})`;
263
+ }
264
+ };
265
+
266
+ // src/chains/index.ts
267
+ var CHAINS = {
268
+ // ============ Mainnet ============
269
+ base: {
270
+ name: "Base",
271
+ chainId: 8453,
272
+ rpc: "https://mainnet.base.org",
273
+ tokens: {
274
+ USDC: {
275
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
276
+ decimals: 6,
277
+ symbol: "USDC",
278
+ eip712Name: "USD Coin"
279
+ // EIP-712 domain name
280
+ },
281
+ USDT: {
282
+ address: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
283
+ decimals: 6,
284
+ symbol: "USDT",
285
+ eip712Name: "Tether USD"
286
+ }
287
+ },
288
+ usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
289
+ // deprecated, for backward compat
290
+ explorer: "https://basescan.org/address/",
291
+ explorerTx: "https://basescan.org/tx/",
292
+ avgBlockTime: 2
293
+ },
294
+ polygon: {
295
+ name: "Polygon",
296
+ chainId: 137,
297
+ rpc: "https://polygon-bor-rpc.publicnode.com",
298
+ tokens: {
299
+ USDC: {
300
+ address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
301
+ decimals: 6,
302
+ symbol: "USDC",
303
+ eip712Name: "USD Coin"
304
+ },
305
+ USDT: {
306
+ address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
307
+ decimals: 6,
308
+ symbol: "USDT",
309
+ eip712Name: "(PoS) Tether USD"
310
+ // Polygon uses this name
311
+ }
312
+ },
313
+ usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
314
+ explorer: "https://polygonscan.com/address/",
315
+ explorerTx: "https://polygonscan.com/tx/",
316
+ avgBlockTime: 2
317
+ },
318
+ // ============ Testnet ============
319
+ base_sepolia: {
320
+ name: "Base Sepolia",
321
+ chainId: 84532,
322
+ rpc: "https://sepolia.base.org",
323
+ tokens: {
324
+ USDC: {
325
+ address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
326
+ decimals: 6,
327
+ symbol: "USDC",
328
+ eip712Name: "USDC"
329
+ // Testnet USDC uses 'USDC' not 'USD Coin'
330
+ },
331
+ USDT: {
332
+ address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
333
+ // Same as USDC on testnet (no official USDT)
334
+ decimals: 6,
335
+ symbol: "USDT",
336
+ eip712Name: "USDC"
337
+ // Uses same contract as USDC
338
+ }
339
+ },
340
+ usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
341
+ explorer: "https://sepolia.basescan.org/address/",
342
+ explorerTx: "https://sepolia.basescan.org/tx/",
343
+ avgBlockTime: 2
344
+ },
345
+ // ============ Tempo Testnet (Moderato) ============
346
+ tempo_moderato: {
347
+ name: "Tempo Moderato",
348
+ chainId: 42431,
349
+ rpc: "https://rpc.moderato.tempo.xyz",
350
+ tokens: {
351
+ // TIP-20 stablecoins on Tempo testnet (from mppx SDK)
352
+ // Note: Tempo uses USD as native gas token, not ETH
353
+ USDC: {
354
+ address: "0x20c0000000000000000000000000000000000000",
355
+ // pathUSD - primary testnet stablecoin
356
+ decimals: 6,
357
+ symbol: "USDC",
358
+ eip712Name: "pathUSD"
359
+ },
360
+ USDT: {
361
+ address: "0x20c0000000000000000000000000000000000001",
362
+ // alphaUSD
363
+ decimals: 6,
364
+ symbol: "USDT",
365
+ eip712Name: "alphaUSD"
366
+ }
367
+ },
368
+ usdc: "0x20c0000000000000000000000000000000000000",
369
+ explorer: "https://explore.testnet.tempo.xyz/address/",
370
+ explorerTx: "https://explore.testnet.tempo.xyz/tx/",
371
+ avgBlockTime: 0.5
372
+ // ~500ms finality
373
+ }
374
+ };
375
+
376
+ // src/facilitators/tempo.ts
377
+ var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
378
+ var TempoFacilitator = class extends BaseFacilitator {
379
+ name = "tempo";
380
+ displayName = "Tempo Testnet";
381
+ supportedNetworks = ["eip155:42431"];
382
+ // Tempo Moderato
383
+ rpcUrl;
384
+ constructor() {
385
+ super();
386
+ this.rpcUrl = CHAINS.tempo_moderato.rpc;
387
+ }
388
+ async healthCheck() {
389
+ const start = Date.now();
390
+ try {
391
+ const response = await fetch(this.rpcUrl, {
392
+ method: "POST",
393
+ headers: { "Content-Type": "application/json" },
394
+ body: JSON.stringify({
395
+ jsonrpc: "2.0",
396
+ method: "eth_chainId",
397
+ params: [],
398
+ id: 1
399
+ })
400
+ });
401
+ const data = await response.json();
402
+ const chainId = parseInt(data.result, 16);
403
+ if (chainId !== 42431) {
404
+ return { healthy: false, error: `Wrong chainId: ${chainId}` };
405
+ }
406
+ return { healthy: true, latencyMs: Date.now() - start };
407
+ } catch (error) {
408
+ return { healthy: false, error: String(error) };
409
+ }
410
+ }
411
+ async verify(paymentPayload, requirements) {
412
+ try {
413
+ const tempoPayload = paymentPayload.payload;
414
+ if (!tempoPayload?.txHash) {
415
+ return { valid: false, error: "Missing txHash in payment payload" };
416
+ }
417
+ const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
418
+ if (!receipt) {
419
+ return { valid: false, error: "Transaction not found" };
420
+ }
421
+ if (receipt.status !== "0x1") {
422
+ return { valid: false, error: "Transaction failed" };
423
+ }
424
+ const transferLog = receipt.logs.find(
425
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC
426
+ );
427
+ if (!transferLog) {
428
+ return { valid: false, error: "No Transfer event found" };
429
+ }
430
+ const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
431
+ const expectedTo = requirements.payTo.toLowerCase();
432
+ if (toAddress !== expectedTo) {
433
+ return {
434
+ valid: false,
435
+ error: `Wrong recipient: ${toAddress}, expected ${expectedTo}`
436
+ };
437
+ }
438
+ const amount = BigInt(transferLog.data);
439
+ const expectedAmount = BigInt(requirements.amount);
440
+ if (amount < expectedAmount) {
441
+ return {
442
+ valid: false,
443
+ error: `Insufficient amount: ${amount}, expected ${expectedAmount}`
444
+ };
445
+ }
446
+ const tokenAddress = transferLog.address.toLowerCase();
447
+ const expectedToken = requirements.asset.toLowerCase();
448
+ if (tokenAddress !== expectedToken) {
449
+ return {
450
+ valid: false,
451
+ error: `Wrong token: ${tokenAddress}, expected ${expectedToken}`
452
+ };
453
+ }
454
+ return {
455
+ valid: true,
456
+ details: {
457
+ txHash: tempoPayload.txHash,
458
+ from: "0x" + transferLog.topics[1].slice(26),
459
+ to: toAddress,
460
+ amount: amount.toString(),
461
+ token: tokenAddress
462
+ }
463
+ };
464
+ } catch (error) {
465
+ return { valid: false, error: `Verification failed: ${error}` };
466
+ }
467
+ }
468
+ async settle(paymentPayload, requirements) {
469
+ const verifyResult = await this.verify(paymentPayload, requirements);
470
+ if (!verifyResult.valid) {
471
+ return { success: false, error: verifyResult.error };
472
+ }
473
+ const tempoPayload = paymentPayload.payload;
474
+ return {
475
+ success: true,
476
+ transaction: tempoPayload.txHash,
477
+ status: "settled"
478
+ };
479
+ }
480
+ async getTransactionReceipt(txHash) {
481
+ const response = await fetch(this.rpcUrl, {
482
+ method: "POST",
483
+ headers: { "Content-Type": "application/json" },
484
+ body: JSON.stringify({
485
+ jsonrpc: "2.0",
486
+ method: "eth_getTransactionReceipt",
487
+ params: [txHash],
488
+ id: 1
489
+ })
490
+ });
491
+ const data = await response.json();
492
+ return data.result;
257
493
  }
258
494
  };
259
495
 
@@ -265,7 +501,8 @@ var FacilitatorRegistry = class {
265
501
  roundRobinIndex = 0;
266
502
  constructor(selection) {
267
503
  this.registerFactory("cdp", (config) => new CDPFacilitator(config));
268
- this.selection = selection || { primary: "cdp", strategy: "failover" };
504
+ this.registerFactory("tempo", () => new TempoFacilitator());
505
+ this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
269
506
  }
270
507
  /**
271
508
  * Register a new facilitator factory
@@ -486,6 +723,9 @@ var X402_VERSION2 = 2;
486
723
  var PAYMENT_REQUIRED_HEADER = "x-payment-required";
487
724
  var PAYMENT_HEADER = "x-payment";
488
725
  var PAYMENT_RESPONSE_HEADER = "x-payment-response";
726
+ var MPP_AUTH_HEADER = "authorization";
727
+ var MPP_WWW_AUTH_HEADER = "www-authenticate";
728
+ var MPP_RECEIPT_HEADER = "payment-receipt";
489
729
  var TOKEN_ADDRESSES = {
490
730
  "eip155:8453": {
491
731
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -499,17 +739,48 @@ var TOKEN_ADDRESSES = {
499
739
  "eip155:137": {
500
740
  USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
501
741
  USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
742
+ },
743
+ "eip155:42431": {
744
+ // Tempo Moderato testnet - TIP-20 stablecoins
745
+ USDC: "0x20c0000000000000000000000000000000000000",
746
+ // pathUSD
747
+ USDT: "0x20c0000000000000000000000000000000000001"
748
+ // alphaUSD
502
749
  }
503
750
  };
504
751
  var CHAIN_TO_NETWORK = {
505
752
  "base": "eip155:8453",
506
753
  "base_sepolia": "eip155:84532",
507
- "polygon": "eip155:137"
754
+ "polygon": "eip155:137",
755
+ "tempo_moderato": "eip155:42431"
508
756
  };
509
757
  var TOKEN_DOMAINS = {
510
- USDC: { name: "USD Coin", version: "2" },
511
- USDT: { name: "Tether USD", version: "2" }
758
+ // Base mainnet
759
+ "eip155:8453": {
760
+ USDC: { name: "USD Coin", version: "2" },
761
+ USDT: { name: "Tether USD", version: "2" }
762
+ },
763
+ // Base Sepolia testnet - USDC uses 'USDC' not 'USD Coin'
764
+ "eip155:84532": {
765
+ USDC: { name: "USDC", version: "2" },
766
+ USDT: { name: "USDC", version: "2" }
767
+ // Same contract as USDC on testnet
768
+ },
769
+ // Polygon mainnet
770
+ "eip155:137": {
771
+ USDC: { name: "USD Coin", version: "2" },
772
+ USDT: { name: "(PoS) Tether USD", version: "2" }
773
+ },
774
+ // Tempo Moderato testnet - TIP-20 stablecoins
775
+ "eip155:42431": {
776
+ USDC: { name: "pathUSD", version: "1" },
777
+ USDT: { name: "alphaUSD", version: "1" }
778
+ }
512
779
  };
780
+ function getTokenDomain(network, token) {
781
+ const networkDomains = TOKEN_DOMAINS[network] || TOKEN_DOMAINS["eip155:8453"];
782
+ return networkDomains[token] || { name: "USD Coin", version: "2" };
783
+ }
513
784
  function getAcceptedCurrencies(config) {
514
785
  return config.acceptedCurrencies ?? [config.currency];
515
786
  }
@@ -561,9 +832,11 @@ var MoltsPayServer = class {
561
832
  };
562
833
  this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
563
834
  this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
835
+ const defaultFallback = ["tempo"];
836
+ const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
564
837
  const facilitatorConfig = options.facilitators || {
565
838
  primary: process.env.FACILITATOR_PRIMARY || "cdp",
566
- fallback: process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean),
839
+ fallback: envFallback || defaultFallback,
567
840
  strategy: process.env.FACILITATOR_STRATEGY || "failover",
568
841
  config: {
569
842
  cdp: { useMainnet: this.useMainnet }
@@ -603,11 +876,14 @@ var MoltsPayServer = class {
603
876
  getProviderChains() {
604
877
  const provider = this.manifest.provider;
605
878
  if (provider.chains && provider.chains.length > 0) {
606
- return provider.chains.map((c) => ({
607
- network: c.network || CHAIN_TO_NETWORK[c.chain] || "eip155:8453",
608
- wallet: c.wallet || provider.wallet,
609
- tokens: c.tokens || ["USDC"]
610
- }));
879
+ return provider.chains.map((c) => {
880
+ const chainName = typeof c === "string" ? c : c.chain;
881
+ return {
882
+ network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
883
+ wallet: (typeof c === "object" ? c.wallet : null) || provider.wallet,
884
+ tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
885
+ };
886
+ });
611
887
  }
612
888
  const chain = provider.chain || "base";
613
889
  const network = CHAIN_TO_NETWORK[chain] || this.networkId;
@@ -654,8 +930,8 @@ var MoltsPayServer = class {
654
930
  async handleRequest(req, res) {
655
931
  res.setHeader("Access-Control-Allow-Origin", "*");
656
932
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
657
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
658
- res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
933
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
934
+ res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
659
935
  if (req.method === "OPTIONS") {
660
936
  res.writeHead(204);
661
937
  res.end();
@@ -686,6 +962,14 @@ var MoltsPayServer = class {
686
962
  const paymentHeader = req.headers[PAYMENT_HEADER];
687
963
  return await this.handleProxy(body, paymentHeader, res);
688
964
  }
965
+ const servicePath = url.pathname.replace(/^\//, "");
966
+ const skill = this.skills.get(servicePath);
967
+ if (skill && (req.method === "POST" || req.method === "GET")) {
968
+ const body = req.method === "POST" ? await this.readBody(req) : {};
969
+ const authHeader = req.headers[MPP_AUTH_HEADER];
970
+ const x402Header = req.headers[PAYMENT_HEADER];
971
+ return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
972
+ }
689
973
  this.sendJson(res, 404, { error: "Not found" });
690
974
  } catch (err) {
691
975
  console.error("[MoltsPay] Error:", err);
@@ -817,7 +1101,7 @@ var MoltsPayServer = class {
817
1101
  const paymentNetwork = payment.accepted?.network || payment.network || this.networkId;
818
1102
  const paymentWallet = this.getWalletForNetwork(paymentNetwork);
819
1103
  const requirements = this.buildPaymentRequirements(skill.config, paymentNetwork, paymentWallet, paymentToken);
820
- console.log(`[MoltsPay] Verifying payment...`);
1104
+ console.log(`[MoltsPay] Verifying payment on ${paymentNetwork}...`);
821
1105
  const verifyResult = await this.registry.verify(payment, requirements);
822
1106
  if (!verifyResult.valid) {
823
1107
  return this.sendJson(res, 402, {
@@ -869,6 +1153,187 @@ var MoltsPayServer = class {
869
1153
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
870
1154
  }, responseHeaders);
871
1155
  }
1156
+ /**
1157
+ * Handle MPP (Machine Payments Protocol) request
1158
+ * Supports both x402 and MPP protocols on service endpoints
1159
+ */
1160
+ async handleMPPRequest(skill, body, authHeader, x402Header, res) {
1161
+ const config = skill.config;
1162
+ const params = body || {};
1163
+ if (x402Header) {
1164
+ return await this.handleExecute({ service: config.id, params }, x402Header, res);
1165
+ }
1166
+ if (authHeader && authHeader.toLowerCase().startsWith("payment ")) {
1167
+ return await this.handleMPPPayment(skill, params, authHeader, res);
1168
+ }
1169
+ return this.sendMPPPaymentRequired(config, res);
1170
+ }
1171
+ /**
1172
+ * Handle MPP payment verification and service execution
1173
+ */
1174
+ async handleMPPPayment(skill, params, authHeader, res) {
1175
+ const config = skill.config;
1176
+ const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
1177
+ if (!credentialMatch) {
1178
+ return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
1179
+ }
1180
+ let mppCredential;
1181
+ try {
1182
+ const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
1183
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
1184
+ mppCredential = JSON.parse(decoded);
1185
+ } catch (err) {
1186
+ console.error("[MoltsPay] Failed to parse MPP credential:", err);
1187
+ return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
1188
+ }
1189
+ let txHash;
1190
+ if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
1191
+ txHash = mppCredential.payload.hash;
1192
+ } else if (mppCredential.payload?.type === "transaction") {
1193
+ return this.sendJson(res, 400, {
1194
+ error: "Transaction type not supported. Please use push mode (hash type)."
1195
+ });
1196
+ }
1197
+ if (!txHash) {
1198
+ return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
1199
+ }
1200
+ let chainId = mppCredential.challenge?.request?.methodDetails?.chainId;
1201
+ if (!chainId && mppCredential.source) {
1202
+ const chainMatch = mppCredential.source.match(/eip155:(\d+)/);
1203
+ if (chainMatch) chainId = parseInt(chainMatch[1], 10);
1204
+ }
1205
+ chainId = chainId || 42431;
1206
+ const network = `eip155:${chainId}`;
1207
+ if (!this.isNetworkAccepted(network)) {
1208
+ return this.sendJson(res, 402, {
1209
+ error: `Network not accepted: ${network}`
1210
+ });
1211
+ }
1212
+ const requirements = this.buildPaymentRequirements(
1213
+ config,
1214
+ network,
1215
+ this.getWalletForNetwork(network),
1216
+ "USDC"
1217
+ );
1218
+ const paymentPayload = {
1219
+ x402Version: X402_VERSION2,
1220
+ scheme: "exact",
1221
+ network,
1222
+ payload: {
1223
+ txHash,
1224
+ chainId
1225
+ }
1226
+ };
1227
+ console.log(`[MoltsPay] Verifying MPP payment: txHash=${txHash}, chainId=${chainId}`);
1228
+ const verification = await this.registry.verify(paymentPayload, requirements);
1229
+ if (!verification.valid) {
1230
+ return this.sendJson(res, 402, {
1231
+ error: `Payment verification failed: ${verification.error}`
1232
+ });
1233
+ }
1234
+ console.log(`[MoltsPay] Payment verified! Executing service: ${config.id}`);
1235
+ let result;
1236
+ try {
1237
+ result = await skill.handler(params);
1238
+ } catch (err) {
1239
+ console.error(`[MoltsPay] Skill execution error:`, err);
1240
+ return this.sendJson(res, 500, {
1241
+ error: `Service execution failed: ${err.message}`
1242
+ });
1243
+ }
1244
+ const receipt = {
1245
+ success: true,
1246
+ txHash,
1247
+ network,
1248
+ facilitator: verification.facilitator
1249
+ };
1250
+ const receiptEncoded = Buffer.from(JSON.stringify(receipt)).toString("base64");
1251
+ res.writeHead(200, {
1252
+ "Content-Type": "application/json",
1253
+ [MPP_RECEIPT_HEADER]: receiptEncoded
1254
+ });
1255
+ res.end(JSON.stringify({
1256
+ success: true,
1257
+ result,
1258
+ payment: {
1259
+ txHash,
1260
+ status: "verified",
1261
+ facilitator: verification.facilitator
1262
+ }
1263
+ }, null, 2));
1264
+ }
1265
+ /**
1266
+ * Return 402 with both x402 and MPP payment requirements
1267
+ */
1268
+ sendMPPPaymentRequired(config, res) {
1269
+ const acceptedTokens = getAcceptedCurrencies(config);
1270
+ const providerChains = this.getProviderChains();
1271
+ const accepts = [];
1272
+ for (const chainConfig of providerChains) {
1273
+ for (const token of acceptedTokens) {
1274
+ if (chainConfig.tokens.includes(token)) {
1275
+ accepts.push(this.buildPaymentRequirements(config, chainConfig.network, chainConfig.wallet, token));
1276
+ }
1277
+ }
1278
+ }
1279
+ const x402PaymentRequired = {
1280
+ x402Version: X402_VERSION2,
1281
+ accepts,
1282
+ acceptedCurrencies: acceptedTokens,
1283
+ resource: {
1284
+ url: `/${config.id}`,
1285
+ description: `${config.name} - $${config.price} ${config.currency}`
1286
+ }
1287
+ };
1288
+ const x402Encoded = Buffer.from(JSON.stringify(x402PaymentRequired)).toString("base64");
1289
+ const tempoChain = providerChains.find((c) => c.network === "eip155:42431");
1290
+ let mppWwwAuth = "";
1291
+ if (tempoChain) {
1292
+ const challengeId = this.generateChallengeId();
1293
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
1294
+ const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
1295
+ const mppRequest = {
1296
+ amount: amountInUnits,
1297
+ currency: tokenAddress,
1298
+ methodDetails: {
1299
+ chainId: 42431,
1300
+ feePayer: true
1301
+ },
1302
+ recipient: tempoChain.wallet
1303
+ };
1304
+ const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
1305
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
1306
+ mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
1307
+ }
1308
+ const headers = {
1309
+ "Content-Type": "application/problem+json",
1310
+ [PAYMENT_REQUIRED_HEADER]: x402Encoded
1311
+ };
1312
+ if (mppWwwAuth) {
1313
+ headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
1314
+ }
1315
+ res.writeHead(402, headers);
1316
+ res.end(JSON.stringify({
1317
+ type: "https://paymentauth.org/problems/payment-required",
1318
+ title: "Payment Required",
1319
+ status: 402,
1320
+ detail: `Payment is required (${config.name}).`,
1321
+ service: config.id,
1322
+ price: config.price,
1323
+ currency: config.currency,
1324
+ acceptedCurrencies: acceptedTokens
1325
+ }, null, 2));
1326
+ }
1327
+ /**
1328
+ * Generate a unique challenge ID for MPP
1329
+ */
1330
+ generateChallengeId() {
1331
+ const bytes = new Uint8Array(24);
1332
+ for (let i = 0; i < bytes.length; i++) {
1333
+ bytes[i] = Math.floor(Math.random() * 256);
1334
+ }
1335
+ return Buffer.from(bytes).toString("base64url");
1336
+ }
872
1337
  /**
873
1338
  * Return 402 with x402 payment requirements (v2 format)
874
1339
  * Includes requirements for all chains and all accepted currencies
@@ -943,7 +1408,7 @@ var MoltsPayServer = class {
943
1408
  const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
944
1409
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
945
1410
  const tokenAddress = tokenAddresses[selectedToken];
946
- const tokenDomain = TOKEN_DOMAINS[selectedToken] || TOKEN_DOMAINS.USDC;
1411
+ const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
947
1412
  return {
948
1413
  scheme: "exact",
949
1414
  network: selectedNetwork,
@@ -1188,7 +1653,7 @@ var MoltsPayServer = class {
1188
1653
  const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
1189
1654
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
1190
1655
  const tokenAddress = tokenAddresses[selectedToken];
1191
- const tokenDomain = TOKEN_DOMAINS[selectedToken] || TOKEN_DOMAINS.USDC;
1656
+ const tokenDomain = getTokenDomain(networkId, selectedToken);
1192
1657
  return {
1193
1658
  scheme: "exact",
1194
1659
  network: networkId,