@agent-score/commerce 2.0.2 → 2.1.1

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 (116) hide show
  1. package/README.md +26 -11
  2. package/dist/_core-kI7FRAiZ.d.mts +10 -0
  3. package/dist/_core-kI7FRAiZ.d.ts +10 -0
  4. package/dist/challenge/index.d.mts +3 -3
  5. package/dist/challenge/index.d.ts +3 -3
  6. package/dist/challenge/index.js +21 -14
  7. package/dist/challenge/index.js.map +1 -1
  8. package/dist/challenge/index.mjs +21 -14
  9. package/dist/challenge/index.mjs.map +1 -1
  10. package/dist/{checkout-jNUIql6D.d.mts → checkout-BRw_caGr.d.mts} +13 -33
  11. package/dist/{checkout-DhSj_h94.d.ts → checkout-CuSNUJFX.d.ts} +13 -33
  12. package/dist/core.js +1 -1
  13. package/dist/core.js.map +1 -1
  14. package/dist/core.mjs +1 -1
  15. package/dist/core.mjs.map +1 -1
  16. package/dist/default_rails-C5gKZJMI.d.ts +198 -0
  17. package/dist/default_rails-XFCuRddA.d.mts +198 -0
  18. package/dist/discovery/index.d.mts +5 -5
  19. package/dist/discovery/index.d.ts +5 -5
  20. package/dist/discovery/index.js +14 -1
  21. package/dist/discovery/index.js.map +1 -1
  22. package/dist/discovery/index.mjs +14 -1
  23. package/dist/discovery/index.mjs.map +1 -1
  24. package/dist/identity/express.d.mts +7 -3
  25. package/dist/identity/express.d.ts +7 -3
  26. package/dist/identity/express.js +39 -96
  27. package/dist/identity/express.js.map +1 -1
  28. package/dist/identity/express.mjs +37 -87
  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 +60 -96
  33. package/dist/identity/fastify.js.map +1 -1
  34. package/dist/identity/fastify.mjs +58 -87
  35. package/dist/identity/fastify.mjs.map +1 -1
  36. package/dist/identity/hono.d.mts +11 -3
  37. package/dist/identity/hono.d.ts +11 -3
  38. package/dist/identity/hono.js +39 -93
  39. package/dist/identity/hono.js.map +1 -1
  40. package/dist/identity/hono.mjs +37 -84
  41. package/dist/identity/hono.mjs.map +1 -1
  42. package/dist/identity/nextjs.d.mts +10 -3
  43. package/dist/identity/nextjs.d.ts +10 -3
  44. package/dist/identity/nextjs.js +49 -93
  45. package/dist/identity/nextjs.js.map +1 -1
  46. package/dist/identity/nextjs.mjs +46 -84
  47. package/dist/identity/nextjs.mjs.map +1 -1
  48. package/dist/identity/policy.js +22 -23317
  49. package/dist/identity/policy.js.map +1 -1
  50. package/dist/identity/policy.mjs +1 -23320
  51. package/dist/identity/policy.mjs.map +1 -1
  52. package/dist/identity/web.d.mts +9 -3
  53. package/dist/identity/web.d.ts +9 -3
  54. package/dist/identity/web.js +45 -93
  55. package/dist/identity/web.js.map +1 -1
  56. package/dist/identity/web.mjs +42 -84
  57. package/dist/identity/web.mjs.map +1 -1
  58. package/dist/index.d.mts +621 -90
  59. package/dist/index.d.ts +621 -90
  60. package/dist/index.js +1202 -328
  61. package/dist/index.js.map +1 -1
  62. package/dist/index.mjs +1188 -327
  63. package/dist/index.mjs.map +1 -1
  64. package/dist/middleware/express.d.mts +10 -0
  65. package/dist/middleware/express.d.ts +10 -0
  66. package/dist/middleware/express.js +128 -0
  67. package/dist/middleware/express.js.map +1 -0
  68. package/dist/middleware/express.mjs +91 -0
  69. package/dist/middleware/express.mjs.map +1 -0
  70. package/dist/middleware/fastify.d.mts +10 -0
  71. package/dist/middleware/fastify.d.ts +10 -0
  72. package/dist/middleware/fastify.js +127 -0
  73. package/dist/middleware/fastify.js.map +1 -0
  74. package/dist/middleware/fastify.mjs +90 -0
  75. package/dist/middleware/fastify.mjs.map +1 -0
  76. package/dist/middleware/hono.d.mts +10 -0
  77. package/dist/middleware/hono.d.ts +10 -0
  78. package/dist/middleware/hono.js +122 -0
  79. package/dist/middleware/hono.js.map +1 -0
  80. package/dist/middleware/hono.mjs +85 -0
  81. package/dist/middleware/hono.mjs.map +1 -0
  82. package/dist/middleware/nextjs.d.mts +22 -0
  83. package/dist/middleware/nextjs.d.ts +22 -0
  84. package/dist/middleware/nextjs.js +143 -0
  85. package/dist/middleware/nextjs.js.map +1 -0
  86. package/dist/middleware/nextjs.mjs +105 -0
  87. package/dist/middleware/nextjs.mjs.map +1 -0
  88. package/dist/middleware/web.d.mts +25 -0
  89. package/dist/middleware/web.d.ts +25 -0
  90. package/dist/middleware/web.js +128 -0
  91. package/dist/middleware/web.js.map +1 -0
  92. package/dist/middleware/web.mjs +91 -0
  93. package/dist/middleware/web.mjs.map +1 -0
  94. package/dist/payment/index.d.mts +32 -21
  95. package/dist/payment/index.d.ts +32 -21
  96. package/dist/payment/index.js +215 -12
  97. package/dist/payment/index.js.map +1 -1
  98. package/dist/payment/index.mjs +205 -12
  99. package/dist/payment/index.mjs.map +1 -1
  100. package/dist/{pricing-CxzwyiO6.d.mts → pricing-4n5Ota0D.d.mts} +14 -4
  101. package/dist/{pricing-CQ9DIFaw.d.ts → pricing-DHfH3ogG.d.ts} +14 -4
  102. package/dist/{rail_spec-XP0wKgJV.d.mts → rail_spec-D6qzh3J0.d.mts} +1 -1
  103. package/dist/{rail_spec-XP0wKgJV.d.ts → rail_spec-D6qzh3J0.d.ts} +1 -1
  104. package/dist/stripe-multichain/index.d.mts +150 -47
  105. package/dist/stripe-multichain/index.d.ts +150 -47
  106. package/dist/stripe-multichain/index.js +19799 -43
  107. package/dist/stripe-multichain/index.js.map +1 -1
  108. package/dist/stripe-multichain/index.mjs +19808 -28
  109. package/dist/stripe-multichain/index.mjs.map +1 -1
  110. package/dist/{x402_server-hgQzWQwB.d.mts → x402_server-Ciz2mls2.d.mts} +1 -1
  111. package/dist/{x402_server-hgQzWQwB.d.ts → x402_server-Ciz2mls2.d.ts} +1 -1
  112. package/package.json +43 -5
  113. package/dist/_response-BFYN3b6i.d.mts +0 -142
  114. package/dist/_response-_iPD5AIj.d.ts +0 -142
  115. package/dist/solana-Cds87OTu.d.mts +0 -67
  116. package/dist/solana-Cds87OTu.d.ts +0 -67
