leak-cli 2026.2.17-beta.1 → 2026.2.17

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/src/index.js CHANGED
@@ -11,6 +11,19 @@ import { x402HTTPResourceServer, HTTPFacilitatorClient } from "@x402/core/http";
11
11
  import { ExactEvmScheme } from "@x402/evm/exact/server";
12
12
  import { isAddress } from "viem";
13
13
  import { resolveSupportedChain } from "./chain_meta.js";
14
+ import {
15
+ ACCESS_MODE_VALUES,
16
+ DEFAULT_ACCESS_MODE,
17
+ accessModeRequiresDownloadCode,
18
+ accessModeRequiresPayment,
19
+ accessModeSummary,
20
+ isValidAccessMode,
21
+ } from "./access_mode.js";
22
+ import {
23
+ DOWNLOAD_CODE_HEADER,
24
+ isValidDownloadCodeHash,
25
+ verifyDownloadCode,
26
+ } from "./download_code.js";
14
27
 
15
28
  dotenv.config();
16
29
 
@@ -87,6 +100,10 @@ const SELLER_PAY_TO = String(
87
100
  process.env.SELLER_PAY_TO || process.env.PAY_TO || "",
88
101
  ).trim();
89
102
  const PRICE_USD = process.env.PRICE_USD || "1.00";
103
+ const ACCESS_MODE = String(
104
+ process.env.ACCESS_MODE || DEFAULT_ACCESS_MODE,
105
+ ).trim().toLowerCase();
106
+ const DOWNLOAD_CODE_HASH = String(process.env.DOWNLOAD_CODE_HASH || "").trim();
90
107
  const RAW_CHAIN_ID =
91
108
  process.env.CHAIN_ID || process.env.NETWORK || "eip155:84532";
92
109
  const ARTIFACT_PATH = process.env.ARTIFACT_PATH || process.env.PROTECTED_FILE;
@@ -117,7 +134,7 @@ const OG_IMAGE_WIDTH = 1200;
117
134
  const OG_IMAGE_HEIGHT = 630;
118
135
  const SKILL_NAME = "leak-buy";
119
136
  const SKILL_DESCRIPTION =
120
- "Buy and download x402-gated leak content from promo or download links using the leak CLI tool";
137
+ "Buy and download leak content from promo or download links using the leak CLI tool";
121
138
  const SKILL_SOURCE = "clawhub";
122
139
  const SKILL_INSTALL_COMMAND = "clawhub install leak-buy";
123
140
  const WELL_KNOWN_CACHE_CONTROL = "public, max-age=60";
@@ -134,6 +151,30 @@ const ENDED_WINDOW_SECONDS = parseNonNegativeInt(
134
151
  0,
135
152
  );
136
153
 
154
+ if (!isValidAccessMode(ACCESS_MODE)) {
155
+ console.error(`Invalid ACCESS_MODE: ${ACCESS_MODE}`);
156
+ console.error(`Supported ACCESS_MODE values: ${ACCESS_MODE_VALUES.join(", ")}`);
157
+ process.exit(1);
158
+ }
159
+
160
+ const REQUIRES_DOWNLOAD_CODE = accessModeRequiresDownloadCode(ACCESS_MODE);
161
+ const REQUIRES_PAYMENT = accessModeRequiresPayment(ACCESS_MODE);
162
+
163
+ if (REQUIRES_DOWNLOAD_CODE && !DOWNLOAD_CODE_HASH) {
164
+ console.error(`ACCESS_MODE=${ACCESS_MODE} requires DOWNLOAD_CODE_HASH.`);
165
+ process.exit(1);
166
+ }
167
+ if (DOWNLOAD_CODE_HASH && !isValidDownloadCodeHash(DOWNLOAD_CODE_HASH)) {
168
+ console.error("Invalid DOWNLOAD_CODE_HASH format.");
169
+ process.exit(1);
170
+ }
171
+ if (!REQUIRES_DOWNLOAD_CODE && DOWNLOAD_CODE_HASH) {
172
+ console.error(
173
+ `ACCESS_MODE=${ACCESS_MODE} does not use DOWNLOAD_CODE_HASH. Remove DOWNLOAD_CODE_HASH or choose a download-code access mode.`,
174
+ );
175
+ process.exit(1);
176
+ }
177
+
137
178
  let CHAIN_META;
