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.
- package/README.md +89 -14
- package/dist/cdp/index.d.mts +1 -1
- package/dist/cdp/index.d.ts +1 -1
- package/dist/cdp/index.js +53 -30368
- package/dist/cdp/index.js.map +1 -1
- package/dist/cdp/index.mjs +37 -30360
- package/dist/cdp/index.mjs.map +1 -1
- package/dist/cdp-DeohBe1o.d.ts +66 -0
- package/dist/cdp-p_eHuQpb.d.mts +66 -0
- package/dist/chains/index.d.mts +1 -1
- package/dist/chains/index.d.ts +1 -1
- package/dist/chains/index.js +29 -0
- package/dist/chains/index.js.map +1 -1
- package/dist/chains/index.mjs +29 -0
- package/dist/chains/index.mjs.map +1 -1
- package/dist/cli/index.js +863 -109
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +867 -109
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.d.mts +25 -2
- package/dist/client/index.d.ts +25 -2
- package/dist/client/index.js +195 -12
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +185 -12
- package/dist/client/index.mjs.map +1 -1
- package/dist/facilitators/index.d.mts +15 -53
- package/dist/facilitators/index.d.ts +15 -53
- package/dist/facilitators/index.js +234 -1
- package/dist/facilitators/index.js.map +1 -1
- package/dist/facilitators/index.mjs +233 -1
- package/dist/facilitators/index.mjs.map +1 -1
- package/dist/{index-DgJPZMBG.d.mts → index-On9ZaGDW.d.mts} +1 -1
- package/dist/{index-DgJPZMBG.d.ts → index-On9ZaGDW.d.ts} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +718 -30584
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +711 -30587
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +17 -0
- package/dist/server/index.d.ts +17 -0
- package/dist/server/index.js +443 -5
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +443 -5
- package/dist/server/index.mjs.map +1 -1
- package/dist/verify/index.d.mts +1 -1
- package/dist/verify/index.d.ts +1 -1
- package/dist/verify/index.js +29 -0
- package/dist/verify/index.js.map +1 -1
- package/dist/verify/index.mjs +29 -0
- package/dist/verify/index.mjs.map +1 -1
- package/dist/wallet/index.d.mts +1 -1
- package/dist/wallet/index.d.ts +1 -1
- package/dist/wallet/index.js +29 -0
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +29 -0
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +5 -2
package/dist/server/index.d.mts
CHANGED
|
@@ -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
|
package/dist/server/index.d.ts
CHANGED
|
@@ -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
|
package/dist/server/index.js
CHANGED
|
@@ -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.
|
|
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:
|
|
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
|