package/README.md CHANGED
@@ -26,15 +26,31 @@ npm install hono mppx @x402/core @x402/evm @solana/mpp @solana/kit stripe # wh
26
26
  | `/identity/{hono,express,fastify}` | Trust gate middleware: KYC, sanctions (account name + signer wallet), age, jurisdiction. Context-getter pattern: `agentscoreGate(opts)` middleware + `getAgentScoreData(ctx)` / `getGateDegradedState(ctx)` / `getGateQuotaInfo(ctx)` / `getSignerVerdict(ctx)` accessors, `captureWallet(...)`. Plus shared denial helpers: `denialReasonStatus`, `denialReasonToBody`, `buildSignerMismatchBody`, `buildContactSupportNextSteps`, `verificationAgentInstructions`, `isFixableDenial`, `FIXABLE_DENIAL_REASONS`. |
27
27
  | `/identity/policy` | Per-product compliance helpers for multi-product merchants (each product carries its own policy: hard gate vs soft vs none, per-product shipping allowlists): `PolicyBlock`, `GateResult`, `EnforcementMode`, `IdentityStatus`, `buildGateOptionsFromPolicy`, `runGateWithEnforcement`, `shippingCountryAllowed`, `shippingStateAllowed`, `validateShippingAgainstPolicy` (one-call country+state validator that raises `CheckoutValidationError` with the canonical envelope on miss). |
28
28
  | `/identity/{nextjs,web}` | Same gate, wrapper pattern: `withAgentScoreGate(opts, handler)` / `createAgentScoreGate(opts) => guard(req)`. The `data` + `degraded` + `infraReason` + `getSignerVerdict` fields land directly on the handler arg / guard result (no separate getter). Plus shared `captureWallet`. |
