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
@@ -229,6 +229,236 @@ var CDPFacilitator = class extends BaseFacilitator {
229
229
  }
230
230
  };
231
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;
459
+ }
460
+ };
461
+
232
462
  // src/facilitators/registry.ts
233
463
  var FacilitatorRegistry = class {
234
464
  factories = /* @__PURE__ */ new Map();
@@ -237,7 +467,8 @@ var FacilitatorRegistry = class {
237
467
  roundRobinIndex = 0;
238
468
  constructor(selection) {
239
469
  this.registerFactory("cdp", (config) => new CDPFacilitator(config));
240
- this.selection = selection || { primary: "cdp", strategy: "failover" };
470
+ this.registerFactory("tempo", () => new TempoFacilitator());
471
+ this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
241
472
  }
242
473
  /**
243
474
  * Register a new facilitator factory
@@ -458,6 +689,9 @@ var X402_VERSION2 = 2;
458
689
  var PAYMENT_REQUIRED_HEADER = "x-payment-required";
459
690
  var PAYMENT_HEADER = "x-payment";
460
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";
461
695
  var TOKEN_ADDRESSES = {
462
696
  "eip155:8453": {
463
697
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -471,12 +705,20 @@ var TOKEN_ADDRESSES = {
471
705
  "eip155:137": {
472
706
  USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
473
707
  USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
708
+ },
709
+ "eip155:42431": {
710
+ // Tempo Moderato testnet - TIP-20 stablecoins
711
+ USDC: "0x20c0000000000000000000000000000000000000",
712
+ // pathUSD
713
+ USDT: "0x20c0000000000000000000000000000000000001"
714
+ // alphaUSD
474
715
  }
475
716
  };
476
717
  var CHAIN_TO_NETWORK = {
477
718
  "base": "eip155:8453",
478
719
  "base_sepolia": "eip155:84532",
479
- "polygon": "eip155:137"
720
+ "polygon": "eip155:137",
721
+ "tempo_moderato": "eip155:42431"
480
722
  };
481
723
  var TOKEN_DOMAINS = {
482
724
  // Base mainnet
@@ -494,6 +736,11 @@ var TOKEN_DOMAINS = {
494
736
  "eip155:137": {
495
737
  USDC: { name: "USD Coin", version: "2" },
496
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" }
497
744
  }
498
745
  };
499
746
  function getTokenDomain(network, token) {
@@ -551,9 +798,11 @@ var MoltsPayServer = class {
551
798
  };
552
799
  this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
553
800
  this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
801
+ const defaultFallback = ["tempo"];
802
+ const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
554
803
  const facilitatorConfig = options.facilitators || {
555
804
  primary: process.env.FACILITATOR_PRIMARY || "cdp",
556
- fallback: process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean),
805
+ fallback: envFallback || defaultFallback,
557
806
  strategy: process.env.FACILITATOR_STRATEGY || "failover",
558
807
  config: {
559
808
  cdp: { useMainnet: this.useMainnet }
@@ -647,8 +896,8 @@ var MoltsPayServer = class {
647
896
  async handleRequest(req, res) {
648
897
  res.setHeader("Access-Control-Allow-Origin", "*");
649
898
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
650
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
651
- 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");
652
901
  if (req.method === "OPTIONS") {
653
902
  res.writeHead(204);
654
903
  res.end();
@@ -679,6 +928,14 @@ var MoltsPayServer = class {
679
928
  const paymentHeader = req.headers[PAYMENT_HEADER];
680
929
  return await this.handleProxy(body, paymentHeader, res);
681
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
+ }
682
939
  this.sendJson(res, 404, { error: "Not found" });
683
940
  } catch (err) {
684
941
  console.error("[MoltsPay] Error:", err);
@@ -862,6 +1119,187 @@ var MoltsPayServer = class {
862
1119
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
863
1120
  }, responseHeaders);
864
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
+ }
865
1303
  /**
866
1304
  * Return 402 with x402 payment requirements (v2 format)
867
1305
  * Includes requirements for all chains and all accepted currencies