@agent-score/commerce 1.8.0 → 2.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.
Files changed (90) hide show
  1. package/README.md +73 -9
  2. package/dist/{_response-BMt2y4Or.d.mts → _response-BFYN3b6i.d.mts} +19 -22
  3. package/dist/{_response-DyJ3mWI3.d.ts → _response-_iPD5AIj.d.ts} +19 -22
  4. package/dist/challenge/index.d.mts +106 -198
  5. package/dist/challenge/index.d.ts +106 -198
  6. package/dist/challenge/index.js +238 -111
  7. package/dist/challenge/index.js.map +1 -1
  8. package/dist/challenge/index.mjs +238 -111
  9. package/dist/challenge/index.mjs.map +1 -1
  10. package/dist/checkout-BoFwnVsj.d.ts +931 -0
  11. package/dist/checkout-DRbQ0Fsh.d.mts +931 -0
  12. package/dist/core.d.mts +2 -2
  13. package/dist/core.d.ts +2 -2
  14. package/dist/core.js +1 -1
  15. package/dist/core.js.map +1 -1
  16. package/dist/core.mjs +1 -1
  17. package/dist/core.mjs.map +1 -1
  18. package/dist/discovery/index.d.mts +453 -51
  19. package/dist/discovery/index.d.ts +453 -51
  20. package/dist/discovery/index.js +1092 -58
  21. package/dist/discovery/index.js.map +1 -1
  22. package/dist/discovery/index.mjs +1060 -57
  23. package/dist/discovery/index.mjs.map +1 -1
  24. package/dist/identity/express.d.mts +3 -3
  25. package/dist/identity/express.d.ts +3 -3
  26. package/dist/identity/express.js +30 -19
  27. package/dist/identity/express.js.map +1 -1
  28. package/dist/identity/express.mjs +30 -19
  29. package/dist/identity/express.mjs.map +1 -1
  30. package/dist/identity/fastify.d.mts +4 -4
  31. package/dist/identity/fastify.d.ts +4 -4
  32. package/dist/identity/fastify.js +30 -19
  33. package/dist/identity/fastify.js.map +1 -1
  34. package/dist/identity/fastify.mjs +30 -19
  35. package/dist/identity/fastify.mjs.map +1 -1
  36. package/dist/identity/hono.d.mts +3 -3
  37. package/dist/identity/hono.d.ts +3 -3
  38. package/dist/identity/hono.js +30 -19
  39. package/dist/identity/hono.js.map +1 -1
  40. package/dist/identity/hono.mjs +30 -19
  41. package/dist/identity/hono.mjs.map +1 -1
  42. package/dist/identity/nextjs.d.mts +6 -7
  43. package/dist/identity/nextjs.d.ts +6 -7
  44. package/dist/identity/nextjs.js +30 -19
  45. package/dist/identity/nextjs.js.map +1 -1
  46. package/dist/identity/nextjs.mjs +30 -19
  47. package/dist/identity/nextjs.mjs.map +1 -1
  48. package/dist/identity/policy.d.mts +41 -4
  49. package/dist/identity/policy.d.ts +41 -4
  50. package/dist/identity/policy.js +3662 -18
  51. package/dist/identity/policy.js.map +1 -1
  52. package/dist/identity/policy.mjs +3648 -3
  53. package/dist/identity/policy.mjs.map +1 -1
  54. package/dist/identity/web.d.mts +3 -3
  55. package/dist/identity/web.d.ts +3 -3
  56. package/dist/identity/web.js +30 -19
  57. package/dist/identity/web.js.map +1 -1
  58. package/dist/identity/web.mjs +30 -19
  59. package/dist/identity/web.mjs.map +1 -1
  60. package/dist/index.d.mts +72 -329
  61. package/dist/index.d.ts +72 -329
  62. package/dist/index.js +3651 -373
  63. package/dist/index.js.map +1 -1
  64. package/dist/index.mjs +3628 -361
  65. package/dist/index.mjs.map +1 -1
  66. package/dist/payment/index.d.mts +257 -266
  67. package/dist/payment/index.d.ts +257 -266
  68. package/dist/payment/index.js +586 -149
  69. package/dist/payment/index.js.map +1 -1
  70. package/dist/payment/index.mjs +573 -148
  71. package/dist/payment/index.mjs.map +1 -1
  72. package/dist/{agent_instructions-DiMSGkdm.d.mts → pricing-CQ9DIFaw.d.ts} +109 -56
  73. package/dist/{agent_instructions-DiMSGkdm.d.ts → pricing-CxzwyiO6.d.mts} +109 -56
  74. package/dist/rail_spec-XP0wKgJV.d.mts +132 -0
  75. package/dist/rail_spec-XP0wKgJV.d.ts +132 -0
  76. package/dist/{signer-CFVQsWjL.d.mts → signer-3FAit11j.d.mts} +27 -1
  77. package/dist/{signer-CFVQsWjL.d.ts → signer-3FAit11j.d.ts} +27 -1
  78. package/dist/solana-Cds87OTu.d.mts +67 -0
  79. package/dist/solana-Cds87OTu.d.ts +67 -0
  80. package/dist/stripe-multichain/index.d.mts +56 -67
  81. package/dist/stripe-multichain/index.d.ts +56 -67
  82. package/dist/stripe-multichain/index.js +68 -42
  83. package/dist/stripe-multichain/index.js.map +1 -1
  84. package/dist/stripe-multichain/index.mjs +68 -41
  85. package/dist/stripe-multichain/index.mjs.map +1 -1
  86. package/dist/{wwwauthenticate-CU1eNvMQ.d.mts → wwwauthenticate-D_FMnPgU.d.mts} +9 -10
  87. package/dist/{wwwauthenticate-CU1eNvMQ.d.ts → wwwauthenticate-D_FMnPgU.d.ts} +9 -10
  88. package/dist/x402_server-hgQzWQwB.d.mts +81 -0
  89. package/dist/x402_server-hgQzWQwB.d.ts +81 -0
  90. package/package.json +13 -9
@@ -108,32 +108,52 @@ function lookupRail(name) {
108
108
  }
109
109
 
110
110
  // src/payment/directive.ts