29
- | `/payment` | `networks`, `USDC`, `rails` registries; `paymentDirective`, `buildPaymentDirective`, `wwwAuthenticateHeader`, `paymentRequiredHeader`, `aliasAmountFields` (v1↔v2 amount field shim: emits both `amount` and `maxAmountRequired` so v1-only x402 parsers like Coinbase awal can read v2 bodies), `settlementOverrideHeader`, `dispatchSettlementByNetwork`, `extractPaymentSigner` (Request-based; recovers signer from x402 EIP-3009 `payload.authorization.from` OR MPP `Authorization: Payment <base64>` `did:pkh:eip155:<chain>:<addr>` / `did:pkh:solana:<genesis>:<addr>` source DID, including Solana `TransferChecked` authority fallback when `@solana/kit` is installed), `extractPaymentSignerFromAuth` (header-string variant for callers that already have the `Authorization` value in hand), `detectRailFromHeaders` (returns `"x402"` / `"mpp"` / `null` from inbound headers); `createX402Server`, `createMppxServer`; drop-in x402 helpers: `validateX402NetworkConfig` (boot-time guard), `verifyX402Request` (parse + validate inbound X-Payment), `processX402Settle` (verify-then-settle with one call), `classifyX402SettleResult` (maps the tagged settle result to a recommended HTTP status / code / nextSteps so merchants get a controlled envelope without coupling to facilitator-specific error text), `classifyOrchestrationError` (same `ClassifiedX402Error` shape but for uncaught exceptions thrown elsewhere in the orchestration; returns `null` for unknown errors so merchants rethrow instead of swallowing); `zeroAmountCarveOut` (skip CDP / mppx upstream verify+settle for $0 settles where the upstream rejects value=0 payloads; parses the credential, lifts signer + network, returns a `ZeroSettleResult` shaped identically to the success path so callers branch on rail, not on result shape); `usdToAtomic` (BigInt-based USD → atomic value, ROUND_HALF_UP — for Tempo / Solana / Base USDC amount construction). |
29
+ | `/payment` | `networks`, `USDC`, `rails` registries; `paymentDirective`, `buildPaymentDirective`, `wwwAuthenticateHeader`, `paymentRequiredHeader`, `aliasAmountFields` (v1↔v2 amount field shim: emits both `amount` and `maxAmountRequired` so v1-only x402 parsers like Coinbase awal can read v2 bodies), `settlementOverrideHeader`, `dispatchSettlementByNetwork`, `extractPaymentSigner` (Request-based; recovers signer from x402 EIP-3009 `payload.authorization.from` OR MPP `Authorization: Payment <base64>` `did:pkh:eip155:<chain>:<addr>` / `did:pkh:solana:<genesis>:<addr>` source DID, including Solana `TransferChecked` authority fallback when `@solana/kit` is installed), `extractPaymentSignerFromAuth` (header-string variant for callers that already have the `Authorization` value in hand), `detectRailFromHeaders` (returns `"x402"` / `"mpp"` / `null` from inbound headers); `createX402Server`, `createMppxServer`, `buildDefaultCheckoutRails({tempo?, x402Base?, solanaMpp?, stripe?})` (canonical 4-rail `rails` dict factory: flipping `network` alone derives the right `token` + `chainId` — Base Sepolia → Sepolia USDC + chainId 84532, Solana devnet → devnet USDC mint. Solana's `network` accepts both CAIP-2 and the raw `@solana/mpp` form `mainnet-beta` / `devnet` / `localnet`. Explicit overrides always win), `buildMppxComposeRails({amountUsd, tempoRecipient?, solanaRecipient?, ...})` (per-call intent factory replacing the hand-rolled `[['tempo/charge',{...}],['solana/charge',{...}],['stripe/charge',{...}]]` array; auto-handles USD→atomic conversion for Solana; auto-drops `stripe/charge` when `amountUsd < 0.50` since Stripe's fixed ~$0.30 fee makes sub-50-cent charges unprofitable — sub-50-cent APIs pass `includeStripe: false` explicitly to silence the warning); drop-in x402 helpers: `validateX402NetworkConfig` (boot-time guard), `verifyX402Request` (parse + validate inbound X-Payment), `processX402Settle` (verify-then-settle with one call), `classifyX402SettleResult` (maps the tagged settle result to a recommended HTTP status / code / nextSteps so merchants get a controlled envelope without coupling to facilitator-specific error text), `classifyOrchestrationError` (same `ClassifiedX402Error` shape but for uncaught exceptions thrown elsewhere in the orchestration; returns `null` for unknown errors so merchants rethrow instead of swallowing); `zeroAmountCarveOut` (skip CDP / mppx upstream verify+settle for $0 settles where the upstream rejects value=0 payloads; parses the credential, lifts signer + network, returns a `ZeroSettleResult` shaped identically to the success path so callers branch on rail, not on result shape); `usdToAtomic` (BigInt-based USD → atomic value, ROUND_HALF_UP — for Tempo / Solana / Base USDC amount construction). |
30
30
  | `/discovery` | `isDiscoveryProbeRequest`, `buildDiscoveryProbeResponse` (with optional `x402Sample` for x402-aware crawlers, e.g. `awal x402 details`), `sampleX402AcceptForNetwork` (USDC sample-accept builder for known CAIP-2 networks), `buildWellKnownMpp`, `buildLlmsTxt` + `llmsTxtIdentitySection` + `llmsTxtPaymentSection` (compact + verbose modes), `buildSkillMd` (Claude-Skill-compatible `/skill.md` agent-discovery manifest; strictly agent-facing data only, no internal posture), `buildRedemptionSkillMd` (delivery-neutral redemption-code template — printed mailers, emailed codes, API trial credits all covered; `endpointPath`/`deliveryIntro`/`bodyShape`/`bodyRules`/`extraRecoveryRows` overrides for non-goods shapes), `agentscoreOpenApiSnippets`, `createBazaarDiscovery`, `noindexNonDiscoveryPaths` (Hono middleware emitting `X-Robots-Tag: noindex` on every path except the agent-discovery surfaces; pure helpers `isDiscoveryPath` + `defaultDiscoveryPaths` for non-Hono frameworks), `buildMerchantIndexJson` (canonical `/` discovery body), `standardEndpointDescriptions({kind})` (canonical method+path → description map for goods vs api merchants; optional `includeOrderStatusRoute` for goods), `buildSuccessNextSteps` (universal Passport-active success block), `buildAgentscoreOnboardingSteps` (canonical skill.md onboarding for goods or API merchants). Plus the UCP/JWKS publish surface: `buildSignedUcpResponse`, `buildSignedJwksResponse`, `wellKnownPreflightResponse`, `defaultA2aServices`, `bootstrapUcpSigningKey`, and the framework-neutral `SignedDiscoveryResponse` + per-framework wrappers `signedResponse{Hono,Express,Fastify,Nextjs,Web}`. |
