@devlab.group/agent-commerce 0.1.0-alpha.0 → 1.0.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
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { ZodError, z } from 'zod';
3
- import { existsSync, mkdirSync, accessSync, constants, readFileSync, chmodSync, promises } from 'fs';
3
+ import { existsSync, mkdirSync, accessSync, constants, closeSync, openSync, readFileSync, chmodSync, promises } from 'fs';
4
4
  import * as path from 'path';
5
5
  import { resolve, dirname, join } from 'path';
6
6
  import { stringify, parse, YAMLError } from 'yaml';
@@ -267,6 +267,64 @@ 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: {
303
+ symbol: "USDC",
304
+ address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
305
+ name: "USDC",
306
+ version: "2"
307
+ }
308
+ },
309
+ {
310
+ id: "eip155:8453",
311
+ chainId: 8453,
312
+ displayName: "Base",
313
+ kind: "mainnet",
314
+ canonicalAsset: {
315
+ symbol: "USDC",
316
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
317
+ name: "USD Coin",
318
+ version: "2"
319
+ }
320
+ }
321
+ ];
322
+ BY_ID = new Map(PROFILES.map((profile) => [profile.id, profile]));
323
+ SUPPORTED_NETWORK_IDS = PROFILES.map((p) => p.id);
324
+ LIVE_MAINNET_BANNER = "LIVE MAINNET MODE \u2014 REAL FUNDS";
325
+ }
326
+ });
327
+
270
328
  // src/config/env.ts