138
179
  try {
139
180
  CHAIN_META = resolveSupportedChain(RAW_CHAIN_ID);
@@ -147,39 +188,41 @@ const CHAIN_NAME = CHAIN_META.name;
147
188
  const CHAIN_NUMERIC_ID = CHAIN_META.id;
148
189
  const IS_BASE_MAINNET = CHAIN_NUMERIC_ID === 8453;
149
190
 
150
- if (!new Set(["testnet", "cdp_mainnet"]).has(FACILITATOR_MODE)) {
151
- console.error(
152
- "Invalid FACILITATOR_MODE. Supported values: testnet, cdp_mainnet",
153
- );
154
- process.exit(1);
155
- }
191
+ if (REQUIRES_PAYMENT) {
192
+ if (!new Set(["testnet", "cdp_mainnet"]).has(FACILITATOR_MODE)) {
193
+ console.error(
194
+ "Invalid FACILITATOR_MODE. Supported values: testnet, cdp_mainnet",
195
+ );
196
+ process.exit(1);
197
+ }
156
198
 
157
- if (IS_BASE_MAINNET && FACILITATOR_MODE !== "cdp_mainnet") {
158
- console.error(
159
- "Invalid config: CHAIN_ID=eip155:8453 requires FACILITATOR_MODE=cdp_mainnet.",
160
- );
161
- console.error(
162
- "Set FACILITATOR_MODE=cdp_mainnet and configure CDP_API_KEY_ID/CDP_API_KEY_SECRET.",
163
- );
164
- process.exit(1);
165
- }
199
+ if (IS_BASE_MAINNET && FACILITATOR_MODE !== "cdp_mainnet") {
200
+ console.error(
201
+ "Invalid config: CHAIN_ID=eip155:8453 requires FACILITATOR_MODE=cdp_mainnet.",
202
+ );
203
+ console.error(
204
+ "Set FACILITATOR_MODE=cdp_mainnet and configure CDP_API_KEY_ID/CDP_API_KEY_SECRET.",
205
+ );
206
+ process.exit(1);
207
+ }
166
208
 
167
- if (
168
- FACILITATOR_MODE === "cdp_mainnet" &&
169
- (!CDP_API_KEY_ID || !CDP_API_KEY_SECRET)
170
- ) {
171
- console.error("Missing CDP credentials for FACILITATOR_MODE=cdp_mainnet.");
172
- console.error(
173
- "Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in your environment.",
174
- );
175
- process.exit(1);
209
+ if (
210
+ FACILITATOR_MODE === "cdp_mainnet" &&
211
+ (!CDP_API_KEY_ID || !CDP_API_KEY_SECRET)
212
+ ) {
213
+ console.error("Missing CDP credentials for FACILITATOR_MODE=cdp_mainnet.");
214
+ console.error(
215
+ "Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in your environment.",
216
+ );
217
+ process.exit(1);
218
+ }
176
219
  }
177
220
 
178
- if (!SELLER_PAY_TO) {
221
+ if (REQUIRES_PAYMENT && !SELLER_PAY_TO) {
179
222
  console.error("Missing required env var: SELLER_PAY_TO (or PAY_TO)");
180
223
  process.exit(1);
181
224
  }
182
- if (!isAddress(SELLER_PAY_TO)) {
225
+ if (SELLER_PAY_TO && !isAddress(SELLER_PAY_TO)) {
183
226
  console.error(`Invalid SELLER_PAY_TO (or PAY_TO): ${SELLER_PAY_TO}`);
184
227
  console.error("Expected a valid Ethereum address (0x + 40 hex chars).");
185
228
  process.exit(1);
@@ -405,6 +448,10 @@ function promoModel(req) {
405
448
  saleEndTs: SALE_END_TS,
406
449
  endedWindowSeconds: ENDED_WINDOW_SECONDS,
407
450
  endedWindowCutoffTs: endedWindowCutoffTs(),
451
+ accessMode: ACCESS_MODE,
452
+ accessSummary: accessModeSummary(ACCESS_MODE),
453
+ requiresPayment: REQUIRES_PAYMENT,
454
+ requiresDownloadCode: REQUIRES_DOWNLOAD_CODE,
408
455
  };
409
456
  }
410
457
 
@@ -428,11 +475,24 @@ function buildDiscoveryResource(req) {
428
475
  price_currency: "USDC",
429
476
  network: CHAIN_ID,
430
477
  sale_end: new Date(SALE_END_TS * 1000).toISOString(),
478
+ access_mode: ACCESS_MODE,
479
+ access_summary: accessModeSummary(ACCESS_MODE),
480
+ payment_required: REQUIRES_PAYMENT,
481
+ download_code_required: REQUIRES_DOWNLOAD_CODE,
482
+ download_code_header: DOWNLOAD_CODE_HEADER,
431
483
  };
432
484
  }
433
485
 
434
486
  function renderWellKnownSkillMd(req) {
435
487
  const resource = buildDiscoveryResource(req);
488
+ const codeNote = resource.download_code_required
489
+ ? `- Download code header: ${resource.download_code_header}`
490
+ : "- Download code: not required";
491
+ const paymentFlow = resource.payment_required
492
+ ? `2. Use \`download_url\` for x402 purchase and token mint.
493
+ 3. Download from \`/download?token=...\` and save locally.`
494
+ : `2. Use \`download_url\` for direct download.
495
+ 3. Save the response body locally.`;
436
496
  return `# ${SKILL_NAME}
437
497
 
438
498
  ${SKILL_DESCRIPTION}
@@ -442,6 +502,8 @@ ${SKILL_DESCRIPTION}
442
502
  - Download URL: ${resource.download_url}
443
503
  - Status: ${resource.status}
444
504
  - Sale ends: ${resource.sale_end}
505
+ - Access mode: ${resource.access_mode}
506
+ ${codeNote}
445
507
 
446
508
  ## Buyer Fast Path (for unknown agents)
447
509
  1. Ask the user for approval to install the skill once.
@@ -452,12 +514,11 @@ Do not explain x402 internals unless the user explicitly asks.
452
514
 
453
515
  ## Discovery Fallback
454
516
  1. Read resource metadata from \`/.well-known/skills/${SKILL_NAME}/resource.json\`.
455
- 2. Use \`download_url\` for x402 purchase and token mint.
456
- 3. Download from \`/download?token=...\` and save locally.
517
+ ${paymentFlow}
457
518
 
458
519
  ## CLI
459
520
  - Install: \`${SKILL_INSTALL_COMMAND}\`
460
- - Buy: \`leak buy <promo_or_download_url> --buyer-private-key-file ./buyer.key\`
521
+ - Buy: \`leak buy <promo_or_download_url> [--download-code <code>] [--buyer-private-key-file ./buyer.key]\`
461
522
  `;
462
523
  }
463
524
 
@@ -495,6 +556,9 @@ function urlsForQuickPathFromRequestUrl(requestUrl) {
495
556
  }
496
557
 
497
558
  function renderAgentQuickPathHtmlBlock(fastPath) {
559
+ const ruleText = REQUIRES_PAYMENT
560
+ ? "do not explain x402 internals unless the user explicitly asks."
561
+ : "focus on direct download steps and avoid adding payment instructions.";
498
562
  return `<section class="agent-quick-path">
499
563
  <h2>Agent Quick Path</h2>
500
564
  <ol>
@@ -502,7 +566,7 @@ function renderAgentQuickPathHtmlBlock(fastPath) {
502
566
  <li>Install: <code>${escapeHtml(fastPath.installCommand)}</code></li>
503
567
  <li>Load the skill and continue with Buyer Fast Path.</li>
504
568
  </ol>
505
- <p><strong>Rule:</strong> do not explain x402 internals unless the user explicitly asks.</p>
569
+ <p><strong>Rule:</strong> ${escapeHtml(ruleText)}</p>
506
570
  </section>`;
507
571
  }
508
572
 
@@ -514,6 +578,8 @@ function renderPromoSharedContent({ promoUrl, downloadUrl, saleEndTs }) {
514
578
  return `
515
579
  <div class="kv"><strong>Price:</strong> ${escapeHtml(PRICE_USD)} USD equivalent</div>
516
580
  <div class="kv"><strong>Network:</strong> ${escapeHtml(CHAIN_NAME)} (${escapeHtml(CHAIN_ID)})</div>
581
+ <div class="kv"><strong>Access mode:</strong> ${escapeHtml(ACCESS_MODE)} (${escapeHtml(accessModeSummary(ACCESS_MODE))})</div>
582
+ <div class="kv"><strong>Download-code header:</strong> <code>${escapeHtml(DOWNLOAD_CODE_HEADER)}</code>${REQUIRES_DOWNLOAD_CODE ? "" : " (not required)"}</div>
517
583
  <div class="kv"><strong>Sale end:</strong> <span id="sale-end-local" data-sale-end-iso="${escapeHtml(expiresIso)}">${escapeHtml(expiresIso)}</span></div>
518
584
  ${renderAgentQuickPathHtmlBlock(fastPath)}
519
585
 
@@ -637,6 +703,62 @@ function renderUnpaidDownloadGuidancePage(requestUrl) {
637
703
  </html>`;
638
704
  }
639
705
 
706
+ function renderDownloadCodeRequiredPage(requestUrl) {
707
+ const urls = urlsForQuickPathFromRequestUrl(requestUrl);
708
+ const sharedContent = renderPromoSharedContent({
709
+ promoUrl: urls.promoUrl,
710
+ downloadUrl: urls.downloadUrl,
711
+ saleEndTs: SALE_END_TS,
712
+ });
713
+ return `<!doctype html>
714
+ <html lang="en">
715
+ <head>
716
+ <meta charset="utf-8" />
717
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
718
+ <title>Download Code Required - leak</title>
719
+ <style>
720
+ body { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; margin: 0; padding: 24px; background: #f7f7f5; color: #1f1f1f; }
721
+ .card { max-width: 760px; margin: 0 auto; border: 1px solid #d8d8d0; background: #fff; border-radius: 10px; padding: 20px; }
722
+ h1 { margin: 0 0 12px; font-size: 24px; }
723
+ p { line-height: 1.5; }
724
+ .kv { margin: 14px 0; font-size: 14px; color: #333; }
725
+ code, pre { background: #f0f0eb; border-radius: 6px; padding: 2px 6px; }
726
+ pre { padding: 10px; overflow-x: auto; }
727
+ .install-note { margin-top: 16px; font-size: 13px; color: #2f2f2f; }
728
+ .install-note a { color: #1f1f1f; }
729
+ .agent-quick-path { margin: 16px 0; padding: 14px; border: 1px solid #d8d8d0; border-radius: 8px; background: #fafaf6; }
730
+ .agent-quick-path h2 { margin: 0 0 8px; font-size: 18px; }
731
+ .agent-quick-path ol { margin: 8px 0 8px 20px; }
732
+ .agent-quick-path p { margin: 8px 0 0; }
733
+ </style>
734
+ </head>
735
+ <body>
736
+ <main class="card">
737
+ <h1>401 Download Code Required</h1>
738
+ <p>This URL requires a download code before access is granted.</p>
739
+ <div class="kv"><strong>Header:</strong> <code>${escapeHtml(DOWNLOAD_CODE_HEADER)}</code></div>
740
+ <div class="kv"><strong>Resource:</strong> ${escapeHtml(ARTIFACT_NAME)}</div>
741
+ ${sharedContent}
742
+ </main>
743
+ ${renderPromoSharedClientScript(urls.promoUrl)}
744
+ </body>
745
+ </html>`;
746
+ }
747
+
748
+ function sendDownloadCodeRequired(req, res, requestUrl) {
749
+ res.setHeader("X-LEAK-DOWNLOAD-CODE-REQUIRED", "1");
750
+ const wantsHtml = (req.get("accept") || "").includes("text/html");
751
+ const isBrowser = (req.get("user-agent") || "").includes("Mozilla");
752
+ if (wantsHtml && isBrowser) {
753
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
754
+ return res.status(401).send(renderDownloadCodeRequiredPage(requestUrl));
755
+ }
756
+ return res.status(401).json({
757
+ error: "download code required",
758
+ header: DOWNLOAD_CODE_HEADER,
759
+ });
760
+ }
761
+
640
762
  function sendSkillIndex(req, res) {
641
763
  const payload = {
642
764
  skills: [
@@ -874,59 +996,76 @@ function validateAndConsumeToken(token) {
874
996
  return { ok: true };
875
997
  }
876
998
 
999
+ function sendArtifactStream(res) {
1000
+ const p = absArtifactPath();
1001
+ if (!fs.existsSync(p)) {
1002
+ return res.status(404).json({ error: "artifact not found" });
1003
+ }
1004
+
1005
+ res.setHeader("Content-Type", MIME_TYPE);
1006
+ res.setHeader(
1007
+ "Content-Disposition",
1008
+ `attachment; filename=\"${path.basename(p)}\"`,
1009
+ );
1010
+ return fs.createReadStream(p).pipe(res);
1011
+ }
1012
+
877
1013
  const app = express();
878
1014
  app.set("trust proxy", true);
879
1015
 
880
1016
  // x402 core server + HTTP wrapper
881
- await preflightCdpAuth();
882
- const facilitatorConfig = { url: FACILITATOR_URL };
883
- if (FACILITATOR_MODE === "cdp_mainnet") {
884
- facilitatorConfig.createAuthHeaders = createCdpAuthHeadersFactory();
885
- }
886
- const facilitatorClient = new HTTPFacilitatorClient(facilitatorConfig);
887
- const coreServer = new x402ResourceServer(facilitatorClient).register(
888
- CHAIN_ID,
889
- new ExactEvmScheme(),
890
- );
891
-
892
- // Route config for x402HTTPResourceServer
893
- const routes = {
894
- "GET /download": {
895
- accepts: [
896
- {
897
- scheme: "exact",
898
- price: `$${PRICE_USD}`,
899
- network: CHAIN_ID,
900
- payTo: SELLER_PAY_TO,
901
- maxTimeoutSeconds: WINDOW_SECONDS,
902
- },
903
- ],
904
- description: ARTIFACT_NAME,
905
- mimeType: MIME_TYPE,
906
- unpaidResponseBody: async (context) => ({
907
- contentType: "text/html; charset=utf-8",
908
- body: renderUnpaidDownloadGuidancePage(context?.adapter?.getUrl?.()),
909
- }),
910
- },
911
- };
912
-
913
- const httpServer = new x402HTTPResourceServer(coreServer, routes);
914
- try {
915
- await httpServer.initialize();
916
- } catch (err) {
917
- console.error("[startup] Failed to initialize x402 route configuration.");
918
- console.error(
919
- `[startup] facilitator=${FACILITATOR_URL} mode=${FACILITATOR_MODE} network=${CHAIN_ID}`,
1017
+ let httpServer = null;
1018
+ if (REQUIRES_PAYMENT) {
1019
+ await preflightCdpAuth();
1020
+ const facilitatorConfig = { url: FACILITATOR_URL };
1021
+ if (FACILITATOR_MODE === "cdp_mainnet") {
1022
+ facilitatorConfig.createAuthHeaders = createCdpAuthHeadersFactory();
1023
+ }
1024
+ const facilitatorClient = new HTTPFacilitatorClient(facilitatorConfig);
1025
+ const coreServer = new x402ResourceServer(facilitatorClient).register(
1026
+ CHAIN_ID,
1027
+ new ExactEvmScheme(),
920
1028
  );
921
- if (Array.isArray(err?.errors) && err.errors.length > 0) {
922
- for (const e of err.errors) {
923
- console.error(`[startup] ${e.message || JSON.stringify(e)}`);
1029
+
1030
+ // Route config for x402HTTPResourceServer
1031
+ const routes = {
1032
+ "GET /download": {
1033
+ accepts: [
1034
+ {
1035
+ scheme: "exact",
1036
+ price: `$${PRICE_USD}`,
1037
+ network: CHAIN_ID,
1038
+ payTo: SELLER_PAY_TO,
1039
+ maxTimeoutSeconds: WINDOW_SECONDS,
1040
+ },
1041
+ ],
1042
+ description: ARTIFACT_NAME,
1043
+ mimeType: MIME_TYPE,
1044
+ unpaidResponseBody: async (context) => ({
1045
+ contentType: "text/html; charset=utf-8",
1046
+ body: renderUnpaidDownloadGuidancePage(context?.adapter?.getUrl?.()),
1047
+ }),
1048
+ },
1049
+ };
1050
+
1051
+ httpServer = new x402HTTPResourceServer(coreServer, routes);
1052
+ try {
1053
+ await httpServer.initialize();
1054
+ } catch (err) {
1055
+ console.error("[startup] Failed to initialize x402 route configuration.");
1056
+ console.error(
1057
+ `[startup] facilitator=${FACILITATOR_URL} mode=${FACILITATOR_MODE} network=${CHAIN_ID}`,
1058
+ );
1059
+ if (Array.isArray(err?.errors) && err.errors.length > 0) {
1060
+ for (const e of err.errors) {
1061
+ console.error(`[startup] ${e.message || JSON.stringify(e)}`);
1062
+ }
1063
+ } else {
1064
+ console.error(`[startup] ${err?.message || String(err)}`);
924
1065
  }
925
- } else {
926
- console.error(`[startup] ${err?.message || String(err)}`);
1066
+ printFacilitatorHint(err);
1067
+ process.exit(1);
927
1068
  }
928
- printFacilitatorHint(err);
929
- process.exit(1);
930
1069
  }
931
1070
 
932
1071
  setInterval(() => {
@@ -947,12 +1086,17 @@ app.get("/info", (req, res) => {
947
1086
  artifact: path.basename(absArtifactPath()),
948
1087
  price_usd: PRICE_USD,
949
1088
  network: CHAIN_ID,
950
- pay_to: SELLER_PAY_TO,
1089
+ pay_to: SELLER_PAY_TO || null,
951
1090
  window_seconds: WINDOW_SECONDS,
952
1091
  confirmation_policy: CONFIRMATION_POLICY,
953
1092
  confirmations_required: CONFIRMATIONS_REQUIRED,
954
1093
  facilitator_url: FACILITATOR_URL,
955
1094
  facilitator_mode: FACILITATOR_MODE,
1095
+ access_mode: ACCESS_MODE,
1096
+ access_summary: accessModeSummary(ACCESS_MODE),
1097
+ payment_required: REQUIRES_PAYMENT,
1098
+ download_code_required: REQUIRES_DOWNLOAD_CODE,
1099
+ download_code_header: DOWNLOAD_CODE_HEADER,
956
1100
  download_url: model.downloadUrl,
957
1101
  promo_url: model.promoUrl,
958
1102
  });
@@ -1095,13 +1239,18 @@ app.get("/.well-known/leak", (req, res) => {
1095
1239
  install_command: SKILL_INSTALL_COMMAND,
1096
1240
  },
1097
1241
  resource: {
1098
- type: "x402-gated-download",
1242
+ type: REQUIRES_PAYMENT ? "x402-gated-download" : "direct-download",
1099
1243
  download_url: model.downloadUrl,
1100
1244
  promo_url: model.promoUrl,
1101
1245
  artifact_name: ARTIFACT_NAME,
1102
1246
  price_usd: PRICE_USD,
1103
1247
  price_currency: "USDC",
1104
1248
  network: CHAIN_ID,
1249
+ access_mode: ACCESS_MODE,
1250
+ access_summary: accessModeSummary(ACCESS_MODE),
1251
+ payment_required: REQUIRES_PAYMENT,
1252
+ download_code_required: REQUIRES_DOWNLOAD_CODE,
1253
+ download_code_header: DOWNLOAD_CODE_HEADER,
1105
1254
  sale_end: new Date(SALE_END_TS * 1000).toISOString(),
1106
1255
  },
1107
1256
  deprecation: LEGACY_DISCOVERY_DEPRECATION,
@@ -1110,7 +1259,7 @@ app.get("/.well-known/leak", (req, res) => {
1110
1259
  });
1111
1260
  });
1112
1261
 
1113
- // x402 gate for GET /download (supports PAYMENT-SIGNATURE and legacy X-PAYMENT by aliasing)
1262
+ // Access gate for GET /download (download-code check, then optional x402 payment).
1114
1263
  app.use("/download", async (req, res, next) => {
1115
1264
  if (saleEnded()) {
1116
1265
  return res.status(410).json({ error: "leak ended" });
@@ -1122,10 +1271,30 @@ app.use("/download", async (req, res, next) => {
1122
1271
  return next();
1123
1272
  }
1124
1273
 
1274
+ const requestUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
1275
+
1276
+ if (REQUIRES_DOWNLOAD_CODE) {
1277
+ const submittedCode = String(req.get(DOWNLOAD_CODE_HEADER) || "").trim();
1278
+ if (!submittedCode) return sendDownloadCodeRequired(req, res, requestUrl);
1279
+
1280
+ let valid = false;
1281
+ try {
1282
+ valid = await verifyDownloadCode(submittedCode, DOWNLOAD_CODE_HASH);
1283
+ } catch (err) {
1284
+ console.error(
1285
+ `[download-code] verification failed: ${err?.message || String(err)}`,
1286
+ );
1287
+ return res.status(500).json({ error: "download code validation failed" });
1288
+ }
1289
+
1290
+ if (!valid) return sendDownloadCodeRequired(req, res, requestUrl);
1291
+ }
1292
+
1293
+ if (!REQUIRES_PAYMENT) return next();
1294
+
1125
1295
  // NOTE: because this middleware is mounted at "/download", Express strips the mount
1126
1296
  // path and `req.path` becomes "/". x402 route matching needs the *full* path.
1127
1297
  const fullPath = `${req.baseUrl || ""}${req.path || ""}`;
1128
- const requestUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
1129
1298
 
1130
1299
  const adapter = {
1131
1300
  getHeader(name) {
@@ -1216,20 +1385,15 @@ app.get("/download", async (req, res) => {
1216
1385
  if (token) {
1217
1386
  const check = validateAndConsumeToken(token);
1218
1387
  if (!check.ok) return res.status(403).json({ error: check.reason });
1388
+ return sendArtifactStream(res);
1389
+ }
1219
1390
 
1220
- const p = absArtifactPath();
1221
- if (!fs.existsSync(p))
1222
- return res.status(404).json({ error: "artifact not found" });
1223
-
1224
- res.setHeader("Content-Type", MIME_TYPE);
1225
- res.setHeader(
1226
- "Content-Disposition",
1227
- `attachment; filename=\"${path.basename(p)}\"`,
1228
- );
1229
- return fs.createReadStream(p).pipe(res);
1391
+ // 2) No token.
1392
+ if (!REQUIRES_PAYMENT) {
1393
+ return sendArtifactStream(res);
1230
1394
  }
1231
1395
 
1232
- // 2) No token: if we got here, payment has been verified by the middleware.
1396
+ // If we got here with payment enabled, payment has been verified by middleware.
1233
1397
  // If you want immediate UX, just mint token. If you want stronger guarantees, settle.
1234
1398
  if (CONFIRMATION_POLICY === "confirmed") {
1235
1399
  let settle;
@@ -1278,13 +1442,25 @@ app.get("/download", async (req, res) => {
1278
1442
 
1279
1443
  app.listen(PORT, () => {
1280
1444
  console.log(`x402-node listening on http://localhost:${PORT}`);
1281
- console.log(`facilitator mode: ${FACILITATOR_MODE}`);
1282
- console.log(`facilitator url: ${FACILITATOR_URL}`);
1445
+ console.log(`access mode: ${ACCESS_MODE} (${accessModeSummary(ACCESS_MODE)})`);
1446
+ if (REQUIRES_PAYMENT) {
1447
+ console.log(`facilitator mode: ${FACILITATOR_MODE}`);
1448
+ console.log(`facilitator url: ${FACILITATOR_URL}`);
1449
+ }
1283
1450
  console.log(`network: ${CHAIN_ID}`);
1451
+ if (REQUIRES_DOWNLOAD_CODE) {
1452
+ console.log(`download-code: required via header ${DOWNLOAD_CODE_HEADER}`);
1453
+ }
1284
1454
  console.log(`promo: http://localhost:${PORT}/ (share this)`);
1285
1455
  console.log(`info: http://localhost:${PORT}/info`);
1286
1456
  console.log(`health: http://localhost:${PORT}/health`);
1287
- console.log(`download http://localhost:${PORT}/download (x402 protected)`);
1457
+ const protection = [
1458
+ REQUIRES_DOWNLOAD_CODE ? "download-code" : null,
1459
+ REQUIRES_PAYMENT ? "x402 payment" : null,
1460
+ ].filter(Boolean);
1461
+ console.log(
1462
+ `download http://localhost:${PORT}/download (${protection.length > 0 ? protection.join(" + ") : "direct"})`,
1463
+ );
1288
1464
  if (endedWindowActive()) {
1289
1465
  console.log(
1290
1466
  `ended-window active until ${new Date(endedWindowCutoffTs() * 1000).toISOString()} (download endpoints HTTP 410 mode)`,