31
31
  | `/challenge` | `build402Body`, `buildAcceptedMethods`, `buildIdentityMetadata`, `buildHowToPay`, `buildAgentInstructions` (auto-emits per-rail `compatible_clients`: smoke-verified CLIs the agent should use; vendor override supported; pure helper `compatibleClientsByRails(rails)` returns the same map for vendors building custom 402s), `buildPricingBlock`, `firstEncounterAgentMemory`, `Receipt` + `ReceiptNextSteps` + `ProductInfo` + `ShippingAddress` (canonical 200-receipt shape returned post-settle — universal: goods merchants fill the shipping/fulfillment/product slots, API merchants populate only the universal fields); `respond402`, a drop-in 402 emit that preserves mppx's `WWW-Authenticate` and layers x402's `PAYMENT-REQUIRED`. `buildValidationError`: structured 4xx body builder (`{error: {code, message}, required_fields?, example_body?, next_steps?, ...extra}`) so vendors compose body shapes by name instead of inlining at every validation site. |
32
- | `/stripe-multichain` | `createMultichainPaymentIntent` (returns `{ paymentIntentId, depositAddresses }` — read `depositAddresses[network]` directly), `simulateCryptoDeposit`, `createMppxStripe`; `createPiCache` (TTL'd PI / deposit-address cache, Redis-backed when `redisUrl` set, in-memory otherwise), `simulateDepositIfTestMode` (gates on `sk_test_` and looks up the PI for you), `STRIPE_TEST_TX_HASH_SUCCESS` / `STRIPE_TEST_TX_HASH_FAILED` constants. Peer dep on `stripe`. |
32
+ | `/stripe-multichain` | `createMultichainPaymentIntent` (returns `{ paymentIntentId, depositAddresses }` — read `depositAddresses[network]` directly), `createPayToAddressFromStripePI({request, amountCents, stripe, piCache, networks?, metadata?, orderId?, preferredNetwork?})` — per-order payTo resolver: on the settle leg, reuses the buyer's signed-against payTo from the MPP credential (after `piCache.hasAddress` check); on the discovery leg, mints a fresh PI and caches it. `simulateCryptoDeposit`, `createMppxStripe`; `createPiCache` (TTL'd PI / deposit-address cache, Redis-backed when `redisUrl` set, in-memory otherwise), `simulateDepositIfTestMode` (gates on `sk_test_` and looks up the PI for you), `STRIPE_TEST_TX_HASH_SUCCESS` / `STRIPE_TEST_TX_HASH_FAILED` constants. Peer dep on `stripe`. |
33
33
  | `/api` | Everything from `@agent-score/sdk` re-exported in one place: `AgentScore` + `AgentScoreError`, `AGENTSCORE_TEST_ADDRESSES` + `isAgentScoreTestAddress`. **Don't add `@agent-score/sdk` as a separate dep**; the two can drift versions and cause subtle type mismatches. |