271
329
  function substituteEnv(value, env, path2 = "$") {
272
330
  if (typeof value === "string") {
@@ -285,19 +343,26 @@ function substituteEnv(value, env, path2 = "$") {
285
343
  return value;
286
344
  }
287
345
  function substituteString(value, env, path2) {
288
- const substituted = substituteOnce(value, env, path2);
289
- PLACEHOLDER_PATTERN.lastIndex = 0;
290
- if (PLACEHOLDER_PATTERN.test(substituted)) {
291
- PLACEHOLDER_PATTERN.lastIndex = 0;
292
- const residual = PLACEHOLDER_PATTERN.exec(substituted)?.[0] ?? "${\u2026}";
293
- PLACEHOLDER_PATTERN.lastIndex = 0;
346
+ assertTemplateIsSupported(value, path2);
347
+ return substituteOnce(value, env, path2);
348
+ }
349
+ function assertTemplateIsSupported(value, path2) {
350
+ if (NESTED_PLACEHOLDER.test(value)) {
294
351
  throw new CommerceError(
295
352
  "CONFIG_INVALID",
296
- `Configuration value at "${path2}" still contains the unresolved placeholder "${residual}" after substitution \u2014 nested placeholders (e.g. "\${A:-\${B}}") are not supported. Use a plain default, or set the variable.`,
297
- { details: { path: path2, residual } }
353
+ `Configuration value at "${path2}" nests placeholders (e.g. "\${A:-\${B}}"), which is not supported \u2014 the inner one is not resolved and its closing brace ends up in the value. Use a plain default, or set the variable.`,
354
+ { details: { path: path2 } }
355
+ );
356
+ }
357
+ BRACE_TOKEN.lastIndex = 0;
358
+ for (const [token] of value.matchAll(BRACE_TOKEN)) {
359
+ if (SUPPORTED_TOKEN.test(token)) continue;
360
+ throw new CommerceError(
361
+ "CONFIG_INVALID",
362
+ `Configuration value at "${path2}" contains "${token}", which is not a supported placeholder. Only "\${VAR}" and "\${VAR:-default}" are recognised; shell forms such as "\${VAR-default}", "\${VAR:=default}" and "\${VAR:?message}" would load as literal text.`,
363
+ { details: { path: path2, token } }
298
364
  );
299
365
  }
300
- return substituted;
301
366
  }
302
367
  function substituteOnce(value, env, path2) {
303
368
  return value.replace(
@@ -322,11 +387,191 @@ function substituteOnce(value, env, path2) {
322
387
  function isPlainObject(value) {
323
388
  return typeof value === "object" && value !== null && !Array.isArray(value);
324
389
  }
325
- var PLACEHOLDER_PATTERN;
390
+ var PLACEHOLDER_PATTERN, BRACE_TOKEN, SUPPORTED_TOKEN, NESTED_PLACEHOLDER;
326
391
  var init_env = __esm({
327
392
  "src/config/env.ts"() {
328
393
  init_core();
329
394
  PLACEHOLDER_PATTERN = /\$\{([A-Za-z_][A-Za-z0-9_]*)(:-([^}]*))?\}/g;
395
+ BRACE_TOKEN = /\$\{[^{}]*\}/g;
396
+ SUPPORTED_TOKEN = /^\$\{[A-Za-z_][A-Za-z0-9_]*(:-[^}]*)?\}$/;
397
+ NESTED_PLACEHOLDER = /\$\{[^{}]*\$\{/;
398
+ }
399
+ });
400
+
401
+ // src/payments/x402/local-chain/accounts.ts
402
+ var ANVIL_WELL_KNOWN_ACCOUNTS;
403
+ var init_accounts = __esm({
404
+ "src/payments/x402/local-chain/accounts.ts"() {
405
+ ANVIL_WELL_KNOWN_ACCOUNTS = [
406
+ {
407
+ address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
408
+ privateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
409
+ },
410
+ {
411
+ address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
412
+ privateKey: "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
413
+ },
414
+ {
415
+ address: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
416
+ privateKey: "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
417
+ }
418
+ ];
419
+ ANVIL_WELL_KNOWN_ACCOUNTS[0];
420
+ ANVIL_WELL_KNOWN_ACCOUNTS[1];
421
+ ANVIL_WELL_KNOWN_ACCOUNTS[2];
422
+ }
423
+ });
424
+
425
+ // src/payments/x402/dev-key-guard.ts
426
+ function isWellKnownDevAddress(address) {
427
+ return WELL_KNOWN_DEV_ADDRESSES.has(address.toLowerCase());
428
+ }
429
+ function isLikelyLocalOrPrivateHost(hostname) {
430
+ const h = hostname.toLowerCase().replace(/^\[|\]$/g, "");
431
+ if (h === "localhost" || h === "127.0.0.1" || h === "::1") return true;
432
+ if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
433
+ if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
434
+ if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
435
+ if (/^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
436
+ if (!h.includes(".") && !h.includes(":")) return true;
437
+ return false;
438
+ }
439
+ var WELL_KNOWN_DEV_ADDRESSES;
440
+ var init_dev_key_guard = __esm({
441
+ "src/payments/x402/dev-key-guard.ts"() {
442
+ init_core();
443
+ init_accounts();
444
+ new Set(
445
+ ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.privateKey.toLowerCase())
446
+ );
447
+ WELL_KNOWN_DEV_ADDRESSES = new Set(
448
+ ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.address.toLowerCase())
449
+ );
450
+ }
451
+ });
452
+
453
+ // src/payments/x402/guardrails.ts
454
+ function invalid(message, path2) {
455
+ return new CommerceError("CONFIG_INVALID", message, { details: { path: path2 } });
456
+ }
457
+ function resolveX402Deployment(input) {
458
+ const profile = requireNetworkProfile(input.network, "payments.x402.network");
459
+ const mode = resolveDeploymentMode(profile, input.facilitator.mode);
460
+ if (profile.kind === "mainnet" && input.facilitator.mode === "local") {
461
+ throw invalid(
462
+ `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.`,
463
+ "payments.x402.facilitator.mode"
464
+ );
465
+ }
466
+ if (mode === "mainnet" && input.allowMainnet !== true) {
467
+ throw invalid(
468
+ `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.`,
469
+ "payments.x402.allowMainnet"
470
+ );
471
+ }
472
+ if (input.facilitator.mode === "remote") {
473
+ assertFacilitatorUrlIsSafe(input.facilitator.url, mode);
474
+ if (mode === "mainnet" && input.facilitator.auth.type === "none" && input.allowUnauthenticatedFacilitator !== true) {
475
+ throw invalid(
476
+ `payments.x402: facilitator ${describeOrigin(input.facilitator.url)} takes no credential, and this is a mainnet deployment. It will see every payment authorisation you handle, with no account, terms or support behind it. Set payments.x402.allowUnauthenticatedFacilitator: true to accept that, or configure facilitator.auth.`,
477
+ "payments.x402.allowUnauthenticatedFacilitator"
478
+ );
479
+ }
480
+ for (const [field, value] of credentialFields(input.facilitator.auth)) {
481
+ if (value.trim() === "") {
482
+ throw invalid(
483
+ `payments.x402: facilitator.auth.type is "${input.facilitator.auth.type}" but ${field} is empty. An empty credential is refused rather than sent.`,
484
+ `payments.x402.facilitator.auth.${field}`
485
+ );
486
+ }
487
+ }
488
+ }
489
+ if (/^0x0{40}$/i.test(input.payTo)) {
490
+ throw invalid(
491
+ 'payments.x402: "payTo" is the zero address. Every payment settled there is destroyed.',
492
+ "payments.x402.payTo"
493
+ );
494
+ }
495
+ if (mode !== "local" && isWellKnownDevAddress(input.payTo)) {
496
+ throw invalid(
497
+ `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.`,
498
+ "payments.x402.payTo"
499
+ );
500
+ }
501
+ const canonical = profile.canonicalAsset;
502
+ if (mode === "mainnet" && canonical) {
503
+ if (!sameAddress(input.asset, canonical.address)) {
504
+ throw invalid(
505
+ `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.`,
506
+ "payments.x402.asset"
507
+ );
508
+ }
509
+ if (input.assetName !== void 0 && input.assetName !== canonical.name) {
510
+ throw invalid(
511
+ `payments.x402: assetName "${input.assetName}" is not the EIP-712 domain name ${canonical.symbol} reports on ${profile.displayName} (expected "${canonical.name}"). Every payment would be refused after the buyer signed.`,
512
+ "payments.x402.assetName"
513
+ );
514
+ }
515
+ if (input.assetVersion !== void 0 && input.assetVersion !== canonical.version) {
516
+ throw invalid(
517
+ `payments.x402: assetVersion "${input.assetVersion}" is not the EIP-712 domain version ${canonical.symbol} reports on ${profile.displayName} (expected "${canonical.version}").`,
518
+ "payments.x402.assetVersion"
519
+ );
520
+ }
521
+ }
522
+ return { profile, mode };
523
+ }
524
+ function assertFacilitatorUrlIsSafe(url, mode) {
525
+ let parsed;
526
+ try {
527
+ parsed = new URL(url);
528
+ } catch (cause) {
529
+ throw new CommerceError("CONFIG_INVALID", `payments.x402: facilitator.url is not a valid URL`, {
530
+ cause,
531
+ details: { path: "payments.x402.facilitator.url" }
532
+ });
533
+ }
534
+ if (parsed.protocol === "https:") return;
535
+ if (parsed.protocol !== "http:") {
536
+ throw invalid(
537
+ `payments.x402: facilitator ${describeOrigin(url)} must be reached over https (or http on a local/private host); got "${parsed.protocol}//"`,
538
+ "payments.x402.facilitator.url"
539
+ );
540
+ }
541
+ if (mode !== "mainnet" && isLikelyLocalOrPrivateHost(parsed.hostname)) return;
542
+ throw invalid(
543
+ `payments.x402: facilitator ${describeOrigin(url)} is reached over plain HTTP. Payment authorisations and settlement results would travel unencrypted. Use https, or point at a local/private host on a non-mainnet deployment.`,
544
+ "payments.x402.facilitator.url"
545
+ );
546
+ }
547
+ function describeOrigin(url) {
548
+ try {
549
+ return new URL(url).origin;
550
+ } catch {
551
+ return "[unparseable facilitator.url]";
552
+ }
553
+ }
554
+ function credentialFields(auth) {
555
+ switch (auth.type) {
556
+ case "bearer":
557
+ return [["token", auth.token]];
558
+ case "cdp":
559
+ return [
560
+ ["apiKeyId", auth.apiKeyId],
561
+ ["apiKeySecret", auth.apiKeySecret]
562
+ ];
563
+ default:
564
+ return [];
565
+ }
566
+ }
567
+ function sameAddress(a, b) {
568
+ return a.toLowerCase() === b.toLowerCase();
569
+ }
570
+ var init_guardrails = __esm({
571
+ "src/payments/x402/guardrails.ts"() {
572
+ init_core();
573
+ init_dev_key_guard();
574
+ init_networks();
330
575
  }
331
576
  });
332
577
  function parseConfig(raw, env) {
@@ -435,14 +680,12 @@ function normalise(raw) {
435
680
  }
436
681
  };
437
682
  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 ? {
683
+ const facilitator = x402Raw === void 0 ? void 0 : x402Raw.facilitator.mode === "local" ? { mode: "local", signerPrivateKey: x402Raw.facilitator.signerPrivateKey } : {
684
+ mode: "remote",
685
+ url: x402Raw.facilitator.url,
686
+ auth: x402Raw.facilitator.auth ?? { type: "none" }
687
+ };
688
+ const x402 = x402Raw && facilitator ? {
446
689
  enabled: toBoolean(x402Raw.enabled, "payments.x402.enabled"),
447
690
  network: x402Raw.network,
448
691
  rpcUrl: x402Raw.rpcUrl,
@@ -454,18 +697,35 @@ function normalise(raw) {
454
697
  max: 36
455
698
  }),
456
699
  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
- }
700
+ maxTimeoutSeconds: toNumber(
701
+ x402Raw.maxTimeoutSeconds,
702
+ "payments.x402.maxTimeoutSeconds",
703
+ {
704
+ min: 1
705
+ }
706
+ ),
707
+ facilitator,
708
+ ...x402Raw.allowMainnet !== void 0 ? { allowMainnet: toBoolean(x402Raw.allowMainnet, "payments.x402.allowMainnet") } : {},
709
+ ...x402Raw.allowUnauthenticatedFacilitator !== void 0 ? {
710
+ allowUnauthenticatedFacilitator: toBoolean(
711
+ x402Raw.allowUnauthenticatedFacilitator,
712
+ "payments.x402.allowUnauthenticatedFacilitator"
713
+ )
714
+ } : {}
465
715
  } : void 0;
466
716
  if (x402) {
467
717
  validateAddress("payments.x402.payTo", x402.payTo);
468
718
  validateAddress("payments.x402.asset", x402.asset);
719
+ resolveX402Deployment({
720
+ network: x402.network,
721
+ payTo: x402.payTo,
722
+ asset: x402.asset,
723
+ assetName: x402.assetName,
724
+ assetVersion: x402.assetVersion,
725
+ facilitator: x402.facilitator,
726
+ ...x402.allowMainnet !== void 0 ? { allowMainnet: x402.allowMainnet } : {},
727
+ ...x402.allowUnauthenticatedFacilitator !== void 0 ? { allowUnauthenticatedFacilitator: x402.allowUnauthenticatedFacilitator } : {}
728
+ });
469
729
  }
470
730
  const resources = Object.entries(raw.resources).map(
471
731
  ([id, entry]) => normaliseResource(id, entry, protocols, x402)
@@ -510,7 +770,7 @@ function normaliseResource(id, entry, protocols, x402) {
510
770
  }
511
771
  for (const protocol of entry.expose) {
512
772
  if (!SUPPORTED_PROTOCOLS.has(protocol)) {
513
- const hint = protocol === "ucp" ? " (UCP is planned, not supported in v0.1.0-alpha)" : "";
773
+ const hint = protocol === "ucp" ? " (UCP is planned, not supported in this release)" : "";
514
774
  throw new CommerceError(
515
775
  "CONFIG_INVALID",
516
776
  `Resource "${id}" exposes unsupported protocol "${protocol}"${hint}. Supported: http, mcp.`,
@@ -546,7 +806,7 @@ function normaliseResource(id, entry, protocols, x402) {
546
806
  if (entry.pricing.type === "dynamic") {
547
807
  throw new CommerceError(
548
808
  "CONFIG_INVALID",
549
- `Resource "${id}" uses pricing.type "dynamic", which is not supported in v0.1.0-alpha`,
809
+ `Resource "${id}" uses pricing.type "dynamic", which is not supported in this release`,
550
810
  { details: { path: `resources.${id}.pricing.type`, resourceId: id } }
551
811
  );
552
812
  }
@@ -623,6 +883,10 @@ function defaultClosedObjectSchema(schema) {
623
883
  if (isPlainObject2(items)) {
624
884
  result["items"] = defaultClosedObjectSchema(items);
625
885
  }
886
+ const additional = schema["additionalProperties"];
887
+ if (isPlainObject2(additional)) {
888
+ result["additionalProperties"] = defaultClosedObjectSchema(additional);
889
+ }
626
890
  return result;
627
891
  }
628
892
  function isPlainObject2(value) {
@@ -670,6 +934,10 @@ function validateResourceSchemaKeywords(id, path2, schema) {
670
934
  if (isPlainObject2(sub)) validateResourceSchemaKeywords(id, `${path2}.properties.${key}`, sub);
671
935
  }
672
936
  }
937
+ const additional = schema["additionalProperties"];
938
+ if (isPlainObject2(additional)) {
939
+ validateResourceSchemaKeywords(id, `${path2}.additionalProperties`, additional);
940
+ }
673
941
  const items = schema["items"];
674
942
  if (isPlainObject2(items)) {
675
943
  validateResourceSchemaKeywords(id, `${path2}.items`, items);
@@ -717,6 +985,23 @@ function validatePathParametersDeclared(id, url, input) {
717
985
  }
718
986
  const params = extractPathParameterNames(url);
719
987
  if (params.length === 0) return;
988
+ const firstBrace = url.indexOf("{");
989
+ if (firstBrace !== -1) {
990
+ const prefix = url.slice(0, firstBrace);
991
+ let origin;
992
+ try {
993
+ const parsedPrefix = new URL(prefix);
994
+ origin = parsedPrefix.hostname === "" ? void 0 : parsedPrefix.origin;
995
+ } catch {
996
+ }
997
+ if (origin === void 0 || !prefix.startsWith(`${origin}/`)) {
998
+ throw new CommerceError(
999
+ "CONFIG_INVALID",
1000
+ `Resource "${id}" has a backend.url parameter before the end of the host ("${url}"). A caller-supplied value would choose which host the gateway calls, which is request forgery with the gateway's own network position. Parameters are supported inside the path and query only`,
1001
+ { details: { path: `resources.${id}.backend.url`, resourceId: id } }
1002
+ );
1003
+ }
1004
+ }
720
1005
  const propertiesRaw = input?.["properties"];
721
1006
  const properties = isPlainObject2(propertiesRaw) ? propertiesRaw : {};
722
1007
  const requiredRaw = input?.["required"];
@@ -778,11 +1063,12 @@ function validateAddress(path2, value) {
778
1063
  );
779
1064
  }
780
1065
  }
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;
1066
+ var SUPPORTED_CONFIG_VERSION, ADDRESS_PATTERN, ZERO_ADDRESS_PATTERN, MCP_TOOL_NAME_PATTERN, NumberOrString, BooleanOrString, MerchantSchema, ServerSchema, StorageSchema, RESERVED_GATEWAY_PATHS, McpMountPathSchema, 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
1067
  var init_schema = __esm({
783
1068
  "src/config/schema.ts"() {
784
1069
  init_execution();
785
1070
  init_core();
1071
+ init_guardrails();
786
1072
  init_env();
787
1073
  SUPPORTED_CONFIG_VERSION = 1;
788
1074
  ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
@@ -820,11 +1106,36 @@ var init_schema = __esm({
820
1106
  path: z.string().min(1)
821
1107
  }).strict()
822
1108
  }).strict();
1109
+ RESERVED_GATEWAY_PATHS = [
1110
+ "/health",
1111
+ "/ready",
1112
+ "/.well-known/agent-commerce",
1113
+ "/api/resources",
1114
+ "/api/resources/:id/invoke",
1115
+ "/api/receipts",
1116
+ "/api/events",
1117
+ "/api/events/stream"
1118
+ ];
1119
+ McpMountPathSchema = z.string().min(1).refine((value) => value.startsWith("/"), {
1120
+ message: 'must start with "/" \u2014 it is an absolute gateway path, e.g. "/mcp".'
1121
+ }).refine((value) => !/[:*?\s]/.test(value), {
1122
+ message: 'must not contain ":", "*", "?" or whitespace \u2014 the mount is a literal path prefix, not a Fastify route pattern, and registers its own wildcard.'
1123
+ }).refine(
1124
+ (value) => {
1125
+ const base = value.replace(/\/+$/, "");
1126
+ return !RESERVED_GATEWAY_PATHS.some(
1127
+ (reserved) => reserved === base || reserved.startsWith(`${base}/`)
1128
+ );
1129
+ },
1130
+ {
1131
+ message: `must not collide with a route the gateway already serves (${RESERVED_GATEWAY_PATHS.join(", ")}); pick a dedicated prefix such as "/mcp".`
1132
+ }
1133
+ );
823
1134
  ProtocolsSchema = z.object({
824
1135
  http: z.object({ enabled: BooleanOrString }).strict(),
825
1136
  mcp: z.object({
826
1137
  enabled: BooleanOrString,
827
- mountPath: z.string().min(1)
1138
+ mountPath: McpMountPathSchema
828
1139
  }).strict()
829
1140
  }).strict();
830
1141
  BackendMethodSchema = z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]);
@@ -859,6 +1170,17 @@ var init_schema = __esm({
859
1170
  payments: z.array(z.string().min(1)).optional()
860
1171
  }).strict();
861
1172
  ResourcesMapSchema = z.record(z.string().min(1), ResourceEntrySchema);
1173
+ FacilitatorAuthSchema = z.discriminatedUnion("type", [
1174
+ z.object({ type: z.literal("none") }).strict(),
1175
+ z.object({ type: z.literal("bearer"), token: z.string().min(1) }).strict(),
1176
+ // Coinbase Developer Platform. Needs the optional peer `@coinbase/x402`,
1177
+ // which signs a fresh JWT per request; a static header cannot express it.
1178
+ z.object({
1179
+ type: z.literal("cdp"),
1180
+ apiKeyId: z.string().min(1),
1181
+ apiKeySecret: z.string().min(1)
1182
+ }).strict()
1183
+ ]);
862
1184
  FacilitatorSchema = z.discriminatedUnion("mode", [
863
1185
  z.object({
864
1186
  mode: z.literal("local"),
@@ -866,7 +1188,12 @@ var init_schema = __esm({
866
1188
  }).strict(),
867
1189
  z.object({
868
1190
  mode: z.literal("remote"),
869
- url: z.string().min(1)
1191
+ url: z.string().min(1),
1192
+ // Absent means "this facilitator takes no credential" — an explicit
1193
+ // statement, normalised to `{ type: 'none' }` below. On a mainnet that
1194
+ // combination is refused outright, so the default can never quietly
1195
+ // become an unauthenticated production facilitator.
1196
+ auth: FacilitatorAuthSchema.optional()
870
1197
  }).strict()
871
1198
  ]);
872
1199
  X402Schema = z.object({
@@ -879,7 +1206,11 @@ var init_schema = __esm({
879
1206
  assetDecimals: NumberOrString,
880
1207
  payTo: z.string().min(1),
881
1208
  maxTimeoutSeconds: NumberOrString,
882
- facilitator: FacilitatorSchema
1209
+ facilitator: FacilitatorSchema,
1210
+ /** Real funds. Never defaulted — see src/payments/x402/guardrails.ts. */
1211
+ allowMainnet: BooleanOrString.optional(),
1212
+ /** Accepts a mainnet facilitator that takes no credential. Never defaulted. */
1213
+ allowUnauthenticatedFacilitator: BooleanOrString.optional()
883
1214
  }).strict();
884
1215
  PaymentsSchema = z.object({
885
1216
  x402: X402Schema.optional()
@@ -1080,10 +1411,11 @@ async function execCommand(command, args) {
1080
1411
  // src/cli/commands/doctor.ts
1081
1412
  init_execution();
1082
1413
  init_core();
1414
+ init_networks();
1083
1415
 
1084
1416
  // src/storage/receipts/store.ts
1085
1417
  init_core();
1086
- var PACKAGE_VERSION = "0.1.0-alpha.0" ;
1418
+ var PACKAGE_VERSION = "1.0.0" ;
1087
1419
 
1088
1420
  // src/storage/receipts/redact.ts
1089
1421
  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;
@@ -1308,6 +1640,12 @@ function createSqliteReceiptStore(options) {
1308
1640
  );
1309
1641
  }
1310
1642
  }
1643
+ if (isFileBacked) {
1644
+ try {
1645
+ closeSync(openSync(path2, "a", 384));
1646
+ } catch {
1647
+ }
1648
+ }
1311
1649
  const db = new Database(path2);
1312
1650
  db.pragma("journal_mode = WAL");
1313
1651
  db.pragma("foreign_keys = ON");
@@ -1598,23 +1936,8 @@ async function fetchJson(fetchImpl, url, timeoutMs = 1500) {
1598
1936
  }
1599
1937
  }
1600
1938
 
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
1939
  // src/cli/lib/init-config.ts
1940
+ init_accounts();
1618
1941
  var LOCAL_DEV_MERCHANT_ADDRESS = ANVIL_WELL_KNOWN_ACCOUNTS[1].address;
1619
1942
  var LOCAL_DEV_FACILITATOR_PRIVATE_KEY = ANVIL_WELL_KNOWN_ACCOUNTS[0].privateKey;
1620
1943
  var PLACEHOLDER_ASSET_ADDRESS = "0x000000000000000000000000000000000000dEaD";
@@ -1692,7 +2015,7 @@ function buildInitConfigObject(answers) {
1692
2015
  payments: answers.x402Enabled ? {
1693
2016
  x402: {
1694
2017
  enabled: true,
1695
- network: "base-sepolia",
2018
+ network: "eip155:84532",
1696
2019
  rpcUrl: "http://127.0.0.1:8545",
1697
2020
  asset: PLACEHOLDER_ASSET_ADDRESS,
1698
2021
  assetName: "MockUSDC",
@@ -1845,7 +2168,7 @@ function maskMiddle(value, keep = 6) {
1845
2168
  }
1846
2169
  createRequire(import.meta.url);
1847
2170
  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"}]}' ;
2171
+ const injected = '{"cliVersion":"1.0.0","pinned":[{"name":"@coinbase/x402","version":"2.1.0","via":"@devlab.group/agent-commerce"},{"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
2172
  {
1850
2173
  return JSON.parse(injected);
1851
2174
  }
@@ -1884,12 +2207,12 @@ function extractWellKnownX402(body) {
1884
2207
  }
1885
2208
  return { asset, network, payTo };
1886
2209
  }
1887
- function sameAddress(a, b) {
2210
+ function sameAddress2(a, b) {
1888
2211
  return a.toLowerCase() === b.toLowerCase();
1889
2212
  }
1890
2213
  function findX402Mismatch(configured, live) {
1891
2214
  const diffs = [];
1892
- if (!sameAddress(configured.asset, live.asset)) {
2215
+ if (!sameAddress2(configured.asset, live.asset)) {
1893
2216
  diffs.push(
1894
2217
  `asset: gateway is using ${live.asset} but local config resolves to ${configured.asset}`
1895
2218
  );
@@ -1899,7 +2222,7 @@ function findX402Mismatch(configured, live) {
1899
2222
  `network: gateway is using "${live.network}" but local config resolves to "${configured.network}"`
1900
2223
  );
1901
2224
  }
1902
- if (!sameAddress(configured.payTo, live.payTo)) {
2225
+ if (!sameAddress2(configured.payTo, live.payTo)) {
1903
2226
  diffs.push(
1904
2227
  `payTo: gateway is using ${live.payTo} but local config resolves to ${configured.payTo}`
1905
2228
  );
@@ -1994,9 +2317,13 @@ async function runDoctor(options, deps = {}) {
1994
2317
  if (x402 === void 0 || !x402.enabled) {
1995
2318
  checks.push({ name: "Payments", status: "INFO", detail: "x402 not configured" });
1996
2319
  } else {
1997
- const summary = `x402 enabled \u2014 network=${x402.network}, destination=${maskMiddle(x402.payTo)}, facilitator=${x402.facilitator.mode}`;
2320
+ const profile = findNetworkProfile(x402.network);
2321
+ const mode = profile ? resolveDeploymentMode(profile, x402.facilitator.mode) : void 0;
2322
+ 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})`;
2323
+ const facilitatorDetail = x402.facilitator.mode === "local" ? "local" : `remote (auth=${x402.facilitator.auth.type})`;
2324
+ const summary = `x402 v2 (scheme=exact) enabled \u2014 ${where}, destination=${maskMiddle(x402.payTo)}, facilitator=${facilitatorDetail}`;
1998
2325
  const live = wellKnown?.ok ? extractWellKnownX402(wellKnown.body) : void 0;
1999
- if (sameAddress(x402.asset, PLACEHOLDER_ASSET_ADDRESS)) {
2326
+ if (sameAddress2(x402.asset, PLACEHOLDER_ASSET_ADDRESS)) {
2000
2327
  checks.push({
2001
2328
  name: "Payments",
2002
2329
  status: "WARN",
@@ -2020,11 +2347,19 @@ async function runDoctor(options, deps = {}) {
2020
2347
  mismatch === void 0 ? { name: "Payments", status: "PASS", detail: summary } : { name: "Payments", status: "FAIL", detail: mismatch }
2021
2348
  );
2022
2349
  }
2350
+ if (mode === "mainnet") {
2351
+ const unauthenticated = x402.facilitator.mode === "remote" && x402.facilitator.auth.type === "none";
2352
+ checks.push({
2353
+ name: "Mainnet safety",
2354
+ status: unauthenticated ? "WARN" : "INFO",
2355
+ detail: unauthenticated ? `${describeDeploymentMode("mainnet")} \u2014 settling through a facilitator that takes no credential, accepted via allowUnauthenticatedFacilitator. It sees every payment authorisation you handle, with no account or terms behind it. Everything else is enforced at config load: explicit allowMainnet opt-in, HTTPS, non-development payTo, canonical asset. Payments are fail-closed.` : `${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.`
2356
+ });
2357
+ }
2023
2358
  }
2024
2359
  checks.push({
2025
2360
  name: "Payments (MPP)",
2026
2361
  status: "INFO",
2027
- detail: "planned \u2014 not implemented in v0.1"
2362
+ detail: "planned \u2014 not implemented in this release"
2028
2363
  });
2029
2364
  if (config === void 0) {
2030
2365
  checks.push({ name: "Storage", status: "WARN", detail: "skipped \u2014 config invalid" });
@@ -2034,9 +2369,8 @@ async function runDoctor(options, deps = {}) {
2034
2369
  // from the host) silently produces a fresh empty database and a false
2035
2370
  // "healthy, receipts=0" PASS — a read-only command must not have this
2036
2371
  // 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.
2372
+ // `:memory:` never exists on disk by definition, so it always falls
2373
+ // through to the real open/health/count branch below, same as today.
2040
2374
  config.storage.receipts.path !== ":memory:" && !existsSync(config.storage.receipts.path)
2041
2375
  ) {
2042
2376
  checks.push({
@@ -2379,6 +2713,12 @@ function buildProgram(io = processIo) {
2379
2713
  }
2380
2714
 
2381
2715
  // src/cli/index.ts
2716
+ for (const stream of [process.stdout, process.stderr]) {
2717
+ stream.on("error", (err) => {
2718
+ if (err.code === "EPIPE") process.exit(0);
2719
+ throw err;
2720
+ });
2721
+ }
2382
2722
  async function main() {
2383
2723
  const program = buildProgram();
2384
2724
  try {