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