34
- | `@agent-score/commerce` (top-level) | `Checkout` orchestrator + `CheckoutContext` + `CheckoutGateConfig` + `CheckoutValidationError` + `DiscoveryProbeConfig` + `MountUcpRoutesOptions` + `SettleOutcome` + `MppxComposeOutcome` + `PricingResult` (the 2.0 high-level surface: one config object, hooks for preValidate/computePricing/onSettled/mintRecipients/composeMppx, auto-derived x402+mppx servers, per-framework adapters `handleHono`/`handleExpress`/`handleFastify`/`handleNextjs`/`handleWeb`, signed UCP routes via `mountUcpRoutes{Hono,Express,Fastify}`); `pricingResult` (factory: cents-denominated → typed `PricingResult` with embedded `PricingBlock`); `validationResponse{Hono,Express,Fastify,Nextjs,Web}` (per-framework 4xx envelope wrappers); `loadUCPSigningKeyFromEnv` + `LoadUCPSigningKeyOptions` (cached env-driven loader for the UCP signing key — reads `UCP_SIGNING_KEY_JWK_PRIVATE` JSON JWK, detects alg from shape, falls back to ephemeral when unset, sanitizes errors so key bytes never reach logs, concurrent-safe via Promise-pinned cache); `hashOperatorToken` (sha256 hex of plaintext `opc_...` — for merchants persisting `operator_token_id` to their own DB without ever storing the plaintext); plus the cross-vendor publishers `buildA2AAgentCard` + `buildUCPProfile` (return unsigned payloads: vendor signs + publishes). |
34
+ | `/middleware/{hono,express,fastify,nextjs,web}` | Framework-specific rate-limit middleware. Hono / Express / Fastify expose middleware factories (`rateLimitHono`, `rateLimitExpress`, `rateLimitFastify`); Next.js exposes `withRateLimit(opts, handler)`; Web Fetch exposes `createRateLimit(opts) => guard(req)`. Shared options: `windowSeconds` (default 60), `maxRequests` (default 60), `keyResolver` (default first hop of `x-forwarded-for`), `redisUrl` (lazy-imports `ioredis` when set, in-memory `Map` fallback otherwise), `keyPrefix`. `ioredis` is an optional peer dep install it only if you want distributed limiting. |
35
+ | `@agent-score/commerce` (top-level) | `Checkout` orchestrator + `CheckoutContext` + `CheckoutGateConfig` + `CheckoutValidationError` + `DiscoveryProbeConfig` + `MountUcpRoutesOptions` + `SettleOutcome` + `MppxComposeOutcome` + `PricingResult` (the 2.0 high-level surface: one config object, hooks for preValidate/computePricing/onSettled/mintRecipients/composeMppx, auto-derived x402+mppx servers, per-framework adapters `handleHono`/`handleExpress`/`handleFastify`/`handleNextjs`/`handleWeb`, signed UCP routes via `mountUcpRoutes{Hono,Express,Fastify}`); `pricingResult` (factory: cents-denominated → typed `PricingResult` with embedded `PricingBlock`); `validationResponse{Hono,Express,Fastify,Nextjs,Web}` (per-framework 4xx envelope wrappers); `loadUCPSigningKeyFromEnv` + `LoadUCPSigningKeyOptions` (cached env-driven loader for the UCP signing key — reads `UCP_SIGNING_KEY_JWK_PRIVATE` JSON JWK, detects alg from shape, falls back to ephemeral when unset, sanitizes errors so key bytes never reach logs, concurrent-safe via Promise-pinned cache); `hashOperatorToken` (sha256 hex of plaintext `opc_...` — for merchants persisting `operator_token_id` to their own DB without ever storing the plaintext); `extractOwnerScope(headers) → { walletAddress?, operatorTokenHash? }` (canonical owner-identity extractor for caller-scoped resource queries — reads `X-Wallet-Address` / `X-Operator-Token`, hashes the token so plaintext never leaves the request); `hasPaymentHeader` / `hasX402Header` / `hasMppxHeader` (request discriminators — any-credential vs x402 vs MPP); `defaultReadOnlyOnDenied(reason)` (canonical `onDenied` for read-only resource gates: 401 + `Cache-Control: no-store` while still spreading `denialReasonToBody`); plus the cross-vendor publishers `buildA2AAgentCard` + `buildUCPProfile` (return unsigned payloads: vendor signs + publishes). |
35
36
 
