@devlab.group/agent-commerce 0.1.0-alpha.0 → 0.2.0-beta.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/dist/cli/index.js CHANGED
@@ -267,6 +267,54 @@ var init_core = __esm({
267
267
  }
268
268
  });
269
269
 
270
+ // src/payments/x402/networks.ts
271
+ function findNetworkProfile(id) {
272
+ return BY_ID.get(id);
273
+ }
274
+ function requireNetworkProfile(id, path2) {
275
+ const profile = BY_ID.get(id);
276
+ if (!profile) {
277
+ throw new CommerceError(
278
+ "CONFIG_INVALID",
279
+ `${path2}: "${id}" is not a supported network. Supported CAIP-2 identifiers: ${SUPPORTED_NETWORK_IDS.join(", ")}`,
280
+ { details: { path: path2, network: id } }
281
+ );
282
+ }
283
+ return profile;
284
+ }
285
+ function resolveDeploymentMode(profile, facilitatorMode) {
286
+ if (facilitatorMode === "local") return "local";
287
+ return profile.kind;
288
+ }
289
+ function describeDeploymentMode(mode) {
290
+ return mode === "mainnet" ? LIVE_MAINNET_BANNER : mode.toUpperCase();
291
+ }
292
+ var PROFILES, BY_ID, SUPPORTED_NETWORK_IDS, LIVE_MAINNET_BANNER;
293
+ var init_networks = __esm({
294
+ "src/payments/x402/networks.ts"() {
295
+ init_core();
296
+ PROFILES = [
297
+ {
298
+ id: "eip155:84532",
299
+ chainId: 84532,
300
+ displayName: "Base Sepolia",
301
+ kind: "testnet",
302
+ canonicalAsset: { symbol: "USDC", address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e" }
303
+ },
304
+ {
305
+ id: "eip155:8453",
306
+ chainId: 8453,
307
+ displayName: "Base",
308
+ kind: "mainnet",
309
+ canonicalAsset: { symbol: "USDC", address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" }
310
+ }
311
+ ];
312
+ BY_ID = new Map(PROFILES.map((profile) => [profile.id, profile]));
313
+ SUPPORTED_NETWORK_IDS = PROFILES.map((p) => p.id);
314
+ LIVE_MAINNET_BANNER = "LIVE MAINNET MODE \u2014 REAL FUNDS";
315
+ }
316
+ });
317
+
270
318
  // src/config/env.ts
271
319
  function substituteEnv(value, env, path2 = "$") {
272
320
  if (typeof value === "string") {
@@ -329,6 +377,142 @@ var init_env = __esm({
329
377
  PLACEHOLDER_PATTERN = /\$\{([A-Za-z_][A-Za-z0-9_]*)(:-([^}]*))?\}/g;
330
378
  }
331
379
  });
380
+
381
+ // src/payments/x402/local-chain/accounts.ts
382
+ var ANVIL_WELL_KNOWN_ACCOUNTS;
383
+ var init_accounts = __esm({
384
+ "src/payments/x402/local-chain/accounts.ts"() {
385
+ ANVIL_WELL_KNOWN_ACCOUNTS = [
386
+ {
387
+ address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
388
+ privateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
389
+ },
390
+ {
391
+ address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
392
+ privateKey: "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
393
+ },
394
+ {
395
+ address: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
396
+ privateKey: "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
397
+ }
398
+ ];
399
+ ANVIL_WELL_KNOWN_ACCOUNTS[0];
400
+ ANVIL_WELL_KNOWN_ACCOUNTS[1];
401
+ ANVIL_WELL_KNOWN_ACCOUNTS[2];
402
+ }
403
+ });
404
+
405
+ // src/payments/x402/dev-key-guard.ts
406
+ function isWellKnownDevAddress(address) {
407
+ return WELL_KNOWN_DEV_ADDRESSES.has(address.toLowerCase());
408
+ }
409
+ function isLikelyLocalOrPrivateHost(hostname) {
410
+ const h = hostname.toLowerCase().replace(/^\[|\]$/g, "");
411
+ if (h === "localhost" || h === "127.0.0.1" || h === "::1") return true;
412
+ if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
413
+ if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
414
+ if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
415
+ if (/^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
416
+ if (!h.includes(".") && !h.includes(":")) return true;
417
+ return false;
418
+ }
419
+ var WELL_KNOWN_DEV_ADDRESSES;
420
+ var init_dev_key_guard = __esm({
421
+ "src/payments/x402/dev-key-guard.ts"() {
422
+ init_core();
423
+ init_accounts();
424
+ new Set(
425
+ ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.privateKey.toLowerCase())
426
+ );
427
+ WELL_KNOWN_DEV_ADDRESSES = new Set(
428
+ ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.address.toLowerCase())
429
+ );
430
+ }
431
+ });
432
+
433
+ // src/payments/x402/guardrails.ts
434
+ function invalid(message, path2) {
435
+ return new CommerceError("CONFIG_INVALID", message, { details: { path: path2 } });
436
+ }
437
+ function resolveX402Deployment(input) {
438
+ const profile = requireNetworkProfile(input.network, "payments.x402.network");
439
+ const mode = resolveDeploymentMode(profile, input.facilitator.mode);
440
+ if (profile.kind === "mainnet" && input.facilitator.mode === "local") {
441
+ throw invalid(
442
+ `payments.x402: network "${profile.id}" (${profile.displayName}) is a mainnet and cannot be served by facilitator.mode "local". A mainnet deployment must settle through a remote facilitator.`,
443
+ "payments.x402.facilitator.mode"
444
+ );
445
+ }
446
+ if (mode === "mainnet" && input.allowMainnet !== true) {
447
+ throw invalid(
448
+ `payments.x402: network "${profile.id}" (${profile.displayName}) settles real funds. Set payments.x402.allowMainnet: true to acknowledge this explicitly \u2014 it is never the default.`,
449
+ "payments.x402.allowMainnet"
450
+ );
451
+ }
452
+ if (input.facilitator.mode === "remote") {
453
+ assertFacilitatorUrlIsSafe(input.facilitator.url, mode);
454
+ if (mode === "mainnet" && input.facilitator.auth.type === "none") {
455
+ throw invalid(
456
+ "payments.x402: a mainnet facilitator must be authenticated. Set payments.x402.facilitator.auth to a supported type \u2014 an unauthenticated production facilitator is refused.",
457
+ "payments.x402.facilitator.auth"
458
+ );
459
+ }
460
+ if (input.facilitator.auth.type === "bearer" && input.facilitator.auth.token.trim() === "") {
461
+ throw invalid(
462
+ 'payments.x402: facilitator.auth.type is "bearer" but the token is empty. An empty credential is refused rather than sent.',
463
+ "payments.x402.facilitator.auth.token"
464
+ );
465
+ }
466
+ }
467
+ if (mode !== "local" && isWellKnownDevAddress(input.payTo)) {
468
+ throw invalid(
469
+ `payments.x402: "payTo" (${input.payTo}) is a well-known Anvil development address and this is a ${mode} deployment. Anyone can spend what settles there. Set payTo to your own merchant wallet.`,
470
+ "payments.x402.payTo"
471
+ );
472
+ }
473
+ const canonical = profile.canonicalAsset;
474
+ if (mode === "mainnet" && canonical && !sameAddress(input.asset, canonical.address)) {
475
+ throw invalid(
476
+ `payments.x402: asset ${input.asset} is not ${canonical.symbol} on ${profile.displayName} (expected ${canonical.address}). Settling a mainnet payment in an unintended token is refused.`,
477
+ "payments.x402.asset"
478
+ );
479
+ }
480
+ return { profile, mode };
481
+ }
482
+ function assertFacilitatorUrlIsSafe(url, mode) {
483
+ let parsed;
484
+ try {
485
+ parsed = new URL(url);
486
+ } catch (cause) {
487
+ throw new CommerceError(
488
+ "CONFIG_INVALID",
489
+ `payments.x402: facilitator.url "${url}" is not a valid URL`,
490
+ { cause, details: { path: "payments.x402.facilitator.url" } }
491
+ );
492
+ }
493
+ if (parsed.protocol === "https:") return;
494
+ if (parsed.protocol !== "http:") {
495
+ throw invalid(
496
+ `payments.x402: facilitator.url must be https (or http on a local/private host); got "${parsed.protocol}//"`,
497
+ "payments.x402.facilitator.url"
498
+ );
499
+ }
500
+ if (mode !== "mainnet" && isLikelyLocalOrPrivateHost(parsed.hostname)) return;
501
+ throw invalid(
502
+ `payments.x402: facilitator.url "${url}" uses plain HTTP. Payment authorisations and settlement results would travel unencrypted. Use https, or point at a local/private host on a non-mainnet deployment.`,
503
+ "payments.x402.facilitator.url"
504
+ );
505
+ }
506
+ function sameAddress(a, b) {
507
+ return a.toLowerCase() === b.toLowerCase();
508
+ }
509
+ var init_guardrails = __esm({
510
+ "src/payments/x402/guardrails.ts"() {
511
+ init_core();
512
+ init_dev_key_guard();
513
+ init_networks();
514
+ }
515
+ });
332
516
  function parseConfig(raw, env) {
333
517
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
334
518
  throw new CommerceError(
@@ -435,14 +619,12 @@ function normalise(raw) {
435
619
  }
436
620
  };
437
621
  const x402Raw = raw.payments.x402;
438
- if (x402Raw?.facilitator.mode === "remote") {
439
- throw new CommerceError(
440
- "CONFIG_INVALID",
441
- 'payments.x402.facilitator.mode "remote" is not supported in v0.1.0-alpha; use "local" (the deterministic dev-chain facilitator)',
442
- { details: { path: "payments.x402.facilitator.mode" } }
443
- );
444
- }
445
- const x402 = x402Raw ? {
622
+ const facilitator = x402Raw === void 0 ? void 0 : x402Raw.facilitator.mode === "local" ? { mode: "local", signerPrivateKey: x402Raw.facilitator.signerPrivateKey } : {
623
+ mode: "remote",
624
+ url: x402Raw.facilitator.url,
625
+ auth: x402Raw.facilitator.auth ?? { type: "none" }
626
+ };
627
+ const x402 = x402Raw && facilitator ? {
446
628
  enabled: toBoolean(x402Raw.enabled, "payments.x402.enabled"),
447
629
  network: x402Raw.network,
448
630
  rpcUrl: x402Raw.rpcUrl,
@@ -454,18 +636,26 @@ function normalise(raw) {
454
636
  max: 36
455
637
  }),
456
638
  payTo: x402Raw.payTo,
457
- maxTimeoutSeconds: toNumber(x402Raw.maxTimeoutSeconds, "payments.x402.maxTimeoutSeconds", {
458
- min: 1
459
- }),
460
- // Only "local" can reach here — "remote" was rejected above.
461
- facilitator: {
462
- mode: "local",
463
- signerPrivateKey: x402Raw.facilitator.signerPrivateKey
464
- }
639
+ maxTimeoutSeconds: toNumber(
640
+ x402Raw.maxTimeoutSeconds,
641
+ "payments.x402.maxTimeoutSeconds",
642
+ {
643
+ min: 1
644
+ }
645
+ ),
646
+ facilitator,
647
+ ...x402Raw.allowMainnet !== void 0 ? { allowMainnet: toBoolean(x402Raw.allowMainnet, "payments.x402.allowMainnet") } : {}
465
648
  } : void 0;
466
649
  if (x402) {
467
650
  validateAddress("payments.x402.payTo", x402.payTo);
468
651
  validateAddress("payments.x402.asset", x402.asset);
652
+ resolveX402Deployment({
653
+ network: x402.network,
654
+ payTo: x402.payTo,
655
+ asset: x402.asset,
656
+ facilitator: x402.facilitator,
657
+ ...x402.allowMainnet !== void 0 ? { allowMainnet: x402.allowMainnet } : {}
658
+ });
469
659
  }
470
660
  const resources = Object.entries(raw.resources).map(
471
661
  ([id, entry]) => normaliseResource(id, entry, protocols, x402)
@@ -510,7 +700,7 @@ function normaliseResource(id, entry, protocols, x402) {
510
700
  }
511
701
  for (const protocol of entry.expose) {
512
702
  if (!SUPPORTED_PROTOCOLS.has(protocol)) {
513
- const hint = protocol === "ucp" ? " (UCP is planned, not supported in v0.1.0-alpha)" : "";
703
+ const hint = protocol === "ucp" ? " (UCP is planned, not supported in this release)" : "";
514
704
  throw new CommerceError(
515
705
  "CONFIG_INVALID",
516
706
  `Resource "${id}" exposes unsupported protocol "${protocol}"${hint}. Supported: http, mcp.`,
@@ -546,7 +736,7 @@ function normaliseResource(id, entry, protocols, x402) {
546
736
  if (entry.pricing.type === "dynamic") {
547
737
  throw new CommerceError(
548
738
  "CONFIG_INVALID",
549
- `Resource "${id}" uses pricing.type "dynamic", which is not supported in v0.1.0-alpha`,
739
+ `Resource "${id}" uses pricing.type "dynamic", which is not supported in this release`,
550
740
  { details: { path: `resources.${id}.pricing.type`, resourceId: id } }
551
741
  );
552
742
  }
@@ -778,11 +968,12 @@ function validateAddress(path2, value) {
778
968
  );
779
969
  }
780
970
  }
781
- var SUPPORTED_CONFIG_VERSION, ADDRESS_PATTERN, ZERO_ADDRESS_PATTERN, MCP_TOOL_NAME_PATTERN, NumberOrString, BooleanOrString, MerchantSchema, ServerSchema, StorageSchema, ProtocolsSchema, BackendMethodSchema, BackendHandlerSchema, PricingSchema, JsonSchemaValueSchema, ResourceEntrySchema, ResourcesMapSchema, FacilitatorSchema, X402Schema, PaymentsSchema, RawConfigSchema, SUPPORTED_PROTOCOLS, SUPPORTED_PAYMENT_METHODS, EMPTY_CLOSED_OBJECT_SCHEMA, UNSUPPORTED_SCHEMA_KEYWORDS, PRICING_AMOUNT_PATTERN, ZERO_AMOUNT_PATTERN;
971
+ var SUPPORTED_CONFIG_VERSION, ADDRESS_PATTERN, ZERO_ADDRESS_PATTERN, MCP_TOOL_NAME_PATTERN, NumberOrString, BooleanOrString, MerchantSchema, ServerSchema, StorageSchema, ProtocolsSchema, BackendMethodSchema, BackendHandlerSchema, PricingSchema, JsonSchemaValueSchema, ResourceEntrySchema, ResourcesMapSchema, FacilitatorAuthSchema, FacilitatorSchema, X402Schema, PaymentsSchema, RawConfigSchema, SUPPORTED_PROTOCOLS, SUPPORTED_PAYMENT_METHODS, EMPTY_CLOSED_OBJECT_SCHEMA, UNSUPPORTED_SCHEMA_KEYWORDS, PRICING_AMOUNT_PATTERN, ZERO_AMOUNT_PATTERN;
782
972
  var init_schema = __esm({
783
973
  "src/config/schema.ts"() {
784
974
  init_execution();
785
975
  init_core();
976
+ init_guardrails();
786
977
  init_env();
787
978
  SUPPORTED_CONFIG_VERSION = 1;
788
979
  ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
@@ -859,6 +1050,10 @@ var init_schema = __esm({
859
1050
  payments: z.array(z.string().min(1)).optional()
860
1051
  }).strict();
861
1052
  ResourcesMapSchema = z.record(z.string().min(1), ResourceEntrySchema);
1053
+ FacilitatorAuthSchema = z.discriminatedUnion("type", [
1054
+ z.object({ type: z.literal("none") }).strict(),
1055
+ z.object({ type: z.literal("bearer"), token: z.string().min(1) }).strict()
1056
+ ]);
862
1057
  FacilitatorSchema = z.discriminatedUnion("mode", [
863
1058
  z.object({
864
1059
  mode: z.literal("local"),
@@ -866,7 +1061,12 @@ var init_schema = __esm({
866
1061
  }).strict(),
867
1062
  z.object({
868
1063
  mode: z.literal("remote"),
869
- url: z.string().min(1)
1064
+ url: z.string().min(1),
1065
+ // Absent means "this facilitator takes no credential" — an explicit
1066
+ // statement, normalised to `{ type: 'none' }` below. On a mainnet that
1067
+ // combination is refused outright, so the default can never quietly
1068
+ // become an unauthenticated production facilitator.
1069
+ auth: FacilitatorAuthSchema.optional()
870
1070
  }).strict()
871
1071
  ]);
872
1072
  X402Schema = z.object({
@@ -879,7 +1079,9 @@ var init_schema = __esm({
879
1079
  assetDecimals: NumberOrString,
880
1080
  payTo: z.string().min(1),
881
1081
  maxTimeoutSeconds: NumberOrString,
882
- facilitator: FacilitatorSchema
1082
+ facilitator: FacilitatorSchema,
1083
+ /** Real funds. Never defaulted — see src/payments/x402/guardrails.ts. */
1084
+ allowMainnet: BooleanOrString.optional()
883
1085
  }).strict();
884
1086
  PaymentsSchema = z.object({
885
1087
  x402: X402Schema.optional()
@@ -1080,10 +1282,11 @@ async function execCommand(command, args) {
1080
1282
  // src/cli/commands/doctor.ts
1081
1283
  init_execution();
1082
1284
  init_core();
1285
+ init_networks();
1083
1286
 
1084
1287
  // src/storage/receipts/store.ts
1085
1288
  init_core();
1086
- var PACKAGE_VERSION = "0.1.0-alpha.0" ;
1289
+ var PACKAGE_VERSION = "0.2.0-beta.0" ;
1087
1290
 
1088
1291
  // src/storage/receipts/redact.ts
1089
1292
  var SECRET_KEY_PATTERN = /private[-_ ]?key|secret|password|mnemonic|seed[-_ ]?phrase|api[-_ ]?key|authoriz(e|ation)|^auth$|auth[-_]?header|payment[-_ ]?proof|^proof$|x-payment|signature|signed[-_ ]?message|token|bearer|credential|cookie|session|jwt/i;
@@ -1598,23 +1801,8 @@ async function fetchJson(fetchImpl, url, timeoutMs = 1500) {
1598
1801
  }
1599
1802
  }
1600
1803
 
1601
- // src/payments/x402/local-chain/accounts.ts
1602
- var ANVIL_WELL_KNOWN_ACCOUNTS = [
1603
- {
1604
- address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1605
- privateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
1606
- },
1607
- {
1608
- address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
1609
- privateKey: "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
1610
- },
1611
- {
1612
- address: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
1613
- privateKey: "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
1614
- }
1615
- ];
1616
-
1617
1804
  // src/cli/lib/init-config.ts
1805
+ init_accounts();
1618
1806
  var LOCAL_DEV_MERCHANT_ADDRESS = ANVIL_WELL_KNOWN_ACCOUNTS[1].address;
1619
1807
  var LOCAL_DEV_FACILITATOR_PRIVATE_KEY = ANVIL_WELL_KNOWN_ACCOUNTS[0].privateKey;
1620
1808
  var PLACEHOLDER_ASSET_ADDRESS = "0x000000000000000000000000000000000000dEaD";
@@ -1692,7 +1880,7 @@ function buildInitConfigObject(answers) {
1692
1880
  payments: answers.x402Enabled ? {
1693
1881
  x402: {
1694
1882
  enabled: true,
1695
- network: "base-sepolia",
1883
+ network: "eip155:84532",
1696
1884
  rpcUrl: "http://127.0.0.1:8545",
1697
1885
  asset: PLACEHOLDER_ASSET_ADDRESS,
1698
1886
  assetName: "MockUSDC",
@@ -1845,7 +2033,7 @@ function maskMiddle(value, keep = 6) {
1845
2033
  }
1846
2034
  createRequire(import.meta.url);
1847
2035
  function readVersionReport() {
1848
- const injected = '{"cliVersion":"0.1.0-alpha.0","pinned":[{"name":"@modelcontextprotocol/sdk","version":"1.30.0","via":"@devlab.group/agent-commerce"},{"name":"x402","version":"1.2.0","via":"@devlab.group/agent-commerce"},{"name":"viem","version":"2.55.18","via":"@devlab.group/agent-commerce"},{"name":"better-sqlite3","version":"13.0.3","via":"@devlab.group/agent-commerce"},{"name":"fastify","version":"5.12.0","via":"@devlab.group/agent-commerce"},{"name":"pino","version":"10.3.1","via":"@devlab.group/agent-commerce"},{"name":"zod","version":"3.25.76","via":"@devlab.group/agent-commerce"}]}' ;
2036
+ const injected = '{"cliVersion":"0.2.0-beta.0","pinned":[{"name":"@modelcontextprotocol/sdk","version":"1.30.0","via":"@devlab.group/agent-commerce"},{"name":"@x402/core","version":"2.23.0","via":"@devlab.group/agent-commerce"},{"name":"@x402/evm","version":"2.23.0","via":"@devlab.group/agent-commerce"},{"name":"viem","version":"2.55.18","via":"@devlab.group/agent-commerce"},{"name":"better-sqlite3","version":"13.0.3","via":"@devlab.group/agent-commerce"},{"name":"fastify","version":"5.12.0","via":"@devlab.group/agent-commerce"},{"name":"pino","version":"10.3.1","via":"@devlab.group/agent-commerce"},{"name":"zod","version":"3.25.76","via":"@devlab.group/agent-commerce"}]}' ;
1849
2037
  {
1850
2038
  return JSON.parse(injected);
1851
2039
  }
@@ -1884,12 +2072,12 @@ function extractWellKnownX402(body) {
1884
2072
  }
1885
2073
  return { asset, network, payTo };
1886
2074
  }
1887
- function sameAddress(a, b) {
2075
+ function sameAddress2(a, b) {
1888
2076
  return a.toLowerCase() === b.toLowerCase();
1889
2077
  }
1890
2078
  function findX402Mismatch(configured, live) {
1891
2079
  const diffs = [];
1892
- if (!sameAddress(configured.asset, live.asset)) {
2080
+ if (!sameAddress2(configured.asset, live.asset)) {
1893
2081
  diffs.push(
1894
2082
  `asset: gateway is using ${live.asset} but local config resolves to ${configured.asset}`
1895
2083
  );
@@ -1899,7 +2087,7 @@ function findX402Mismatch(configured, live) {
1899
2087
  `network: gateway is using "${live.network}" but local config resolves to "${configured.network}"`
1900
2088
  );
1901
2089
  }
1902
- if (!sameAddress(configured.payTo, live.payTo)) {
2090
+ if (!sameAddress2(configured.payTo, live.payTo)) {
1903
2091
  diffs.push(
1904
2092
  `payTo: gateway is using ${live.payTo} but local config resolves to ${configured.payTo}`
1905
2093
  );
@@ -1994,9 +2182,13 @@ async function runDoctor(options, deps = {}) {
1994
2182
  if (x402 === void 0 || !x402.enabled) {
1995
2183
  checks.push({ name: "Payments", status: "INFO", detail: "x402 not configured" });
1996
2184
  } else {
1997
- const summary = `x402 enabled \u2014 network=${x402.network}, destination=${maskMiddle(x402.payTo)}, facilitator=${x402.facilitator.mode}`;
2185
+ const profile = findNetworkProfile(x402.network);
2186
+ const mode = profile ? resolveDeploymentMode(profile, x402.facilitator.mode) : void 0;
2187
+ const where = profile === void 0 || mode === void 0 ? `unknown network ${x402.network}` : mode === "local" ? `LOCAL dev chain (${x402.network}, chain id shared with ${profile.displayName})` : `${describeDeploymentMode(mode)} on ${profile.displayName} (${x402.network})`;
2188
+ const facilitatorDetail = x402.facilitator.mode === "local" ? "local" : `remote (auth=${x402.facilitator.auth.type})`;
2189
+ const summary = `x402 v2 (scheme=exact) enabled \u2014 ${where}, destination=${maskMiddle(x402.payTo)}, facilitator=${facilitatorDetail}`;
1998
2190
  const live = wellKnown?.ok ? extractWellKnownX402(wellKnown.body) : void 0;
1999
- if (sameAddress(x402.asset, PLACEHOLDER_ASSET_ADDRESS)) {
2191
+ if (sameAddress2(x402.asset, PLACEHOLDER_ASSET_ADDRESS)) {
2000
2192
  checks.push({
2001
2193
  name: "Payments",
2002
2194
  status: "WARN",
@@ -2020,11 +2212,18 @@ async function runDoctor(options, deps = {}) {
2020
2212
  mismatch === void 0 ? { name: "Payments", status: "PASS", detail: summary } : { name: "Payments", status: "FAIL", detail: mismatch }
2021
2213
  );
2022
2214
  }
2215
+ if (mode === "mainnet") {
2216
+ checks.push({
2217
+ name: "Mainnet safety",
2218
+ status: "INFO",
2219
+ detail: `${describeDeploymentMode("mainnet")} \u2014 enforced at config load: explicit allowMainnet opt-in, remote facilitator over HTTPS with a credential, non-development payTo, and the canonical asset for this network. Payments are fail-closed.`
2220
+ });
2221
+ }
2023
2222
  }
2024
2223
  checks.push({
2025
2224
  name: "Payments (MPP)",
2026
2225
  status: "INFO",
2027
- detail: "planned \u2014 not implemented in v0.1"
2226
+ detail: "planned \u2014 not implemented in this release"
2028
2227
  });
2029
2228
  if (config === void 0) {
2030
2229
  checks.push({ name: "Storage", status: "WARN", detail: "skipped \u2014 config invalid" });
@@ -2034,9 +2233,8 @@ async function runDoctor(options, deps = {}) {
2034
2233
  // from the host) silently produces a fresh empty database and a false
2035
2234
  // "healthy, receipts=0" PASS — a read-only command must not have this
2036
2235
  // side effect, and it shadows the real store's real receipts entirely.
2037
- //. `:memory:` never exists on disk by
2038
- // definition, so it always falls through to the real open/health/count
2039
- // branch below, same as today.
2236
+ // `:memory:` never exists on disk by definition, so it always falls
2237
+ // through to the real open/health/count branch below, same as today.
2040
2238
  config.storage.receipts.path !== ":memory:" && !existsSync(config.storage.receipts.path)
2041
2239
  ) {
2042
2240
  checks.push({
@@ -2379,6 +2577,12 @@ function buildProgram(io = processIo) {
2379
2577
  }
2380
2578
 
2381
2579
  // src/cli/index.ts
2580
+ for (const stream of [process.stdout, process.stderr]) {
2581
+ stream.on("error", (err) => {
2582
+ if (err.code === "EPIPE") process.exit(0);
2583
+ throw err;
2584
+ });
2585
+ }
2382
2586
  async function main() {
2383
2587
  const program = buildProgram();
2384
2588
  try {