moltspay 0.9.0 → 0.9.2
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/.env.example +48 -3
- package/dist/cli/index.js +163 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +163 -2
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.js +163 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +163 -2
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +25 -0
- package/dist/server/index.d.ts +25 -0
- package/dist/server/index.js +163 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +163 -2
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -1,10 +1,55 @@
|
|
|
1
1
|
# MoltsPay Server Configuration
|
|
2
2
|
# Copy to ~/.moltspay/.env or ./.env and fill in values
|
|
3
3
|
|
|
4
|
-
#
|
|
4
|
+
# ===========================================
|
|
5
|
+
# Network Configuration
|
|
6
|
+
# ===========================================
|
|
7
|
+
# true = Base mainnet, false = Base Sepolia testnet
|
|
5
8
|
USE_MAINNET=true
|
|
6
9
|
|
|
7
|
-
#
|
|
8
|
-
#
|
|
10
|
+
# ===========================================
|
|
11
|
+
# Facilitator Selection (v0.9.0+)
|
|
12
|
+
# ===========================================
|
|
13
|
+
# Primary facilitator to use
|
|
14
|
+
# Available: cdp
|
|
15
|
+
# Coming soon: chaoschain, questflow
|
|
16
|
+
FACILITATOR_PRIMARY=cdp
|
|
17
|
+
|
|
18
|
+
# Fallback facilitators (comma-separated, optional)
|
|
19
|
+
# Example: chaoschain,questflow
|
|
20
|
+
# FACILITATOR_FALLBACK=
|
|
21
|
+
|
|
22
|
+
# Selection strategy when multiple facilitators available
|
|
23
|
+
# Options: failover | cheapest | fastest | random | roundrobin
|
|
24
|
+
FACILITATOR_STRATEGY=failover
|
|
25
|
+
|
|
26
|
+
# ===========================================
|
|
27
|
+
# Proxy Endpoint (v0.9.2+)
|
|
28
|
+
# ===========================================
|
|
29
|
+
# IP whitelist for /proxy endpoint (comma-separated)
|
|
30
|
+
# Used by external services like moltspay-creators to delegate payment handling
|
|
31
|
+
# If not set, /proxy endpoint is disabled (secure by default)
|
|
32
|
+
# Example: 127.0.0.1,::1,203.0.113.50
|
|
33
|
+
PROXY_ALLOWED_IPS=127.0.0.1,::1
|
|
34
|
+
|
|
35
|
+
# ===========================================
|
|
36
|
+
# CDP Facilitator (Coinbase)
|
|
37
|
+
# ===========================================
|
|
38
|
+
# Required for mainnet. Get credentials from:
|
|
39
|
+
# https://portal.cdp.coinbase.com/
|
|
9
40
|
CDP_API_KEY_ID=
|
|
10
41
|
CDP_API_KEY_SECRET=
|
|
42
|
+
|
|
43
|
+
# ===========================================
|
|
44
|
+
# ChaosChain Facilitator (Coming Soon)
|
|
45
|
+
# ===========================================
|
|
46
|
+
# https://github.com/ChaosChain/chaoschain-x402
|
|
47
|
+
# CHAOSCHAIN_ENDPOINT=https://x402.chaoschain.io
|
|
48
|
+
# CHAOSCHAIN_API_KEY=
|
|
49
|
+
|
|
50
|
+
# ===========================================
|
|
51
|
+
# Questflow Facilitator (Coming Soon)
|
|
52
|
+
# ===========================================
|
|
53
|
+
# https://facilitator.questflow.ai/
|
|
54
|
+
# QUESTFLOW_ENDPOINT=https://facilitator.questflow.ai
|
|
55
|
+
# QUESTFLOW_API_KEY=
|
package/dist/cli/index.js
CHANGED
|
@@ -945,8 +945,9 @@ var MoltsPayServer = class {
|
|
|
945
945
|
this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
|
|
946
946
|
this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
|
|
947
947
|
const facilitatorConfig = options.facilitators || {
|
|
948
|
-
primary: "cdp",
|
|
949
|
-
|
|
948
|
+
primary: process.env.FACILITATOR_PRIMARY || "cdp",
|
|
949
|
+
fallback: process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean),
|
|
950
|
+
strategy: process.env.FACILITATOR_STRATEGY || "failover",
|
|
950
951
|
config: {
|
|
951
952
|
cdp: { useMainnet: this.useMainnet }
|
|
952
953
|
}
|
|
@@ -984,6 +985,7 @@ var MoltsPayServer = class {
|
|
|
984
985
|
console.log(`[MoltsPay] Endpoints:`);
|
|
985
986
|
console.log(` GET /services - List available services`);
|
|
986
987
|
console.log(` POST /execute - Execute service (x402 payment)`);
|
|
988
|
+
console.log(` POST /proxy - Proxy payment for external services`);
|
|
987
989
|
console.log(` GET /health - Health check (incl. facilitators)`);
|
|
988
990
|
});
|
|
989
991
|
}
|
|
@@ -1013,6 +1015,15 @@ var MoltsPayServer = class {
|
|
|
1013
1015
|
const paymentHeader = req.headers[PAYMENT_HEADER2];
|
|
1014
1016
|
return await this.handleExecute(body, paymentHeader, res);
|
|
1015
1017
|
}
|
|
1018
|
+
if (url.pathname === "/proxy" && req.method === "POST") {
|
|
1019
|
+
const clientIP = req.headers["x-real-ip"] || req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "";
|
|
1020
|
+
if (!this.isProxyAllowed(clientIP)) {
|
|
1021
|
+
return this.sendJson(res, 403, { error: "Forbidden: IP not allowed" });
|
|
1022
|
+
}
|
|
1023
|
+
const body = await this.readBody(req);
|
|
1024
|
+
const paymentHeader = req.headers[PAYMENT_HEADER2];
|
|
1025
|
+
return await this.handleProxy(body, paymentHeader, res);
|
|
1026
|
+
}
|
|
1016
1027
|
this.sendJson(res, 404, { error: "Not found" });
|
|
1017
1028
|
} catch (err) {
|
|
1018
1029
|
console.error("[MoltsPay] Error:", err);
|
|
@@ -1222,6 +1233,156 @@ var MoltsPayServer = class {
|
|
|
1222
1233
|
res.writeHead(status, headers);
|
|
1223
1234
|
res.end(JSON.stringify(data, null, 2));
|
|
1224
1235
|
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Check if IP is allowed for /proxy endpoint
|
|
1238
|
+
*/
|
|
1239
|
+
isProxyAllowed(clientIP) {
|
|
1240
|
+
const allowedIPs = process.env.PROXY_ALLOWED_IPS?.split(",").map((ip) => ip.trim()) || [];
|
|
1241
|
+
if (allowedIPs.length === 0) {
|
|
1242
|
+
console.log(`[MoltsPay] /proxy denied: no PROXY_ALLOWED_IPS configured`);
|
|
1243
|
+
return false;
|
|
1244
|
+
}
|
|
1245
|
+
const normalizedIP = clientIP === "::1" ? "127.0.0.1" : clientIP.replace("::ffff:", "");
|
|
1246
|
+
const allowed = allowedIPs.includes(normalizedIP) || allowedIPs.includes(clientIP);
|
|
1247
|
+
if (!allowed) {
|
|
1248
|
+
console.log(`[MoltsPay] /proxy denied for IP: ${clientIP} (normalized: ${normalizedIP})`);
|
|
1249
|
+
}
|
|
1250
|
+
return allowed;
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* POST /proxy - Handle payment for external services (moltspay-creators)
|
|
1254
|
+
*
|
|
1255
|
+
* This endpoint allows other services to delegate x402 payment handling.
|
|
1256
|
+
* It does NOT execute any skill - just handles payment verification/settlement.
|
|
1257
|
+
*
|
|
1258
|
+
* Request body:
|
|
1259
|
+
* { wallet, amount, currency, chain, memo, serviceId, description }
|
|
1260
|
+
*
|
|
1261
|
+
* Without X-Payment header: returns 402 with payment requirements
|
|
1262
|
+
* With X-Payment header: verifies payment and returns result
|
|
1263
|
+
*/
|
|
1264
|
+
async handleProxy(body, paymentHeader, res) {
|
|
1265
|
+
const { wallet, amount, currency, chain, memo, serviceId, description } = body;
|
|
1266
|
+
if (!wallet || !amount) {
|
|
1267
|
+
return this.sendJson(res, 400, { error: "Missing required fields: wallet, amount" });
|
|
1268
|
+
}
|
|
1269
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
|
|
1270
|
+
return this.sendJson(res, 400, { error: "Invalid wallet address format" });
|
|
1271
|
+
}
|
|
1272
|
+
const amountNum = parseFloat(amount);
|
|
1273
|
+
if (isNaN(amountNum) || amountNum <= 0) {
|
|
1274
|
+
return this.sendJson(res, 400, { error: "Invalid amount" });
|
|
1275
|
+
}
|
|
1276
|
+
const proxyConfig = {
|
|
1277
|
+
id: serviceId || "proxy",
|
|
1278
|
+
name: description || "Proxy Payment",
|
|
1279
|
+
description: description || "",
|
|
1280
|
+
price: amountNum,
|
|
1281
|
+
currency: currency || "USDC",
|
|
1282
|
+
function: "",
|
|
1283
|
+
// Not used
|
|
1284
|
+
input: {},
|
|
1285
|
+
output: {}
|
|
1286
|
+
};
|
|
1287
|
+
const requirements = this.buildProxyPaymentRequirements(proxyConfig, wallet);
|
|
1288
|
+
if (!paymentHeader) {
|
|
1289
|
+
return this.sendProxyPaymentRequired(proxyConfig, wallet, memo, res);
|
|
1290
|
+
}
|
|
1291
|
+
let payment;
|
|
1292
|
+
try {
|
|
1293
|
+
const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
|
|
1294
|
+
payment = JSON.parse(decoded);
|
|
1295
|
+
} catch {
|
|
1296
|
+
return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
|
|
1297
|
+
}
|
|
1298
|
+
if (payment.x402Version !== X402_VERSION3) {
|
|
1299
|
+
return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
|
|
1300
|
+
}
|
|
1301
|
+
const scheme = payment.accepted?.scheme || payment.scheme;
|
|
1302
|
+
const network = payment.accepted?.network || payment.network;
|
|
1303
|
+
if (scheme !== "exact") {
|
|
1304
|
+
return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
|
|
1305
|
+
}
|
|
1306
|
+
if (network !== this.networkId) {
|
|
1307
|
+
return this.sendJson(res, 402, { error: `Network mismatch: expected ${this.networkId}, got ${network}` });
|
|
1308
|
+
}
|
|
1309
|
+
console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
|
|
1310
|
+
const verifyResult = await this.registry.verify(payment, requirements);
|
|
1311
|
+
if (!verifyResult.valid) {
|
|
1312
|
+
return this.sendJson(res, 402, {
|
|
1313
|
+
success: false,
|
|
1314
|
+
error: `Payment verification failed: ${verifyResult.error}`,
|
|
1315
|
+
facilitator: verifyResult.facilitator
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
|
|
1319
|
+
console.log(`[MoltsPay] /proxy: Settling payment...`);
|
|
1320
|
+
let settlement = null;
|
|
1321
|
+
try {
|
|
1322
|
+
settlement = await this.registry.settle(payment, requirements);
|
|
1323
|
+
console.log(`[MoltsPay] /proxy: Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
|
|
1324
|
+
} catch (err) {
|
|
1325
|
+
console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
|
|
1326
|
+
return this.sendJson(res, 500, {
|
|
1327
|
+
success: false,
|
|
1328
|
+
error: `Settlement failed: ${err.message}`
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
this.sendJson(res, 200, {
|
|
1332
|
+
success: true,
|
|
1333
|
+
verified: true,
|
|
1334
|
+
settled: settlement?.success || false,
|
|
1335
|
+
txHash: settlement?.transaction,
|
|
1336
|
+
paidTo: wallet,
|
|
1337
|
+
amount: amountNum,
|
|
1338
|
+
currency: currency || "USDC",
|
|
1339
|
+
facilitator: settlement?.facilitator,
|
|
1340
|
+
memo
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Build payment requirements for proxy endpoint (uses provided wallet)
|
|
1345
|
+
*/
|
|
1346
|
+
buildProxyPaymentRequirements(config, wallet) {
|
|
1347
|
+
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
1348
|
+
const usdcAddress = USDC_ADDRESSES[this.networkId];
|
|
1349
|
+
return {
|
|
1350
|
+
scheme: "exact",
|
|
1351
|
+
network: this.networkId,
|
|
1352
|
+
asset: usdcAddress,
|
|
1353
|
+
amount: amountInUnits,
|
|
1354
|
+
payTo: wallet,
|
|
1355
|
+
// Use provided wallet, not manifest
|
|
1356
|
+
maxTimeoutSeconds: 300,
|
|
1357
|
+
extra: USDC_DOMAIN
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Return 402 with x402 payment requirements for proxy endpoint
|
|
1362
|
+
*/
|
|
1363
|
+
sendProxyPaymentRequired(config, wallet, memo, res) {
|
|
1364
|
+
const requirements = this.buildProxyPaymentRequirements(config, wallet);
|
|
1365
|
+
const paymentRequired = {
|
|
1366
|
+
x402Version: X402_VERSION3,
|
|
1367
|
+
accepts: [requirements],
|
|
1368
|
+
resource: {
|
|
1369
|
+
url: `/proxy`,
|
|
1370
|
+
description: `${config.name} - $${config.price} ${config.currency}`,
|
|
1371
|
+
mimeType: "application/json",
|
|
1372
|
+
memo
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
|
|
1376
|
+
res.writeHead(402, {
|
|
1377
|
+
"Content-Type": "application/json",
|
|
1378
|
+
[PAYMENT_REQUIRED_HEADER2]: encoded
|
|
1379
|
+
});
|
|
1380
|
+
res.end(JSON.stringify({
|
|
1381
|
+
error: "Payment required",
|
|
1382
|
+
message: `Payment requires $${config.price} ${config.currency}`,
|
|
1383
|
+
x402: paymentRequired
|
|
1384
|
+
}, null, 2));
|
|
1385
|
+
}
|
|
1225
1386
|
};
|
|
1226
1387
|
|
|
1227
1388
|
// src/cli/index.ts
|