36
37
  ## Quick start
37
38
 
39
+ ### Rate limiting (Hono / Express / Fastify / Next.js / Web)
40
+
41
+ Mount globally before any payment route so probe and settle legs share the same bucket. Defaults: 60 req / 60s / IP. Redis when `REDIS_URL` is set, in-memory fallback otherwise.
42
+
43
+ ```typescript
44
+ import { Hono } from "hono";
45
+ import { rateLimitHono } from "@agent-score/commerce/middleware/hono";
46
+
47
+ const app = new Hono();
48
+ app.use("*", rateLimitHono());
49
+ // ... your routes ...
50
+ ```
51
+
52
+ Same factory shape per framework: `rateLimitExpress`, `rateLimitFastify` (preHandler hook), `withRateLimit(opts, handler)` for Next.js App Router, and `createRateLimit(opts) => guard(req)` for Web Fetch. Override `maxRequests` / `windowSeconds` / `keyResolver` / `redisUrl` / `keyPrefix` as needed.
53
+
38
54
  ### Identity gate (Hono)
39
55
 
40
56
  ```typescript
@@ -60,13 +76,10 @@ const _gate = agentscoreGate({
60
76
  // Anonymous discovery (no payment header) flows through to the handler so any spec-
61
77
  // compliant x402 wallet can read the 402 challenge with rails + pricing without first
62
78
  // proving identity. Identity is verified at settle time on the retry leg.
79
+ import { hasPaymentHeader } from "@agent-score/commerce/payment";
80
+
63
81
  app.use("/purchase", async (c, next) => {
64
- const hasPaymentHeader = Boolean(
65
- c.req.header("payment-signature") ||
66
- c.req.header("x-payment") ||
67
- c.req.header("authorization")?.startsWith("Payment "),
68
- );
69
- if (!hasPaymentHeader) { await next(); return; }
82
+ if (!hasPaymentHeader(c.req.raw)) { await next(); return; }
70
83
  return _gate(c, next);
71
84
  });
72
85
 
@@ -81,7 +94,9 @@ app.post("/purchase", async (c) => {
81
94
 
82
95
  ### Checkout orchestrator (the 2.0 high-level surface)
83
96
 
84
- `Checkout` is the canonical merchant surface: one config object, hooks for the merchant-specific pieces, and the SDK handles 402 emit, identity gating, x402 verify+settle, mppx compose, $0 carve-out, and the per-framework adapter. Most merchants reach for `Checkout` first and drop to lower-level helpers only when they need custom flows (variable-cost streaming, multi-protocol composition, etc).
97
+ `Checkout` is the canonical merchant surface for fixed-price one-shot endpoints: one config object, hooks for the merchant-specific pieces, and the SDK handles 402 emit, identity gating, x402 verify+settle, mppx compose, $0 carve-out, and the per-framework adapter. Most fixed-price merchants reach for `Checkout` first.
98
+
99
+ For **variable-cost pay-per-result** endpoints (per-result search, per-token LLM, per-byte transcoding), reach for `computeFirstCheckout` — same config shape, but the probe leg runs the work, caches by body content-hash, and emits a 402 with the EXACT computed price. The retry pays that exact amount and receives the cached body. Works on every exact-mode rail (x402-exact Base, tempo/charge, solana/charge, Stripe SPT) without upto / Permit2 / Settlement-Overrides. Tradeoff: the work runs on the unpaid probe leg, so mount `rateLimitHono` (from `@agent-score/commerce/middleware/hono`) globally — it's load-bearing. See `examples/compute-first-merchant.ts`.
85
100
 
86
101
  ```typescript
87
102
  import { Checkout, pricingResult } from "@agent-score/commerce";
@@ -230,7 +245,7 @@ const responseBody = build402Body({
230
245
  });
