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
@@ -163,6 +163,23 @@ declare class MoltsPayServer {
163
163
  * POST /execute - Execute service with x402 payment
164
164
  */
165
165
  private handleExecute;
166
+ /**
167
+ * Handle MPP (Machine Payments Protocol) request
168
+ * Supports both x402 and MPP protocols on service endpoints
169
+ */
170
+ private handleMPPRequest;
171
+ /**
172
+ * Handle MPP payment verification and service execution
173
+ */
174
+ private handleMPPPayment;
175
+ /**
176
+ * Return 402 with both x402 and MPP payment requirements
177
+ */
178
+ private sendMPPPaymentRequired;
179
+ /**
180
+ * Generate a unique challenge ID for MPP
181
+ */
182
+ private generateChallengeId;
166
183
  /**
167
184
  * Return 402 with x402 payment requirements (v2 format)
168
185
  * Includes requirements for all chains and all accepted currencies
@@ -163,6 +163,23 @@ declare class MoltsPayServer {
163
163
  * POST /execute - Execute service with x402 payment
164
164
  */
165
165
  private handleExecute;
166
+ /**
167
+ * Handle MPP (Machine Payments Protocol) request
168
+ * Supports both x402 and MPP protocols on service endpoints
169
+ */
170
+ private handleMPPRequest;
171
+ /**
172
+ * Handle MPP payment verification and service execution
173
+ */
174
+ private handleMPPPayment;
175
+ /**
176
+ * Return 402 with both x402 and MPP payment requirements
177
+ */
178
+ private sendMPPPaymentRequired;
179
+ /**
180
+ * Generate a unique challenge ID for MPP
181
+ */
182
+ private generateChallengeId;
166
183
  /**
167
184
  * Return 402 with x402 payment requirements (v2 format)
168
185
  * Includes requirements for all chains and all accepted currencies
@@ -263,6 +263,236 @@ var CDPFacilitator = class extends BaseFacilitator {
263
263
  }
264
264
  };
265
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;
493
+ }
494
+ };
495
+
266
496
  // src/facilitators/registry.ts
267
497
  var FacilitatorRegistry = class {
268
498
  factories = /* @__PURE__ */ new Map();
@@ -271,7 +501,8 @@ var FacilitatorRegistry = class {
271
501
  roundRobinIndex = 0;
272
502
  constructor(selection) {
273
503
  this.registerFactory("cdp", (config) => new CDPFacilitator(config));
274
- this.selection = selection || { primary: "cdp", strategy: "failover" };
504
+ this.registerFactory("tempo", () => new TempoFacilitator());
505
+ this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
275
506
  }
276
507
  /**
277
508
  * Register a new facilitator factory
@@ -492,6 +723,9 @@ var X402_VERSION2 = 2;
492
723
  var PAYMENT_REQUIRED_HEADER = "x-payment-required";
493
724
  var PAYMENT_HEADER = "x-payment";
494
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";
495
729
  var TOKEN_ADDRESSES = {
496
730
  "eip155:8453": {
497
731
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -505,12 +739,20 @@ var TOKEN_ADDRESSES = {
505
739
  "eip155:137": {
506
740
  USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
507
741
  USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
742
+ },
743
+ "eip155:42431": {
744
+ // Tempo Moderato testnet - TIP-20 stablecoins
745
+ USDC: "0x20c0000000000000000000000000000000000000",
746
+ // pathUSD
747
+ USDT: "0x20c0000000000000000000000000000000000001"
748
+ // alphaUSD
508
749
  }
509
750
  };
510
751
  var CHAIN_TO_NETWORK = {
511
752
  "base": "eip155:8453",
512
753
  "base_sepolia": "eip155:84532",
513
- "polygon": "eip155:137"
754
+ "polygon": "eip155:137",
755
+ "tempo_moderato": "eip155:42431"
514
756
  };
515
757
  var TOKEN_DOMAINS = {
516
758
  // Base mainnet
@@ -528,6 +770,11 @@ var TOKEN_DOMAINS = {
528
770
  "eip155:137": {
529
771
  USDC: { name: "USD Coin", version: "2" },
530
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" }
531
778
  }
532
779
  };
533
780
  function getTokenDomain(network, token) {
@@ -585,9 +832,11 @@ var MoltsPayServer = class {
585
832
  };
586
833
  this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
587
834
  this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
835
+ const defaultFallback = ["tempo"];
836
+ const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
588
837
  const facilitatorConfig = options.facilitators || {
589
838
  primary: process.env.FACILITATOR_PRIMARY || "cdp",
590
- fallback: process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean),
839
+ fallback: envFallback || defaultFallback,
591
840
  strategy: process.env.FACILITATOR_STRATEGY || "failover",
592
841
  config: {
593
842
  cdp: { useMainnet: this.useMainnet }
@@ -681,8 +930,8 @@ var MoltsPayServer = class {
681
930
  async handleRequest(req, res) {
682
931
  res.setHeader("Access-Control-Allow-Origin", "*");
683
932
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
684
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
685
- 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");
686
935
  if (req.method === "OPTIONS") {
687
936
  res.writeHead(204);
688
937
  res.end();
@@ -713,6 +962,14 @@ var MoltsPayServer = class {
713
962
  const paymentHeader = req.headers[PAYMENT_HEADER];
714
963
  return await this.handleProxy(body, paymentHeader, res);
715
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
+ }
716
973
  this.sendJson(res, 404, { error: "Not found" });
717
974
  } catch (err) {
718
975
  console.error("[MoltsPay] Error:", err);
@@ -896,6 +1153,187 @@ var MoltsPayServer = class {
896
1153
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
897
1154
  }, responseHeaders);
898
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
+ }
899
1337
  /**
900
1338
  * Return 402 with x402 payment requirements (v2 format)
901
1339
  * Includes requirements for all chains and all accepted currencies