Skip to main content
@agent-score/commerce is the full merchant-side SDK for agent commerce. One install bundles identity gating, payment-protocol helpers, 402 challenge builders, discovery doc generators, and Stripe multichain support. Subpath imports keep bundle size focused: identity-only consumers pay no cost for payment helpers, and vice versa.

Installation

npm install @agent-score/commerce
Framework + integration packages are optional peer deps; install only what you use:
npm install hono mppx @x402/core @x402/evm stripe   # whatever your stack needs

Subpaths

ImportWhat it gives you
@agent-score/commerce (top-level: the 2.0 high-level surface)Checkout orchestrator + CheckoutContext + CheckoutGateConfig + CheckoutValidationError + DiscoveryProbeConfig + MountUcpRoutesOptions + SettleOutcome + MppxComposeOutcome + PricingResult (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}). Plus computeFirstCheckout — variable-cost pay-per-result helper (compute-first + exact-x402) that works on every exact-mode rail without upto / Permit2 / Settlement-Overrides. createQuoteCache — content-hash quote cache used by the compute-first helper (in-memory default; pass redisUrl for distributed deployments). createDefaultOnDenied({merchantName, supportEmail, supportContext?, walletNotTrustedMessage?, paymentRequiredMessage?}) — canonical onDenied(reason) => {status, body, headers?} factory matching Checkout’s gate hook (handles wallet_signer_mismatch, wallet_not_trusted unfixable fallback, payment_required, token_expired/invalid_credential/api_error). `hasPaymentHeader(reqheaders) — discriminator that splits discovery legs (no payment credential → 402) from settle legs (payment-signature/x-payment/Authorization: Payment JWT-BYTES); hasX402Header/hasMppxHeader— granular dispatch helpers (x402 vs MPP credential present).defaultReadOnlyOnDenied(reason)— canonicalonDenied for read-only resource gates (GET /orders/:id): collapses every denial to 401 unauthorized+Cache-Control: no-storewhile still spreadingdenialReasonToBody. extractOwnerScope(headers)(returns optionalwalletAddress+ optionaloperatorTokenHash) — pull canonical owner identity from X-Wallet-Address/X-Operator-Tokenwith safe token hashing. Plus factories:pricingResult(cents → typed PricingResult with optionaldecimalsfor sub-cent precision),validationResponse(4xx envelope per framework),Receipt/ReceiptNextSteps/ProductInfo/ShippingAddress` (canonical 200-receipt shape — universal across goods + API merchants).
@agent-score/commerce/identity/{hono,express,fastify}Trust gate middleware: KYC, age, sanctions (account name + signer wallet), jurisdiction. Context-getter pattern: agentscoreGate(opts) middleware + getAgentScoreData(ctx) / getGateDegradedState(ctx) / getGateQuotaInfo(ctx) / getSignerVerdict(ctx) accessors, captureWallet(...).
@agent-score/commerce/identity/policyPer-product compliance helpers: PolicyBlock, buildGateFromPolicy, runGateWithEnforcement, shippingCountryAllowed, shippingStateAllowed, validateShippingAgainstPolicy (one-call country+state validator raising CheckoutValidationError with the canonical envelope on miss). All five are also re-exported at @agent-score/commerce top-level.
@agent-score/commerce/identity/{nextjs,web}Same gate, wrapper pattern: withAgentScoreGate(opts, handler) / createAgentScoreGate(opts) => guard(req). The data + degraded + infraReason fields land directly on the handler arg / guard result (no separate getter). Plus shared captureWallet, getSignerVerdict.
@agent-score/commerce/paymentnetworks, USDC, rails registries; paymentDirective, buildPaymentHeaders (one-call WWW-Authenticate + PAYMENT-REQUIRED bundle), wwwAuthenticateHeader, paymentRequiredHeader, aliasAmountFields (v1↔v2 amount field shim for v1-only x402 parser compat), settlementOverrideHeader, dispatchSettlementByNetwork, extractPaymentSigner (recovers signer from a Request: 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), buildIdempotencyKey; createX402Server, buildX402AcceptsFor402 (one-call helper for the 402-emit path; derives extra.name from the registered scheme so EIP-712 domain matches the on-chain USDC contract), 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.30feemakessub50centchargesunprofitablesub50centAPIspassincludeStripe:falseexplicitly),composeMppxRequest(typedwrapperaroundmppx.compose(...intents)(request)returninganarrowedMppxComposeResultunion;replacesthe(mppxasany).compose(...)castincustomcomposeMppxhooks),mppxChallengeHeaders(onecallObject.fromEntries(challenge.headers)forthe402path);dropinx402helpers:validateX402NetworkConfig(boottimeguard),verifyX402Request(parse+validateinboundXPayment),processX402Settle(verifythensettleinonecall),classifyX402SettleResult(mapsthetaggedsettleresulttoarecommendedHTTPstatus/code/nextstepssomerchantsgetacontrolledenvelopewithoutcouplingtofacilitatorspecificerrortext),classifyOrchestrationError(sameClassifiedX402Errorshapebutforuncaughtexceptionsthrownelsewhereintheorchestration;conservativesubstringfallbackreturnsnullforunknownerrorssomerchantsrethrowinsteadofswallowing);zeroAmountCarveOut(skipCDP/mppxupstreamverify+settlefor0.30 fee makes sub-50-cent charges unprofitable — sub-50-cent APIs pass `includeStripe: false` explicitly), `composeMppxRequest` (typed wrapper around `mppx.compose(...intents)(request)` returning a narrowed `MppxComposeResult` union; replaces the `(mppx as any).compose(...)` cast in custom `composeMppx` hooks), `mppxChallengeHeaders` (one-call `Object.fromEntries(challenge.headers)` for the 402 path); drop-in x402 helpers: `validateX402NetworkConfig` (boot-time guard), `verifyX402Request` (parse + validate inbound X-Payment), `processX402Settle` (verify-then-settle in one call), `classifyX402SettleResult` (maps the tagged settle result to a recommended HTTP status / code / next_steps 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; conservative substring fallback 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, rejects NaN / negative / infinite — for Tempo / Solana / Base USDC amount construction).
@agent-score/commerce/discoveryisDiscoveryProbeRequest, buildDiscoveryProbeResponse (with optional x402Sample for x402-aware crawlers), sampleX402AcceptForNetwork, buildWellKnownMpp, buildWellKnownX402 (x402scan v1 /.well-known/x402 shape: {version: 1, resources: ["METHOD /path"]}), buildLlmsTxt, 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), buildMerchantIndexJson + standardEndpointDescriptions({kind}) (canonical / discovery body for goods vs api merchants), buildSuccessNextSteps (universal Passport-active success block), buildAgentscoreOnboardingSteps, agentscoreOpenApiSnippets, siwxSecurityScheme + xPaymentInfoExtension + xGuidanceExtension per the x402scan OpenAPI spec, createBazaarDiscovery; noindexNonDiscoveryPaths (Hono middleware) + noindexNonDiscoveryPathsExpress + noindexNonDiscoveryPathsFastify + wrapNoindexResponse / applyNoindexHeader (Web Fetch + Next.js): emits X-Robots-Tag: noindex on every path except the agent-discovery surfaces. Plus the UCP/JWKS publish surface: buildSignedUcpResponse, buildSignedJwksResponse, wellKnownPreflightResponse, defaultA2aServices, bootstrapUcpSigningKey, framework-neutral SignedDiscoveryResponse + per-framework wrappers signedResponse{Hono,Express,Fastify,Nextjs,Web}.
@agent-score/commerce/challengebuildAcceptedMethods, buildIdentityMetadata (auto-attached by Checkout when wallet header present), buildHowToPay, buildAgentInstructions (auto-emits per-rail compatible_clients; pure helper compatibleClientsByRails(rails) returns the same map for vendors building custom 402s), build402Body, buildPricingBlock (cents → dollar-string; optional shippingCents + discountCents — pass discountCents for redemption codes / coupons so the 402 body surfaces discount alongside subtotal / total for agent-renderable savings; optional decimals for sub-cent precision so per-token / per-byte unit pricing advertises real amounts instead of rounding to two decimals), firstEncounterAgentMemory, Receipt/ReceiptNextSteps/ProductInfo/ShippingAddress types (canonical 200-receipt shape); respond402: 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.
@agent-score/commerce/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 for distributed limiting.
@agent-score/commerce/stripe-multichaincreateMultichainPaymentIntent (returns { paymentIntentId, depositAddresses }; read depositAddresses[network] directly), createPayToAddressFromStripePI({request, amountCents, stripe, piCache, networks?, staticRecipients?, metadata?, orderId?, preferredNetwork?}) — per-order payTo resolver matching Checkout.mintRecipients: on the settle leg, reuses the buyer’s signed-against payTo from the MPP credential (after piCache.hasAddress check OR a staticRecipients match — the static address is always-accepted because the merchant owns it); on the discovery leg, mints a fresh PI for the rails NOT covered by staticRecipients, caches the merged map, registers static addresses with piCache.cacheAddress so verify-leg lookups pass. mintMultichainRecipients({...same opts}) — structured variant returning { recipients, paymentIntentId?, reusedFromCredential } for the full per-rail map (typical multi-rail merchant hook). Use staticRecipients: { solana: '<wallet>' } for low-margin endpoints where Solana per-call ATA rent (~0.50againstMPPspec§13.6)dominatesrevenuetheSDKskipsStripemintingonthatnetwork,reusesthestaticrecipientforever,and(whenpairedwithaonetimeexternalprefundingoftherecipientsUSDCATA)letseverysettlepayonlythe 0.50 against MPP spec §13.6) dominates revenue — the SDK skips Stripe minting on that network, reuses the static recipient forever, and (when paired with a one-time external pre-funding of the recipient's USDC ATA) lets every settle pay only the ~0.001 per-tx fee. simulateCryptoDeposit, createMppxStripe; createPiCache (TTL’d PI / deposit-address cache, Redis-backed when redisUrl set), simulateDepositIfTestMode (gates on sk_test_ and looks up the PI for you), STRIPE_TEST_TX_HASH_SUCCESS / STRIPE_TEST_TX_HASH_FAILED constants.
@agent-score/commerce/apiEverything 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.
@agent-score/commerce (top-level: publishers + key/token helpers)Identity-publishing helpers for cross-vendor standards: buildA2AAgentCard (Google A2A v1.0 Signed Agent Card, served at /.well-known/agent-card.json), buildUCPProfile (Google Universal Commerce Protocol, served at /.well-known/ucp). Both return unsigned payloads: vendor signs + publishes. Key + token helpers: loadUCPSigningKeyFromEnv + LoadUCPSigningKeyOptions (cached env-driven loader: 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) (returns optional walletAddress + optional 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).

Identity model

Two identity types: wallet (X-Wallet-Address) and operator-token (X-Operator-Token). Default checks operator-token first, then wallet. Address normalization is network-aware: EVM lowercased, Solana base58 preserved verbatim. DenialReason codes (missing_identity, identity_verification_required, token_expired, invalid_credential, wallet_signer_mismatch, wallet_auth_requires_wallet_signing, wallet_not_trusted, api_error, payment_required) each carry a structured agent_instructions JSON block describing concrete recovery actions. createSessionOnMissing auto-mints a verification session for two paths: cold-start (no identity headers) AND wallet_not_trusted with fixable reasons (kyc_required / kyc_pending / kyc_failed). Both paths rewrite the denial to identity_verification_required before reaching onDenied, so merchants only handle one code. When the merchant omits createSessionOnMissing from the gate config, Checkout auto-defaults it from gate.apiKey + gate.baseUrl + gate.context + gate.merchantName — every gated route gets the bootstrap UX out of the box; merchants that need per-request session context or onBeforeSession side effects (goods merchants pre-minting an order_id) supply their own config to override. buildVerificationRequiredBody(reason, opts?) collapses the per-merchant body-mapping boilerplate into one call (status: 403, body: buildVerificationRequiredBody(reason, { message, agentInstructions, extra })). The gate middleware extracts the payment signer pre-evaluate (extractPaymentSigner(req, x402Header) covers MPP did:pkh:eip155 + Solana did:pkh:solana + Solana TransferChecked authority fallback + x402 EIP-3009) and passes signer: { address, network } to /v1/assess; one round trip now carries the gate verdict AND the wallet-signer-match outcome AND the signer_sanctions OFAC SDN wallet-address verdict. Merchants read both back synchronously via getSignerVerdict(ctx) (or the gate.getSignerVerdict() accessor on the wrapper adapters) off the gate’s cache (no extra HTTP call). Wallet-OFAC SDN enforcement is unconditional whenever the signer is supplied — an SDN hit OR an unavailable wallet-sanctions lookup flips the gate decision to deny before the handler runs (fail-closed; OFAC strict-liability), without requiring policy.require_sanctions_clear to opt in. policy.require_sanctions_clear is the separate NAME-based screen on the operator’s KYC identity. captureWallet is fire-and-forget; POSTs the signer to /v1/credentials/wallets so the operator’s cross-merchant credential↔wallet profile builds up over time.

Identity publishing (cross-vendor standards)

Two helpers compose AgentScore identity into the payload formats published to other agent-commerce ecosystems. Each returns an unsigned object; your service signs + serves it however its key infrastructure works.
import { buildA2AAgentCard, buildUCPProfile, ucpA2AExtension } from '@agent-score/commerce';

// 1. Google A2A v1.0 Signed Agent Card, published at /.well-known/agent-card.json.
//    Per UCP §A2A binding (https://ucp.dev/specification/checkout-a2a/), agents
//    that serve UCP via the A2A transport MUST declare the canonical UCP extension
//    URI in `capabilities.extensions[]` so platforms arriving via A2A detect UCP
//    support without re-fetching the profile. Pass an empty capabilities map when
//    you serve UCP at the discovery layer but have no formal capability bindings
//    yet. Skills declare what the agent can do; they are AgentSkill objects
//    (top-level on the card). Identity claims live in a separate AgentCardSignature
//    (RFC 7515 JWS) wrapping the serialized card, NOT in the card body itself.
const card = buildA2AAgentCard({
  name: 'My Agent Service',
  description: 'Buy products via agent payments.',
  url: 'https://agents.example.com',
  version: '1.0.0',
  skills: [
    { id: 'purchase', name: 'Purchase', description: 'Buy products via agent payments.', tags: ['commerce', 'payment'] },
  ],
  extensions: [ucpA2AExtension()],
});

// 2. Google Universal Commerce Protocol profile, published at /.well-known/ucp
//    Output shape: { ucp: { version, services, capabilities, payment_handlers,
//    name?, supported_versions? }, signing_keys: [...] } — services /
//    capabilities / payment_handlers are MAPS keyed by reverse-DNS service /
//    capability / handler name (UCP spec §3 + §6).
const profile = buildUCPProfile({
  name: 'My Agent Service',
  services: {
    'dev.ucp.shopping': [
      {
        version: '2026-04-08',
        spec: 'https://ucp.dev/2026-04-08/specification/overview',
        transport: 'mcp',
        endpoint: 'https://agents.example.com/api/ucp/mcp',
        schema: 'https://ucp.dev/services/shopping/mcp.openrpc.json',
      },
    ],
  },
  payment_handlers: {
    ...mppPaymentHandler({ networks: [{ network: 'tempo-mainnet', chain_id: 4217, recipient: TEMPO_ADDR }] }),
    ...x402PaymentHandler({ networks: [{ network: 'base-8453', recipient: BASE_ADDR }] }),
    ...stripeSptPaymentHandler({ profile_id: 'profile_5xKvNqM9BaH' }),
  },
  signing_keys: yourJWKS,
  // Optional: declare merchant gate policy as an `sh.agentscore.identity` capability
  // binding inside the public profile. Static policy declaration only — no per-operator
  // claims. Per-operator identity attestation flows through the AP2 risk-signal endpoint.
  agentscore_gate: { require_kyc: true, min_age: 21, allowed_jurisdictions: ['US'] },
});
Note: ACP (Stripe + OpenAI Agentic Commerce Protocol) is a transactional checkout protocol. Not an identity-publishing surface. ACP merchants integrate through the existing build402Body + buildPaymentHeaders + Stripe SPT rail.

Signing UCP profiles (vendor extension; opt-in for trust-mode verifiers)

UCP §6 doesn’t mandate profile-body JWS signing; production UCP merchants commonly ship unsigned. AgentScore’s agentscore-profile+jws is a vendor extension layered on top of the unsigned UCP profile for trust-mode verifiers (regulated-commerce, AP2-aware) that opt into auditable cryptographic provenance. Vanilla UCP agents read the canonical body and ignore the signature field. Sign + verify via the optional jose peer dep (install via bun add jose):
import {
  buildJWKSResponse,
  generateUCPSigningKey,
  signUCPProfile,
  verifyUCPProfile,
} from '@agent-score/commerce';

// One-time per merchant: generate or load a key. Persist privateJWK in your secrets manager.
const { privateKey, publicJWK } = await generateUCPSigningKey({ kid: 'merchant-2026-05' });

// On every /.well-known/ucp request:
const profile = buildUCPProfile({
  name: 'My Agent Service',
  services: { /* keyed by service name, see above */ },
  payment_handlers: { /* keyed by handler reverse-DNS name, see above */ },
  signing_keys: [publicJWK],
});
const signed = await signUCPProfile(profile, {
  signingKey: privateKey,
  kid: publicJWK.kid,
  alg: 'EdDSA',
});
return c.json(signed);

// On /.well-known/jwks.json:
return c.json(buildJWKSResponse([publicJWK]));
Verifiers reconstruct the canonical body (everything except signature, keys sorted at every level), look up the kid in JWKS, and check the JWS. verifyUCPProfile(signed, jwks) does this for you. It enforces the JWS protected header typ: "agentscore-profile+jws" (vendor-namespaced — UCP §6 doesn’t define a profile-as-JWS typ), restricts alg to EdDSA / ES256, requires a kid, rejects duplicate kids, and compares canonical body bytes against the JWS payload. Failures throw UCPVerificationError with a discriminated code field. Both EdDSA (Ed25519) and ES256 are supported. EdDSA is the default and recommended. signUCPProfile rejects profiles containing non-integer Number values: cross-language float canonicalization is not stable, so use decimal strings (e.g. "9.99") for any monetary or fractional fields you put in extras. Persisting the private JWK. Mint once via generateUCPSigningKey(), export with jose.exportJWK(privateKey), store in your secret manager. On each container start, read the secret, jose.importJWK(jwk, alg) to re-hydrate. KMS-backed flows require an adapter that exposes a KeyLike jose can call; jose does not natively wrap KMS endpoints. See examples/signed-ucp-merchant.ts for the runtime-independent re-hydrate pattern. Key rotation. Mint a new key with a new kid, add the public JWK to your JWKS endpoint alongside the old one, then sign new profiles with the new key. Drop the old JWK after your verifier-side cache TTL has elapsed. Inline JWK in the profile vs separate JWKS endpoint. UCP §6 mandates the separate /.well-known/jwks.json endpoint as the canonical trust source. The profile’s signing_keys[] is informational; verifiers MUST resolve the kid against the JWKS to prevent a swap-after-sign attack.

Quick start: full merchant via the Checkout orchestrator

Checkout is the canonical merchant surface in 2.0 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, identity_metadata auto-attach, and the per-framework adapter. 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 the result by content-hash of the request body, and emits a 402 with the EXACT computed price. The retry pays that exact amount and the merchant returns 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 (@agent-score/commerce/middleware/hono) globally — it’s load-bearing.
import { Hono } from 'hono';
import { Checkout, pricingResult, validateShippingAgainstPolicy } from '@agent-score/commerce';
import { defaultA2aServices } from '@agent-score/commerce/discovery';

const checkout = new Checkout({
  rails: {
    tempo:     { recipient: process.env.TEMPO_RECIPIENT! },
    x402_base: { recipient: process.env.X402_BASE_RECIPIENT!, network: 'eip155:8453' },
    solana_mpp:{ recipient: process.env.SOLANA_RECIPIENT!,    network: 'solana:mainnet' },
    stripe:    { profileId: process.env.STRIPE_PROFILE_ID! },
  },
  url: 'https://merchant.example/purchase',
  preValidate: async (ctx) => {
    const body = (ctx.request.body ?? {}) as { product_slug?: string; shipping?: { country?: string; state?: string } };
    const product = await lookupProduct(body.product_slug);
    validateShippingAgainstPolicy({ country: body.shipping?.country ?? '', state: body.shipping?.state ?? '', policy: product, productName: product.name });
    return { product };
  },
  computePricing: async (ctx) => pricingResult({
    subtotalCents: ctx.state.product.priceCents,
    taxCents: ctx.state.product.taxCents,
    taxRate: ctx.state.product.taxRate,
    taxState: ctx.state.product.taxState,
  }),
  onSettled: async (ctx, outcome) => ({ ok: true, order_id: ctx.referenceId, tx_hash: outcome.txHash }),
  cdpApiKeyId:     process.env.CDP_API_KEY_ID,
  cdpApiKeySecret: process.env.CDP_API_KEY_SECRET,
  mppxSecretKey:   process.env.MPP_SECRET_KEY,
  gate: {
    apiKey: process.env.AGENTSCORE_API_KEY!,
    merchantName: 'Merchant',
    requireKyc: true, requireSanctionsClear: true, minAge: 21, allowedJurisdictions: ['US'],
  },
  // Optional: empty-body POSTs without a payment header auto-route to a sample 402
  // so x402 crawlers (awal x402 details, x402-proxy, ...) can discover the surface.
  discoveryProbe: {
    realm: 'merchant.example',
    sampleRail: 'tempo-mainnet',
    sampleAmountUsd: 1.0,
    sampleRecipient: process.env.TEMPO_RECIPIENT!,
  },
});

const app = new Hono();

// One-call mount: GET /.well-known/ucp + GET /.well-known/jwks.json + OPTIONS preflights.
// Express / Fastify have the same helper (`mountUcpRoutesExpress` / `mountUcpRoutesFastify`).
// Next.js / Web Fetch use file-based routing — call `buildSignedUcpResponse` directly inside the route handler.
checkout.mountUcpRoutesHono(app, {
  name: 'Merchant',
  wellKnownUcpUrl: 'https://merchant.example/.well-known/ucp',
  services: defaultA2aServices({ agentCardUrl: 'https://merchant.example/.well-known/agent-card.json' }),
  signingKid: 'merchant-2026-05',
});

app.post('/purchase', (c) => checkout.handleHono(c));
The 402 body Checkout emits auto-attaches identity_mode + required_signer + signer_constraint (and linked_wallets when the gate populated them) when an inbound X-Wallet-Address header is present — so agents self-correct at discovery instead of at the 403 retry. See the compute-first merchant example for the canonical variable-cost pattern via computeFirstCheckout.

Fail-open (opt-in)

By default AgentScore Gate fails closed on AgentScore-side infra failure (429 / 5xx / network timeout); buyer gets 503. Pass failOpen: true to opt in to graceful degradation, then read the per-request degraded state via getGateDegradedState(c):
import { agentscoreGate, getGateDegradedState } from '@agent-score/commerce/identity/hono';

const gate = agentscoreGate({ apiKey: process.env.AGENTSCORE_API_KEY!, failOpen: true });
app.use('/purchase', gate);

app.post('/purchase', async (c) => {
  const { degraded, infraReason } = getGateDegradedState(c);
  if (degraded) console.warn(`[gate] degraded: ${infraReason}`);
  // ...
});
getGateDegradedState is exported by every Node adapter (Hono, Express, Fastify). For withAgentScoreGate (Next.js / Web Fetch), the degraded + infraReason fields land on the gate object passed to your handler. Compliance denials (sanctions, age, jurisdiction, signer-mismatch) still deny regardless of failOpen; see compliance-gating › Fail-open behavior.

Examples

The examples/ directory has runnable single-file Hono apps for each common merchant scenario:
ExampleScenario
api-provider.tsPer-call API billing on multiple rails: Tempo MPP + x402 (Base + Solana); no compliance gate
identity-only.tsCompliance gate without payment (vendor handles their own)
multi-rail-merchant.tsFull agent-commerce: identity + Tempo MPP + x402 + Stripe SPT
stripe-multichain-merchant.tsStripe-anchored multichain (PaymentIntent → tempo/base/solana deposit addresses)
compute-first-merchant.tsPay-per-result variable-cost merchant via computeFirstCheckout. Probe runs the work + caches by body content-hash; settle replays the cached result. Works on every exact-mode rail (no upto / Permit2 / Settlement-Overrides).
compliance-merchant.tsRegulated-goods merchant: full compliance gate + custom onDenied composing the denial helpers (verificationAgentInstructions, isFixableDenial, buildSignerMismatchBody, buildContactSupportNextSteps, denialReasonStatus / denialReasonToBody to assemble the pair for any DenialReason)
per-product-policy-merchant.tsMulti-product merchant where each row carries its own compliance policy (hard gate, soft, or none). Uses PolicyBlock, buildGateFromPolicy, runGateWithEnforcement, and the one-call validateShippingAgainstPolicy.
signed-ucp-merchant.tsSigned UCP profile + JWKS endpoint via the one-call checkout.mountUcpRoutesHono(app, ...). Demonstrates ephemeral-for-dev / env-JWK-for-prod and bootstrapUcpSigningKey lifespan-hook usage.