231
246
  ```
232
247
 
233
- `buildPricingBlock` handles cents → dollar-string conversion (with optional shipping). Pass `discountCents` for redemption codes / coupons: `subtotal` stays the list price, the block surfaces `discount` as a dollar-string, and `total` becomes `subtotal + tax + shipping - discount` (floored at 0). `pricingResult` accepts the same `discountCents` and propagates it to `block.discount` so agents reading the 402 see the savings line. `firstEncounterAgentMemory` returns the canonical hint or `undefined` based on a per-merchant first-seen flag. `Receipt` is a universal TS interface for the post-settlement 200 response shape — goods merchants populate the shipping/fulfillment/tracking slots, API merchants fill only the universal fields (id, created_at, pricing, payment_status, next_steps).
248
+ `buildPricingBlock` handles cents → dollar-string conversion (with optional shipping). Pass `discountCents` for redemption codes / coupons: `subtotal` stays the list price, the block surfaces `discount` as a dollar-string, and `total` becomes `subtotal + tax + shipping - discount` (floored at 0). `pricingResult` accepts the same `discountCents` and propagates it to `block.discount` so agents reading the 402 see the savings line. Pass `decimals: N` (default `2`) on either helper for sub-cent unit pricing — e.g. `decimals: 4` advertises `$0.0005`-precision instead of rounding to two decimals. Set `decimals` on `PricingResult` and the SDK threads it through `buildHowToPay`, `buildPricingBlock`, and the x402 settle `price` string automatically; the `cents` inputs accept fractional values under that mode (per-token / per-byte unit pricing). `firstEncounterAgentMemory` returns the canonical hint or `undefined` based on a per-merchant first-seen flag. `Receipt` is a universal TS interface for the post-settlement 200 response shape — goods merchants populate the shipping/fulfillment/tracking slots, API merchants fill only the universal fields (id, created_at, pricing, payment_status, next_steps).
234
249
 
235
250
  ### Idempotency-key + multi-rail header bundle
236
251
 
@@ -0,0 +1,10 @@
1
+ interface RateLimitCoreOptions {
2
+ windowSeconds?: number;
3
+ maxRequests?: number;
4
+ /** Redis connection URL. Default: `process.env.REDIS_URL`. Falls back to in-memory when unset or the lazy `ioredis` import fails. */
5
+ redisUrl?: string;
6
+ /** Per-instance key prefix so multiple limiters sharing a Redis don't collide. */
7
+ keyPrefix?: string;
8
+ }
9
+
10
+ export type { RateLimitCoreOptions as R };
@@ -0,0 +1,10 @@
1
+ interface RateLimitCoreOptions {
2
+ windowSeconds?: number;
3
+ maxRequests?: number;
4
+ /** Redis connection URL. Default: `process.env.REDIS_URL`. Falls back to in-memory when unset or the lazy `ioredis` import fails. */
5
+ redisUrl?: string;
6
+ /** Per-instance key prefix so multiple limiters sharing a Redis don't collide. */
7
+ keyPrefix?: string;
8
+ }
9
+
10
+ export type { RateLimitCoreOptions as R };
@@ -1,6 +1,6 @@
1
- import { T as TempoRailSpec, X as X402BaseRailSpec, S as SolanaMppRailSpec, b as StripeRailSpec } from '../rail_spec-XP0wKgJV.mjs';
2
- import { A as AgentInstructions, P as PricingBlock } from '../pricing-CxzwyiO6.mjs';
3
- export { C as CompatibleClients, H as HowToPayBlock, a as HowToPayRailEntry, b as HowToPayRails, c as HowToPayStripeEntry, R as RailKey, d as buildAgentInstructions, e as buildHowToPay, f as buildPricingBlock, g as compatibleClientsByRails } from '../pricing-CxzwyiO6.mjs';
1
+ import { T as TempoRailSpec, X as X402BaseRailSpec, S as SolanaMppRailSpec, b as StripeRailSpec } from '../rail_spec-D6qzh3J0.mjs';
2
+ import { A as AgentInstructions, P as PricingBlock } from '../pricing-4n5Ota0D.mjs';
3
+ export { C as CompatibleClients, H as HowToPayBlock, a as HowToPayRailEntry, b as HowToPayRails, c as HowToPayStripeEntry, R as RailKey, d as buildAgentInstructions, e as buildHowToPay, f as buildPricingBlock, g as compatibleClientsByRails } from '../pricing-4n5Ota0D.mjs';
4
4
  import { AgentMemoryHint } from '../core.mjs';
5
5
  export { buildAgentMemoryHint } from '../core.mjs';
6
6
  import { p as paymentRequiredHeader } from '../wwwauthenticate-D_FMnPgU.mjs';
@@ -1,6 +1,6 @@
1
- import { T as TempoRailSpec, X as X402BaseRailSpec, S as SolanaMppRailSpec, b as StripeRailSpec } from '../rail_spec-XP0wKgJV.js';
2
- import { A as AgentInstructions, P as PricingBlock } from '../pricing-CQ9DIFaw.js';
3
- export { C as CompatibleClients, H as HowToPayBlock, a as HowToPayRailEntry, b as HowToPayRails, c as HowToPayStripeEntry, R as RailKey, d as buildAgentInstructions, e as buildHowToPay, f as buildPricingBlock, g as compatibleClientsByRails } from '../pricing-CQ9DIFaw.js';
1
+ import { T as TempoRailSpec, X as X402BaseRailSpec, S as SolanaMppRailSpec, b as StripeRailSpec } from '../rail_spec-D6qzh3J0.js';
2
+ import { A as AgentInstructions, P as PricingBlock } from '../pricing-DHfH3ogG.js';
3
+ export { C as CompatibleClients, H as HowToPayBlock, a as HowToPayRailEntry, b as HowToPayRails, c as HowToPayStripeEntry, R as RailKey, d as buildAgentInstructions, e as buildHowToPay, f as buildPricingBlock, g as compatibleClientsByRails } from '../pricing-DHfH3ogG.js';
4
4
  import { AgentMemoryHint } from '../core.js';
5
5
  export { buildAgentMemoryHint } from '../core.js';
6
6
  import { p as paymentRequiredHeader } from '../wwwauthenticate-D_FMnPgU.js';
@@ -181,10 +181,13 @@ function buildHowToPay({
181
181
  totalUsd,
182
182
  rails,
183
183
  opTokenPlaceholder,
184
- maxSpend
184
+ maxSpend,
185
+ decimals
185
186
  }) {
186
187
  const totalNum = typeof totalUsd === "string" ? Number(totalUsd) : totalUsd;
187
- const maxSpendStr = String(maxSpend ?? (Math.ceil(totalNum) + 1).toFixed(2));
188
+ const d = decimals ?? 2;
189
+ const defaultMaxSpend = totalNum >= 1 ? (Math.ceil(totalNum) + 1).toFixed(d) : totalNum.toFixed(d);
190
+ const maxSpendStr = String(maxSpend ?? defaultMaxSpend);
188
191
  const opToken = opTokenPlaceholder ?? "<your_opc_token>";
189
192
  const block = {};
190
193
  if (rails.tempo) {
@@ -471,25 +474,32 @@ function buildPricingBlock({
471
474
  totalCents,
472
475
  taxRate,
473
476
  taxState,
474
- currency
477
+ currency,
478
+ decimals
475
479
  }) {
476
480
  const shipping = shippingCents ?? 0;
477
481
  const discount = discountCents ?? 0;
478
482
  const total = totalCents ?? Math.max(0, subtotalCents + taxCents + shipping - discount);
483
+ const d = decimals ?? 2;
484
+ const fmt = (cents) => (cents / 100).toFixed(d);
479
485
  const block = {
480
- subtotal: formatCents(subtotalCents),
481
- tax: formatCents(taxCents),
482
- total: formatCents(total)
486
+ subtotal: fmt(subtotalCents),
487
+ tax: fmt(taxCents),
488
+ total: fmt(total)
483
489
  };
484
- if (shippingCents !== void 0) block.shipping = formatCents(shipping);
485
- if (discountCents !== void 0) block.discount = formatCents(discount);
490
+ if (shippingCents !== void 0) block.shipping = fmt(shipping);
491
+ if (discountCents !== void 0) block.discount = fmt(discount);
486
492
  if (taxRate !== void 0) block.tax_rate = taxRate;
487
493
  if (taxState !== void 0) block.tax_state = taxState;
488
494
  if (currency !== void 0) block.currency = currency;
489
495
  return block;
490
496
  }
491
- function formatCents(cents) {
492
- return (cents / 100).toFixed(2);
497
+
498
+ // src/_headers.ts
499
+ function normalizeHeadersToLowercase(headers) {
500
+ const out = {};
501
+ for (const [k, v] of Object.entries(headers)) out[k.toLowerCase()] = v;
502
+ return out;
493
503
  }
494
504
 
495
505
  // src/payment/wwwauthenticate.ts
@@ -507,10 +517,7 @@ function respond402({
507
517
  body,
508
518
  x402
509
519
  }) {
510
- const headers = {};
511
- for (const [k, v] of Object.entries(mppxChallengeHeaders)) {
512
- headers[k.toLowerCase()] = v;
513
- }
520
+ const headers = normalizeHeadersToLowercase(mppxChallengeHeaders);
514
521
  headers["content-type"] = "application/json";
515
522
  if (x402) {
516
523
  headers["payment-required"] = paymentRequiredHeader(x402);