111
- function buildPaymentRequestBlob(input) {
112
- const railDef = input.rail ? lookupRail(input.rail) : void 0;
113
- const decimals = input.decimals ?? railDef?.decimals ?? 6;
114
- const currency = input.currency ?? railDef?.currency ?? "usd";
115
- const chainId = input.chainId ?? railDef?.chainId;
116
- const amountNum = typeof input.amountUsd === "string" ? Number(input.amountUsd) : input.amountUsd;
117
- const amountRaw = BigInt(Math.round(amountNum * 10 ** decimals)).toString();
118
- const blob = { amount: amountRaw, currency, decimals };
119
- if (input.recipient) blob.recipient = input.recipient;
111
+ function buildPaymentRequestBlob({
112
+ rail,
113
+ amountUsd,
114
+ currency,
115
+ decimals,
116
+ recipient,
117
+ chainId,
118
+ networkId
119
+ }) {
120
+ const railDef = rail ? lookupRail(rail) : void 0;
121
+ const decimalsResolved = decimals ?? railDef?.decimals ?? 6;
122
+ const currencyResolved = currency ?? railDef?.currency ?? "usd";
123
+ const chainIdResolved = chainId ?? railDef?.chainId;
124
+ const amountNum = typeof amountUsd === "string" ? Number(amountUsd) : amountUsd;
125
+ const amountRaw = BigInt(Math.round(amountNum * 10 ** decimalsResolved)).toString();
126
+ const blob = { amount: amountRaw, currency: currencyResolved, decimals: decimalsResolved };
127
+ if (recipient) blob.recipient = recipient;
120
128
  const methodDetails = {};
121
- if (chainId !== void 0) methodDetails.chainId = chainId;
122
- if (input.networkId) methodDetails.networkId = input.networkId;
129
+ if (chainIdResolved !== void 0) methodDetails.chainId = chainIdResolved;
130
+ if (networkId) methodDetails.networkId = networkId;
123
131
  if (Object.keys(methodDetails).length > 0) blob.methodDetails = methodDetails;
124
132
  return Buffer.from(JSON.stringify(blob)).toString("base64url");
125
133
  }
126
- function paymentDirective(input) {
127
- const railDef = input.rail ? lookupRail(input.rail) : void 0;
128
- const method = input.method ?? railDef?.method ?? "unknown";
129
- const intent = input.intent ?? "charge";
130
- const expires = input.expires ?? new Date(Date.now() + 5 * 60 * 1e3).toISOString();
131
- return `Payment id="${input.id}", realm="${input.realm}", method="${method}", intent="${intent}", expires="${expires}", request="${input.request}"`;
134
+ function paymentDirective({
135
+ rail,
136
+ id,
137
+ realm,
138
+ method,
139
+ intent,
140
+ expires,
141
+ request
142
+ }) {
143
+ const railDef = rail ? lookupRail(rail) : void 0;
144
+ const methodResolved = method ?? railDef?.method ?? "unknown";
145
+ const intentResolved = intent ?? "charge";
146
+ const expiresResolved = expires ?? new Date(Date.now() + 5 * 60 * 1e3).toISOString();
147
+ return `Payment id="${id}", realm="${realm}", method="${methodResolved}", intent="${intentResolved}", expires="${expiresResolved}", request="${request}"`;
132
148
  }
133
149
 
134
150
  // src/payment/wwwauthenticate.ts
135
- function paymentRequiredHeader(input) {
136
- return Buffer.from(JSON.stringify(input)).toString("base64");
151
+ function paymentRequiredHeader({
152
+ x402Version,
153
+ accepts,
154
+ resource
155
+ }) {
156
+ return Buffer.from(JSON.stringify({ x402Version, accepts, ...resource ? { resource } : {} })).toString("base64");
137
157
  }
138
158
 
139
159
  // src/discovery/probe.ts
@@ -261,26 +281,36 @@ async function dynamicImport(moduleName) {
261
281
  }
262
282
 
263
283
  // src/discovery/well_known_mpp.ts
264
- function buildWellKnownMpp(input) {
284
+ function buildWellKnownMpp({
285
+ name,
286
+ description,
287
+ url,
288
+ openapi,
289
+ endpoints,
290
+ catalog,
291
+ purchase,
292
+ shipping,
293
+ extra
294
+ }) {
265
295
  return {
266
- name: input.name,
267
- ...input.description ? { description: input.description } : {},
268
- url: input.url,
269
- ...input.openapi ? { openapi: input.openapi } : {},
270
- endpoints: input.endpoints,
271
- ...input.catalog ? { catalog: input.catalog } : {},
296
+ name,
297
+ ...description ? { description } : {},
298
+ url,
299
+ ...openapi ? { openapi } : {},
300
+ endpoints,
301
+ ...catalog ? { catalog } : {},
272
302
  purchase: {
273
- ...input.purchase.required_fields ? { required_fields: input.purchase.required_fields } : {},
274
- ...input.purchase.optional_fields ? { optional_fields: input.purchase.optional_fields } : {},
275
- ...input.purchase.extra ?? {},
276
- ...input.purchase.identity ? { identity: input.purchase.identity } : {},
277
- ...input.purchase.identity_paths ? { identity_paths: input.purchase.identity_paths } : {},
278
- payment_methods: input.purchase.methods,
279
- ...input.purchase.x402 ? { x402: input.purchase.x402 } : {},
280
- ...input.purchase.compliance ? { compliance: input.purchase.compliance } : {}
303
+ ...purchase.required_fields ? { required_fields: purchase.required_fields } : {},
304
+ ...purchase.optional_fields ? { optional_fields: purchase.optional_fields } : {},
305
+ ...purchase.extra ?? {},
306
+ ...purchase.identity ? { identity: purchase.identity } : {},
307
+ ...purchase.identity_paths ? { identity_paths: purchase.identity_paths } : {},
308
+ payment_methods: purchase.methods,
309
+ ...purchase.x402 ? { x402: purchase.x402 } : {},
310
+ ...purchase.compliance ? { compliance: purchase.compliance } : {}
281
311
  },
282
- ...input.shipping ? { shipping: input.shipping } : {},
283
- ...input.extra ?? {}
312
+ ...shipping ? { shipping } : {},
313
+ ...extra ?? {}
284
314
  };
285
315
  }
286
316
 
@@ -293,11 +323,13 @@ function buildWellKnownX402(input) {
293
323
  }
294
324
 
295
325
  // src/discovery/llms_txt.ts
296
- function llmsTxtIdentitySection(input = {}) {
297
- if (!input.agentscore) {
326
+ function llmsTxtIdentitySection({
327
+ agentscore,
328
+ compliance
329
+ } = {}) {
330
+ if (!agentscore) {
298
331
  return "";
299
332
  }
300
- const compliance = input.compliance;
301
333
  const complianceNote = compliance ? `
302
334
 
303
335
  Compliance: ${[
@@ -402,31 +434,37 @@ function llmsTxtPaymentSectionVerbose(input) {
402
434
  lines.push("Mint a SharedPaymentToken scoped to the `profile_id` from the 402 body, then submit via `Authorization: Payment` with `method=stripe/charge`. Either your own Stripe account or `link-cli spend-request create --credential-type shared_payment_token --network-id <profileId> ...` for Stripe Link wallets.");
403
435
  lines.push("");
404
436
  }
405
- lines.push("IMPORTANT: Use the CLIs above. Raw on-chain transfers (e.g. `tempo wallet transfer`, sending USDC manually to deposit addresses) bypass the protocol handshake and the order will not complete.");
437
+ lines.push("IMPORTANT: Use the CLIs above. Raw on-chain transfers (e.g. `tempo wallet transfer`, sending USDC manually to deposit addresses) bypass the protocol handshake and the request will not complete.");
406
438
  if (hasBase || hasSolana) {
407
439
  lines.push("IMPORTANT: Pay the exact amount in the 402 challenge. Overpayments and underpayments cannot be matched.");
408
440
  }
409
441
  lines.push("");
410
442
  return lines.join("\n");
411
443
  }
412
- function buildLlmsTxt(input) {
413
- const parts = [`# ${input.merchantName}`];
414
- if (input.tagline) {
415
- parts.push(`> ${input.tagline}`);
444
+ function buildLlmsTxt({
445
+ merchantName,
446
+ tagline,
447
+ sections,
448
+ agentscoreIdentity,
449
+ payment
450
+ }) {
451
+ const parts = [`# ${merchantName}`];
452
+ if (tagline) {
453
+ parts.push(`> ${tagline}`);
416
454
  }
417
455
  parts.push("");
418
- for (const s of input.sections) {
456
+ for (const s of sections) {
419
457
  parts.push(`## ${s.heading}`);
420
458
  parts.push("");
421
459
  parts.push(s.content);
422
460
  parts.push("");
423
461
  }
424
- if (input.agentscoreIdentity) {
425
- parts.push(llmsTxtIdentitySection(input.agentscoreIdentity));
462
+ if (agentscoreIdentity) {
463
+ parts.push(llmsTxtIdentitySection(agentscoreIdentity));
426
464
  parts.push("");
427
465
  }
428
- if (input.payment) {
429
- parts.push(llmsTxtPaymentSection(input.payment));
466
+ if (payment) {
467
+ parts.push(llmsTxtPaymentSection(payment));
430
468
  }
431
469
  return parts.join("\n");
432
470
  }
@@ -527,21 +565,88 @@ function agentscorePaymentRequiredSchema() {
527
565
  }
528
566
  };
529
567
  }
530
- function xPaymentInfoExtension(input) {
531
- return { "x-payment-info": { price: input.price, protocols: input.protocols } };
568
+ function xPaymentInfoExtension({
569
+ price,
570
+ protocols,
571
+ description
572
+ }) {
573
+ return {
574
+ "x-payment-info": {
575
+ authMode: "payment",
576
+ price,
577
+ protocols,
578
+ ...description !== void 0 && { description }
579
+ }
580
+ };
532
581
  }
533
582
  function xGuidanceExtension(text) {
534
583
  return { "x-guidance": text };
535
584
  }
536
- function agentscoreOpenApiSnippets(opts = {}) {
585
+ function xServiceInfoExtension(opts) {
586
+ return {
587
+ "x-service-info": {
588
+ categories: opts.categories,
589
+ ...opts.docs !== void 0 && { docs: opts.docs }
590
+ }
591
+ };
592
+ }
593
+ function xPaymentInfoFromCheckout(opts) {
594
+ const protocols = [];
595
+ const extras = opts.protocolExtras ?? {};
596
+ for (const spec of Object.values(opts.checkout.rails)) {
597
+ const isStripe2 = !("recipient" in spec);
598
+ const network = typeof spec.network === "string" ? spec.network : "";
599
+ const tokenCurrency = (typeof spec.currency === "string" ? spec.currency : "") || (typeof spec.token === "string" ? spec.token : "");
600
+ if (isStripe2) {
601
+ protocols.push({ mpp: { method: "stripe", intent: "charge", currency: "usd", ...extras.stripe ?? {} } });
602
+ } else if (network.startsWith("eip155:")) {
603
+ protocols.push({
604
+ x402: {
605
+ scheme: "exact",
606
+ network: "base",
607
+ asset: "USDC",
608
+ ...extras.base ?? {}
609
+ }
610
+ });
611
+ } else if (network.startsWith("solana:")) {
612
+ protocols.push({
613
+ mpp: {
614
+ method: "solana",
615
+ intent: "charge",
616
+ ...tokenCurrency ? { currency: tokenCurrency } : {},
617
+ ...extras.solana ?? {}
618
+ }
619
+ });
620
+ } else {
621
+ protocols.push({
622
+ mpp: {
623
+ method: "tempo",
624
+ intent: "charge",
625
+ ...tokenCurrency ? { currency: tokenCurrency } : {},
626
+ ...extras.tempo ?? {}
627
+ }
628
+ });
629
+ }
630
+ }
631
+ return xPaymentInfoExtension({
632
+ price: opts.price,
633
+ protocols,
634
+ ...opts.description !== void 0 && { description: opts.description }
635
+ });
636
+ }
637
+ function agentscoreOpenApiSnippets({
638
+ security = true,
639
+ denials = true,
640
+ paymentRequired = true
641
+ } = {}) {
537
642
  const out = {};
538
- if (opts.security !== false) {
643
+ if (security) {
539
644
  out.securitySchemes = agentscoreSecuritySchemes();
540
645
  }
541
- if (opts.denials !== false || opts.paymentRequired !== false) {
646
+ if (denials || paymentRequired) {
542
647
  out.schemas = {
543
- ...opts.denials !== false ? agentscoreDenialSchemas() : {},
544
- ...opts.paymentRequired !== false ? agentscorePaymentRequiredSchema() : {}
648
+ ...denials ? agentscoreDenialSchemas() : {},
649
+ ...paymentRequired ? agentscorePaymentRequiredSchema() : {}
545
650
  };
546
651
  }
547
652
  return out;
@@ -819,20 +924,907 @@ function buildSkillMd(input) {
819
924
  ].filter((s) => s !== "");
820
925
  return sections.join("\n\n").replace(/\n{3,}/g, "\n\n").trim() + "\n";
821
926
  }
927
+
928
+ // src/discovery/agentscore_content.ts
929
+ var PURCHASE_MODE_NOTES = Object.freeze({
930
+ redemption_only: "Requires a single-use redemption code (printed on a mailer or other out-of-band delivery). Submit the code in the request body as `redemption_code`. Without a valid code the order is rejected.",
931
+ coupon_applicable: "Codes are optional. Without one, settle at list price. With a valid code the discount is applied automatically (percent_off, fixed_off, or fixed_settle).",
932
+ paid_only: "Codes are NOT accepted. Settle at the listed price. Submitting a `redemption_code` field returns 400 codes_not_accepted."
933
+ });
934
+ function purchaseModeNote(mode) {
935
+ return PURCHASE_MODE_NOTES[mode] ?? "";
936
+ }
937
+ function buildAgentscoreOnboardingSteps(opts) {
938
+ const { merchantName, appUrl, acceptedRails, requiresKyc = false, vendorType = "goods" } = opts;
939
+ const railWordMap = {
940
+ tempo: "Tempo USDC",
941
+ "x402-base": "x402 USDC on Base",
942
+ "solana-mpp": "Solana SPL USDC",
943
+ "stripe-spt": "Stripe Shared Payment Token"
944
+ };
945
+ const railsHuman = acceptedRails.map((r) => railWordMap[r] ?? r).join(", ");
946
+ const chainPairs = [
947
+ ["tempo", "tempo"],
948
+ ["x402-base", "base"],
949
+ ["solana-mpp", "solana"]
950
+ ];
951
+ const flags = chainPairs.filter(([rail]) => acceptedRails.includes(rail)).map(([, flag]) => flag);
952
+ const chainFlags = flags.length > 0 ? flags.join(" | ") : "tempo|base";
953
+ const compatibleHints = [
954
+ ["tempo", "`tempo request` works for tempo USDC.e"],
955
+ ["x402-base", "`x402-proxy` / `purl` work for Base x402"],
956
+ ["stripe-spt", "`@stripe/link-cli` works for Stripe SPT"]
957
+ ];
958
+ const compatibleFragment = compatibleHints.filter(([rail]) => acceptedRails.includes(rail)).map(([, hint]) => hint).join(", ");
959
+ const installStep = `Install agentscore-pay if you don't already have a compatible client for your funded chain: \`npm i -g @agent-score/pay\` (or \`brew install agentscore/tap/agentscore-pay\`). ${merchantName} accepts: ${railsHuman}. agentscore-pay speaks every supported rail; ` + (compatibleFragment ? `the rails table also lists per-rail \`compatible_clients\` \u2014 ${compatibleFragment}. ` : "") + "Any spec-compliant client for an individual rail works too.";
960
+ const bootstrapStep = `First-run only: bootstrap wallet + Passport. Run \`agentscore-pay agent-guide --json\` for the canonical cold-start path \u2014 it walks \`agentscore-pay init\` (creates keystore + per-chain wallet), \`agentscore-pay passport login\` (one-time KYC${requiresKyc ? "; required for this merchant" : ""}; the human completes a verify URL once and pay caches the operator_token), and \`agentscore-pay balance\` to see which chain has USDC. Skip if your wallet+Passport are already provisioned.`;
961
+ const stripeFallbackStep = "If your only payment method is a Stripe / Link card (no crypto), install `@stripe/link-cli` instead of agentscore-pay and use it on the SPT rail. Identity gating still applies \u2014 the merchant's 403 with `verify_url` lets you bootstrap a Passport even with no crypto wallet involved.";
962
+ const returningUserStep = "Returning user note: if you've paid an AgentScore-gated merchant before from this wallet, the wallet is already in your Passport's `linked_wallets[]` and identity flows through automatically with no re-KYC prompt. Paying from a NEW wallet while you already hold an `opc_...` token returns 403 `wallet_signer_mismatch`; the body lists `linked_wallets[]` and `agent_instructions.action: resign_or_switch_to_operator_token` with three deterministic recoveries (switch to a linked wallet, drop the operator_token to re-KYC the new wallet, or pre-claim the new wallet via SIWE on agentscore.sh/verify).";
963
+ const pickRailStep = `Pick the rail your wallet is funded for. The 402 advertises ${acceptedRails.length} rail${acceptedRails.length === 1 ? "" : "s"}. \`agentscore-pay balance\` (without \`--chain\`) lists every chain's USDC; pay rejects with \`multi_rail_ambiguity\` if you don't pass \`--chain\` on a multi-rail challenge.`;
964
+ const placeOrderStep = `Place the order: \`agentscore-pay pay POST ${appUrl}/purchase --chain <${chainFlags}> -d '<body>' --max-spend <amount>\` for crypto rails. For Stripe SPT, follow the handoff hint pay emits and use \`@stripe/link-cli\` instead. Either way pay handles the 402 retry, signing, and Passport attachment; branch on the structured CliError \`code\` on non-zero exit (insufficient_balance, multi_rail_ambiguity, config_error for missing wallet/Passport, etc.).`;
965
+ const makeCallStep = `Make the paid call: \`agentscore-pay pay POST ${appUrl}/<endpoint> --chain <${chainFlags}> --max-spend <amount>\`; pay handles 402 retry, rail selection, signing, and Passport attachment. Branch on the structured CliError \`code\` on non-zero exit (insufficient_balance, multi_rail_ambiguity, config_error for missing wallet/Passport, etc.).`;
966
+ const acceptsStripe = acceptedRails.includes("stripe-spt");
967
+ if (vendorType === "api") {
968
+ return [
969
+ installStep,
970
+ bootstrapStep,
971
+ ...acceptsStripe ? [stripeFallbackStep] : [],
972
+ returningUserStep,
973
+ pickRailStep,
974
+ makeCallStep
975
+ ];
976
+ }
977
+ return [
978
+ installStep,
979
+ bootstrapStep,
980
+ ...acceptsStripe ? [stripeFallbackStep] : [],
981
+ returningUserStep,
982
+ `Browse the catalog: \`curl ${appUrl}/catalog\`.`,
983
+ "Read each product's `purchase_mode` and `purchase_note` to decide whether a redemption code is required, optional, or rejected.",
984
+ pickRailStep,
985
+ placeOrderStep
986
+ ];
987
+ }
988
+ function standardEndpointDescriptions(opts) {
989
+ if (opts?.kind === "api") {
990
+ return {
991
+ "POST /<endpoint>": "Per-call paid endpoint. Returns 402 on the discovery leg with payment rails; 400 on body rejection; 403 + recovery payload when identity is required; 200 with the call result on success.",
992
+ "GET /usage": "Per-credential usage / billing summary. Identity-scoped."
993
+ };
994
+ }
995
+ const out = {
996
+ "GET /catalog": "List purchasable products.",
997
+ "GET /catalog/{slug}": "Single product detail.",
998
+ "POST /purchase": "Place an order. Returns 402 on the discovery leg with payment rails; 400 on body rejection; 403 + recovery payload when identity is required; 200 with order confirmation on success.",
999
+ "GET /orders/{id}": "Order detail (PII). Identity-scoped."
1000
+ };
1001
+ if (opts?.includeOrderStatusRoute) {
1002
+ out["GET /orders/{id}/status"] = "Payment status only (no PII).";
1003
+ }
1004
+ return out;
1005
+ }
1006
+ function buildMerchantIndexJson(opts) {
1007
+ return {
1008
+ name: opts.name,
1009
+ description: opts.description,
1010
+ docs: opts.docs,
1011
+ endpoints: opts.endpoints,
1012
+ audience: "agents",
1013
+ supported_rails: opts.supportedRails,
1014
+ ...opts.extra ?? {}
1015
+ };
1016
+ }
1017
+ function buildSuccessNextSteps(opts) {
1018
+ const out = {
1019
+ action: "done",
1020
+ user_message: opts.userMessage ?? "Payment complete. Your AgentScore Passport is now active across every AgentScore-gated merchant."
1021
+ };
1022
+ if (opts.orderStatusUrl) out.order_status_url = opts.orderStatusUrl;
1023
+ if (opts.fulfillmentEta !== void 0) out.fulfillment_eta = opts.fulfillmentEta;
1024
+ return out;
1025
+ }
1026
+
1027
+ // src/discovery/redemption_md.ts
1028
+ var DEFAULT_BODY_SHAPE = `{
1029
+ "product_slug": "<slug>",
1030
+ "redemption_code": "<code>",
1031
+ "email": "user@example.com",
1032
+ "shipping": { "name": "...", "address_1": "...", "city": "...", "state": "CA", "zip": "94573" }
1033
+ }`;
1034
+ var DEFAULT_BODY_RULES = `## Body rules
1035
+
1036
+ - \`quantity\` is fixed at 1; one product per code.
1037
+ - \`shipping.country\` defaults to \`"US"\`; non-US shipping is rejected for
1038
+ redemption-eligible products.
1039
+ - \`shipping.state\` must be a 2-letter US state code; \`unsupported_jurisdiction\`
1040
+ 400 if the state isn't on the merchant's allowlist.
1041
+ - \`email\` must be valid; the merchant returns 422 on malformed input.`;
1042
+ function buildRedemptionSkillMd(opts) {
1043
+ const {
1044
+ merchantName,
1045
+ appUrl,
1046
+ endpointPath = "/purchase",
1047
+ skuIntro,
1048
+ deliveryIntro,
1049
+ bodyShape,
1050
+ bodyRules,
1051
+ extraRecoveryRows,
1052
+ peerMerchantPointer
1053
+ } = opts;
1054
+ const skuText = skuIntro ?? "The code redeems a product or paid call at this merchant which you'll find in the merchant's catalog or per-endpoint documentation with `purchase_mode = redemption_only`.";
1055
+ const deliveryText = deliveryIntro ?? `You're reading this because the user you're working for received a single-use redemption code from ${merchantName} (printed on a mailer, emailed, surfaced in-app, or distributed out-of-band). This page tells you, the agent, exactly how to turn that code into a settled call.`;
1056
+ const renderedBodyShape = bodyShape ?? DEFAULT_BODY_SHAPE;
1057
+ const renderedBodyRules = bodyRules !== void 0 ? bodyRules : DEFAULT_BODY_RULES;
1058
+ const bodyRulesSection = renderedBodyRules ? `
1059
+ ${renderedBodyRules}
1060
+ ` : "";
1061
+ const extraRows = extraRecoveryRows ? `
1062
+ ${extraRecoveryRows.trimEnd()}` : "";
1063
+ const peerSection = peerMerchantPointer ? `
1064
+ ## Don't have a code?
1065
+
1066
+ This page is the redemption flow for single-use codes. If you're looking to buy or call without a code, see: ${peerMerchantPointer}
1067
+ ` : "";
1068
+ return `# Redeeming an AgentScore code at ${merchantName}
1069
+
1070
+ ${deliveryText}
1071
+
1072
+ ${skuText} The 402 challenge on ${endpointPath} tells you the actual settle amount
1073
+ after the code is applied; discounts can range from a partial amount off list
1074
+ down to free.
1075
+
1076
+ ## Cold-start bootstrap (skip if your wallet + Passport are already set up)
1077
+
1078
+ If \`agentscore-pay\` isn't installed yet, install it (\`npm i -g @agent-score/pay\`
1079
+ or \`brew install agentscore/tap/agentscore-pay\`), then run \`agentscore-pay
1080
+ agent-guide --json\` for the canonical cold-start path. That walks
1081
+ \`agentscore-pay init\` (creates keystore + per-chain wallet),
1082
+ \`agentscore-pay passport login\` (one-time KYC; opens a verify URL the human
1083
+ completes, after which pay caches the \`operator_token\`), and
1084
+ \`agentscore-pay balance\` to confirm funds. Fund enough to cover the
1085
+ post-discount settle amount the 402 advertises; for $0 codes the merchant
1086
+ skips the on-chain settle entirely so funds aren't required, but the wallet
1087
+ still needs to exist so the credential can be signed.
1088
+
1089
+ You don't have to use \`agentscore-pay\` specifically; any spec-compliant client
1090
+ for the merchant's accepted rails (Tempo MPP, x402 Base, Solana MPP, Stripe SPT)
1091
+ works. The 402 challenge lists every accepted rail in \`accepted_methods\`.
1092
+
1093
+ ## TL;DR
1094
+
1095
+ 1. Ask the user for their redemption code, plus any merchant-specific fields the
1096
+ body requires (email, shipping address for goods merchants, identifiers for
1097
+ API merchants, etc.).
1098
+ 2. Discover the redemption-eligible target. Goods merchants: \`GET ${appUrl}/catalog\`
1099
+ and find the product whose \`purchase_mode\` is \`redemption_only\`. API merchants:
1100
+ read the per-endpoint docs for the route that accepts \`redemption_code\`.
1101
+ Read any \`purchase_note\` for product-specific rules.
1102
+ 3. \`POST ${appUrl}${endpointPath}\` with body:
1103
+ \`\`\`json
1104
+ ${renderedBodyShape}
1105
+ \`\`\`
1106
+ 4. If you get **403 \`operator_verification_required\`**, surface the body's
1107
+ \`verify_url\` to the user for one-time KYC and poll \`poll_url\` with
1108
+ \`poll_secret\`. After verification, retry with \`X-Operator-Token\` attached.
1109
+ If you already have an \`opc_...\` from a prior AgentScore-gated merchant,
1110
+ attach it on the first call and skip this step.
1111
+ 5. On **402**, the body carries \`accepted_methods\` and \`agent_instructions.how_to_pay\`.
1112
+ Settle with \`agentscore-pay pay POST ${appUrl}${endpointPath} --chain <rail> -d '<body>'
1113
+ --max-spend <amount>\`; pay handles 402 retry, rail selection, signing, and
1114
+ Passport attachment. Pass \`--max-spend\` >= the amount in the 402.
1115
+ 6. **200**; the call settled. Response carries a receipt \`id\` (order id for goods,
1116
+ request id for API), \`next_steps.order_status_url\` (or usage dashboard URL),
1117
+ and an \`agent_memory\` block you should persist (the cross-merchant pattern
1118
+ hint, NOT the operator_token or poll_secret). For $0 redemptions \`tx_hash\`
1119
+ is \`null\`; the credential is still authenticated and the code is burned
1120
+ single-use.
1121
+ ${bodyRulesSection}
1122
+ ## Code rules
1123
+
1124
+ - Codes are case-insensitive (server uppercases on receipt), single-use, and
1125
+ burned atomically against \`(code, operator_token)\` OR \`(code, signer_address)\`
1126
+ for token-less wallet flows. A second attempt returns 400 \`redemption_already_used\`.
1127
+ - Submit the code in the JSON body as \`"redemption_code"\`; never as a header.
1128
+
1129
+ ## Recovery on common errors
1130
+
1131
+ | HTTP | error.code | What it means | What to do |
1132
+ |---|---|---|---|
1133
+ | 403 | \`operator_verification_required\` | User has no Passport / KYC pending | Surface \`verify_url\`; poll \`poll_url\` with \`poll_secret\`; retry with \`X-Operator-Token\` |
1134
+ | 403 | \`wallet_signer_mismatch\` | Operator token + signer wallet aren't linked to the same identity | Switch to a wallet in \`linked_wallets[]\`, or drop the operator_token to re-KYC the new wallet |
1135
+ | 400 | \`invalid_body\` | JSON parse failed | Fix the JSON and retry |
1136
+ | 400 | \`missing_fields\` | Required field absent | Add the field per \`error.message\` and retry |
1137
+ | 400 | \`product_not_found\` | Identifier doesn't match an active product / endpoint | Re-check the catalog or endpoint docs and use the exact slug / route |
1138
+ | 400 | \`product_out_of_stock\` | Goods-only: stock 0 | Tell the user; no retry possible |
1139
+ | 400 | \`invalid_redemption_code\` | Code unknown / expired | Ask the user for the code as printed; do not invent variants |
1140
+ | 400 | \`redemption_already_used\` | Code burned | Tell the user; codes are single-use |
1141
+ | 400 | \`codes_not_accepted\` | Target is \`paid_only\` and rejects codes | Drop \`redemption_code\` and retry, or pick a different target |
1142
+ | 402 | (challenge) | Identity OK; payment required | Run \`agentscore-pay pay\` against the same URL |${extraRows}
1143
+ ${peerSection}`;
1144
+ }
1145
+
1146
+ // src/identity/ucp.ts
1147
+ function ucpSigningKeyFromJWKImpl(jwk) {
1148
+ if (!jwk || typeof jwk !== "object") {
1149
+ throw new Error(`UCPSigningKey.fromJWK expected a non-null object; got ${typeof jwk}.`);
1150
+ }
1151
+ if (typeof jwk.kid !== "string" || !jwk.kid) {
1152
+ throw new Error("UCPSigningKey.fromJWK: JWK missing required field `kid` (or non-string).");
1153
+ }
1154
+ if (typeof jwk.kty !== "string" || !jwk.kty) {
1155
+ throw new Error("UCPSigningKey.fromJWK: JWK missing required field `kty` (or non-string).");
1156
+ }
1157
+ if (jwk.kty !== "OKP" && jwk.kty !== "EC" && jwk.kty !== "RSA") {
1158
+ throw new Error(
1159
+ `UCPSigningKey.fromJWK: kty=${JSON.stringify(jwk.kty)} is not a supported asymmetric key type (expected OKP, EC, or RSA). Symmetric \`oct\` keys are rejected because they cannot publicly verify a JWS in the trust-mode UCP flow.`
1160
+ );
1161
+ }
1162
+ if ((jwk.kty === "EC" || jwk.kty === "OKP") && (typeof jwk.crv !== "string" || !jwk.crv)) {
1163
+ throw new Error(`UCPSigningKey.fromJWK: kty=${jwk.kty} requires a non-empty \`crv\` field (e.g., "P-256" for EC, "Ed25519" for OKP).`);
1164
+ }
1165
+ return jwk;
1166
+ }
1167
+ var UCPSigningKey = {
1168
+ fromJWK: ucpSigningKeyFromJWKImpl
1169
+ };
1170
+ var DEFAULT_VERSION = "2026-04-08";
1171
+ var AGENTSCORE_CAPABILITY_NAME = "sh.agentscore.identity";
1172
+ var AGENTSCORE_CAPABILITY_VERSION = "2026-04-08";
1173
+ var AGENTSCORE_DEFAULT_SPEC_URL = "https://agentscore.sh/specification/identity";
1174
+ var AGENTSCORE_DEFAULT_SCHEMA_URL = "https://agentscore.sh/schemas/ucp/sh-agentscore-identity-v1.json";
1175
+ var AGENTSCORE_EXTENDS = ["dev.ucp.shopping.checkout", "dev.ucp.shopping.cart"];
1176
+ var RESERVED_TOP_LEVEL = /* @__PURE__ */ new Set([
1177
+ "ucp",
1178
+ "signing_keys",
1179
+ "signature",
1180
+ "__proto__",
1181
+ "constructor",
1182
+ "prototype"
1183
+ ]);
1184
+ var RESERVED_UCP_FIELDS = /* @__PURE__ */ new Set([
1185
+ "version",
1186
+ "name",
1187
+ "services",
1188
+ "capabilities",
1189
+ "payment_handlers",
1190
+ "supported_versions",
1191
+ "__proto__",
1192
+ "constructor",
1193
+ "prototype"
1194
+ ]);
1195
+ function buildUCPProfile(input) {
1196
+ for (const [name, bindings] of Object.entries(input.services ?? {})) {
1197
+ for (const binding of bindings) {
1198
+ if ((binding.transport === "rest" || binding.transport === "mcp" || binding.transport === "a2a") && (binding.endpoint === void 0 || binding.endpoint === null || binding.endpoint === "")) {
1199
+ throw new Error(
1200
+ `buildUCPProfile: service "${name}" transport=${binding.transport} requires \`endpoint\`. Per UCP spec service.json business_schema, rest/mcp/a2a bindings MUST carry an endpoint URL.`
1201
+ );
1202
+ }
1203
+ }
1204
+ }
1205
+ const paymentHandlers = {};
1206
+ for (const [name, bindings] of Object.entries(input.payment_handlers ?? {})) {
1207
+ paymentHandlers[name] = bindings.map((binding) => {
1208
+ if (Array.isArray(binding.available_instruments) && binding.available_instruments.length === 0) {
1209
+ const { available_instruments: _drop, ...rest } = binding;
1210
+ return rest;
1211
+ }
1212
+ return binding;
1213
+ });
1214
+ }
1215
+ const capabilities = {};
1216
+ for (const [name, bindings] of Object.entries(input.capabilities ?? {})) {
1217
+ capabilities[name] = [...bindings];
1218
+ }
1219
+ if (input.agentscore_gate) {
1220
+ const gateConfig = { ...input.agentscore_gate };
1221
+ const agentscoreBinding = {
1222
+ version: AGENTSCORE_CAPABILITY_VERSION,
1223
+ spec: input.agentscore_spec_url ?? AGENTSCORE_DEFAULT_SPEC_URL,
1224
+ schema: input.agentscore_schema_url ?? AGENTSCORE_DEFAULT_SCHEMA_URL,
1225
+ extends: AGENTSCORE_EXTENDS
1226
+ };
1227
+ if (Object.keys(gateConfig).length > 0) agentscoreBinding.config = gateConfig;
1228
+ const existing = capabilities[AGENTSCORE_CAPABILITY_NAME];
1229
+ if (existing) existing.push(agentscoreBinding);
1230
+ else capabilities[AGENTSCORE_CAPABILITY_NAME] = [agentscoreBinding];
1231
+ }
1232
+ const ucp = {
1233
+ version: input.version ?? DEFAULT_VERSION,
1234
+ services: input.services ?? {},
1235
+ capabilities,
1236
+ payment_handlers: paymentHandlers
1237
+ };
1238
+ if (input.name !== void 0) ucp.name = input.name;
1239
+ if (input.supported_versions !== void 0) ucp.supported_versions = input.supported_versions;
1240
+ if (input.ucp_extras) {
1241
+ for (const k of Object.keys(input.ucp_extras)) {
1242
+ if (RESERVED_UCP_FIELDS.has(k)) {
1243
+ throw new Error(`buildUCPProfile: ucp_extras key "${k}" collides with a reserved \`ucp\` field; rejected.`);
1244
+ }
1245
+ }
1246
+ Object.assign(ucp, input.ucp_extras);
1247
+ }
1248
+ const profile = {
1249
+ ucp,
1250
+ signing_keys: input.signing_keys
1251
+ };
1252
+ if (input.extras) {
1253
+ for (const k of Object.keys(input.extras)) {
1254
+ if (RESERVED_TOP_LEVEL.has(k)) {
1255
+ throw new Error(`buildUCPProfile: extras key "${k}" collides with a reserved profile field; rejected.`);
1256
+ }
1257
+ }
1258
+ Object.assign(profile, input.extras);
1259
+ }
1260
+ return profile;
1261
+ }
1262
+ var HANDLER_VERSION = "2026-04-08";
1263
+ var SPEC_BASE = "https://agentscore.sh/specification/payment-handlers";
1264
+ var SCHEMA_BASE = "https://agentscore.sh/schemas/payment-handlers";
1265
+ var CAIP2_TO_UCP_NETWORK = {
1266
+ "eip155:8453": "base-8453",
1267
+ "eip155:84532": "base-84532",
1268
+ "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": "solana-mainnet-beta",
1269
+ "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": "solana-devnet"
1270
+ };
1271
+ function ucpNetworkName(caip2OrUcp, fallback) {
1272
+ if (caip2OrUcp === void 0) return fallback;
1273
+ return CAIP2_TO_UCP_NETWORK[caip2OrUcp] ?? caip2OrUcp;
1274
+ }
1275
+ function staticRecipient(r) {
1276
+ return typeof r === "string" && r.length > 0 ? r : void 0;
1277
+ }
1278
+ function tempoToNetworkEntry(spec) {
1279
+ const entry = {
1280
+ network: spec.testnet ? "tempo-testnet" : spec.network ?? "tempo-mainnet",
1281
+ chain_id: spec.chainId ?? 4217
1282
+ };
1283
+ const recipient = staticRecipient(spec.recipient);
1284
+ if (recipient !== void 0) entry.recipient = recipient;
1285
+ return entry;
1286
+ }
1287
+ function solanaMppToNetworkEntry(spec) {
1288
+ const entry = {
1289
+ network: ucpNetworkName(spec.network, "solana-mainnet-beta")
1290
+ };
1291
+ const recipient = staticRecipient(spec.recipient);
1292
+ if (recipient !== void 0) entry.recipient = recipient;
1293
+ return entry;
1294
+ }
1295
+ function tempoSessionToNetworkEntry(spec) {
1296
+ const entry = {
1297
+ network: spec.testnet ? "tempo-testnet" : "tempo-mainnet",
1298
+ escrow_contract: spec.escrowContract
1299
+ };
1300
+ const recipient = staticRecipient(spec.recipient);
1301
+ if (recipient !== void 0) entry.recipient = recipient;
1302
+ return entry;
1303
+ }
1304
+ function isTempoRailSpec(s) {
1305
+ return !("escrowContract" in s) && !("rpcUrl" in s) && !("tokenProgram" in s);
1306
+ }
1307
+ function isTempoSessionRailSpec(s) {
1308
+ return "escrowContract" in s && "store" in s;
1309
+ }
1310
+ function mppRailToNetworkEntry(spec) {
1311
+ if (isTempoSessionRailSpec(spec)) return tempoSessionToNetworkEntry(spec);
1312
+ if ("rpcUrl" in spec || "tokenProgram" in spec || (spec.network?.startsWith("solana:") ?? false)) {
1313
+ return solanaMppToNetworkEntry(spec);
1314
+ }
1315
+ if (isTempoRailSpec(spec)) return tempoToNetworkEntry(spec);
1316
+ return tempoToNetworkEntry(spec);
1317
+ }
1318
+ function mppPaymentHandler({
1319
+ networks: networks2
1320
+ }) {
1321
+ return {
1322
+ "sh.agentscore.payment.mpp": [{
1323
+ id: "mpp",
1324
+ version: HANDLER_VERSION,
1325
+ spec: `${SPEC_BASE}/mpp`,
1326
+ schema: `${SCHEMA_BASE}/mpp.json`,
1327
+ config: { networks: networks2.map(mppRailToNetworkEntry) }
1328
+ }]
1329
+ };
1330
+ }
1331
+ function x402RailToNetworkEntry(spec) {
1332
+ const entry = {
1333
+ network: ucpNetworkName(spec.network, "base-8453")
1334
+ };
1335
+ const recipient = staticRecipient(spec.recipient);
1336
+ if (recipient !== void 0) entry.recipient = recipient;
1337
+ return entry;
1338
+ }
1339
+ function x402PaymentHandler({
1340
+ networks: networks2
1341
+ }) {
1342
+ return {
1343
+ "sh.agentscore.payment.x402": [{
1344
+ id: "x402",
1345
+ version: HANDLER_VERSION,
1346
+ spec: `${SPEC_BASE}/x402`,
1347
+ schema: `${SCHEMA_BASE}/x402.json`,
1348
+ config: { networks: networks2.map(x402RailToNetworkEntry) }
1349
+ }]
1350
+ };
1351
+ }
1352
+ function stripeSptPaymentHandler({
1353
+ spec
1354
+ }) {
1355
+ return {
1356
+ "sh.agentscore.payment.stripe_spt": [{
1357
+ id: "stripe-spt",
1358
+ version: HANDLER_VERSION,
1359
+ spec: `${SPEC_BASE}/stripe_spt`,
1360
+ schema: `${SCHEMA_BASE}/stripe_spt.json`,
1361
+ config: { rail: "stripe-spt", profile_id: spec.profileId ?? null }
1362
+ }]
1363
+ };
1364
+ }
1365
+
1366
+ // src/identity/ucp-jwks.ts
1367
+ var JOSE_INSTALL_HINT = "Install the optional peer dependency: `npm install jose@^6` (or `bun add jose`). Tested against jose v6.x.";
1368
+ var ALLOWED_ALGS = ["EdDSA", "ES256"];
1369
+ var PROFILE_TYP = "agentscore-profile+jws";
1370
+ async function loadJose() {
1371
+ try {
1372
+ return await import("jose");
1373
+ } catch (err) {
1374
+ throw new Error(
1375
+ `UCP signing requires the \`jose\` library, which is an optional peer dependency. ${JOSE_INSTALL_HINT}
1376
+ Original error: ${err instanceof Error ? err.message : String(err)}`
1377
+ );
1378
+ }
1379
+ }
1380
+ function canonicalizeProfile(profile) {
1381
+ const stripped = { ...profile };
1382
+ delete stripped.signature;
1383
+ return stableStringify(stripped);
1384
+ }
1385
+ function stableStringify(value) {
1386
+ if (value === void 0) {
1387
+ throw new Error(
1388
+ "stableStringify: undefined values are not allowed in canonicalized JSON. Object fields with no value must be omitted."
1389
+ );
1390
+ }
1391
+ if (typeof value === "function" || typeof value === "symbol") {
1392
+ throw new Error(`stableStringify: ${typeof value} values are not allowed in canonicalized JSON.`);
1393
+ }
1394
+ if (typeof value === "bigint") {
1395
+ throw new Error("stableStringify: BigInt values are not allowed; use a decimal string.");
1396
+ }
1397
+ if (value instanceof Date) {
1398
+ throw new Error(
1399
+ "stableStringify: Date instances are not allowed; serialize to an ISO string before passing."
1400
+ );
1401
+ }
1402
+ if (value instanceof Map || value instanceof Set || value instanceof WeakMap || value instanceof WeakSet) {
1403
+ throw new Error(
1404
+ `stableStringify: ${value.constructor.name} values are not allowed; convert to a plain object/array first.`
1405
+ );
1406
+ }
1407
+ if (ArrayBuffer.isView(value)) {
1408
+ throw new Error("stableStringify: typed arrays are not allowed; convert to a plain array first.");
1409
+ }
1410
+ if (typeof value === "number") {
1411
+ if (!Number.isFinite(value)) {
1412
+ throw new Error(
1413
+ `UCP profile canonicalization rejects non-finite Number ${value}. Use a decimal string for any value that may be NaN/Infinity.`
1414
+ );
1415
+ }
1416
+ if (!Number.isInteger(value)) {
1417
+ throw new Error(
1418
+ `UCP profile canonicalization rejects non-integer Number ${value}. Use a decimal string (e.g. "9.99") for monetary or fractional fields to preserve cross-language byte-parity.`
1419
+ );
1420
+ }
1421
+ if (!Number.isSafeInteger(value)) {
1422
+ throw new Error(
1423
+ `stableStringify: integer ${value} exceeds Number.MAX_SAFE_INTEGER. For values >2^53, use a decimal string to preserve cross-language byte parity.`
1424
+ );
1425
+ }
1426
+ }
1427
+ if (typeof value === "string") {
1428
+ if (value.includes("\u2028") || value.includes("\u2029")) {
1429
+ throw new Error(
1430
+ "stableStringify: strings containing U+2028 (LINE SEPARATOR) or U+2029 (PARAGRAPH SEPARATOR) are not allowed; cross-language byte parity requires neither be present (Node JSON.stringify on older V8 escapes them; Python json.dumps with ensure_ascii=False does not)."
1431
+ );
1432
+ }
1433
+ return JSON.stringify(value);
1434
+ }
1435
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
1436
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
1437
+ const obj = value;
1438
+ const keys = Object.keys(obj).sort((a, b) => {
1439
+ const aPoints = [...a].map((c) => c.codePointAt(0));
1440
+ const bPoints = [...b].map((c) => c.codePointAt(0));
1441
+ const len = Math.min(aPoints.length, bPoints.length);
1442
+ for (let i = 0; i < len; i += 1) {
1443
+ if (aPoints[i] !== bPoints[i]) return aPoints[i] - bPoints[i];
1444
+ }
1445
+ return aPoints.length - bPoints.length;
1446
+ });
1447
+ for (const k of keys) {
1448
+ if (k.includes("\u2028") || k.includes("\u2029")) {
1449
+ throw new Error(
1450
+ "stableStringify: object keys containing U+2028 (LINE SEPARATOR) or U+2029 (PARAGRAPH SEPARATOR) are not allowed; cross-language byte parity (Node JSON.stringify on older V8 escapes them; Python json.dumps with ensure_ascii=False does not)."
1451
+ );
1452
+ }
1453
+ }
1454
+ const pairs = keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`);
1455
+ return `{${pairs.join(",")}}`;
1456
+ }
1457
+ async function generateUCPSigningKey(opts) {
1458
+ const jose = await loadJose();
1459
+ const alg = opts.alg ?? "EdDSA";
1460
+ const { privateKey, publicKey } = await jose.generateKeyPair(alg, { extractable: true });
1461
+ const exportedJwk = await jose.exportJWK(publicKey);
1462
+ const publicJWK = {
1463
+ kid: opts.kid,
1464
+ alg,
1465
+ use: "sig",
1466
+ ...exportedJwk
1467
+ };
1468
+ return { privateKey, publicJWK };
1469
+ }
1470
+ async function signUCPProfile(profile, {
1471
+ signingKey,
1472
+ kid,
1473
+ alg = "EdDSA"
1474
+ }) {
1475
+ const jose = await loadJose();
1476
+ if (!ALLOWED_ALGS.includes(alg)) {
1477
+ throw new Error(
1478
+ `signUCPProfile: alg ${JSON.stringify(alg)} is not in the supported set [${ALLOWED_ALGS.join(", ")}].`
1479
+ );
1480
+ }
1481
+ if (typeof kid !== "string" || !kid) {
1482
+ throw new Error("signUCPProfile: kid must be a non-empty string.");
1483
+ }
1484
+ const kids = (profile.signing_keys ?? []).map((k) => k.kid);
1485
+ if (!kids.includes(kid)) {
1486
+ throw new Error(
1487
+ `signUCPProfile: kid ${JSON.stringify(kid)} is not present in profile.signing_keys[] (declared kids: ${JSON.stringify(kids)}). Verifiers will not find the key.`
1488
+ );
1489
+ }
1490
+ const canonicalBody = canonicalizeProfile(profile);
1491
+ const payloadBytes = new TextEncoder().encode(canonicalBody);
1492
+ const signature = await new jose.CompactSign(payloadBytes).setProtectedHeader({ alg, kid, typ: PROFILE_TYP }).sign(signingKey);
1493
+ return { ...profile, signature };
1494
+ }
1495
+ function buildJWKSResponse(keys) {
1496
+ return { keys };
1497
+ }
1498
+ var DEFAULT_LOAD_OPTS = {
1499
+ envJwkVar: "UCP_SIGNING_KEY_JWK_PRIVATE",
1500
+ envKidVar: "UCP_SIGNING_KEY_KID",
1501
+ envAlgVar: "UCP_SIGNING_KEY_ALG",
1502
+ defaultKid: "merchant-default",
1503
+ defaultAlg: "EdDSA"
1504
+ };
1505
+ function readEnvTrimmed(name) {
1506
+ const raw = process.env[name];
1507
+ if (raw === void 0) return void 0;
1508
+ const trimmed = raw.trim();
1509
+ return trimmed === "" ? void 0 : trimmed;
1510
+ }
1511
+ function detectAlgFromJwk(jwk) {
1512
+ if (jwk.kty === "OKP" && jwk.crv === "Ed25519") return "EdDSA";
1513
+ if (jwk.kty === "EC" && jwk.crv === "P-256") return "ES256";
1514
+ return null;
1515
+ }
1516
+ var envLoaderCache = /* @__PURE__ */ new Map();
1517
+ function cacheKey(opts) {
1518
+ return `${opts.envJwkVar}|${opts.envKidVar}|${opts.envAlgVar}|${opts.defaultKid}|${opts.defaultAlg}`;
1519
+ }
1520
+ async function buildEnvSigningKey(opts) {
1521
+ const kidDefault = readEnvTrimmed(opts.envKidVar) ?? opts.defaultKid;
1522
+ const rawAlg = (readEnvTrimmed(opts.envAlgVar) ?? "").toUpperCase();
1523
+ const algFallback = rawAlg === "ES256" ? "ES256" : opts.defaultAlg;
1524
+ const envJwk = readEnvTrimmed(opts.envJwkVar);
1525
+ if (envJwk) {
1526
+ let jwkDict;
1527
+ try {
1528
+ jwkDict = JSON.parse(envJwk);
1529
+ } catch (err) {
1530
+ throw new Error(
1531
+ `${opts.envJwkVar} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
1532
+ );
1533
+ }
1534
+ if (!jwkDict || typeof jwkDict !== "object" || Array.isArray(jwkDict) || Object.keys(jwkDict).length === 0) {
1535
+ throw new Error(`${opts.envJwkVar} must be a non-empty JWK object.`);
1536
+ }
1537
+ const detectedAlg = detectAlgFromJwk(jwkDict);
1538
+ if (!detectedAlg) {
1539
+ throw new Error(
1540
+ `${opts.envJwkVar} has unsupported kty/crv (got kty=${String(jwkDict.kty)} crv=${String(jwkDict.crv)}); expected OKP+Ed25519 or EC+P-256.`
1541
+ );
1542
+ }
1543
+ const canonicalPrivateJwk = detectedAlg === "EdDSA" ? { kty: jwkDict.kty, crv: jwkDict.crv, x: jwkDict.x, d: jwkDict.d } : { kty: jwkDict.kty, crv: jwkDict.crv, x: jwkDict.x, y: jwkDict.y, d: jwkDict.d };
1544
+ const { importJWK } = await import("jose");
1545
+ const { createPublicKey } = await import("crypto");
1546
+ let privateKey;
1547
+ let publicNodeKey;
1548
+ try {
1549
+ privateKey = await importJWK(
1550
+ canonicalPrivateJwk,
1551
+ detectedAlg
1552
+ );
1553
+ publicNodeKey = createPublicKey({ key: canonicalPrivateJwk, format: "jwk" });
1554
+ } catch (err) {
1555
+ const className = err instanceof Error ? err.constructor.name : typeof err;
1556
+ const code = err && typeof err === "object" && "code" in err ? String(err.code) : null;
1557
+ const codeSuffix = code ? ` [${code}]` : "";
1558
+ throw new Error(
1559
+ `${opts.envJwkVar} has malformed key material (${className}${codeSuffix}). Verify the JWK is well-formed and matches the declared kty/crv. Underlying details suppressed to avoid leaking key bytes.`
1560
+ );
1561
+ }
1562
+ const publicJWK = publicNodeKey.export({ format: "jwk" });
1563
+ publicJWK.kid = jwkDict.kid || kidDefault;
1564
+ publicJWK.alg = detectedAlg;
1565
+ publicJWK.use = "sig";
1566
+ return { privateKey, publicJWK };
1567
+ }
1568
+ return generateUCPSigningKey({ kid: kidDefault, alg: algFallback });
1569
+ }
1570
+ async function loadUCPSigningKeyFromEnv({
1571
+ envJwkVar,
1572
+ envKidVar,
1573
+ envAlgVar,
1574
+ defaultKid,
1575
+ defaultAlg
1576
+ } = {}) {
1577
+ const resolved = {
1578
+ ...DEFAULT_LOAD_OPTS,
1579
+ ...envJwkVar !== void 0 && { envJwkVar },
1580
+ ...envKidVar !== void 0 && { envKidVar },
1581
+ ...envAlgVar !== void 0 && { envAlgVar },
1582
+ ...defaultKid !== void 0 && { defaultKid },
1583
+ ...defaultAlg !== void 0 && { defaultAlg }
1584
+ };
1585
+ const key = cacheKey(resolved);
1586
+ let cached = envLoaderCache.get(key);
1587
+ if (cached) return cached;
1588
+ cached = buildEnvSigningKey(resolved).catch((err) => {
1589
+ envLoaderCache.delete(key);
1590
+ throw err;
1591
+ });
1592
+ envLoaderCache.set(key, cached);
1593
+ return cached;
1594
+ }
1595
+
1596
+ // src/discovery/well_known.ts
1597
+ var UCP_CACHE_SECONDS = 60;
1598
+ var JWKS_CACHE_SECONDS = 300;
1599
+ var UCP_SHOPPING_SPEC_2026_04_08 = "https://ucp.dev/2026-04-08/specification/overview";
1600
+ function requestId(headers) {
1601
+ if (headers === void 0) return void 0;
1602
+ if (headers instanceof Headers) return headers.get("x-request-id") ?? void 0;
1603
+ for (const [k, v] of Object.entries(headers)) {
1604
+ if (k.toLowerCase() === "x-request-id") return v;
1605
+ }
1606
+ return void 0;
1607
+ }
1608
+ function attachRequestId(headers, requestHeaders) {
1609
+ const rid = requestId(requestHeaders);
1610
+ if (rid !== void 0) headers["X-Request-ID"] = rid;
1611
+ }
1612
+ function isTempoSession(s) {
1613
+ return "escrowContract" in s && "store" in s;
1614
+ }
1615
+ function isStripe(s) {
1616
+ return !("recipient" in s);
1617
+ }
1618
+ function railHasRecipientField(spec) {
1619
+ return Object.hasOwn(spec, "recipient");
1620
+ }
1621
+ function composeHandlers(checkout) {
1622
+ const handlers = {};
1623
+ const mpp = [];
1624
+ const x402 = [];
1625
+ const stripe = [];
1626
+ for (const spec of Object.values(checkout.rails)) {
1627
+ if (isStripe(spec)) {
1628
+ stripe.push(spec);
1629
+ continue;
1630
+ }
1631
+ if (isTempoSession(spec)) {
1632
+ if (railHasRecipientField(spec)) mpp.push(spec);
1633
+ continue;
1634
+ }
1635
+ const network = spec.network ?? "";
1636
+ if (network.startsWith("eip155:") || "mode" in spec) {
1637
+ if (railHasRecipientField(spec)) x402.push(spec);
1638
+ } else if (network.startsWith("solana:") || "rpcUrl" in spec) {
1639
+ if (railHasRecipientField(spec)) mpp.push(spec);
1640
+ } else {
1641
+ if (railHasRecipientField(spec)) mpp.push(spec);
1642
+ }
1643
+ }
1644
+ if (mpp.length > 0) Object.assign(handlers, mppPaymentHandler({ networks: mpp }));
1645
+ if (x402.length > 0) Object.assign(handlers, x402PaymentHandler({ networks: x402 }));
1646
+ for (const spec of stripe) Object.assign(handlers, stripeSptPaymentHandler({ spec }));
1647
+ return handlers;
1648
+ }
1649
+ function misconfiguredResponse(requestHeaders) {
1650
+ const body = {
1651
+ error: {
1652
+ code: "ucp_misconfigured",
1653
+ message: "Merchant has no configured payment handlers."
1654
+ },
1655
+ next_steps: {
1656
+ action: "contact_merchant",
1657
+ user_message: "This merchant is temporarily unable to accept agent payments."
1658
+ },
1659
+ agent_instructions: {
1660
+ action: "contact_merchant",
1661
+ steps: [
1662
+ "Surface a transient error to the user.",
1663
+ "Retry later; the merchant operator will repair the configuration."
1664
+ ],
1665
+ user_message: "Merchant temporarily offline for agent payments."
1666
+ }
1667
+ };
1668
+ const headers = {
1669
+ "Access-Control-Allow-Origin": "*",
1670
+ "Cache-Control": `public, max-age=${UCP_CACHE_SECONDS}`
1671
+ };
1672
+ attachRequestId(headers, requestHeaders);
1673
+ return {
1674
+ body: JSON.stringify(body),
1675
+ mediaType: "application/json",
1676
+ headers,
1677
+ status: 503
1678
+ };
1679
+ }
1680
+ async function buildSignedUcpResponse(opts) {
1681
+ const {
1682
+ checkout,
1683
+ name,
1684
+ wellKnownUcpUrl,
1685
+ services,
1686
+ requestHeaders,
1687
+ signingKid = "merchant-default",
1688
+ agentscoreGate
1689
+ } = opts;
1690
+ const handlers = composeHandlers(checkout);
1691
+ if (Object.keys(handlers).length === 0) {
1692
+ return misconfiguredResponse(requestHeaders);
1693
+ }
1694
+ const key = await loadUCPSigningKeyFromEnv({ defaultKid: signingKid });
1695
+ const signingKeyEntry = UCPSigningKey.fromJWK(key.publicJWK);
1696
+ const profile = buildUCPProfile({
1697
+ name,
1698
+ supported_versions: { "2026-04-08": wellKnownUcpUrl },
1699
+ agentscore_gate: agentscoreGate,
1700
+ services,
1701
+ payment_handlers: handlers,
1702
+ signing_keys: [signingKeyEntry]
1703
+ });
1704
+ const signed = await signUCPProfile(profile, {
1705
+ signingKey: key.privateKey,
1706
+ kid: key.publicJWK.kid,
1707
+ alg: key.publicJWK.alg ?? "EdDSA"
1708
+ });
1709
+ const headers = {
1710
+ "Cache-Control": `public, max-age=${UCP_CACHE_SECONDS}`,
1711
+ "Access-Control-Allow-Origin": "*"
1712
+ };
1713
+ attachRequestId(headers, requestHeaders);
1714
+ return {
1715
+ body: JSON.stringify(signed),
1716
+ mediaType: "application/json",
1717
+ headers,
1718
+ status: 200
1719
+ };
1720
+ }
1721
+ async function buildSignedJwksResponse(opts) {
1722
+ const { requestHeaders, signingKid = "merchant-default" } = opts ?? {};
1723
+ const key = await loadUCPSigningKeyFromEnv({ defaultKid: signingKid });
1724
+ const jwks = buildJWKSResponse([UCPSigningKey.fromJWK(key.publicJWK)]);
1725
+ const headers = {
1726
+ "Cache-Control": `public, max-age=${JWKS_CACHE_SECONDS}`,
1727
+ "Access-Control-Allow-Origin": "*"
1728
+ };
1729
+ attachRequestId(headers, requestHeaders);
1730
+ return {
1731
+ body: JSON.stringify(jwks),
1732
+ mediaType: "application/jwk-set+json",
1733
+ headers,
1734
+ status: 200
1735
+ };
1736
+ }
1737
+ function wellKnownCorsPreflightHeaders(requestHeaders) {
1738
+ const headers = {
1739
+ "Access-Control-Allow-Origin": "*",
1740
+ "Access-Control-Allow-Methods": "GET, OPTIONS",
1741
+ "Access-Control-Max-Age": "86400",
1742
+ Vary: "Access-Control-Request-Headers"
1743
+ };
1744
+ if (requestHeaders === void 0) return headers;
1745
+ const acrh = requestHeaders instanceof Headers ? requestHeaders.get("access-control-request-headers") : Object.entries(requestHeaders).find(([k]) => k.toLowerCase() === "access-control-request-headers")?.[1];
1746
+ if (acrh) headers["Access-Control-Allow-Headers"] = acrh;
1747
+ return headers;
1748
+ }
1749
+ function wellKnownPreflightResponse(requestHeaders) {
1750
+ return new Response(null, {
1751
+ status: 204,
1752
+ headers: wellKnownCorsPreflightHeaders(requestHeaders)
1753
+ });
1754
+ }
1755
+ function defaultA2aServices(opts) {
1756
+ return {
1757
+ "dev.ucp.shopping": [
1758
+ {
1759
+ version: "2026-04-08",
1760
+ spec: UCP_SHOPPING_SPEC_2026_04_08,
1761
+ transport: "a2a",
1762
+ endpoint: opts.agentCardUrl
1763
+ }
1764
+ ]
1765
+ };
1766
+ }
1767
+ async function bootstrapUcpSigningKey(opts) {
1768
+ const defaultKid = opts?.defaultKid ?? "merchant-default";
1769
+ await loadUCPSigningKeyFromEnv({ defaultKid });
1770
+ }
1771
+ function signedResponseHono(resp) {
1772
+ return new Response(resp.body, {
1773
+ status: resp.status,
1774
+ headers: { ...resp.headers, "Content-Type": resp.mediaType }
1775
+ });
1776
+ }
1777
+ function signedResponseNextjs(resp) {
1778
+ return signedResponseHono(resp);
1779
+ }
1780
+ function signedResponseWeb(resp) {
1781
+ return signedResponseHono(resp);
1782
+ }
1783
+ function signedResponseExpress(res, resp) {
1784
+ res.status(resp.status);
1785
+ res.set(resp.headers);
1786
+ res.type(resp.mediaType);
1787
+ res.send(resp.body);
1788
+ }
1789
+ function signedResponseFastify(reply, resp) {
1790
+ reply.code(resp.status);
1791
+ for (const [k, v] of Object.entries(resp.headers)) reply.header(k, v);
1792
+ reply.type(resp.mediaType);
1793
+ return reply.send(resp.body);
1794
+ }
1795
+
1796
+ // src/discovery/request_id.ts
1797
+ function echoRequestIdHeaderHono() {
1798
+ return async (c, next) => {
1799
+ await next();
1800
+ const id = c.get("requestId");
1801
+ if (id) c.header("X-Request-ID", id);
1802
+ };
1803
+ }
822
1804
  export {
1805
+ PURCHASE_MODE_NOTES,
823
1806
  agentscoreDenialSchemas,
824
1807
  agentscoreOpenApiSnippets,
825
1808
  agentscorePaymentRequiredSchema,
826
1809
  agentscoreSecuritySchemes,
827
1810
  applyNoindexHeader,
1811
+ bootstrapUcpSigningKey,
1812
+ buildAgentscoreOnboardingSteps,
828
1813
  buildDiscoveryProbeResponse,
829
1814
  buildLlmsTxt,
1815
+ buildMerchantIndexJson,
1816
+ buildRedemptionSkillMd,
1817
+ buildSignedJwksResponse,
1818
+ buildSignedUcpResponse,
830
1819
  buildSkillMd,
1820
+ buildSuccessNextSteps,
831
1821
  buildWellKnownMpp,
832
1822
  buildWellKnownX402,
833
1823
  compatibleClientsByRails,
834
1824
  createBazaarDiscovery,
1825
+ defaultA2aServices,
835
1826
  defaultDiscoveryPaths,
1827
+ echoRequestIdHeaderHono,
836
1828
  isDiscoveryPath,
837
1829
  isDiscoveryProbeRequest,
838
1830
  llmsTxtIdentitySection,
@@ -840,10 +1832,21 @@ export {
840
1832
  noindexNonDiscoveryPaths,
841
1833
  noindexNonDiscoveryPathsExpress,
842
1834
  noindexNonDiscoveryPathsFastify,
1835
+ purchaseModeNote,
843
1836
  sampleX402AcceptForNetwork,
1837
+ signedResponseExpress,
1838
+ signedResponseFastify,
1839
+ signedResponseHono,
1840
+ signedResponseNextjs,
1841
+ signedResponseWeb,
844
1842
  siwxSecurityScheme,
1843
+ standardEndpointDescriptions,
1844
+ wellKnownCorsPreflightHeaders,
1845
+ wellKnownPreflightResponse,
845
1846
  wrapNoindexResponse,
846
1847
  xGuidanceExtension,
847
- xPaymentInfoExtension
1848
+ xPaymentInfoExtension,
1849
+ xPaymentInfoFromCheckout,
1850
+ xServiceInfoExtension
848
1851
  };
849
1852
  //# sourceMappingURL=index.mjs.map