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
@@ -14,8 +14,8 @@ var BaseFacilitator = class {
14
14
  import { readFileSync, existsSync } from "fs";
15
15
  import * as path from "path";
16
16
  var X402_VERSION = 2;
17
- var CDP_MAINNET_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
18
- var CDP_TESTNET_URL = "https://www.x402.org/facilitator";
17
+ var CDP_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
18
+ var TESTNET_CHAIN_IDS = [84532];
19
19
  function loadEnvFile() {
20
20
  const envPaths = [
21
21
  path.join(process.cwd(), ".env"),
@@ -50,31 +50,33 @@ var CDPFacilitator = class extends BaseFacilitator {
50
50
  displayName = "Coinbase CDP";
51
51
  supportedNetworks;
52
52
  endpoint;
53
- useMainnet;
54
53
  apiKeyId;
55
54
  apiKeySecret;
56
55
  constructor(config = {}) {
57
56
  super();
58
57
  loadEnvFile();
59
- this.useMainnet = config.useMainnet ?? process.env.USE_MAINNET?.toLowerCase() === "true";
60
58
  this.apiKeyId = config.apiKeyId || process.env.CDP_API_KEY_ID;
61
59
  this.apiKeySecret = config.apiKeySecret || process.env.CDP_API_KEY_SECRET;
62
- this.endpoint = this.useMainnet ? CDP_MAINNET_URL : CDP_TESTNET_URL;
63
- this.supportedNetworks = this.useMainnet ? ["eip155:8453", "eip155:137"] : ["eip155:8453", "eip155:84532", "eip155:137"];
64
- if (this.useMainnet && (!this.apiKeyId || !this.apiKeySecret)) {
65
- console.warn("[CDPFacilitator] WARNING: Mainnet mode but missing CDP credentials!");
66
- console.warn("[CDPFacilitator] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET");
60
+ this.endpoint = CDP_URL;
61
+ this.supportedNetworks = [
62
+ "eip155:8453",
63
+ // Base mainnet
64
+ "eip155:137",
65
+ // Polygon mainnet
66
+ "eip155:84532"
67
+ // Base Sepolia (testnet)
68
+ ];
69
+ if (!this.apiKeyId || !this.apiKeySecret) {
70
+ console.warn("[CDPFacilitator] WARNING: Missing CDP credentials!");
71
+ console.warn("[CDPFacilitator] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in ~/.moltspay/.env");
67
72
  }
68
73
  }
69
74
  /**
70
75
  * Get auth headers for CDP API requests
71
76
  */
72
77
  async getAuthHeaders(method, urlPath, body) {
73
- if (!this.useMainnet) {
74
- return {};
75
- }
76
78
  if (!this.apiKeyId || !this.apiKeySecret) {
77
- throw new Error("CDP credentials required for mainnet");
79
+ throw new Error("CDP credentials required. Set CDP_API_KEY_ID and CDP_API_KEY_SECRET");
78
80
  }
79
81
  try {
80
82
  const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
@@ -126,23 +128,23 @@ var CDPFacilitator = class extends BaseFacilitator {
126
128
  paymentPayload,
127
129
  paymentRequirements: requirements
128
130
  };
131
+ console.log("[CDP Verify] Payload:", JSON.stringify(paymentPayload, null, 2));
132
+ const authHeaders = await this.getAuthHeaders(
133
+ "POST",
134
+ "/platform/v2/x402/verify",
135
+ requestBody
136
+ );
129
137
  const headers = {
130
- "Content-Type": "application/json"
138
+ "Content-Type": "application/json",
139
+ ...authHeaders
131
140
  };
132
- if (this.useMainnet) {
133
- const authHeaders = await this.getAuthHeaders(
134
- "POST",
135
- "/platform/v2/x402/verify",
136
- requestBody
137
- );
138
- Object.assign(headers, authHeaders);
139
- }
140
141
  const response = await fetch(`${this.endpoint}/verify`, {
141
142
  method: "POST",
142
143
  headers,
143
144
  body: JSON.stringify(requestBody)
144
145
  });
145
146
  const result = await response.json();
147
+ console.log("[CDP Verify] Response:", response.status, JSON.stringify(result));
146
148
  if (!response.ok || !result.isValid) {
147
149
  return {
148
150
  valid: false,
@@ -168,17 +170,15 @@ var CDPFacilitator = class extends BaseFacilitator {
168
170
  paymentPayload,
169
171
  paymentRequirements: requirements
170
172
  };
173
+ const authHeaders = await this.getAuthHeaders(
174
+ "POST",
175
+ "/platform/v2/x402/settle",
176
+ requestBody
177
+ );
171
178
  const headers = {
172
- "Content-Type": "application/json"
179
+ "Content-Type": "application/json",
180
+ ...authHeaders
173
181
  };
174
- if (this.useMainnet) {
175
- const authHeaders = await this.getAuthHeaders(
176
- "POST",
177
- "/platform/v2/x402/settle",
178
- requestBody
179
- );
180
- Object.assign(headers, authHeaders);
181
- }
182
182
  const response = await fetch(`${this.endpoint}/settle`, {
183
183
  method: "POST",
184
184
  headers,
@@ -213,13 +213,249 @@ var CDPFacilitator = class extends BaseFacilitator {
213
213
  freeQuota: 1e3
214
214
  };
215
215
  }
216
+ /**
217
+ * Check if a chain ID is testnet
218
+ */
219
+ static isTestnet(chainId) {
220
+ return TESTNET_CHAIN_IDS.includes(chainId);
221
+ }
216
222
  /**
217
223
  * Get configuration summary (for logging)
218
224
  */
219
225
  getConfigSummary() {
220
- const mode = this.useMainnet ? "mainnet" : "testnet";
221
226
  const hasCredentials = !!(this.apiKeyId && this.apiKeySecret);
222
- return `CDP Facilitator (${mode}, credentials: ${hasCredentials ? "yes" : "no"})`;
227
+ const networks = this.supportedNetworks.join(", ");
228
+ return `CDP Facilitator (networks: ${networks}, credentials: ${hasCredentials ? "yes" : "no"})`;
229
+ }
230
+ };
231
+
232
+ // src/chains/index.ts
233
+ var CHAINS = {
234
+ // ============ Mainnet ============
235
+ base: {
236
+ name: "Base",
237
+ chainId: 8453,
238
+ rpc: "https://mainnet.base.org",
239
+ tokens: {
240
+ USDC: {
241
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
242
+ decimals: 6,
243
+ symbol: "USDC",
244
+ eip712Name: "USD Coin"
245
+ // EIP-712 domain name
246
+ },
247
+ USDT: {
248
+ address: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
249
+ decimals: 6,
250
+ symbol: "USDT",
251
+ eip712Name: "Tether USD"
252
+ }
253
+ },
254
+ usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
255
+ // deprecated, for backward compat
256
+ explorer: "https://basescan.org/address/",
257
+ explorerTx: "https://basescan.org/tx/",
258
+ avgBlockTime: 2
259
+ },
260
+ polygon: {
261
+ name: "Polygon",
262
+ chainId: 137,
263
+ rpc: "https://polygon-bor-rpc.publicnode.com",
264
+ tokens: {
265
+ USDC: {
266
+ address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
267
+ decimals: 6,
268
+ symbol: "USDC",
269
+ eip712Name: "USD Coin"
270
+ },
271
+ USDT: {
272
+ address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
273
+ decimals: 6,
274
+ symbol: "USDT",
275
+ eip712Name: "(PoS) Tether USD"
276
+ // Polygon uses this name
277
+ }
278
+ },
279
+ usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
280
+ explorer: "https://polygonscan.com/address/",
281
+ explorerTx: "https://polygonscan.com/tx/",
282
+ avgBlockTime: 2
283
+ },
284
+ // ============ Testnet ============
285
+ base_sepolia: {
286
+ name: "Base Sepolia",
287
+ chainId: 84532,
288
+ rpc: "https://sepolia.base.org",
289
+ tokens: {
290
+ USDC: {
291
+ address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
292
+ decimals: 6,
293
+ symbol: "USDC",
294
+ eip712Name: "USDC"
295
+ // Testnet USDC uses 'USDC' not 'USD Coin'
296
+ },
297
+ USDT: {
298
+ address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
299
+ // Same as USDC on testnet (no official USDT)
300
+ decimals: 6,
301
+ symbol: "USDT",
302
+ eip712Name: "USDC"
303
+ // Uses same contract as USDC
304
+ }
305
+ },
306
+ usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
307
+ explorer: "https://sepolia.basescan.org/address/",
308
+ explorerTx: "https://sepolia.basescan.org/tx/",
309
+ avgBlockTime: 2
310
+ },
311
+ // ============ Tempo Testnet (Moderato) ============
312
+ tempo_moderato: {
313
+ name: "Tempo Moderato",
314
+ chainId: 42431,
315
+ rpc: "https://rpc.moderato.tempo.xyz",
316
+ tokens: {
317
+ // TIP-20 stablecoins on Tempo testnet (from mppx SDK)
318
+ // Note: Tempo uses USD as native gas token, not ETH
319
+ USDC: {
320
+ address: "0x20c0000000000000000000000000000000000000",
321
+ // pathUSD - primary testnet stablecoin
322
+ decimals: 6,
323
+ symbol: "USDC",
324
+ eip712Name: "pathUSD"
325
+ },
326
+ USDT: {
327
+ address: "0x20c0000000000000000000000000000000000001",
328
+ // alphaUSD
329
+ decimals: 6,
330
+ symbol: "USDT",
331
+ eip712Name: "alphaUSD"
332
+ }
333
+ },
334
+ usdc: "0x20c0000000000000000000000000000000000000",
335
+ explorer: "https://explore.testnet.tempo.xyz/address/",
336
+ explorerTx: "https://explore.testnet.tempo.xyz/tx/",
337
+ avgBlockTime: 0.5
338
+ // ~500ms finality
339
+ }
340
+ };
341
+
342
+ // src/facilitators/tempo.ts
343
+ var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
344
+ var TempoFacilitator = class extends BaseFacilitator {
345
+ name = "tempo";
346
+ displayName = "Tempo Testnet";
347
+ supportedNetworks = ["eip155:42431"];
348
+ // Tempo Moderato
349
+ rpcUrl;
350
+ constructor() {
351
+ super();
352
+ this.rpcUrl = CHAINS.tempo_moderato.rpc;
353
+ }
354
+ async healthCheck() {
355
+ const start = Date.now();
356
+ try {
357
+ const response = await fetch(this.rpcUrl, {
358
+ method: "POST",
359
+ headers: { "Content-Type": "application/json" },
360
+ body: JSON.stringify({
361
+ jsonrpc: "2.0",
362
+ method: "eth_chainId",
363
+ params: [],
364
+ id: 1
365
+ })
366
+ });
367
+ const data = await response.json();
368
+ const chainId = parseInt(data.result, 16);
369
+ if (chainId !== 42431) {
370
+ return { healthy: false, error: `Wrong chainId: ${chainId}` };
371
+ }
372
+ return { healthy: true, latencyMs: Date.now() - start };
373
+ } catch (error) {
374
+ return { healthy: false, error: String(error) };
375
+ }
376
+ }
377
+ async verify(paymentPayload, requirements) {
378
+ try {
379
+ const tempoPayload = paymentPayload.payload;
380
+ if (!tempoPayload?.txHash) {
381
+ return { valid: false, error: "Missing txHash in payment payload" };
382
+ }
383
+ const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
384
+ if (!receipt) {
385
+ return { valid: false, error: "Transaction not found" };
386
+ }
387
+ if (receipt.status !== "0x1") {
388
+ return { valid: false, error: "Transaction failed" };
389
+ }
390
+ const transferLog = receipt.logs.find(
391
+ (log) => log.topics[0] === TRANSFER_EVENT_TOPIC
392
+ );
393
+ if (!transferLog) {
394
+ return { valid: false, error: "No Transfer event found" };
395
+ }
396
+ const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
397
+ const expectedTo = requirements.payTo.toLowerCase();
398
+ if (toAddress !== expectedTo) {
399
+ return {
400
+ valid: false,
401
+ error: `Wrong recipient: ${toAddress}, expected ${expectedTo}`
402
+ };
403
+ }
404
+ const amount = BigInt(transferLog.data);
405
+ const expectedAmount = BigInt(requirements.amount);
406
+ if (amount < expectedAmount) {
407
+ return {
408
+ valid: false,
409
+ error: `Insufficient amount: ${amount}, expected ${expectedAmount}`
410
+ };
411
+ }
412
+ const tokenAddress = transferLog.address.toLowerCase();
413
+ const expectedToken = requirements.asset.toLowerCase();
414
+ if (tokenAddress !== expectedToken) {
415
+ return {
416
+ valid: false,
417
+ error: `Wrong token: ${tokenAddress}, expected ${expectedToken}`
418
+ };
419
+ }
420
+ return {
421
+ valid: true,
422
+ details: {
423
+ txHash: tempoPayload.txHash,
424
+ from: "0x" + transferLog.topics[1].slice(26),
425
+ to: toAddress,
426
+ amount: amount.toString(),
427
+ token: tokenAddress
428
+ }
429
+ };
430
+ } catch (error) {
431
+ return { valid: false, error: `Verification failed: ${error}` };
432
+ }
433
+ }
434
+ async settle(paymentPayload, requirements) {
435
+ const verifyResult = await this.verify(paymentPayload, requirements);
436
+ if (!verifyResult.valid) {
437
+ return { success: false, error: verifyResult.error };
438
+ }
439
+ const tempoPayload = paymentPayload.payload;
440
+ return {
441
+ success: true,
442
+ transaction: tempoPayload.txHash,
443
+ status: "settled"
444
+ };
445
+ }
446
+ async getTransactionReceipt(txHash) {
447
+ const response = await fetch(this.rpcUrl, {
448
+ method: "POST",
449
+ headers: { "Content-Type": "application/json" },
450
+ body: JSON.stringify({
451
+ jsonrpc: "2.0",
452
+ method: "eth_getTransactionReceipt",
453
+ params: [txHash],
454
+ id: 1
455
+ })
456
+ });
457
+ const data = await response.json();
458
+ return data.result;
223
459
  }
224
460
  };
225
461
 
@@ -231,7 +467,8 @@ var FacilitatorRegistry = class {
231
467
  roundRobinIndex = 0;
232
468
  constructor(selection) {
233
469
  this.registerFactory("cdp", (config) => new CDPFacilitator(config));
234
- this.selection = selection || { primary: "cdp", strategy: "failover" };
470
+ this.registerFactory("tempo", () => new TempoFacilitator());
471
+ this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
235
472
  }
236
473
  /**
237
474
  * Register a new facilitator factory
@@ -452,6 +689,9 @@ var X402_VERSION2 = 2;
452
689
  var PAYMENT_REQUIRED_HEADER = "x-payment-required";
453
690
  var PAYMENT_HEADER = "x-payment";
454
691
  var PAYMENT_RESPONSE_HEADER = "x-payment-response";
692
+ var MPP_AUTH_HEADER = "authorization";
693
+ var MPP_WWW_AUTH_HEADER = "www-authenticate";
694
+ var MPP_RECEIPT_HEADER = "payment-receipt";
455
695
  var TOKEN_ADDRESSES = {
456
696
  "eip155:8453": {
457
697
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -465,17 +705,48 @@ var TOKEN_ADDRESSES = {
465
705
  "eip155:137": {
466
706
  USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
467
707
  USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
708
+ },
709
+ "eip155:42431": {
710
+ // Tempo Moderato testnet - TIP-20 stablecoins
711
+ USDC: "0x20c0000000000000000000000000000000000000",
712
+ // pathUSD
713
+ USDT: "0x20c0000000000000000000000000000000000001"
714
+ // alphaUSD
468
715
  }
469
716
  };
470
717
  var CHAIN_TO_NETWORK = {
471
718
  "base": "eip155:8453",
472
719
  "base_sepolia": "eip155:84532",
473
- "polygon": "eip155:137"
720
+ "polygon": "eip155:137",
721
+ "tempo_moderato": "eip155:42431"
474
722
  };
475
723
  var TOKEN_DOMAINS = {
476
- USDC: { name: "USD Coin", version: "2" },
477
- USDT: { name: "Tether USD", version: "2" }
724
+ // Base mainnet
725
+ "eip155:8453": {
726
+ USDC: { name: "USD Coin", version: "2" },
727
+ USDT: { name: "Tether USD", version: "2" }
728
+ },
729
+ // Base Sepolia testnet - USDC uses 'USDC' not 'USD Coin'
730
+ "eip155:84532": {
731
+ USDC: { name: "USDC", version: "2" },
732
+ USDT: { name: "USDC", version: "2" }
733
+ // Same contract as USDC on testnet
734
+ },
735
+ // Polygon mainnet
736
+ "eip155:137": {
737
+ USDC: { name: "USD Coin", version: "2" },
738
+ USDT: { name: "(PoS) Tether USD", version: "2" }
739
+ },
740
+ // Tempo Moderato testnet - TIP-20 stablecoins
741
+ "eip155:42431": {
742
+ USDC: { name: "pathUSD", version: "1" },
743
+ USDT: { name: "alphaUSD", version: "1" }
744
+ }
478
745
  };
746
+ function getTokenDomain(network, token) {
747
+ const networkDomains = TOKEN_DOMAINS[network] || TOKEN_DOMAINS["eip155:8453"];
748
+ return networkDomains[token] || { name: "USD Coin", version: "2" };
749
+ }
479
750
  function getAcceptedCurrencies(config) {
480
751
  return config.acceptedCurrencies ?? [config.currency];
481
752
  }
@@ -527,9 +798,11 @@ var MoltsPayServer = class {
527
798
  };
528
799
  this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
529
800
  this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
801
+ const defaultFallback = ["tempo"];
802
+ const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
530
803
  const facilitatorConfig = options.facilitators || {
531
804
  primary: process.env.FACILITATOR_PRIMARY || "cdp",
532
- fallback: process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean),
805
+ fallback: envFallback || defaultFallback,
533
806
  strategy: process.env.FACILITATOR_STRATEGY || "failover",
534
807
  config: {
535
808
  cdp: { useMainnet: this.useMainnet }
@@ -569,11 +842,14 @@ var MoltsPayServer = class {
569
842
  getProviderChains() {
570
843
  const provider = this.manifest.provider;
571
844
  if (provider.chains && provider.chains.length > 0) {
572
- return provider.chains.map((c) => ({
573
- network: c.network || CHAIN_TO_NETWORK[c.chain] || "eip155:8453",
574
- wallet: c.wallet || provider.wallet,
575
- tokens: c.tokens || ["USDC"]
576
- }));
845
+ return provider.chains.map((c) => {
846
+ const chainName = typeof c === "string" ? c : c.chain;
847
+ return {
848
+ network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
849
+ wallet: (typeof c === "object" ? c.wallet : null) || provider.wallet,
850
+ tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
851
+ };
852
+ });
577
853
  }
578
854
  const chain = provider.chain || "base";
579
855
  const network = CHAIN_TO_NETWORK[chain] || this.networkId;
@@ -620,8 +896,8 @@ var MoltsPayServer = class {
620
896
  async handleRequest(req, res) {
621
897
  res.setHeader("Access-Control-Allow-Origin", "*");
622
898
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
623
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
624
- res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
899
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
900
+ res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
625
901
  if (req.method === "OPTIONS") {
626
902
  res.writeHead(204);
627
903
  res.end();
@@ -652,6 +928,14 @@ var MoltsPayServer = class {
652
928
  const paymentHeader = req.headers[PAYMENT_HEADER];
653
929
  return await this.handleProxy(body, paymentHeader, res);
654
930
  }
931
+ const servicePath = url.pathname.replace(/^\//, "");
932
+ const skill = this.skills.get(servicePath);
933
+ if (skill && (req.method === "POST" || req.method === "GET")) {
934
+ const body = req.method === "POST" ? await this.readBody(req) : {};
935
+ const authHeader = req.headers[MPP_AUTH_HEADER];
936
+ const x402Header = req.headers[PAYMENT_HEADER];
937
+ return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
938
+ }
655
939
  this.sendJson(res, 404, { error: "Not found" });
656
940
  } catch (err) {
657
941
  console.error("[MoltsPay] Error:", err);
@@ -783,7 +1067,7 @@ var MoltsPayServer = class {
783
1067
  const paymentNetwork = payment.accepted?.network || payment.network || this.networkId;
784
1068
  const paymentWallet = this.getWalletForNetwork(paymentNetwork);
785
1069
  const requirements = this.buildPaymentRequirements(skill.config, paymentNetwork, paymentWallet, paymentToken);
786
- console.log(`[MoltsPay] Verifying payment...`);
1070
+ console.log(`[MoltsPay] Verifying payment on ${paymentNetwork}...`);
787
1071
  const verifyResult = await this.registry.verify(payment, requirements);
788
1072
  if (!verifyResult.valid) {
789
1073
  return this.sendJson(res, 402, {
@@ -835,6 +1119,187 @@ var MoltsPayServer = class {
835
1119
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
836
1120
  }, responseHeaders);
837
1121
  }
1122
+ /**
1123
+ * Handle MPP (Machine Payments Protocol) request
1124
+ * Supports both x402 and MPP protocols on service endpoints
1125
+ */
1126
+ async handleMPPRequest(skill, body, authHeader, x402Header, res) {
1127
+ const config = skill.config;
1128
+ const params = body || {};
1129
+ if (x402Header) {
1130
+ return await this.handleExecute({ service: config.id, params }, x402Header, res);
1131
+ }
1132
+ if (authHeader && authHeader.toLowerCase().startsWith("payment ")) {
1133
+ return await this.handleMPPPayment(skill, params, authHeader, res);
1134
+ }
1135
+ return this.sendMPPPaymentRequired(config, res);
1136
+ }
1137
+ /**
1138
+ * Handle MPP payment verification and service execution
1139
+ */
1140
+ async handleMPPPayment(skill, params, authHeader, res) {
1141
+ const config = skill.config;
1142
+ const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
1143
+ if (!credentialMatch) {
1144
+ return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
1145
+ }
1146
+ let mppCredential;
1147
+ try {
1148
+ const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
1149
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
1150
+ mppCredential = JSON.parse(decoded);
1151
+ } catch (err) {
1152
+ console.error("[MoltsPay] Failed to parse MPP credential:", err);
1153
+ return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
1154
+ }
1155
+ let txHash;
1156
+ if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
1157
+ txHash = mppCredential.payload.hash;
1158
+ } else if (mppCredential.payload?.type === "transaction") {
1159
+ return this.sendJson(res, 400, {
1160
+ error: "Transaction type not supported. Please use push mode (hash type)."
1161
+ });
1162
+ }
1163
+ if (!txHash) {
1164
+ return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
1165
+ }
1166
+ let chainId = mppCredential.challenge?.request?.methodDetails?.chainId;
1167
+ if (!chainId && mppCredential.source) {
1168
+ const chainMatch = mppCredential.source.match(/eip155:(\d+)/);
1169
+ if (chainMatch) chainId = parseInt(chainMatch[1], 10);
1170
+ }
1171
+ chainId = chainId || 42431;
1172
+ const network = `eip155:${chainId}`;
1173
+ if (!this.isNetworkAccepted(network)) {
1174
+ return this.sendJson(res, 402, {
1175
+ error: `Network not accepted: ${network}`
1176
+ });
1177
+ }
1178
+ const requirements = this.buildPaymentRequirements(
1179
+ config,
1180
+ network,
1181
+ this.getWalletForNetwork(network),
1182
+ "USDC"
1183
+ );
1184
+ const paymentPayload = {
1185
+ x402Version: X402_VERSION2,
1186
+ scheme: "exact",
1187
+ network,
1188
+ payload: {
1189
+ txHash,
1190
+ chainId
1191
+ }
1192
+ };
1193
+ console.log(`[MoltsPay] Verifying MPP payment: txHash=${txHash}, chainId=${chainId}`);
1194
+ const verification = await this.registry.verify(paymentPayload, requirements);
1195
+ if (!verification.valid) {
1196
+ return this.sendJson(res, 402, {
1197
+ error: `Payment verification failed: ${verification.error}`
1198
+ });
1199
+ }
1200
+ console.log(`[MoltsPay] Payment verified! Executing service: ${config.id}`);
1201
+ let result;
1202
+ try {
1203
+ result = await skill.handler(params);
1204
+ } catch (err) {
1205
+ console.error(`[MoltsPay] Skill execution error:`, err);
1206
+ return this.sendJson(res, 500, {
1207
+ error: `Service execution failed: ${err.message}`
1208
+ });
1209
+ }
1210
+ const receipt = {
1211
+ success: true,
1212
+ txHash,
1213
+ network,
1214
+ facilitator: verification.facilitator
1215
+ };
1216
+ const receiptEncoded = Buffer.from(JSON.stringify(receipt)).toString("base64");
1217
+ res.writeHead(200, {
1218
+ "Content-Type": "application/json",
1219
+ [MPP_RECEIPT_HEADER]: receiptEncoded
1220
+ });
1221
+ res.end(JSON.stringify({
1222
+ success: true,
1223
+ result,
1224
+ payment: {
1225
+ txHash,
1226
+ status: "verified",
1227
+ facilitator: verification.facilitator
1228
+ }
1229
+ }, null, 2));
1230
+ }
1231
+ /**
1232
+ * Return 402 with both x402 and MPP payment requirements
1233
+ */
1234
+ sendMPPPaymentRequired(config, res) {
1235
+ const acceptedTokens = getAcceptedCurrencies(config);
1236
+ const providerChains = this.getProviderChains();
1237
+ const accepts = [];
1238
+ for (const chainConfig of providerChains) {
1239
+ for (const token of acceptedTokens) {
1240
+ if (chainConfig.tokens.includes(token)) {
1241
+ accepts.push(this.buildPaymentRequirements(config, chainConfig.network, chainConfig.wallet, token));
1242
+ }
1243
+ }
1244
+ }
1245
+ const x402PaymentRequired = {
1246
+ x402Version: X402_VERSION2,
1247
+ accepts,
1248
+ acceptedCurrencies: acceptedTokens,
1249
+ resource: {
1250
+ url: `/${config.id}`,
1251
+ description: `${config.name} - $${config.price} ${config.currency}`
1252
+ }
1253
+ };
1254
+ const x402Encoded = Buffer.from(JSON.stringify(x402PaymentRequired)).toString("base64");
1255
+ const tempoChain = providerChains.find((c) => c.network === "eip155:42431");
1256
+ let mppWwwAuth = "";
1257
+ if (tempoChain) {
1258
+ const challengeId = this.generateChallengeId();
1259
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
1260
+ const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
1261
+ const mppRequest = {
1262
+ amount: amountInUnits,
1263
+ currency: tokenAddress,
1264
+ methodDetails: {
1265
+ chainId: 42431,
1266
+ feePayer: true
1267
+ },
1268
+ recipient: tempoChain.wallet
1269
+ };
1270
+ const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
1271
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
1272
+ mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
1273
+ }
1274
+ const headers = {
1275
+ "Content-Type": "application/problem+json",
1276
+ [PAYMENT_REQUIRED_HEADER]: x402Encoded
1277
+ };
1278
+ if (mppWwwAuth) {
1279
+ headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
1280
+ }
1281
+ res.writeHead(402, headers);
1282
+ res.end(JSON.stringify({
1283
+ type: "https://paymentauth.org/problems/payment-required",
1284
+ title: "Payment Required",
1285
+ status: 402,
1286
+ detail: `Payment is required (${config.name}).`,
1287
+ service: config.id,
1288
+ price: config.price,
1289
+ currency: config.currency,
1290
+ acceptedCurrencies: acceptedTokens
1291
+ }, null, 2));
1292
+ }
1293
+ /**
1294
+ * Generate a unique challenge ID for MPP
1295
+ */
1296
+ generateChallengeId() {
1297
+ const bytes = new Uint8Array(24);
1298
+ for (let i = 0; i < bytes.length; i++) {
1299
+ bytes[i] = Math.floor(Math.random() * 256);
1300
+ }
1301
+ return Buffer.from(bytes).toString("base64url");
1302
+ }
838
1303
  /**
839
1304
  * Return 402 with x402 payment requirements (v2 format)
840
1305
  * Includes requirements for all chains and all accepted currencies
@@ -909,7 +1374,7 @@ var MoltsPayServer = class {
909
1374
  const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
910
1375
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
911
1376
  const tokenAddress = tokenAddresses[selectedToken];
912
- const tokenDomain = TOKEN_DOMAINS[selectedToken] || TOKEN_DOMAINS.USDC;
1377
+ const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
913
1378
  return {
914
1379
  scheme: "exact",
915
1380
  network: selectedNetwork,
@@ -1154,7 +1619,7 @@ var MoltsPayServer = class {
1154
1619
  const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
1155
1620
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
1156
1621
  const tokenAddress = tokenAddresses[selectedToken];
1157
- const tokenDomain = TOKEN_DOMAINS[selectedToken] || TOKEN_DOMAINS.USDC;
1622
+ const tokenDomain = getTokenDomain(networkId, selectedToken);
1158
1623
  return {
1159
1624
  scheme: "exact",
1160
1625
  network: networkId,