moltspay 0.9.1 → 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.
@@ -962,6 +962,7 @@ var MoltsPayServer = class {
962
962
  console.log(`[MoltsPay] Endpoints:`);
963
963
  console.log(` GET /services - List available services`);
964
964
  console.log(` POST /execute - Execute service (x402 payment)`);
965
+ console.log(` POST /proxy - Proxy payment for external services`);
965
966
  console.log(` GET /health - Health check (incl. facilitators)`);
966
967
  });
967
968
  }
@@ -991,6 +992,15 @@ var MoltsPayServer = class {
991
992
  const paymentHeader = req.headers[PAYMENT_HEADER2];
992
993
  return await this.handleExecute(body, paymentHeader, res);
993
994
  }
995
+ if (url.pathname === "/proxy" && req.method === "POST") {
996
+ const clientIP = req.headers["x-real-ip"] || req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "";
997
+ if (!this.isProxyAllowed(clientIP)) {
998
+ return this.sendJson(res, 403, { error: "Forbidden: IP not allowed" });
999
+ }
1000
+ const body = await this.readBody(req);
1001
+ const paymentHeader = req.headers[PAYMENT_HEADER2];
1002
+ return await this.handleProxy(body, paymentHeader, res);
1003
+ }
994
1004
  this.sendJson(res, 404, { error: "Not found" });
