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