995
1005
  } catch (err) {
996
1006
  console.error("[MoltsPay] Error:", err);
@@ -1200,6 +1210,156 @@ var MoltsPayServer = class {
1200
1210
  res.writeHead(status, headers);
1201
1211
  res.end(JSON.stringify(data, null, 2));
1202
1212
  }
1213
+ /**
1214
+ * Check if IP is allowed for /proxy endpoint
1215
+ */
1216
+ isProxyAllowed(clientIP) {
1217
+ const allowedIPs = process.env.PROXY_ALLOWED_IPS?.split(",").map((ip) => ip.trim()) || [];
1218
+ if (allowedIPs.length === 0) {
1219
+ console.log(`[MoltsPay] /proxy denied: no PROXY_ALLOWED_IPS configured`);
1220
+ return false;
1221
+ }
1222
+ const normalizedIP = clientIP === "::1" ? "127.0.0.1" : clientIP.replace("::ffff:", "");
1223
+ const allowed = allowedIPs.includes(normalizedIP) || allowedIPs.includes(clientIP);
1224
+ if (!allowed) {
1225
+ console.log(`[MoltsPay] /proxy denied for IP: ${clientIP} (normalized: ${normalizedIP})`);
1226
+ }
1227
+ return allowed;
1228
+ }
1229
+ /**
1230
+ * POST /proxy - Handle payment for external services (moltspay-creators)
1231
+ *
1232
+ * This endpoint allows other services to delegate x402 payment handling.
1233
+ * It does NOT execute any skill - just handles payment verification/settlement.
1234
+ *
1235
+ * Request body:
1236
+ * { wallet, amount, currency, chain, memo, serviceId, description }
1237
+ *
1238
+ * Without X-Payment header: returns 402 with payment requirements
1239
+ * With X-Payment header: verifies payment and returns result
1240
+ */
1241
+ async handleProxy(body, paymentHeader, res) {
1242
+ const { wallet, amount, currency, chain, memo, serviceId, description } = body;
1243
+ if (!wallet || !amount) {
1244
+ return this.sendJson(res, 400, { error: "Missing required fields: wallet, amount" });
1245
+ }
1246
+ if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
1247
+ return this.sendJson(res, 400, { error: "Invalid wallet address format" });
1248
+ }
1249
+ const amountNum = parseFloat(amount);
1250
+ if (isNaN(amountNum) || amountNum <= 0) {
1251
+ return this.sendJson(res, 400, { error: "Invalid amount" });
1252
+ }
1253
+ const proxyConfig = {
1254
+ id: serviceId || "proxy",
1255
+ name: description || "Proxy Payment",
1256
+ description: description || "",
1257
+ price: amountNum,
1258
+ currency: currency || "USDC",
1259
+ function: "",
1260
+ // Not used
1261
+ input: {},
1262
+ output: {}
1263
+ };
1264
+ const requirements = this.buildProxyPaymentRequirements(proxyConfig, wallet);
1265
+ if (!paymentHeader) {
1266
+ return this.sendProxyPaymentRequired(proxyConfig, wallet, memo, res);
1267
+ }
1268
+ let payment;
1269
+ try {
1270
+ const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
1271
+ payment = JSON.parse(decoded);
1272
+ } catch {
1273
+ return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
1274
+ }
1275
+ if (payment.x402Version !== X402_VERSION3) {
1276
+ return this.sendJson(res, 402, { error: `Unsupported x402 version: ${payment.x402Version}` });
1277
+ }
1278
+ const scheme = payment.accepted?.scheme || payment.scheme;
1279
+ const network = payment.accepted?.network || payment.network;
1280
+ if (scheme !== "exact") {
1281
+ return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
1282
+ }
1283
+ if (network !== this.networkId) {
1284
+ return this.sendJson(res, 402, { error: `Network mismatch: expected ${this.networkId}, got ${network}` });
1285
+ }
1286
+ console.log(`[MoltsPay] /proxy: Verifying payment for ${wallet}...`);
1287
+ const verifyResult = await this.registry.verify(payment, requirements);
1288
+ if (!verifyResult.valid) {
1289
+ return this.sendJson(res, 402, {
1290
+ success: false,
1291
+ error: `Payment verification failed: ${verifyResult.error}`,
1292
+ facilitator: verifyResult.facilitator
1293
+ });
1294
+ }
1295
+ console.log(`[MoltsPay] /proxy: Verified by ${verifyResult.facilitator}`);
1296
+ console.log(`[MoltsPay] /proxy: Settling payment...`);
1297
+ let settlement = null;
1298
+ try {
1299
+ settlement = await this.registry.settle(payment, requirements);
1300
+ console.log(`[MoltsPay] /proxy: Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1301
+ } catch (err) {
1302
+ console.error("[MoltsPay] /proxy: Settlement failed:", err.message);
1303
+ return this.sendJson(res, 500, {
1304
+ success: false,
1305
+ error: `Settlement failed: ${err.message}`
1306
+ });
1307
+ }
1308
+ this.sendJson(res, 200, {
1309
+ success: true,
1310
+ verified: true,
1311
+ settled: settlement?.success || false,
1312
+ txHash: settlement?.transaction,
1313
+ paidTo: wallet,
1314
+ amount: amountNum,
1315
+ currency: currency || "USDC",
1316
+ facilitator: settlement?.facilitator,
1317
+ memo
1318
+ });
1319
+ }
1320
+ /**
1321
+ * Build payment requirements for proxy endpoint (uses provided wallet)
1322
+ */
1323
+ buildProxyPaymentRequirements(config, wallet) {
1324
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
1325
+ const usdcAddress = USDC_ADDRESSES[this.networkId];
1326
+ return {
1327
+ scheme: "exact",
1328
+ network: this.networkId,
1329
+ asset: usdcAddress,
1330
+ amount: amountInUnits,
1331
+ payTo: wallet,
1332
+ // Use provided wallet, not manifest
1333
+ maxTimeoutSeconds: 300,
1334
+ extra: USDC_DOMAIN
1335
+ };
1336
+ }
1337
+ /**
1338
+ * Return 402 with x402 payment requirements for proxy endpoint
1339
+ */
1340
+ sendProxyPaymentRequired(config, wallet, memo, res) {
1341
+ const requirements = this.buildProxyPaymentRequirements(config, wallet);
1342
+ const paymentRequired = {
1343
+ x402Version: X402_VERSION3,
1344
+ accepts: [requirements],
1345
+ resource: {
1346
+ url: `/proxy`,
1347
+ description: `${config.name} - $${config.price} ${config.currency}`,
1348
+ mimeType: "application/json",
1349
+ memo
1350
+ }
1351
+ };
1352
+ const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
1353
+ res.writeHead(402, {
1354
+ "Content-Type": "application/json",
1355
+ [PAYMENT_REQUIRED_HEADER2]: encoded
1356
+ });
1357
+ res.end(JSON.stringify({
1358
+ error: "Payment required",
1359
+ message: `Payment requires $${config.price} ${config.currency}`,
1360
+ x402: paymentRequired
1361
+ }, null, 2));
1362
+ }
1203
1363
  };
1204
1364
 
1205
1365
  // src/cli/index.ts