@nevermined-io/payments 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/nvm-api.d.ts +4 -0
- package/dist/api/nvm-api.d.ts.map +1 -1
- package/dist/api/nvm-api.js +7 -0
- package/dist/api/nvm-api.js.map +1 -1
- package/dist/api/organizations-api/organizations-api.d.ts +19 -1
- package/dist/api/organizations-api/organizations-api.d.ts.map +1 -1
- package/dist/api/organizations-api/organizations-api.js +49 -0
- package/dist/api/organizations-api/organizations-api.js.map +1 -1
- package/dist/api/organizations-api/types.d.ts +27 -0
- package/dist/api/organizations-api/types.d.ts.map +1 -1
- package/dist/api/organizations-api/types.js.map +1 -1
- package/dist/common/api-version.d.ts +1 -1
- package/dist/common/api-version.js +1 -1
- package/dist/common/api-version.js.map +1 -1
- package/dist/common/types.d.ts +1 -1
- package/dist/common/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/core/server-manager.d.ts.map +1 -1
- package/dist/mcp/core/server-manager.js +3 -0
- package/dist/mcp/core/server-manager.js.map +1 -1
- package/dist/mcp/http/mcp-handler.d.ts +7 -0
- package/dist/mcp/http/mcp-handler.d.ts.map +1 -1
- package/dist/mcp/http/mcp-handler.js +8 -1
- package/dist/mcp/http/mcp-handler.js.map +1 -1
- package/dist/mcp/http/oauth-metadata.d.ts.map +1 -1
- package/dist/mcp/http/oauth-metadata.js +31 -6
- package/dist/mcp/http/oauth-metadata.js.map +1 -1
- package/dist/mcp/http/oauth-router.d.ts +17 -16
- package/dist/mcp/http/oauth-router.d.ts.map +1 -1
- package/dist/mcp/http/oauth-router.js +56 -28
- package/dist/mcp/http/oauth-router.js.map +1 -1
- package/dist/mpp/codec.d.ts +92 -0
- package/dist/mpp/codec.d.ts.map +1 -0
- package/dist/mpp/codec.js +405 -0
- package/dist/mpp/codec.js.map +1 -0
- package/dist/mpp/errors.d.ts +165 -0
- package/dist/mpp/errors.d.ts.map +1 -0
- package/dist/mpp/errors.js +177 -0
- package/dist/mpp/errors.js.map +1 -0
- package/dist/mpp/fetch.d.ts +196 -0
- package/dist/mpp/fetch.d.ts.map +1 -0
- package/dist/mpp/fetch.js +493 -0
- package/dist/mpp/fetch.js.map +1 -0
- package/dist/mpp/index.d.ts +11 -0
- package/dist/mpp/index.d.ts.map +1 -0
- package/dist/mpp/index.js +20 -0
- package/dist/mpp/index.js.map +1 -0
- package/dist/mpp/mpp-api.d.ts +153 -0
- package/dist/mpp/mpp-api.d.ts.map +1 -0
- package/dist/mpp/mpp-api.js +298 -0
- package/dist/mpp/mpp-api.js.map +1 -0
- package/dist/mpp/types.d.ts +43 -0
- package/dist/mpp/types.d.ts.map +1 -0
- package/dist/mpp/types.js +9 -0
- package/dist/mpp/types.js.map +1 -0
- package/dist/payments.d.ts +13 -0
- package/dist/payments.d.ts.map +1 -1
- package/dist/payments.js +3 -0
- package/dist/payments.js.map +1 -1
- package/dist/x402/delegation-api.d.ts +3 -1
- package/dist/x402/delegation-api.d.ts.map +1 -1
- package/dist/x402/delegation-api.js.map +1 -1
- package/dist/x402/express/index.d.ts +2 -0
- package/dist/x402/express/index.d.ts.map +1 -1
- package/dist/x402/express/index.js +2 -0
- package/dist/x402/express/index.js.map +1 -1
- package/dist/x402/express/middleware.d.ts +33 -1
- package/dist/x402/express/middleware.d.ts.map +1 -1
- package/dist/x402/express/middleware.js +831 -1
- package/dist/x402/express/middleware.js.map +1 -1
- package/dist/x402/express/mpp-support.d.ts +58 -0
- package/dist/x402/express/mpp-support.d.ts.map +1 -0
- package/dist/x402/express/mpp-support.js +67 -0
- package/dist/x402/express/mpp-support.js.map +1 -0
- package/dist/x402/express/raw-body.d.ts +24 -0
- package/dist/x402/express/raw-body.d.ts.map +1 -0
- package/dist/x402/express/raw-body.js +34 -0
- package/dist/x402/express/raw-body.js.map +1 -0
- package/dist/x402/token-request.d.ts +15 -0
- package/dist/x402/token-request.d.ts.map +1 -0
- package/dist/x402/token-request.js +66 -0
- package/dist/x402/token-request.js.map +1 -0
- package/dist/x402/token.d.ts.map +1 -1
- package/dist/x402/token.js +7 -51
- package/dist/x402/token.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../src/x402/express/middleware.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Express middleware for Nevermined payment protection using the x402 protocol.\n *\n * This middleware provides a simple way to protect Express routes with\n * Nevermined payment verification and settlement.\n *\n * ## x402 HTTP Transport Headers\n *\n * Following the x402 spec (https://github.com/coinbase/x402/blob/main/specs/transports-v2/http.md):\n *\n * - **Client → Server**: `payment-signature` header with base64-encoded token\n * - **Server → Client (402)**: `payment-required` header with base64-encoded PaymentRequired\n * - **Server → Client (success)**: `payment-response` header with settlement receipt\n *\n * @example\n * ```typescript\n * import express from 'express'\n * import { Payments } from '@nevermined-io/payments'\n * import { paymentMiddleware } from '@nevermined-io/payments/express'\n *\n * const app = express()\n * const payments = Payments.getInstance({ nvmApiKey: '...', environment: 'testing' })\n *\n * // Protect routes with payment middleware\n * app.use(paymentMiddleware(payments, {\n * 'POST /ask': { planId: '123', credits: 1 },\n * 'POST /generate': { planId: '123', credits: 5 },\n * }))\n *\n * // Route handlers - no payment logic needed!\n * app.post('/ask', (req, res) => res.json({ answer: '...' }))\n * ```\n *\n * @example Client usage\n * ```typescript\n * const token = await payments.x402.getX402AccessToken(planId)\n *\n * const response = await fetch('/ask', {\n * method: 'POST',\n * headers: {\n * 'Content-Type': 'application/json',\n * 'payment-signature': token.accessToken, // x402 header\n * },\n * body: JSON.stringify({ query: 'Hello!' }),\n * })\n * ```\n */\n\nimport type { Request, Response, NextFunction } from 'express'\n\n/**\n * Express middleware function type.\n * Using explicit signature instead of RequestHandler to avoid type resolution issues\n * when SDK's \\@types/express version differs from consumer's.\n */\nexport type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void\nimport type { Payments } from '../../payments.js'\nimport type { StartAgentRequest, X402SchemeType } from '../../common/types.js'\nimport {\n buildPaymentRequired,\n resolveNetwork,\n resolveScheme,\n type X402PaymentRequired,\n type VerifyPermissionsResult,\n} from '../facilitator-api.js'\n\n/**\n * Configuration for a protected route\n */\nexport interface RouteConfig {\n /** The Nevermined plan ID that protects this route */\n planId: string\n /** Number of credits to charge for this route (default: 1) */\n credits?: number | ((req: Request, res: Response) => number | Promise<number>)\n /** Optional agent ID */\n agentId?: string\n /** Network identifier (default: auto-derived from scheme) */\n network?: string\n /** x402 scheme override (auto-detected from plan metadata if omitted) */\n scheme?: X402SchemeType\n /** Human-readable description of the protected resource */\n description?: string\n /** Expected response MIME type (e.g., \"application/json\") */\n mimeType?: string\n}\n\n/**\n * Route configuration map: \"METHOD \\/path\" -> RouteConfig\n */\nexport type RouteConfigMap = Record<string, RouteConfig>\n\n/**\n * x402 HTTP Transport header names (v2 spec)\n * @see https://github.com/coinbase/x402/blob/main/specs/transports-v2/http.md\n */\nexport const X402_HEADERS = {\n /** Client sends payment token in this header */\n PAYMENT_SIGNATURE: 'payment-signature',\n /** Server sends PaymentRequired in this header (base64-encoded) */\n PAYMENT_REQUIRED: 'payment-required',\n /** Server sends settlement receipt in this header (base64-encoded) */\n PAYMENT_RESPONSE: 'payment-response',\n} as const\n\n/**\n * Payment context attached to the request after verification.\n * Available as `req.paymentContext` in route handlers.\n */\nexport interface PaymentContext {\n /** The x402 access token */\n token: string\n /** The payment required object */\n paymentRequired: X402PaymentRequired\n /** Number of credits to settle */\n creditsToSettle: number\n /** Whether verification was successful */\n verified: boolean\n /** Agent request context for observability (from verification response) */\n agentRequest?: StartAgentRequest\n /** Agent request ID for observability tracking */\n agentRequestId?: string\n}\n\n/**\n * Options for the payment middleware\n */\nexport interface PaymentMiddlewareOptions {\n /**\n * Header name(s) to check for the x402 access token.\n * Default: 'payment-signature' (x402 v2 compliant)\n */\n tokenHeader?: string | string[]\n /** Custom error handler for payment failures */\n onPaymentError?: (error: Error, req: Request, res: Response) => void\n /** Hook called before verification */\n onBeforeVerify?: (req: Request, paymentRequired: X402PaymentRequired) => void | Promise<void>\n /**\n * Hook called after successful verification.\n * Use this to access agentRequest for observability configuration.\n */\n onAfterVerify?: (req: Request, verification: VerifyPermissionsResult) => void | Promise<void>\n /** Hook called after successful settlement */\n onAfterSettle?: (req: Request, creditsUsed: number, result: unknown) => void | Promise<void>\n}\n\n/**\n * Default header for token extraction (x402 v2 compliant)\n */\nconst DEFAULT_TOKEN_HEADERS = [X402_HEADERS.PAYMENT_SIGNATURE]\n\n/**\n * Extract the x402 access token from the request headers.\n * Checks multiple headers in priority order.\n */\nfunction extractToken(req: Request, headerNames: string | string[]): string | null {\n const headers = Array.isArray(headerNames) ? headerNames : [headerNames]\n\n for (const headerName of headers) {\n const header = req.headers[headerName.toLowerCase()]\n if (header && typeof header === 'string') {\n return header\n }\n }\n\n return null\n}\n\n/**\n * Match a request to a route config.\n * Returns the config if found, null otherwise.\n */\nfunction matchRoute(req: Request, routes: RouteConfigMap): RouteConfig | null {\n const method = req.method.toUpperCase()\n const path = req.path\n\n // Try exact match first: \"POST /ask\"\n const exactKey = `${method} ${path}`\n if (routes[exactKey]) {\n return routes[exactKey]\n }\n\n // Try pattern matching with path parameters\n for (const [routeKey, config] of Object.entries(routes)) {\n const [routeMethod, routePath] = routeKey.split(' ')\n if (routeMethod !== method) continue\n\n // Simple pattern matching: /users/:id -> /users/123\n const routeParts = routePath.split('/')\n const pathParts = path.split('/')\n\n if (routeParts.length !== pathParts.length) continue\n\n let match = true\n for (let i = 0; i < routeParts.length; i++) {\n if (routeParts[i].startsWith(':')) continue // Parameter - always matches\n if (routeParts[i] !== pathParts[i]) {\n match = false\n break\n }\n }\n\n if (match) return config\n }\n\n return null\n}\n\n/**\n * Create an Express middleware that protects routes with Nevermined payments.\n *\n * The middleware:\n * 1. Checks if the request matches a protected route\n * 2. Extracts the x402 token from headers\n * 3. Verifies the subscriber has sufficient credits\n * 4. Lets the route handler execute\n * 5. Settles (burns) the credits after successful response\n *\n * @param payments - The Payments instance\n * @param routes - Map of routes to protect: \\{ \"METHOD \\/path\": \\{ planId, credits \\} \\}\n * @param options - Optional middleware configuration\n * @returns Express middleware function\n *\n * @example\n * ```typescript\n * app.use(paymentMiddleware(payments, {\n * 'POST /ask': { planId: PLAN_ID, credits: 1 },\n * 'POST /generate': { planId: PLAN_ID, credits: 5 },\n * 'GET /status/:id': { planId: PLAN_ID, credits: 0 }, // Free but requires auth\n * }))\n * ```\n */\n/**\n * Helper to send a 402 Payment Required response with proper x402 headers.\n */\nfunction sendPaymentRequired(\n res: Response,\n paymentRequired: X402PaymentRequired,\n message: string,\n): void {\n // Base64 encode the PaymentRequired object for the header (per x402 spec)\n const paymentRequiredBase64 = Buffer.from(JSON.stringify(paymentRequired)).toString('base64')\n\n res.status(402).setHeader(X402_HEADERS.PAYMENT_REQUIRED, paymentRequiredBase64).json({\n error: 'Payment Required',\n message,\n })\n}\n\nexport function paymentMiddleware(\n payments: Payments,\n routes: RouteConfigMap,\n options: PaymentMiddlewareOptions = {},\n): ExpressMiddleware {\n const {\n tokenHeader = DEFAULT_TOKEN_HEADERS,\n onPaymentError,\n onBeforeVerify,\n onAfterVerify,\n onAfterSettle,\n } = options\n\n return (req: Request, res: Response, next: NextFunction): void => {\n // Wrap async logic to handle promises properly\n const handleRequest = async (): Promise<void> => {\n // Check if this route requires payment\n const routeConfig = matchRoute(req, routes)\n if (!routeConfig) {\n // Route not protected - pass through\n next()\n return\n }\n\n const {\n planId,\n credits = 1,\n agentId,\n network,\n scheme: explicitScheme,\n description,\n mimeType,\n } = routeConfig\n\n // Resolve scheme and network from plan metadata (cached) or explicit overrides\n const scheme = await resolveScheme(payments, planId, explicitScheme)\n const resolvedNetwork = await resolveNetwork(payments, planId, network)\n\n // Build payment required object (needed for both error responses and verification)\n const paymentRequired = buildPaymentRequired(planId, {\n endpoint: req.originalUrl || req.url,\n agentId,\n httpVerb: req.method,\n network: resolvedNetwork,\n description,\n mimeType,\n scheme,\n environment: payments.getEnvironmentName(),\n })\n\n // Extract token from headers (x402 v2: payment-signature)\n const token = extractToken(req, tokenHeader)\n if (!token) {\n const error = new Error('Payment required: missing x402 access token')\n if (onPaymentError) {\n onPaymentError(error, req, res)\n return\n }\n sendPaymentRequired(\n res,\n paymentRequired,\n `Missing x402 payment token. Send token in ${X402_HEADERS.PAYMENT_SIGNATURE} header.`,\n )\n return\n }\n\n // Calculate credits to verify\n const creditsToVerify = typeof credits === 'function' ? await credits(req, res) : credits\n\n try {\n // Hook: before verification\n if (onBeforeVerify) {\n await onBeforeVerify(req, paymentRequired)\n }\n\n // Verify permissions\n const verification = await payments.facilitator.verifyPermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: BigInt(creditsToVerify),\n })\n\n if (!verification.isValid) {\n const error = new Error(verification.invalidReason || 'Payment verification failed')\n if (onPaymentError) {\n onPaymentError(error, req, res)\n return\n }\n sendPaymentRequired(\n res,\n paymentRequired,\n verification.invalidReason || 'Insufficient credits or invalid token',\n )\n return\n }\n\n // Hook: after verification (use for observability setup)\n if (onAfterVerify) {\n await onAfterVerify(req, verification)\n }\n\n // Store payment context for settlement and route handler access\n const paymentContext: PaymentContext = {\n token,\n paymentRequired,\n creditsToSettle: creditsToVerify,\n verified: true,\n agentRequest: verification.agentRequest,\n agentRequestId: verification.agentRequest?.agentRequestId || verification.agentRequestId,\n }\n\n // Attach to request for potential use by route handler\n ;(req as Request & { paymentContext?: PaymentContext }).paymentContext = paymentContext\n\n // Wrap res.end so settlement runs no matter how the handler responds\n // (res.json, res.send, res.sendFile, res.end, res.pipe → res.end).\n // Previously only res.json was intercepted, so any other response\n // method would deliver the resource without burning credits and\n // without emitting the payment-response receipt header (#1728).\n const originalEnd = res.end.bind(res) as (...args: Parameters<Response['end']>) => Response\n let settlementStarted = false\n\n const runSettlement = (): Promise<void> => {\n return (\n typeof credits === 'function'\n ? Promise.resolve(credits(req, res))\n : Promise.resolve(creditsToVerify)\n )\n .then((creditsToSettle) => {\n paymentContext.creditsToSettle = creditsToSettle\n return payments.facilitator\n .settlePermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: BigInt(creditsToSettle),\n agentRequestId: paymentContext.agentRequestId,\n })\n .then((settlement) => {\n // Only attach the receipt header if headers haven't flushed\n // yet — streaming responses fire writeHead on the first\n // chunk and may have already sent them by the time we land\n // here.\n if (!res.headersSent) {\n const settlementBase64 = Buffer.from(JSON.stringify(settlement)).toString(\n 'base64',\n )\n res.setHeader(X402_HEADERS.PAYMENT_RESPONSE, settlementBase64)\n } else {\n console.warn(\n '[paymentMiddleware] headers already flushed; payment-response receipt not attached',\n )\n }\n if (onAfterSettle) {\n return Promise.resolve(onAfterSettle(req, creditsToSettle, settlement)).then(\n () => undefined,\n )\n }\n return undefined\n })\n })\n .catch((settleError) => {\n console.error('Payment settlement failed:', settleError)\n })\n }\n\n ;(res as unknown as { end: Response['end'] }).end = function (\n this: Response,\n ...args: Parameters<Response['end']>\n ): Response {\n // Only bill on 2xx success. Skipping 3xx avoids charging when the\n // handler redirects (e.g. `res.redirect(...)`), 304 Not Modified,\n // etc. Skipping 4xx/5xx avoids charging when the handler signals\n // failure — including `sendPaymentRequired`'s 402 which lands here.\n const isSuccess = res.statusCode >= 200 && res.statusCode < 300\n if (settlementStarted || !isSuccess) {\n return originalEnd(...args)\n }\n settlementStarted = true\n\n // If the handler streamed before calling end, headers were already\n // flushed. Settle anyway (so we still charge the card) but accept\n // we cannot inject the receipt header.\n if (res.headersSent) {\n void runSettlement()\n return originalEnd(...args)\n }\n\n // Buffered response path: defer the real `end` until settlement\n // finishes so the receipt header makes it into the same response.\n runSettlement().finally(() => {\n originalEnd(...args)\n })\n return res\n } as Response['end']\n\n // Continue to route handler\n next()\n } catch (error) {\n if (onPaymentError) {\n onPaymentError(error as Error, req, res)\n return\n }\n sendPaymentRequired(\n res,\n paymentRequired,\n error instanceof Error ? error.message : 'Payment verification failed',\n )\n }\n }\n\n // Execute async handler with error handling\n handleRequest().catch(next)\n }\n}\n\nexport default paymentMiddleware\n"]}
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../src/x402/express/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAYH,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,aAAa,GAGd,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,OAAO,EACP,gBAAgB,GAEjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC7D,OAAO,EACL,QAAQ,EACR,0BAA0B,EAC1B,gCAAgC,EAChC,kBAAkB,GAEnB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAiCvD;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,gDAAgD;IAChD,iBAAiB,EAAE,mBAAmB;IACtC,mEAAmE;IACnE,gBAAgB,EAAE,kBAAkB;IACpC,sEAAsE;IACtE,gBAAgB,EAAE,kBAAkB;CAC5B,CAAA;AAoEV;;GAEG;AACH,MAAM,qBAAqB,GAAG,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAA;AAE9D;;;GAGG;AACH,SAAS,YAAY,CAAC,GAAY,EAAE,WAA8B;IAChE,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;IAExE,KAAK,MAAM,UAAU,IAAI,OAAO,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAA;QACpD,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YACzC,OAAO,MAAM,CAAA;QACf,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,GAAY,EAAE,MAAsB;IACtD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAA;IACvC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAA;IAErB,qCAAqC;IACrC,MAAM,QAAQ,GAAG,GAAG,MAAM,IAAI,IAAI,EAAE,CAAA;IACpC,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAA;IACzB,CAAC;IAED,4CAA4C;IAC5C,KAAK,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,MAAM,CAAC,WAAW,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACpD,IAAI,WAAW,KAAK,MAAM;YAAE,SAAQ;QAEpC,oDAAoD;QACpD,MAAM,UAAU,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACvC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAEjC,IAAI,UAAU,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM;YAAE,SAAQ;QAEpD,IAAI,KAAK,GAAG,IAAI,CAAA;QAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAQ,CAAC,6BAA6B;YACzE,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;gBACnC,KAAK,GAAG,KAAK,CAAA;gBACb,MAAK;YACP,CAAC;QACH,CAAC;QAED,IAAI,KAAK;YAAE,OAAO,MAAM,CAAA;IAC1B,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;;;;;;;;;;;GAWG;AACH,SAAS,cAAc,CAAC,GAAY;IAClC,IAAI,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IAC/D,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAA;IACnD,OAAO,aAAa,KAAK,SAAS,IAAI,aAAa,KAAK,GAAG,CAAA;AAC7D,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,SAAS,gBAAgB,CAAC,GAAa;IACrC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAA;IACzB,MAAM,kBAAkB,GAAG,MAAM,EAAE,YAAY,IAAI,CAAC,CAAA;IAEpD,OAAO,GAAG,EAAE;QACV,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,YAAY,IAAI,kBAAkB,CAAC,GAAG,kBAAkB,CAAA;QACtF,MAAM,gBAAgB,GAAG,GAAG,CAAC,WAAW,IAAI,YAAY,GAAG,CAAC,CAAA;QAC5D,MAAM,gBAAgB,GAAG,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,GAAG,CAAC,MAAM,CAAA;QACtD,OAAO,gBAAgB,IAAI,gBAAgB,CAAA;IAC7C,CAAC,CAAA;AACH,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAC1B,GAAa,EACb,eAAoC,EACpC,OAAe;IAEf,0EAA0E;IAC1E,MAAM,qBAAqB,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAE7F,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,CAAC,IAAI,CAAC;QACnF,KAAK,EAAE,kBAAkB;QACzB,OAAO;KACR,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,sBAAsB,GAAG,IAAI,GAAG,EAAU,CAAA;AAEhD;;wDAEwD;AACxD,MAAM,iBAAiB,GAAG,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AAE5D;;;;;GAKG;AACH,MAAM,uBAAuB,GAAG,MAAO,CAAA;AAEvC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAkB,CAAA;AAErD;;;;;;GAMG;AACH,SAAS,mBAAmB,CAAC,YAAoB;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,mBAAmB,EAAE,CAAC;QACrD,IAAI,SAAS,GAAG,GAAG;YAAE,MAAK;QAC1B,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACnC,CAAC;IACD,mBAAmB,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,GAAG,uBAAuB,CAAC,CAAA;AACtE,CAAC;AAED,6DAA6D;AAC7D,SAAS,iBAAiB,CAAC,YAAoB;IAC7C,MAAM,SAAS,GAAG,mBAAmB,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IACvD,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IACzC,IAAI,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC5B,mBAAmB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;QACxC,OAAO,KAAK,CAAA;IACd,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,gBAAgB,CAAC,IAc/B;IACC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,IAAI,CAAA;IACjE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,WAAW,CAAA;IACjE,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAA;IACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IAC7B,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;IAEnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,MAAM,kBAAkB,GAAG,CAAC,KAAY,EAAQ,EAAE;QAChD,IAAI,CAAC,cAAc;YAAE,OAAM;QAC3B,MAAM,cAAc,GAAG,CAAC,SAAkB,EAAQ,EAAE;YAClD,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,SAAS,CAAC,CAAA;QAC7D,CAAC,CAAA;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAY,CAAA;YACzD,IAAI,OAAQ,MAAwC,EAAE,IAAI,KAAK,UAAU,EAAE,CAAC;gBAC1E,KAAK,OAAO,CAAC,OAAO,CAAC,MAA2B,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;YACzF,CAAC;QACH,CAAC;QAAC,OAAO,SAAS,EAAE,CAAC;YACnB,cAAc,CAAC,SAAS,CAAC,CAAA;QAC3B,CAAC;IACH,CAAC,CAAA;IAED,4EAA4E;IAC5E,0EAA0E;IAC1E,qEAAqE;IACrE,EAAE;IACF,qEAAqE;IACrE,4EAA4E;IAC5E,yEAAyE;IACzE,2EAA2E;IAC3E,+DAA+D;IAC/D,8BAA8B;IAC9B,IAAI,eAAuB,CAAA;IAC3B,IAAI,CAAC;QACH,eAAe,GAAG,OAAO,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;IACrF,CAAC;IAAC,OAAO,YAAY,EAAE,CAAC;QACtB,kBAAkB,CAAC,YAAqB,CAAC,CAAA;QACzC,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,YAAY,CAAC,CAAA;QAC7D,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YACrB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,KAAK,EAAE,uBAAuB;gBAC9B,OAAO,EAAE,iDAAiD;aAC3D,CAAC,CAAA;QACJ,CAAC;QACD,OAAM;IACR,CAAC;IAED,0EAA0E;IAC1E,wEAAwE;IACxE,wBAAwB;IACxB,EAAE;IACF,2EAA2E;IAC3E,yEAAyE;IACzE,0EAA0E;IAC1E,wEAAwE;IACxE,wEAAwE;IACxE,kEAAkE;IAClE,IAAI,UAA8B,CAAA;IAClC,IAAI,eAA+E,CAAA;IACnF,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,GAAG,KAAK,SAAS,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7C,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAA;YACrD,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;gBAC7D,oEAAoE;gBACpE,+DAA+D;gBAC/D,iEAAiE;gBACjE,eAAe,GAAG;oBAChB,MAAM,EAAE,GAAG;oBACX,IAAI,EAAE,IAAI;oBACV,OAAO,EACL,iEAAiE;wBACjE,kIAAkI;iBACrI,CAAA;YACH,CAAC;iBAAM,CAAC;gBACN,gEAAgE;gBAChE,kEAAkE;gBAClE,+DAA+D;gBAC/D,eAAe,GAAG;oBAChB,MAAM,EAAE,GAAG;oBACX,IAAI,EAAE,KAAK;oBACX,OAAO,EAAE,qFAAqF,WAAW,IAAI,QAAQ,0BAA0B;iBAChJ,CAAA;YACH,CAAC;QACH,CAAC;aAAM,IAAI,GAAG,EAAE,CAAC;YACf,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAA;QACrC,CAAC;aAAM,CAAC;YACN,kEAAkE;YAClE,uEAAuE;YACvE,uEAAuE;YACvE,oEAAoE;YACpE,uEAAuE;YACvE,iEAAiE;YACjE,oEAAoE;YACpE,mBAAmB;YACnB,EAAE;YACF,kEAAkE;YAClE,mEAAmE;YACnE,iEAAiE;YACjE,mEAAmE;YACnE,oEAAoE;YACpE,4DAA4D;YAC5D,UAAU,GAAG,iBAAiB,CAAA;QAChC,CAAC;IACH,CAAC;IACD,IAAI,eAAe,EAAE,CAAC;QACpB,kBAAkB,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAA;QACtD,oEAAoE;QACpE,wEAAwE;QACxE,sEAAsE;QACtE,iEAAiE;QACjE,uEAAuE;QACvE,IAAI,eAAe,CAAC,IAAI,EAAE,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,eAAe,CAAC,OAAO,CAAC,CAAA;QAC1E,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,kDAAkD,EAAE,eAAe,CAAC,OAAO,CAAC,CAAA;QAC3F,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YACrB,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;gBACtC,KAAK,EAAE,eAAe,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,aAAa;gBAC9E,OAAO,EAAE,eAAe,CAAC,OAAO;aACjC,CAAC,CAAA;QACJ,CAAC;QACD,OAAM;IACR,CAAC;IAED,MAAM,aAAa,GAAG,KAAK,EAAE,OAAe,EAAE,IAAa,EAAiB,EAAE;QAC5E,iEAAiE;QACjE,yEAAyE;QACzE,0EAA0E;QAC1E,wEAAwE;QACxE,gEAAgE;QAChE,sEAAsE;QACtE,mEAAmE;QACnE,wBAAwB;QACxB,IAAI,SAAiB,CAAA;QACrB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC;gBAC/C,MAAM;gBACN,mEAAmE;gBACnE,oEAAoE;gBACpE,mEAAmE;gBACnE,+DAA+D;gBAC/D,iEAAiE;gBACjE,wCAAwC;gBACxC,OAAO,EAAE,gBAAgB,CAAC,eAAe,CAAC;gBAC1C,GAAG,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,CAAC;gBAC3B,QAAQ;gBACR,QAAQ;gBACR,GAAG,CAAC,UAAU,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;gBACzC,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;aACpC,CAAC,CAAA;YACF,SAAS,GAAG,MAAM,CAAC,SAAS,CAAA;QAC9B,CAAC;QAAC,OAAO,cAAc,EAAE,CAAC;YACxB,kBAAkB,CAAC,cAAuB,CAAC,CAAA;YAC3C,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,cAAc,CAAC,CAAA;YAC/D,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gBACrB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACnB,KAAK,EAAE,uBAAuB;oBAC9B,OAAO,EAAE,mEAAmE;iBAC7E,CAAC,CAAA;YACJ,CAAC;YACD,OAAM;QACR,CAAC;QAED,mEAAmE;QACnE,yDAAyD;QACzD,iDAAiD;QACjD,IAAI,GAAG,CAAC,WAAW;YAAE,OAAM;QAE3B,MAAM,qBAAqB,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAC7F,GAAG;aACA,MAAM,CAAC,GAAG,CAAC;aACX,SAAS,CAAC,WAAW,CAAC,SAAS,EAAE,SAAS,CAAC;YAC5C,iEAAiE;aAChE,SAAS,CAAC,YAAY,CAAC,gBAAgB,EAAE,qBAAqB,CAAC;YAChE,sEAAsE;YACtE,kEAAkE;YAClE,wEAAwE;YACxE,oEAAoE;YACpE,oEAAoE;YACpE,6DAA6D;YAC7D,EAAE;YACF,uEAAuE;YACvE,wEAAwE;YACxE,sEAAsE;YACtE,qEAAqE;YACrE,qEAAqE;YACrE,UAAU;aACT,IAAI,CAAC;YACJ,KAAK,EAAE,kBAAkB;YACzB,OAAO;YACP,GAAG,CAAC,IAAI,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;SAC3D,CAAC,CAAA;IACN,CAAC,CAAA;IAED,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAA;IACzC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,oEAAoE;QACpE,yEAAyE;QACzE,SAAS;QACT,EAAE;QACF,wEAAwE;QACxE,uEAAuE;QACvE,uEAAuE;QACvE,kEAAkE;QAClE,0EAA0E;QAC1E,kEAAkE;QAClE,qDAAqD;QACrD,EAAE;QACF,uEAAuE;QACvE,uEAAuE;QACvE,sEAAsE;QACtE,sEAAsE;QACtE,uEAAuE;QACvE,yEAAyE;QACzE,qEAAqE;QACrE,sEAAsE;QACtE,+DAA+D;QAC/D,EAAE;QACF,qEAAqE;QACrE,uCAAuC;QACvC,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;YACxC,kBAAkB,CAChB,IAAI,KAAK,CACP,yEAAyE;gBACvE,sEAAsE,CACzE,CACF,CAAA;QACH,CAAC;QACD,MAAM,aAAa,CAAC,sEAAsE,CAAC,CAAA;QAC3F,OAAM;IACR,CAAC;IAED,0EAA0E;IAC1E,yEAAyE;IACzE,0EAA0E;IAC1E,2EAA2E;IAC3E,0EAA0E;IAC1E,qEAAqE;IACrE,6DAA6D;IAC7D,EAAE;IACF,qEAAqE;IACrE,mEAAmE;IACnE,wEAAwE;IACxE,yDAAyD;IACzD,MAAM,YAAY,GAAG,eAAe,CAAC,UAAU,CAAC,CAAA;IAChD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,WAAW,GAAG,IAAI,0BAA0B,CAAC,qBAAqB,CAAC,CAAA;QACzE,kBAAkB,CAAC,WAAW,CAAC,CAAA;QAC/B,OAAO,CAAC,KAAK,CAAC,uEAAuE,CAAC,CAAA;QACtF,MAAM,aAAa,CAAC,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,CAAA;QAC1D,OAAM;IACR,CAAC;IAED,yEAAyE;IACzE,0EAA0E;IAC1E,yEAAyE;IACzE,sEAAsE;IACtE,uEAAuE;IACvE,kDAAkD;IAClD,EAAE;IACF,yEAAyE;IACzE,2EAA2E;IAC3E,mBAAmB;IACnB,IAAI,iBAAiB,CAAC,YAAY,CAAC,EAAE,CAAC;QACpC,MAAM,aAAa,CACjB,4FAA4F,EAC5F,cAAc,CACf,CAAA;QACD,OAAM;IACR,CAAC;IAED,IAAI,YAAqC,CAAA;IACzC,IAAI,CAAC;QACH,sEAAsE;QACtE,oEAAoE;QACpE,gEAAgE;QAChE,uDAAuD;QACvD,IAAI,cAAc,EAAE,CAAC;YACnB,MAAM,cAAc,CAAC,GAAG,EAAE,eAAe,CAAC,CAAA;QAC5C,CAAC;QAED,YAAY,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,gBAAgB,CAAC;YACjD,UAAU;YACV,QAAQ;YACR,QAAQ;YACR,GAAG,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,CAAC;SAClC,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,kBAAkB,CAAC,KAAc,CAAC,CAAA;QAClC,wEAAwE;QACxE,wEAAwE;QACxE,oEAAoE;QACpE,sEAAsE;QACtE,uEAAuE;QACvE,sEAAsE;QACtE,uBAAuB;QACvB,MAAM,IAAI,GACR,KAAK,YAAY,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;QAC1F,oEAAoE;QACpE,qEAAqE;QACrE,qEAAqE;QACrE,mEAAmE;QACnE,uEAAuE;QACvE,oEAAoE;QACpE,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,KAAK,CAAC,CAAA;QAC3D,MAAM,aAAa,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAA;QAChD,OAAM;IACR,CAAC;IAED,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;QAC1B,sEAAsE;QACtE,mEAAmE;QACnE,sEAAsE;QACtE,sEAAsE;QACtE,mEAAmE;QACnE,kEAAkE;QAClE,uEAAuE;QACvE,0CAA0C;QAC1C,MAAM,cAAc,GAAG,IAAI,0BAA0B,CACnD,YAAY,CAAC,aAAa,IAAI,qBAAqB,CACpD,CAAA;QACD,kEAAkE;QAClE,uEAAuE;QACvE,4DAA4D;QAC5D,kBAAkB,CAAC,cAAc,CAAC,CAAA;QAClC,yEAAyE;QACzE,oEAAoE;QACpE,uEAAuE;QACvE,qEAAqE;QACrE,uEAAuE;QACvE,uEAAuE;QACvE,mEAAmE;QACnE,OAAO,CAAC,KAAK,CACX,0BAA0B,EAC1B,YAAY,CAAC,aAAa,IAAI,2BAA2B,CAC1D,CAAA;QACD,MAAM,aAAa,CAAC,qBAAqB,EAAE,cAAc,CAAC,IAAI,CAAC,CAAA;QAC/D,OAAM;IACR,CAAC;IAED,sEAAsE;IACtE,yEAAyE;IACzE,yEAAyE;IACzE,uEAAuE;IACvE,0DAA0D;IAC1D,IAAI,aAAa,EAAE,CAAC;QAClB,IAAI,CAAC;YACH,MAAM,aAAa,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;QACxC,CAAC;QAAC,OAAO,SAAS,EAAE,CAAC;YACnB,kBAAkB,CAAC,SAAkB,CAAC,CAAA;YACtC,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,SAAS,CAAC,CAAA;YAC1D,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gBACrB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACnB,KAAK,EAAE,uBAAuB;oBAC9B,OAAO,EAAE,uDAAuD;iBACjE,CAAC,CAAA;YACJ,CAAC;YACD,OAAM;QACR,CAAC;IACH,CAAC;IAED,0EAA0E;IAC1E,uEAAuE;IACvE,oEAAoE;IACpE,yEAAyE;IACzE,8DAA8D;IAC9D,EAAE;IACF,2EAA2E;IAC3E,2EAA2E;IAC3E,kEAAkE;IAClE,2EAA2E;IAC3E,0EAA0E;IAC1E,0EAA0E;IAC1E,wEAAwE;IACxE,4EAA4E;IAC5E,0EAA0E;IAC1E,gCAAgC;IAChC,IAAI,iBAAiB,CAAC,YAAY,CAAC,EAAE,CAAC;QACpC,sEAAsE;QACtE,qEAAqE;QACrE,sEAAsE;QACtE,wEAAwE;QACxE,eAAe;QACf,MAAM,aAAa,CACjB,4FAA4F,EAC5F,cAAc,CACf,CAAA;QACD,OAAM;IACR,CAAC;IACD,IAAI,sBAAsB,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,IAAI,0BAA0B,CAC1C,qEAAqE,CACtE,CAAA;QACD,kBAAkB,CAAC,KAAK,CAAC,CAAA;QACzB,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YACrB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;QACrE,CAAC;QACD,OAAM;IACR,CAAC;IACD,sBAAsB,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IACxC,uEAAuE;IACvE,0EAA0E;IAC1E,wEAAwE;IACxE,wEAAwE;IACxE,uEAAuE;IACvE,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACnB,sBAAsB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IAC7C,CAAC,CAAC,CAAA;IACF,kEAAkE;IAClE,yEAAyE;IACzE,4EAA4E;IAC5E,uEAAuE;IACvE,0EAA0E;IAC1E,0EAA0E;IAC1E,wDAAwD;IACxD,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACf,sBAAsB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IAC7C,CAAC;IAED,MAAM,cAAc,GAAmB;QACrC,KAAK,EAAE,UAAU;QACjB,eAAe;QACf,eAAe,EAAE,eAAe;QAChC,QAAQ,EAAE,IAAI;QACd,YAAY,EAAE,YAAY,CAAC,YAAY;QACvC,cAAc,EAAE,YAAY,CAAC,YAAY,EAAE,cAAc,IAAI,YAAY,CAAC,cAAc;QACxF,GAAG,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE;KACxC,CACA;IAAC,GAAqD,CAAC,cAAc,GAAG,cAAc,CAAA;IAEvF,MAAM,WAAW,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAoD,CAAA;IACxF,IAAI,iBAAiB,GAAG,KAAK,CAAA;IAE7B,yEAAyE;IACzE,+DAA+D;IAC/D,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAA;IAE9C,2EAA2E;IAC3E,2EAA2E;IAC3E,wEAAwE;IACxE,0EAA0E;IAC1E,2EAA2E;IAC3E,0EAA0E;IAC1E,wEAAwE;IACxE,0EAA0E;IAC1E,cAAc;IACd,wDAAwD;IACxD,qEAAqE;IACrE,0EAA0E;IAC1E,2DAA2D;IAC3D,MAAM,kBAAkB,GAAG,CAAC,OAAe,EAAE,UAAmB,EAAiB,EAAE,CACjF,OAAO,CAAC,OAAO,EAAE;SACd,IAAI,CAAC,GAAG,EAAE,CAAC,aAAc,CAAC,GAAG,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;SACpD,IAAI,CACH,GAAG,EAAE,CAAC,SAAS,EACf,CAAC,SAAS,EAAE,EAAE;QACZ,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,SAAS,CAAC,CAAA;IAC5D,CAAC,CACF,CAAA;IAEL,MAAM,aAAa,GAAG,GAAkB,EAAE,CACxC,QAAQ,CAAC,GAAG;SACT,gBAAgB,CAAC;QAChB,UAAU;QACV,QAAQ;QACR,QAAQ;QACR,GAAG,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,CAAC;KAClC,CAAC;SACD,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE;QACnB,sEAAsE;QACtE,iEAAiE;QACjE,6DAA6D;QAC7D,mEAAmE;QACnE,gEAAgE;QAChE,gEAAgE;QAChE,kEAAkE;QAClE,mEAAmE;QACnE,+CAA+C;QAC/C,IAAI,UAAU,CAAC,OAAO,IAAI,UAAU,CAAC,cAAc,EAAE,CAAC;YACpD,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gBACrB,8DAA8D;gBAC9D,+DAA+D;gBAC/D,6DAA6D;gBAC7D,6DAA6D;gBAC7D,6DAA6D;gBAC7D,iEAAiE;gBACjE,yDAAyD;gBACzD,IAAI,CAAC;oBACH,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC,CAAA;gBAC/D,CAAC;gBAAC,OAAO,WAAW,EAAE,CAAC;oBACrB,OAAO,CAAC,KAAK,CACX,iGAAiG,EACjG,WAAW,CACZ,CAAA;gBACH,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,IAAI,CACV,2EAA2E,CAC5E,CAAA;YACH,CAAC;QACH,CAAC;aAAM,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;YAC/B,OAAO,CAAC,KAAK,CACX,wBAAwB,EACxB,UAAU,CAAC,WAAW,IAAI,yBAAyB,CACpD,CAAA;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,4EAA4E,CAAC,CAAA;QAC5F,CAAC;QAED,oEAAoE;QACpE,yDAAyD;QACzD,+DAA+D;QAC/D,gEAAgE;QAChE,qEAAqE;QACrE,8DAA8D;QAC9D,8DAA8D;QAC9D,gEAAgE;QAChE,8CAA8C;QAC9C,EAAE;QACF,qEAAqE;QACrE,iEAAiE;QACjE,+DAA+D;QAC/D,mEAAmE;QACnE,kEAAkE;QAClE,+DAA+D;QAC/D,qDAAqD;QACrD,IAAI,cAAsB,CAAA;QAC1B,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;YACxB,cAAc,GAAG,CAAC,CAAA;QACpB,CAAC;aAAM,IAAI,UAAU,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;YACpD,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,eAAe,CAAC,CAAA;YACnD,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC9B,cAAc,GAAG,QAAQ,CAAA;YAC3B,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,IAAI,CACV,4EAA4E,MAAM,CAChF,UAAU,CAAC,eAAe,CAC3B,yCAAyC,CAC3C,CAAA;gBACD,cAAc,GAAG,eAAe,CAAA;YAClC,CAAC;QACH,CAAC;aAAM,CAAC;YACN,mEAAmE;YACnE,gEAAgE;YAChE,kEAAkE;YAClE,gEAAgE;YAChE,gEAAgE;YAChE,2DAA2D;YAC3D,OAAO,CAAC,IAAI,CACV,sGAAsG,CACvG,CAAA;YACD,cAAc,GAAG,eAAe,CAAA;QAClC,CAAC;QACD,cAAc,CAAC,eAAe,GAAG,cAAc,CAAA;QAE/C,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,kBAAkB,CAAC,cAAc,EAAE,UAAU,CAAC,CAAA;QACvD,CAAC;QACD,OAAO,SAAS,CAAA;IAClB,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,WAAW,EAAE,EAAE;QACrB,IAAI,WAAW,YAAY,gCAAgC,EAAE,CAAC;YAC5D,kEAAkE;YAClE,mEAAmE;YACnE,6DAA6D;YAC7D,kEAAkE;YAClE,kEAAkE;YAClE,+DAA+D;YAC/D,4DAA4D;YAC5D,OAAO,CAAC,IAAI,CACV,gHAAgH,EAChH,WAAW,CAAC,OAAO,CACpB,CAAA;YACD,IAAI,aAAa,EAAE,CAAC;gBAClB,MAAM,OAAO,GAAgC;oBAC3C,OAAO,EAAE,SAAS;oBAClB,MAAM,EAAE,WAAW,CAAC,OAAO;iBAC5B,CAAA;gBACD,OAAO,kBAAkB,CAAC,eAAe,EAAE,OAAO,CAAC,CAAA;YACrD,CAAC;YACD,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,sEAAsE;QACtE,oEAAoE;QACpE,iEAAiE;QACjE,oEAAoE;QACpE,qEAAqE;QACrE,gEAAgE;QAChE,EAAE;QACF,iEAAiE;QACjE,8DAA8D;QAC9D,oEAAoE;QACpE,kEAAkE;QAClE,4DAA4D;QAC5D,cAAc,CAAC,eAAe,GAAG,CAAC,CAAA;QAClC,kBAAkB,CAAC,WAAoB,CAAC,CAAA;QACxC,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,WAAW,CAAC,CAAA;IACtD,CAAC,CAAC,CAEL;IAAC,GAA2C,CAAC,GAAG,GAAG,UAElD,GAAG,OAAoC;QAEvC,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,IAAI,GAAG,CAAC,UAAU,GAAG,GAAG,CAAA;QAC/D,wEAAwE;QACxE,sEAAsE;QACtE,yEAAyE;QACzE,kEAAkE;QAClE,wEAAwE;QACxE,uEAAuE;QACvE,4DAA4D;QAC5D,MAAM,WAAW,GAAG,gBAAgB,EAAE,CAAA;QACtC,IAAI,iBAAiB,IAAI,CAAC,SAAS,IAAI,CAAC,WAAW,EAAE,CAAC;YACpD,IAAI,SAAS,IAAI,CAAC,WAAW,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACpD,OAAO,CAAC,IAAI,CACV,gIAAgI,CACjI,CAAA;YACH,CAAC;YACD,OAAO,WAAW,CAAC,GAAG,OAAO,CAAC,CAAA;QAChC,CAAC;QACD,iBAAiB,GAAG,IAAI,CAAA;QACxB,sEAAsE;QACtE,wEAAwE;QACxE,qEAAqE;QACrE,wEAAwE;QACxE,wEAAwE;QACxE,qEAAqE;QACrE,6CAA6C;QAC7C,EAAE;QACF,uEAAuE;QACvE,8DAA8D;QAC9D,mBAAmB,CAAC,YAAY,CAAC,CAAA;QAEjC,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;YACpB,KAAK,aAAa,EAAE,CAAA;YACpB,OAAO,WAAW,CAAC,GAAG,OAAO,CAAC,CAAA;QAChC,CAAC;QAED,aAAa,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YAC3B,wEAAwE;YACxE,uEAAuE;YACvE,uEAAuE;YACvE,sEAAsE;YACtE,EAAE;YACF,sEAAsE;YACtE,wEAAwE;YACxE,iEAAiE;YACjE,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBAChC,MAAM,WAAW,GAAG,IAAI,KAAK,CAC3B,2JAA2J,CAC5J,CAAA;gBACD,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;gBAClC,kBAAkB,CAAC,WAAW,CAAC,CAAA;YACjC,CAAC;YACD,WAAW,CAAC,GAAG,OAAO,CAAC,CAAA;QACzB,CAAC,CAAC,CAAA;QACF,OAAO,GAAG,CAAA;IACZ,CAAoB,CAAA;IAEpB,IAAI,CAAC,IAAI,EAAE,CAAA;AACb,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,QAAkB,EAClB,MAAsB,EACtB,UAAoC,EAAE;IAEtC,MAAM,EACJ,WAAW,GAAG,qBAAqB,EACnC,cAAc,EACd,cAAc,EACd,aAAa,EACb,aAAa,GACd,GAAG,OAAO,CAAA;IAEX,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;QAC/D,+CAA+C;QAC/C,MAAM,aAAa,GAAG,KAAK,IAAmB,EAAE;YAC9C,uCAAuC;YACvC,MAAM,WAAW,GAAG,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;YAC3C,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,qCAAqC;gBACrC,IAAI,EAAE,CAAA;gBACN,OAAM;YACR,CAAC;YAED,MAAM,EACJ,MAAM,EACN,OAAO,GAAG,CAAC,EACX,OAAO,EACP,OAAO,EACP,MAAM,EAAE,cAAc,EACtB,WAAW,EACX,QAAQ,GACT,GAAG,WAAW,CAAA;YAEf,+EAA+E;YAC/E,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,CAAA;YACpE,MAAM,eAAe,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;YAEvE,mFAAmF;YACnF,MAAM,eAAe,GAAG,oBAAoB,CAAC,MAAM,EAAE;gBACnD,QAAQ,EAAE,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,GAAG;gBACpC,OAAO;gBACP,QAAQ,EAAE,GAAG,CAAC,MAAM;gBACpB,OAAO,EAAE,eAAe;gBACxB,WAAW;gBACX,QAAQ;gBACR,MAAM;gBACN,WAAW,EAAE,QAAQ,CAAC,kBAAkB,EAAE;aAC3C,CAAC,CAAA;YAEF,0DAA0D;YAC1D,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,EAAE,WAAW,CAAC,CAAA;YAE5C,MAAM,SAAS,GAAG,gBAAgB,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;YACnD,wEAAwE;YACxE,oEAAoE;YACpE,gEAAgE;YAChE,qEAAqE;YACrE,mEAAmE;YACnE,wEAAwE;YACxE,kCAAkC;YAClC,IAAI,SAAS,CAAC,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5D,MAAM,gBAAgB,CAAC;oBACrB,GAAG;oBACH,GAAG;oBACH,IAAI;oBACJ,QAAQ;oBACR,WAAW;oBACX,eAAe;oBACf,QAAQ,EAAE,SAAS,CAAC,QAAQ;oBAC5B,KAAK,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE;iBACxE,CAAC,CAAA;gBACF,OAAM;YACR,CAAC;YACD,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;gBACtE,IAAI,cAAc,EAAE,CAAC;oBACnB,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;oBAC/B,OAAM;gBACR,CAAC;gBACD,mBAAmB,CACjB,GAAG,EACH,eAAe,EACf,6CAA6C,YAAY,CAAC,iBAAiB,UAAU,CACtF,CAAA;gBACD,OAAM;YACR,CAAC;YAED,8BAA8B;YAC9B,MAAM,eAAe,GAAG,OAAO,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;YAEzF,IAAI,CAAC;gBACH,4BAA4B;gBAC5B,IAAI,cAAc,EAAE,CAAC;oBACnB,MAAM,cAAc,CAAC,GAAG,EAAE,eAAe,CAAC,CAAA;gBAC5C,CAAC;gBAED,qBAAqB;gBACrB,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,iBAAiB,CAAC;oBAChE,eAAe;oBACf,eAAe,EAAE,KAAK;oBACtB,SAAS,EAAE,MAAM,CAAC,eAAe,CAAC;iBACnC,CAAC,CAAA;gBAEF,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;oBAC1B,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,YAAY,CAAC,aAAa,IAAI,6BAA6B,CAAC,CAAA;oBACpF,IAAI,cAAc,EAAE,CAAC;wBACnB,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;wBAC/B,OAAM;oBACR,CAAC;oBACD,mBAAmB,CACjB,GAAG,EACH,eAAe,EACf,YAAY,CAAC,aAAa,IAAI,uCAAuC,CACtE,CAAA;oBACD,OAAM;gBACR,CAAC;gBAED,yDAAyD;gBACzD,IAAI,aAAa,EAAE,CAAC;oBAClB,MAAM,aAAa,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;gBACxC,CAAC;gBAED,gEAAgE;gBAChE,MAAM,cAAc,GAAmB;oBACrC,KAAK;oBACL,eAAe;oBACf,eAAe,EAAE,eAAe;oBAChC,QAAQ,EAAE,IAAI;oBACd,YAAY,EAAE,YAAY,CAAC,YAAY;oBACvC,cAAc,EAAE,YAAY,CAAC,YAAY,EAAE,cAAc,IAAI,YAAY,CAAC,cAAc;iBACzF,CAGA;gBAAC,GAAqD,CAAC,cAAc,GAAG,cAAc,CAAA;gBAEvF,qEAAqE;gBACrE,mEAAmE;gBACnE,kEAAkE;gBAClE,gEAAgE;gBAChE,gEAAgE;gBAChE,MAAM,WAAW,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAuD,CAAA;gBAC3F,IAAI,iBAAiB,GAAG,KAAK,CAAA;gBAE7B,uEAAuE;gBACvE,qEAAqE;gBACrE,oEAAoE;gBACpE,uEAAuE;gBACvE,mEAAmE;gBACnE,qCAAqC;gBACrC,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAA;gBAE9C,MAAM,aAAa,GAAG,GAAkB,EAAE;oBACxC,OAAO,CACL,OAAO,OAAO,KAAK,UAAU;wBAC3B,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;wBACpC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CACrC;yBACE,IAAI,CAAC,CAAC,eAAe,EAAE,EAAE;wBACxB,cAAc,CAAC,eAAe,GAAG,eAAe,CAAA;wBAChD,OAAO,QAAQ,CAAC,WAAW;6BACxB,iBAAiB,CAAC;4BACjB,eAAe;4BACf,eAAe,EAAE,KAAK;4BACtB,SAAS,EAAE,MAAM,CAAC,eAAe,CAAC;4BAClC,cAAc,EAAE,cAAc,CAAC,cAAc;yBAC9C,CAAC;6BACD,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE;4BACnB,4DAA4D;4BAC5D,wDAAwD;4BACxD,2DAA2D;4BAC3D,QAAQ;4BACR,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gCACrB,MAAM,gBAAgB,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CACvE,QAAQ,CACT,CAAA;gCACD,GAAG,CAAC,SAAS,CAAC,YAAY,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAA;4BAChE,CAAC;iCAAM,CAAC;gCACN,OAAO,CAAC,IAAI,CACV,oFAAoF,CACrF,CAAA;4BACH,CAAC;4BACD,IAAI,aAAa,EAAE,CAAC;gCAClB,OAAO,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAC1E,GAAG,EAAE,CAAC,SAAS,CAChB,CAAA;4BACH,CAAC;4BACD,OAAO,SAAS,CAAA;wBAClB,CAAC,CAAC,CAAA;oBACN,CAAC,CAAC;yBACD,KAAK,CAAC,CAAC,WAAW,EAAE,EAAE;wBACrB,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,WAAW,CAAC,CAAA;oBAC1D,CAAC,CAAC,CAAA;gBACN,CAAC,CAEA;gBAAC,GAA2C,CAAC,GAAG,GAAG,UAElD,GAAG,IAAiC;oBAEpC,kEAAkE;oBAClE,kEAAkE;oBAClE,iEAAiE;oBACjE,oEAAoE;oBACpE,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,IAAI,GAAG,CAAC,UAAU,GAAG,GAAG,CAAA;oBAC/D,iEAAiE;oBACjE,iEAAiE;oBACjE,gEAAgE;oBAChE,MAAM,WAAW,GAAG,gBAAgB,EAAE,CAAA;oBACtC,IAAI,iBAAiB,IAAI,CAAC,SAAS,IAAI,CAAC,WAAW,EAAE,CAAC;wBACpD,IAAI,SAAS,IAAI,CAAC,WAAW,IAAI,CAAC,iBAAiB,EAAE,CAAC;4BACpD,OAAO,CAAC,IAAI,CACV,wGAAwG,CACzG,CAAA;wBACH,CAAC;wBACD,OAAO,WAAW,CAAC,GAAG,IAAI,CAAC,CAAA;oBAC7B,CAAC;oBACD,iBAAiB,GAAG,IAAI,CAAA;oBAExB,mEAAmE;oBACnE,kEAAkE;oBAClE,uCAAuC;oBACvC,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;wBACpB,KAAK,aAAa,EAAE,CAAA;wBACpB,OAAO,WAAW,CAAC,GAAG,IAAI,CAAC,CAAA;oBAC7B,CAAC;oBAED,gEAAgE;oBAChE,kEAAkE;oBAClE,aAAa,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;wBAC3B,WAAW,CAAC,GAAG,IAAI,CAAC,CAAA;oBACtB,CAAC,CAAC,CAAA;oBACF,OAAO,GAAG,CAAA;gBACZ,CAAoB,CAAA;gBAEpB,4BAA4B;gBAC5B,IAAI,EAAE,CAAA;YACR,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,cAAc,EAAE,CAAC;oBACnB,cAAc,CAAC,KAAc,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;oBACxC,OAAM;gBACR,CAAC;gBACD,mBAAmB,CACjB,GAAG,EACH,eAAe,EACf,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,6BAA6B,CACvE,CAAA;YACH,CAAC;QACH,CAAC,CAAA;QAED,4CAA4C;QAC5C,aAAa,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC,CAAA;AACH,CAAC;AAED,eAAe,iBAAiB,CAAA","sourcesContent":["/**\n * Express middleware for Nevermined payment protection using the x402 protocol.\n *\n * This middleware provides a simple way to protect Express routes with\n * Nevermined payment verification and settlement.\n *\n * ## x402 HTTP Transport Headers\n *\n * Following the x402 spec (https://github.com/coinbase/x402/blob/main/specs/transports-v2/http.md):\n *\n * - **Client → Server**: `payment-signature` header with base64-encoded token\n * - **Server → Client (402)**: `payment-required` header with base64-encoded PaymentRequired\n * - **Server → Client (success)**: `payment-response` header with settlement receipt\n *\n * @example\n * ```typescript\n * import express from 'express'\n * import { Payments } from '@nevermined-io/payments'\n * import { paymentMiddleware } from '@nevermined-io/payments/express'\n *\n * const app = express()\n * const payments = Payments.getInstance({ nvmApiKey: '...', environment: 'testing' })\n *\n * // Protect routes with payment middleware\n * app.use(paymentMiddleware(payments, {\n * 'POST /ask': { planId: '123', credits: 1 },\n * 'POST /generate': { planId: '123', credits: 5 },\n * }))\n *\n * // Route handlers - no payment logic needed!\n * app.post('/ask', (req, res) => res.json({ answer: '...' }))\n * ```\n *\n * @example Client usage\n * ```typescript\n * const token = await payments.x402.getX402AccessToken(planId)\n *\n * const response = await fetch('/ask', {\n * method: 'POST',\n * headers: {\n * 'Content-Type': 'application/json',\n * 'payment-signature': token.accessToken, // x402 header\n * },\n * body: JSON.stringify({ query: 'Hello!' }),\n * })\n * ```\n */\n\nimport type { Request, Response, NextFunction } from 'express'\n\n/**\n * Express middleware function type.\n * Using explicit signature instead of RequestHandler to avoid type resolution issues\n * when SDK's \\@types/express version differs from consumer's.\n */\nexport type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void\nimport type { Payments } from '../../payments.js'\nimport type { StartAgentRequest, X402SchemeType } from '../../common/types.js'\nimport {\n buildPaymentRequired,\n resolveNetwork,\n resolveScheme,\n type X402PaymentRequired,\n type VerifyPermissionsResult,\n} from '../facilitator-api.js'\nimport {\n MPP_HEADERS,\n extractCredential,\n mppCredentialId,\n mppResource,\n mppVerb,\n resolveMppOption,\n type MppRouteOption,\n} from './mpp-support.js'\nimport { computeBodyDigest, getRawBody } from './raw-body.js'\nimport {\n MppError,\n MppCredentialRejectedError,\n MppSettlementOutcomeUnknownError,\n isRetryableMppCode,\n type MppSettlementOutcomeUnknown,\n} from '../../mpp/errors.js'\nimport { normalizeCredits } from '../../mpp/mpp-api.js'\n\n/**\n * Configuration for a protected route\n */\nexport interface RouteConfig {\n /** The Nevermined plan ID that protects this route */\n planId: string\n /** Number of credits to charge for this route (default: 1) */\n credits?: number | ((req: Request, res: Response) => number | Promise<number>)\n /** Optional agent ID */\n agentId?: string\n /** Network identifier (default: auto-derived from scheme) */\n network?: string\n /** x402 scheme override (auto-detected from plan metadata if omitted) */\n scheme?: X402SchemeType\n /** Human-readable description of the protected resource */\n description?: string\n /** Expected response MIME type (e.g., \"application/json\") */\n mimeType?: string\n /**\n * Accept MPP (Machine Payments Protocol) on this route in addition to x402.\n * Default off. `{ bindBody: true }` additionally binds the challenge to the\n * request body, which requires `captureRawBody` on the body parser.\n */\n mpp?: MppRouteOption\n}\n\n/**\n * Route configuration map: \"METHOD \\/path\" -> RouteConfig\n */\nexport type RouteConfigMap = Record<string, RouteConfig>\n\n/**\n * x402 HTTP Transport header names (v2 spec)\n * @see https://github.com/coinbase/x402/blob/main/specs/transports-v2/http.md\n */\nexport const X402_HEADERS = {\n /** Client sends payment token in this header */\n PAYMENT_SIGNATURE: 'payment-signature',\n /** Server sends PaymentRequired in this header (base64-encoded) */\n PAYMENT_REQUIRED: 'payment-required',\n /** Server sends settlement receipt in this header (base64-encoded) */\n PAYMENT_RESPONSE: 'payment-response',\n} as const\n\n/**\n * Payment context attached to the request after verification.\n * Available as `req.paymentContext` in route handlers.\n */\nexport interface PaymentContext {\n /** The x402 access token */\n token: string\n /** The payment required object */\n paymentRequired: X402PaymentRequired\n /** Number of credits to settle */\n creditsToSettle: number\n /** Whether verification was successful */\n verified: boolean\n /** Agent request context for observability (from verification response) */\n agentRequest?: StartAgentRequest\n /** Agent request ID for observability tracking */\n agentRequestId?: string\n /** MPP framing details, present only when the route was paid over MPP. */\n mpp?: {\n /** The `Authorization: Payment …` value the buyer presented. */\n credential: string\n resource: string\n httpVerb: string\n }\n}\n\n/**\n * Options for the payment middleware\n */\nexport interface PaymentMiddlewareOptions {\n /**\n * Header name(s) to check for the x402 access token.\n * Default: 'payment-signature' (x402 v2 compliant)\n */\n tokenHeader?: string | string[]\n /**\n * Custom error handler for payment failures.\n *\n * Two contracts hang off this one hook, because the protocols differ:\n *\n * - **x402**: the hook TAKES OVER the response. It is called instead of the\n * 402 and nothing is written after it, so a handler that writes nothing\n * leaves the request hanging.\n * - **MPP**: the hook only NOTIFIES. The middleware keeps ownership and\n * still sends the fresh challenge the documented retry loop needs —\n * guarded on `res.headersSent`, so a hook that answers the request itself\n * still wins. It is NOT called for the credential-less opening request of\n * a payment cycle, which is normal traffic on MPP rather than an error;\n * it IS called when an `Authorization` header arrived but carried no\n * `Payment` scheme, which means an intermediary rewrote it.\n *\n * An `async` handler is accepted (the `void` return type does not forbid\n * one) and its rejection is logged rather than left unhandled.\n */\n onPaymentError?: (error: Error, req: Request, res: Response) => void\n /** Hook called before verification */\n onBeforeVerify?: (req: Request, paymentRequired: X402PaymentRequired) => void | Promise<void>\n /**\n * Hook called after successful verification.\n * Use this to access agentRequest for observability configuration.\n */\n onAfterVerify?: (req: Request, verification: VerifyPermissionsResult) => void | Promise<void>\n /** Hook called after successful settlement */\n onAfterSettle?: (req: Request, creditsUsed: number, result: unknown) => void | Promise<void>\n}\n\n/**\n * Default header for token extraction (x402 v2 compliant)\n */\nconst DEFAULT_TOKEN_HEADERS = [X402_HEADERS.PAYMENT_SIGNATURE]\n\n/**\n * Extract the x402 access token from the request headers.\n * Checks multiple headers in priority order.\n */\nfunction extractToken(req: Request, headerNames: string | string[]): string | null {\n const headers = Array.isArray(headerNames) ? headerNames : [headerNames]\n\n for (const headerName of headers) {\n const header = req.headers[headerName.toLowerCase()]\n if (header && typeof header === 'string') {\n return header\n }\n }\n\n return null\n}\n\n/**\n * Match a request to a route config.\n * Returns the config if found, null otherwise.\n */\nfunction matchRoute(req: Request, routes: RouteConfigMap): RouteConfig | null {\n const method = req.method.toUpperCase()\n const path = req.path\n\n // Try exact match first: \"POST /ask\"\n const exactKey = `${method} ${path}`\n if (routes[exactKey]) {\n return routes[exactKey]\n }\n\n // Try pattern matching with path parameters\n for (const [routeKey, config] of Object.entries(routes)) {\n const [routeMethod, routePath] = routeKey.split(' ')\n if (routeMethod !== method) continue\n\n // Simple pattern matching: /users/:id -> /users/123\n const routeParts = routePath.split('/')\n const pathParts = path.split('/')\n\n if (routeParts.length !== pathParts.length) continue\n\n let match = true\n for (let i = 0; i < routeParts.length; i++) {\n if (routeParts[i].startsWith(':')) continue // Parameter - always matches\n if (routeParts[i] !== pathParts[i]) {\n match = false\n break\n }\n }\n\n if (match) return config\n }\n\n return null\n}\n\n/**\n * Create an Express middleware that protects routes with Nevermined payments.\n *\n * The middleware:\n * 1. Checks if the request matches a protected route\n * 2. Extracts the x402 token from headers\n * 3. Verifies the subscriber has sufficient credits\n * 4. Lets the route handler execute\n * 5. Settles (burns) the credits after successful response\n *\n * @param payments - The Payments instance\n * @param routes - Map of routes to protect: \\{ \"METHOD \\/path\": \\{ planId, credits \\} \\}\n * @param options - Optional middleware configuration\n * @returns Express middleware function\n *\n * @example\n * ```typescript\n * app.use(paymentMiddleware(payments, {\n * 'POST /ask': { planId: PLAN_ID, credits: 1 },\n * 'POST /generate': { planId: PLAN_ID, credits: 5 },\n * 'GET /status/:id': { planId: PLAN_ID, credits: 0 }, // Free but requires auth\n * }))\n * ```\n */\n/**\n * Whether a request carries a body, per RFC 9110 §6.4.1 / what `type-is`'s\n * `hasBody()` encodes: a `Transfer-Encoding` header (chunked, streamed —\n * never carries `Content-Length`), or a non-zero `Content-Length`.\n *\n * `bindBody`'s guard used to check `Content-Length` alone, so a chunked\n * request (no `Content-Length` at all) took neither the \"captured\" nor the\n * \"refuse\" branch: the challenge was minted unbound, silently. Both\n * `Content-Length` and `Transfer-Encoding` are the BUYER's choice, so a guard\n * that only recognizes one of the two shapes lets the buyer — not the seller\n * — decide whether the seller's opt-in body binding applies.\n */\nfunction requestHasBody(req: Request): boolean {\n if (req.headers['transfer-encoding'] !== undefined) return true\n const contentLength = req.headers['content-length']\n return contentLength !== undefined && contentLength !== '0'\n}\n\n/**\n * Arms a probe that answers, at `res.end` time, whether this response is\n * actually being DELIVERED to the buyer.\n *\n * Must be called BEFORE the handler runs — it captures a baseline — and the\n * returned predicate read inside the `res.end` wrapper.\n *\n * Both payment paths in this file bill from that wrapper, and both get this\n * wrong in a different direction if they simply look at the status code or at\n * the socket:\n *\n * - **A status code is not a delivery.** An aborted request still carries\n * `200`: after a client hangs up, `res.closed` and `res.destroyed` are true,\n * `res.headersSent` is false, `res.statusCode` is still 200 — and `res.end()`\n * still runs the wrapper. Billing on the status alone charges for a response\n * that went into a dead socket.\n * - **A dead socket is not proof of non-delivery.** On a STREAMED response\n * delivery happens at `res.write()`, while this is read in the `res.end`\n * wrapper: an SSE or token-streaming endpoint — the shape an agent endpoint\n * takes — hands the buyer the whole value and only then calls `end()`. A\n * buyer who reads the payload and hangs up has been served in full.\n *\n * So delivery is: the socket is still alive (we are about to write), OR bytes\n * for THIS response already reached the wire.\n *\n * The DELTA is load-bearing — `socket.bytesWritten` alone is not a usable\n * signal. It is cumulative for the whole connection, so on a keep-alive socket\n * that already carried an earlier response it reads non-zero for a response\n * that has written nothing at all (measured: 249 bytes from the previous\n * request, delta 0 for this one). Keying on the raw value bills undelivered\n * buffered responses on every reused connection.\n *\n * The socket reference is HELD rather than re-read from `res.socket`, which is\n * allowed to be nulled once the response detaches.\n */\nfunction armDeliveryProbe(res: Response): () => boolean {\n const socket = res.socket\n const bytesBeforeHandler = socket?.bytesWritten ?? 0\n\n return () => {\n const bytesFlushed = (socket?.bytesWritten ?? bytesBeforeHandler) - bytesBeforeHandler\n const alreadyDelivered = res.headersSent && bytesFlushed > 0\n const socketStillAlive = !res.destroyed && !res.closed\n return socketStillAlive || alreadyDelivered\n }\n}\n\n/**\n * Helper to send a 402 Payment Required response with proper x402 headers.\n */\nfunction sendPaymentRequired(\n res: Response,\n paymentRequired: X402PaymentRequired,\n message: string,\n): void {\n // Base64 encode the PaymentRequired object for the header (per x402 spec)\n const paymentRequiredBase64 = Buffer.from(JSON.stringify(paymentRequired)).toString('base64')\n\n res.status(402).setHeader(X402_HEADERS.PAYMENT_REQUIRED, paymentRequiredBase64).json({\n error: 'Payment Required',\n message,\n })\n}\n\n/**\n * Challenge ids of the credentials currently between \"verified\" and\n * \"settled\", shared across every `handleMppRequest` call in this process.\n *\n * Keyed on the id, never on the header bytes: the bytes are buyer-malleable\n * (scheme case, inner whitespace, JSON key order) while the backend collapses\n * every variant onto one burn, so a byte-keyed set is a guard a buyer walks\n * around by flipping one character. See {@link mppCredentialId}.\n *\n * `verifyCredential` burns nothing (its own docstring says so explicitly)\n * and `settleCredential` settling the SAME credential twice burns once —\n * that idempotency is what makes settlement safe to retry, and it is\n * exactly what makes concurrent delivery cheap: N concurrent requests\n * presenting the same credential would each pass verify, each get served,\n * and the N settles would collapse to a single burn. This set closes that\n * window WITHIN one Node process: a second request presenting a credential\n * already in this set is refused rather than served.\n *\n * What this does NOT close: multiple processes or horizontally-scaled\n * instances of this middleware, which do not share this in-memory set and\n * would need a shared store (e.g. Redis) this package does not provide.\n * Deployments that scale this middleware horizontally need an external\n * mitigation for the same race across instances.\n */\nconst inFlightMppCredentials = new Set<string>()\n\n/** Digest of zero bytes — what a `bindBody` route binds when the request\n * carries no body, so \"no body\" is a bound state rather than an unbound\n * challenge the buyer can attach anything to later. */\nconst EMPTY_BODY_DIGEST = computeBodyDigest(Buffer.alloc(0))\n\n/**\n * How long a settled credential stays refused. Matches the backend's\n * `MPP_CHALLENGE_TTL_SECONDS` (300): past it the challenge itself is\n * expired, so the credential is refused by the backend anyway and there is\n * nothing left for this set to protect.\n */\nconst SPENT_CREDENTIAL_TTL_MS = 300_000\n\n/**\n * Challenge ids of the credentials whose settlement has already been started,\n * and the instant each stops being worth remembering. Keyed on the id for the\n * same reason as {@link inFlightMppCredentials}.\n *\n * {@link inFlightMppCredentials} closes only the OVERLAP window — it is\n * released when the response closes. Without this second set, a buyer who\n * simply REPEATS the same credential after the first response finished is\n * served again: `verifyCredential` burns nothing so it verifies clean, and\n * the backend's settle idempotency (the challenge id doubles as the burn\n * key) collapses the second settle onto the first burn instead of\n * rejecting it. The result is pay once, be served for the whole challenge\n * TTL — the backend cannot refuse it, because from its side a replayed\n * settle succeeding IS the idempotency contract working as designed.\n *\n * So single-use is the seller edge's job, and this is where it lives.\n *\n * Same process-local caveat as the in-flight set: this does not span\n * processes or horizontally-scaled instances, which need a shared store\n * this package does not provide.\n */\nconst spentMppCredentials = new Map<string, number>()\n\n/**\n * Marks a credential spent, and drops the entries that have aged out.\n *\n * Every entry gets the same TTL, so `Map`'s insertion order IS expiry\n * order: the sweep can stop at the first entry still alive instead of\n * walking the whole map on every settlement.\n */\nfunction markCredentialSpent(credentialId: string): void {\n const now = Date.now()\n for (const [spent, expiresAt] of spentMppCredentials) {\n if (expiresAt > now) break\n spentMppCredentials.delete(spent)\n }\n spentMppCredentials.set(credentialId, now + SPENT_CREDENTIAL_TTL_MS)\n}\n\n/** Whether this credential has already bought a response. */\nfunction isCredentialSpent(credentialId: string): boolean {\n const expiresAt = spentMppCredentials.get(credentialId)\n if (expiresAt === undefined) return false\n if (expiresAt <= Date.now()) {\n spentMppCredentials.delete(credentialId)\n return false\n }\n return true\n}\n\n/**\n * The MPP request path.\n *\n * Kept whole and separate from the x402 path so that with `mpp` unset nothing\n * here runs and the x402 behaviour is unchanged.\n */\nasync function handleMppRequest(args: {\n req: Request\n res: Response\n next: NextFunction\n payments: Payments\n routeConfig: RouteConfig\n paymentRequired: X402PaymentRequired\n bindBody: boolean\n hooks: {\n onPaymentError?: PaymentMiddlewareOptions['onPaymentError']\n onBeforeVerify?: PaymentMiddlewareOptions['onBeforeVerify']\n onAfterVerify?: PaymentMiddlewareOptions['onAfterVerify']\n onAfterSettle?: PaymentMiddlewareOptions['onAfterSettle']\n }\n}): Promise<void> {\n const { req, res, payments, routeConfig, paymentRequired } = args\n const { planId, credits = 1, agentId, description } = routeConfig\n const resource = mppResource(req)\n const httpVerb = mppVerb(req)\n const { onPaymentError, onBeforeVerify, onAfterVerify, onAfterSettle } = args.hooks\n\n /**\n * One policy for `onPaymentError` on MPP routes: it NOTIFIES, and this\n * middleware keeps ownership of the response.\n *\n * The x402 branch lets the hook take over the response and returns. MPP\n * cannot copy that: the protocol only lets a buyer make progress if the\n * 402 carries a fresh `WWW-Authenticate` challenge, so a seller who wired\n * the hook for observability would silently strip the one thing the\n * documented retry loop needs — and, in the other direction, an unpaid\n * request sent a challenge but never notified the hook at all, so adding\n * `mpp: true` to a working route quietly stopped those events.\n *\n * The hook may still respond itself; every write on this path is guarded\n * on `res.headersSent`, so it wins if it does. A throwing hook is logged\n * rather than propagated — it must not turn into a buyer-visible 500 on\n * the path whose whole job is to hand back a challenge.\n *\n * The hook is INVOKED SYNCHRONOUSLY and only its result is deferred. The\n * sibling `runAfterSettleHook` wraps its call in `Promise.resolve().then()`,\n * which is right there (nothing downstream depends on when the hook runs)\n * and wrong here: the callers below write the response on the very next\n * synchronous line, so deferring the call would make every `res.headersSent`\n * guard read `false` and the middleware would win the race it is supposed\n * to lose. Calling directly inside `try/catch` keeps that ordering and\n * catches a synchronous throw; the `then` on whatever the hook returns\n * covers the async half — the declared return type is `void`, which happily\n * accepts an `async` function whose rejection would otherwise be unhandled\n * and process-fatal under Node's default.\n */\n const notifyPaymentError = (error: Error): void => {\n if (!onPaymentError) return\n const logHookFailure = (hookError: unknown): void => {\n console.error('MPP onPaymentError hook failed:', hookError)\n }\n try {\n const result = onPaymentError(error, req, res) as unknown\n if (typeof (result as PromiseLike<void> | undefined)?.then === 'function') {\n void Promise.resolve(result as PromiseLike<void>).then(() => undefined, logHookFailure)\n }\n } catch (hookError) {\n logHookFailure(hookError)\n }\n }\n\n // Credits are sealed into the challenge, so a credits function is evaluated\n // exactly once — here. MPP has no equivalent of the x402 re-evaluation at\n // settle time; the backend settles the amount the challenge carries.\n //\n // Wrapped for the same reason as the three sites below: `credits` is\n // caller-supplied and a throw in it (a DB lookup, a rate-table fetch) would\n // otherwise reject out of handleMppRequest, which is awaited outside any\n // try/catch, land in Express's default error handler, and hand the buyer a\n // 500 with a stack trace and no challenge — while a configured\n // onPaymentError never fired.\n let creditsToCharge: number\n try {\n creditsToCharge = typeof credits === 'function' ? await credits(req, res) : credits\n } catch (creditsError) {\n notifyPaymentError(creditsError as Error)\n console.error('MPP credits evaluation failed:', creditsError)\n if (!res.headersSent) {\n res.status(500).json({\n error: 'Internal Server Error',\n message: 'Unable to determine the price of this resource.',\n })\n }\n return\n }\n\n // A bound challenge must be minted, verified and settled against the SAME\n // digest. A request with no body binds nothing, which is correct: there\n // are no bytes to bind.\n //\n // The guard is intentionally FAIL CLOSED: `requestHasBody(req)` recognizes\n // both a non-zero Content-Length AND a bare Transfer-Encoding (chunked —\n // the shape a Content-Length-only check let straight through, unbound and\n // silent). Whenever the request has a body and the raw bytes were never\n // captured, we refuse rather than mint an unbound challenge — the buyer\n // must never get to decide whether the seller's bindBody applies.\n let bodyDigest: string | undefined\n let bindBodyRefusal: { status: number; message: string; loud: boolean } | undefined\n if (args.bindBody) {\n const raw = getRawBody(req)\n if (raw === undefined && requestHasBody(req)) {\n const contentType = req.headers['content-type'] ?? ''\n if (contentType.toLowerCase().startsWith('application/json')) {\n // The documented setup is express.json({ verify: captureRawBody }).\n // A JSON body with no captured bytes means that hook was never\n // wired — a seller configuration mistake, not the buyer's fault.\n bindBodyRefusal = {\n status: 500,\n loud: true,\n message:\n 'paymentMiddleware: mpp.bindBody requires the raw request body. ' +\n \"Mount the parser as express.json({ verify: captureRawBody }) — import { captureRawBody } from '@nevermined-io/payments/express'.\",\n }\n } else {\n // captureRawBody only runs for content-types the mounted parser\n // matches. A buyer sending a content-type this route's parser was\n // never wired for is a client-side mismatch, not a server bug.\n bindBodyRefusal = {\n status: 400,\n loud: false,\n message: `paymentMiddleware: this route requires a captured request body, and Content-Type '${contentType || '(none)'}' is not supported here.`,\n }\n }\n } else if (raw) {\n bodyDigest = computeBodyDigest(raw)\n } else {\n // No captured bytes AND no body on the wire. Leaving `bodyDigest`\n // undefined here would mint an UNBOUND challenge on a route the seller\n // marked `bindBody: true`, and the backend's `assertBodyDigestMatches`\n // returns early when the challenge carries no digest — so the buyer\n // could mint with an empty request and then attach any body they liked\n // to the paid retry. That is the same \"the buyer decides whether\n // bindBody applies\" hole the guard above closes for chunked bodies,\n // one branch over.\n //\n // The digest of zero bytes is a well-defined value, so bind it: a\n // bodyless retry reproduces it and passes, and a retry that grew a\n // body computes a different digest and is refused by the backend\n // (BCK.MPP.0005). `captureRawBody` skipping zero-length buffers is\n // what makes \"no body\" and \"empty body\" indistinguishable here, and\n // both bind to the same digest, so the two stay equivalent.\n bodyDigest = EMPTY_BODY_DIGEST\n }\n }\n if (bindBodyRefusal) {\n notifyPaymentError(new Error(bindBodyRefusal.message))\n // Loud in the server-side log either way — a seller can act on both\n // causes — but never as an uncaught throw: that would skip a configured\n // onPaymentError and fall through to Express's default error handler,\n // leaking a stack trace to the client on every request the guard\n // rejects, exactly the failure sendChallenge below guards against too.\n if (bindBodyRefusal.loud) {\n console.error('MPP bindBody misconfiguration:', bindBodyRefusal.message)\n } else {\n console.warn('MPP bindBody could not capture the request body:', bindBodyRefusal.message)\n }\n if (!res.headersSent) {\n res.status(bindBodyRefusal.status).json({\n error: bindBodyRefusal.status >= 500 ? 'Internal Server Error' : 'Bad Request',\n message: bindBodyRefusal.message,\n })\n }\n return\n }\n\n const sendChallenge = async (message: string, code?: string): Promise<void> => {\n // issueChallenge itself can fail (e.g. MPP is turned off on this\n // environment: BCK.MPP.0002 -> MppNotConfiguredError). This call site is\n // reached from three places — no credential, rejected credential, and the\n // verifyCredential catch block — so a failure here must never propagate\n // out of handleMppRequest: unhandled it would skip a configured\n // onPaymentError and fall through to Express's default error handler,\n // which leaks a stack trace to the client on every unauthenticated\n // request to the route.\n let challenge: string\n try {\n const issued = await payments.mpp.issueChallenge({\n planId,\n // Normalized here (not left to MppAPI.issueChallenge) so the exact\n // wire shape is visible to a mocked payments.mpp in tests, matching\n // the decimal-string contract of IssueMppChallengeParams.credits —\n // and so a NaN/Infinity/non-integer credits function result is\n // rejected before a mocked payments.mpp in a test could hide the\n // defect by never validating it itself.\n credits: normalizeCredits(creditsToCharge),\n ...(agentId && { agentId }),\n resource,\n httpVerb,\n ...(bodyDigest && { digest: bodyDigest }),\n ...(description && { description }),\n })\n challenge = issued.challenge\n } catch (challengeError) {\n notifyPaymentError(challengeError as Error)\n console.error('MPP challenge issuance failed:', challengeError)\n if (!res.headersSent) {\n res.status(500).json({\n error: 'Internal Server Error',\n message: 'Unable to issue an MPP payment challenge. Please try again later.',\n })\n }\n return\n }\n\n // An onPaymentError that answered the request itself owns it — see\n // notifyPaymentError. Writing the 402 on top would throw\n // ERR_HTTP_HEADERS_SENT out of an async handler.\n if (res.headersSent) return\n\n const paymentRequiredBase64 = Buffer.from(JSON.stringify(paymentRequired)).toString('base64')\n res\n .status(402)\n .setHeader(MPP_HEADERS.CHALLENGE, challenge)\n // Advertise x402 on the same 402 so an x402 buyer is unaffected.\n .setHeader(X402_HEADERS.PAYMENT_REQUIRED, paymentRequiredBase64)\n // The backend's own BCK.MPP.* code rides along when we have one, so a\n // buyer can tell \"this credential was refused\" (terminal — paying\n // again with a fresh one is pointless) from \"here is a fresh challenge,\n // pay it\" (retryable). This echoes a distinction the backend itself\n // already publishes on the wire; it adds no new detail and does not\n // reopen the \"one rejection code\" forgery-oracle discipline.\n //\n // `retryable` is carried alongside the code as an explicit wire signal\n // rather than leaving every buyer to hardcode which BCK.MPP.* codes are\n // exceptions to \"code present means terminal\": BCK.MPP.0004 (expired)\n // and BCK.MPP.0005 (body digest mismatch) are both retryable against\n // the fresh challenge on this same 402, while BCK.MPP.0003 (refused)\n // is not.\n .json({\n error: 'Payment Required',\n message,\n ...(code && { code, retryable: isRetryableMppCode(code) }),\n })\n }\n\n const credential = extractCredential(req)\n if (!credential) {\n // An ABSENT Authorization header is deliberately NOT routed through\n // onPaymentError, unlike the x402 branch which does notify for a missing\n // token.\n //\n // The two protocols make \"unpaid\" mean different things. An x402 access\n // token is reusable, so a request arriving without one is an anomaly —\n // a misconfigured client — and worth an error event. In MPP the mint /\n // redeem handshake makes the FIRST request of every payment cycle\n // credential-less by design: notifying there would fire onPaymentError on\n // every successful payment, drowning the rejections, expiries and\n // configuration failures the hook exists to surface.\n //\n // A PRESENT Authorization header that yielded no Payment scheme is the\n // opposite case, and extractCredential returns null for both. It means\n // something between the buyer and here rewrote the header — a gateway\n // injecting its own Bearer, a reverse proxy with its own auth — which\n // puts the buyer in a silent infinite loop: mint, pay, present, header\n // rewritten, fresh challenge, repeat. Every iteration costs the seller a\n // real issueChallenge round-trip, the buyer is never served, and the\n // failure is invisible on the only side that can fix it. So notify on\n // exactly that shape and never on the healthy opening request.\n //\n // Every genuine failure below does notify, and the challenge is sent\n // either way — see notifyPaymentError.\n if (req.headers[MPP_HEADERS.CREDENTIAL]) {\n notifyPaymentError(\n new Error(\n 'An Authorization header was present but carried no MPP Payment scheme. ' +\n 'An intermediary may be rewriting it; the buyer cannot make progress.',\n ),\n )\n }\n await sendChallenge('Payment required. Present the challenge credential in Authorization.')\n return\n }\n\n // The identity every guard below keys on. NOT the header bytes: those are\n // buyer-malleable — the scheme match is case-insensitive and returns its\n // slice verbatim, and the body is base64url of JSON the buyer assembles —\n // while the backend collapses every variant onto ONE burn, because its own\n // idempotency key is this same decoded id. Keyed on the bytes, single-use\n // and the in-flight guard both cost what a guard costs and guarantee\n // nothing: flip one byte of case and buy the response again.\n //\n // A credential that carries no usable id is refused here rather than\n // forwarded. The backend rejects it anyway, so this only saves the\n // round-trip — and the alternative, falling back to the raw string as a\n // key, is precisely the guard-shaped hole this replaces.\n const credentialId = mppCredentialId(credential)\n if (!credentialId) {\n const undecodable = new MppCredentialRejectedError('Credential rejected')\n notifyPaymentError(undecodable)\n console.error('MPP credential carries no decodable challenge id; refused at the edge')\n await sendChallenge(undecodable.message, undecodable.code)\n return\n }\n\n // Already bought a response. Checked before verifyCredential so a replay\n // costs no backend round-trip, and answered with a FRESH challenge rather\n // than a bare error so the buyer can make progress by paying again — the\n // same shape every other MPP rejection uses. The code is BCK.MPP.0003\n // (credential rejected, non-retryable): a replay is a client-side bug,\n // and a buyer that blindly retried it would loop.\n //\n // This is an optimisation, not the guard: three awaits follow before the\n // claim is taken, so the authoritative check is the one immediately before\n // the claim below.\n if (isCredentialSpent(credentialId)) {\n await sendChallenge(\n 'Credential already used. Each credential buys exactly one response; pay the new challenge.',\n 'BCK.MPP.0003',\n )\n return\n }\n\n let verification: VerifyPermissionsResult\n try {\n // Hook: before verification, mirroring the x402 path (middleware.ts's\n // x402 branch calls it in the same position). Dropped entirely here\n // before this fix: adding mpp: true to a working route silently\n // disabled a documented PaymentMiddlewareOptions hook.\n if (onBeforeVerify) {\n await onBeforeVerify(req, paymentRequired)\n }\n\n verification = await payments.mpp.verifyCredential({\n credential,\n resource,\n httpVerb,\n ...(bodyDigest && { bodyDigest }),\n })\n } catch (error) {\n notifyPaymentError(error as Error)\n // Every MPP rejection — expired, replayed, refused — is answered with a\n // fresh challenge, so a buyer can always make progress by paying again.\n // The backend's BCK.MPP.* code (when the failure carries one) rides\n // along so the buyer can tell which kind of rejection this was; it is\n // confined to BCK.MPP.* so an unrelated code (network_error, http_500,\n // a backend code from a different namespace) is never forwarded as if\n // it were one of ours.\n const code =\n error instanceof MppError && error.code?.startsWith('BCK.MPP.') ? error.code : undefined\n // Log the full detail — MppAPI.post folds a backend `hint` onto the\n // error's message — for the seller's own diagnostics. The buyer only\n // ever sees a fixed generic message plus the coarse code: forwarding\n // error.message verbatim would re-widen the anti-oracle discipline\n // src/mpp/errors.ts documents, handing a hint the backend deliberately\n // withheld straight back to the caller most likely to probe for it.\n console.error('MPP credential verification failed:', error)\n await sendChallenge('Credential rejected', code)\n return\n }\n\n if (!verification.isValid) {\n // This IS a credential rejection, even though VerifyPermissionsResult\n // carries no code of its own. The wire contract is positional, not\n // incidental: any 402 answering a request that presented a credential\n // must carry a code, or a buyer cannot tell this fresh-but-otherwise-\n // unmarked challenge apart from \"you had not paid yet\" and mints a\n // second credential for a rejection that already proved terminal.\n // 'BCK.MPP.0003' is the backend's own generic rejection code — nothing\n // new is invented or published beyond it.\n const rejectionError = new MppCredentialRejectedError(\n verification.invalidReason || 'Credential rejected',\n )\n // A seller who wires onPaymentError is notified of every rejected\n // payment, not just the ones where verifyCredential itself threw — and\n // the hook, being server-side, gets the full invalidReason.\n notifyPaymentError(rejectionError)\n // The BUYER gets the same fixed string the sibling rejection path sends,\n // for the same anti-oracle reason spelled out nineteen lines above:\n // forwarding invalidReason verbatim would hand a hint straight back to\n // the caller most likely to probe for it. Latent today — the backend\n // throws BCK.MPP.0003 with suppressReason rather than returning a soft\n // isValid:false — which is exactly why it must not sit here waiting to\n // un-sanitize itself the moment any backend softens that response.\n console.error(\n 'MPP credential rejected:',\n verification.invalidReason ?? 'no invalidReason provided',\n )\n await sendChallenge('Credential rejected', rejectionError.code)\n return\n }\n\n // onAfterVerify runs OUTSIDE the verify try/catch above: a bug in the\n // seller's OWN hook, after the credential has already been proven valid,\n // must never be misreported as a payment rejection (no re-challenge) and\n // must never leak the hook's own exception text into the buyer-visible\n // 402 body the catch above would otherwise build from it.\n if (onAfterVerify) {\n try {\n await onAfterVerify(req, verification)\n } catch (hookError) {\n notifyPaymentError(hookError as Error)\n console.error('MPP onAfterVerify hook failed:', hookError)\n if (!res.headersSent) {\n res.status(500).json({\n error: 'Internal Server Error',\n message: 'A server-side hook failed after payment verification.',\n })\n }\n return\n }\n }\n\n // The credential is now verified but not yet settled — exactly the window\n // where an idempotent settle makes concurrent delivery cheap: a second\n // request presenting this SAME credential right now would also pass\n // verify (verifyCredential burns nothing) and also get served, while the\n // two settles collapse into a single burn. Refuse it instead.\n //\n // CHECK AND CLAIM ARE ONE STEP, with no `await` between them — that is the\n // whole point of re-checking `isCredentialSpent` here rather than trusting\n // the early check. Three awaits separate the two (onBeforeVerify,\n // verifyCredential, onAfterVerify), and in that gap a concurrent request A\n // holding the same credential can complete, mark itself spent AND release\n // its in-flight claim on 'close'. B, which passed the early check while A\n // was still unspent, would then find both sets empty and be served: two\n // responses, one burn, from nothing more exotic than ordinary latency skew.\n // The window is widest on the streaming branch, where 'close' fires while\n // the settle is still detached.\n if (isCredentialSpent(credentialId)) {\n // A 402 with a fresh challenge, not the 409 below: this credential is\n // finished, so a conflict code would tell the buyer to retry the one\n // thing that can never work again. BCK.MPP.0003 marks it terminal for\n // THIS credential while the challenge on the same response gives them a\n // way forward.\n await sendChallenge(\n 'Credential already used. Each credential buys exactly one response; pay the new challenge.',\n 'BCK.MPP.0003',\n )\n return\n }\n if (inFlightMppCredentials.has(credentialId)) {\n const error = new MppCredentialRejectedError(\n 'This credential is already being processed by a concurrent request.',\n )\n notifyPaymentError(error)\n if (!res.headersSent) {\n res.status(409).json({ error: 'Conflict', message: error.message })\n }\n return\n }\n inFlightMppCredentials.add(credentialId)\n // Released on 'close', not from inside the settlement promise: 'close'\n // fires whether this request ends in a successful settle, a failed one, a\n // non-2xx handler response that never reaches settlement at all, or the\n // connection simply dropping — the one event that reliably covers every\n // exit from here, so the credential can never be left claimed forever.\n res.on('close', () => {\n inFlightMppCredentials.delete(credentialId)\n })\n // This listener is registered AFTER three awaits (onBeforeVerify,\n // verifyCredential, onAfterVerify). A buyer who disconnected while those\n // were in flight has already made `res` emit 'close' — Node emits it once —\n // so the listener above would never fire and the credential would stay\n // claimed for the lifetime of the process. That is worse than a leak: the\n // credential was never settled, so the buyer still owns an unspent claim,\n // and every legitimate retry with it would 409 forever.\n if (res.closed) {\n inFlightMppCredentials.delete(credentialId)\n }\n\n const paymentContext: PaymentContext = {\n token: credential,\n paymentRequired,\n creditsToSettle: creditsToCharge,\n verified: true,\n agentRequest: verification.agentRequest,\n agentRequestId: verification.agentRequest?.agentRequestId || verification.agentRequestId,\n mpp: { credential, resource, httpVerb },\n }\n ;(req as Request & { paymentContext?: PaymentContext }).paymentContext = paymentContext\n\n const originalEnd = res.end.bind(res) as (...a: Parameters<Response['end']>) => Response\n let settlementStarted = false\n\n // Armed HERE, before next() hands control to the handler — it captures a\n // byte baseline, so it has to run first. See armDeliveryProbe.\n const hasBeenDelivered = armDeliveryProbe(res)\n\n // A seller's own onAfterSettle can throw, and where that lands differed by\n // branch: on the success path it rejected into the settlement .catch() and\n // was logged as \"MPP settlement failed\" (blaming the backend for a hook\n // bug), and on the timeout branch — which runs INSIDE that .catch(), with\n // nothing downstream — it escaped as an unhandled rejection, process-fatal\n // under Node's default. Both now log and continue: the settle has already\n // happened either way, and the worst moment to take the process down is\n // the branch where credits may have been burned and the seller most needs\n // the record.\n // `Promise.resolve().then(() => hook(...))` rather than\n // `Promise.resolve(hook(...))`: the latter CALLS the hook before any\n // promise wraps it, so a SYNCHRONOUS throw — the ordinary shape of a hook\n // bug — escapes past the rejection handler below entirely.\n const runAfterSettleHook = (credits: number, settlement: unknown): Promise<void> =>\n Promise.resolve()\n .then(() => onAfterSettle!(req, credits, settlement))\n .then(\n () => undefined,\n (hookError) => {\n console.error('MPP onAfterSettle hook failed:', hookError)\n },\n )\n\n const runSettlement = (): Promise<void> =>\n payments.mpp\n .settleCredential({\n credential,\n resource,\n httpVerb,\n ...(bodyDigest && { bodyDigest }),\n })\n .then((settlement) => {\n // Never call res.setHeader with an undefined value: MppSettleResult's\n // type says success: true implies a string paymentReceipt, but a\n // real backend response isn't statically checked, so this is\n // defensive too. Distinguishing the three cases matters: a genuine\n // settlement failure, a successful settle that (contrary to the\n // type) carried no receipt, and the normal success-with-receipt\n // path each get their own accurate log — collapsing them all into\n // \"MPP settlement failed\" pointed an on-call engineer at the wrong\n // system when the burn had actually succeeded.\n if (settlement.success && settlement.paymentReceipt) {\n if (!res.headersSent) {\n // Guarded because this is inside the SUCCESS branch of a call\n // that burns: res.setHeader throws ERR_INVALID_CHAR on a value\n // carrying CR/LF or a non-latin1 character, and an unguarded\n // throw here would fall into the terminal .catch() and log a\n // definitively-burned settlement as \"MPP settlement failed\",\n // skipping onAfterSettle for credits that are gone. A receipt we\n // cannot attach is a lost header; it is not a lost burn.\n try {\n res.setHeader(MPP_HEADERS.RECEIPT, settlement.paymentReceipt)\n } catch (headerError) {\n console.error(\n '[paymentMiddleware] MPP settlement succeeded but its receipt could not be attached as a header:',\n headerError,\n )\n }\n } else {\n console.warn(\n '[paymentMiddleware] headers already flushed; Payment-Receipt not attached',\n )\n }\n } else if (!settlement.success) {\n console.error(\n 'MPP settlement failed:',\n settlement.errorReason ?? 'no errorReason provided',\n )\n } else {\n console.warn('[paymentMiddleware] MPP settlement succeeded but carried no paymentReceipt')\n }\n\n // The amount actually burned is whatever the challenge sealed on an\n // EARLIER request, reported back on the settlement — not\n // creditsToCharge, which is recomputed on THIS request and can\n // diverge from the minting request when `credits` is a function\n // (evaluated once per request, so at least twice per payment cycle).\n // A FAILED settle burned nothing, so it reports 0 rather than\n // falling back to creditsToCharge — a seller writing usage or\n // revenue records off this argument would otherwise over-report\n // every failed settlement at the full charge.\n //\n // `creditsRedeemed` is a decimal string precisely because the amount\n // can exceed what a JS number represents (see normalizeCredits).\n // `Number()` narrows it for PaymentContext.creditsToSettle and\n // onAfterSettle, which are typed `number` on the shared x402 path;\n // an unparseable value would otherwise hand `NaN` straight to the\n // seller's accounting hook with no signal, so it is treated as\n // \"the backend did not report an amount\" and logged.\n let creditsSettled: number\n if (!settlement.success) {\n creditsSettled = 0\n } else if (settlement.creditsRedeemed !== undefined) {\n const redeemed = Number(settlement.creditsRedeemed)\n if (Number.isFinite(redeemed)) {\n creditsSettled = redeemed\n } else {\n console.warn(\n `[paymentMiddleware] MPP settlement reported an unusable creditsRedeemed (${String(\n settlement.creditsRedeemed,\n )}); reporting the charged amount instead`,\n )\n creditsSettled = creditsToCharge\n }\n } else {\n // A successful settle that reported no amount at all. Falling back\n // to creditsToCharge is the best available answer — but it is a\n // GUESS, recomputed on this request and free to diverge from what\n // the challenge sealed whenever `credits` is a function, so the\n // seller's revenue record must not be told it is a measurement.\n // The sibling branch above warns for the same uncertainty.\n console.warn(\n '[paymentMiddleware] MPP settlement reported no creditsRedeemed; reporting the charged amount instead',\n )\n creditsSettled = creditsToCharge\n }\n paymentContext.creditsToSettle = creditsSettled\n\n if (onAfterSettle) {\n return runAfterSettleHook(creditsSettled, settlement)\n }\n return undefined\n })\n .catch((settleError) => {\n if (settleError instanceof MppSettlementOutcomeUnknownError) {\n // Settlement is the one MPP call that burns: this rejection means\n // OUR OWN deadline fired, not that the backend rejected anything —\n // the credits may already be burned even though this request\n // never heard back. Logging it through the same line as a genuine\n // failure would tell an on-call engineer nothing was charged when\n // it may well have been, and skipping onAfterSettle here would\n // make a real burn vanish from the seller's own accounting.\n console.warn(\n 'MPP settlement outcome unknown (request timed out before the backend responded; credits may have been burned):',\n settleError.message,\n )\n if (onAfterSettle) {\n const outcome: MppSettlementOutcomeUnknown = {\n outcome: 'unknown',\n reason: settleError.message,\n }\n return runAfterSettleHook(creditsToCharge, outcome)\n }\n return undefined\n }\n // A DEFINITE failure: the resource has been served and the seller has\n // not been paid — the most expensive thing that can happen to them.\n // stdout was previously the only record of it. onPaymentError is\n // documented as the handler for payment failures, so notify through\n // it; this is the one MPP failure site that runs after the response,\n // so the hook cannot answer the request and is purely a signal.\n //\n // creditsToSettle is reset with it. It still held the optimistic\n // creditsToCharge from before the settle, so anything reading\n // paymentContext from res.on('finish') was told the full amount had\n // settled when nothing had — while the resolved-but-failed branch\n // above is scrupulous about reporting 0. The two now agree.\n paymentContext.creditsToSettle = 0\n notifyPaymentError(settleError as Error)\n console.error('MPP settlement failed:', settleError)\n })\n\n ;(res as unknown as { end: Response['end'] }).end = function (\n this: Response,\n ...endArgs: Parameters<Response['end']>\n ): Response {\n const isSuccess = res.statusCode >= 200 && res.statusCode < 300\n // MPP's stake in this: MppAPI exposes no void, refund or reversal, so a\n // burn for a response nobody received is permanent — and in the other\n // direction a streamed payload the buyer already read must still be paid\n // for, or the credential stays spendable for the rest of the 300s\n // challenge TTL. markdown/mpp-integration.md states the invariant as \"a\n // credential is only marked spent when a 2xx is actually delivered for\n // it\"; this is where that becomes true, in both directions.\n const isDelivered = hasBeenDelivered()\n if (settlementStarted || !isSuccess || !isDelivered) {\n if (isSuccess && !isDelivered && !settlementStarted) {\n console.warn(\n '[paymentMiddleware] MPP: the buyer disconnected before any of the response reached them; not settling, credential left unspent',\n )\n }\n return originalEnd(...endArgs)\n }\n settlementStarted = true\n // Spent at the moment a response is actually being delivered for this\n // credential, not when its settle resolves. Marking here rather than in\n // the settlement callback is what covers the streaming branch below,\n // where settlement is fired detached and the response closes (releasing\n // the in-flight claim) while the settle is still outstanding — a second\n // request arriving in that window would otherwise be served too, and\n // both settles would collapse onto one burn.\n //\n // Only reached on a 2xx: a non-2xx never settles, so its credential is\n // still unspent and the buyer can legitimately retry with it.\n markCredentialSpent(credentialId)\n\n if (res.headersSent) {\n void runSettlement()\n return originalEnd(...endArgs)\n }\n\n runSettlement().finally(() => {\n // The buffered branch holds the response until the settle resolves, and\n // REQUEST_TIMEOUT_MS is 30s — so a buyer whose own deadline is shorter\n // can hang up while the burn is in flight. The gate above cannot catch\n // that one: at the moment we decided to settle, the socket was alive.\n //\n // There is no reversal to call, so the honest handling is to make the\n // loss VISIBLE to the side that can compensate for it rather than let a\n // burn-without-delivery look like an ordinary completed request.\n if (res.destroyed || res.closed) {\n const undelivered = new Error(\n 'MPP: the buyer disconnected while settlement was in flight. The credits may have been burned for a response they never received, and MPP has no reversal.',\n )\n console.error(undelivered.message)\n notifyPaymentError(undelivered)\n }\n originalEnd(...endArgs)\n })\n return res\n } as Response['end']\n\n args.next()\n}\n\nexport function paymentMiddleware(\n payments: Payments,\n routes: RouteConfigMap,\n options: PaymentMiddlewareOptions = {},\n): ExpressMiddleware {\n const {\n tokenHeader = DEFAULT_TOKEN_HEADERS,\n onPaymentError,\n onBeforeVerify,\n onAfterVerify,\n onAfterSettle,\n } = options\n\n return (req: Request, res: Response, next: NextFunction): void => {\n // Wrap async logic to handle promises properly\n const handleRequest = async (): Promise<void> => {\n // Check if this route requires payment\n const routeConfig = matchRoute(req, routes)\n if (!routeConfig) {\n // Route not protected - pass through\n next()\n return\n }\n\n const {\n planId,\n credits = 1,\n agentId,\n network,\n scheme: explicitScheme,\n description,\n mimeType,\n } = routeConfig\n\n // Resolve scheme and network from plan metadata (cached) or explicit overrides\n const scheme = await resolveScheme(payments, planId, explicitScheme)\n const resolvedNetwork = await resolveNetwork(payments, planId, network)\n\n // Build payment required object (needed for both error responses and verification)\n const paymentRequired = buildPaymentRequired(planId, {\n endpoint: req.originalUrl || req.url,\n agentId,\n httpVerb: req.method,\n network: resolvedNetwork,\n description,\n mimeType,\n scheme,\n environment: payments.getEnvironmentName(),\n })\n\n // Extract token from headers (x402 v2: payment-signature)\n const token = extractToken(req, tokenHeader)\n\n const mppOption = resolveMppOption(routeConfig.mpp)\n // The 402 an MPP-enabled route sends advertises both a WWW-Authenticate\n // challenge and the x402 payment-required header, so both protocols\n // must be payable, not just the one that minted the 402. An MPP\n // credential always takes the MPP path; with no MPP credential but a\n // valid x402 token present, fall through to the existing x402 flow\n // below unchanged. With neither present, handleMppRequest still runs so\n // the 402 keeps advertising both.\n if (mppOption.enabled && (extractCredential(req) || !token)) {\n await handleMppRequest({\n req,\n res,\n next,\n payments,\n routeConfig,\n paymentRequired,\n bindBody: mppOption.bindBody,\n hooks: { onPaymentError, onBeforeVerify, onAfterVerify, onAfterSettle },\n })\n return\n }\n if (!token) {\n const error = new Error('Payment required: missing x402 access token')\n if (onPaymentError) {\n onPaymentError(error, req, res)\n return\n }\n sendPaymentRequired(\n res,\n paymentRequired,\n `Missing x402 payment token. Send token in ${X402_HEADERS.PAYMENT_SIGNATURE} header.`,\n )\n return\n }\n\n // Calculate credits to verify\n const creditsToVerify = typeof credits === 'function' ? await credits(req, res) : credits\n\n try {\n // Hook: before verification\n if (onBeforeVerify) {\n await onBeforeVerify(req, paymentRequired)\n }\n\n // Verify permissions\n const verification = await payments.facilitator.verifyPermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: BigInt(creditsToVerify),\n })\n\n if (!verification.isValid) {\n const error = new Error(verification.invalidReason || 'Payment verification failed')\n if (onPaymentError) {\n onPaymentError(error, req, res)\n return\n }\n sendPaymentRequired(\n res,\n paymentRequired,\n verification.invalidReason || 'Insufficient credits or invalid token',\n )\n return\n }\n\n // Hook: after verification (use for observability setup)\n if (onAfterVerify) {\n await onAfterVerify(req, verification)\n }\n\n // Store payment context for settlement and route handler access\n const paymentContext: PaymentContext = {\n token,\n paymentRequired,\n creditsToSettle: creditsToVerify,\n verified: true,\n agentRequest: verification.agentRequest,\n agentRequestId: verification.agentRequest?.agentRequestId || verification.agentRequestId,\n }\n\n // Attach to request for potential use by route handler\n ;(req as Request & { paymentContext?: PaymentContext }).paymentContext = paymentContext\n\n // Wrap res.end so settlement runs no matter how the handler responds\n // (res.json, res.send, res.sendFile, res.end, res.pipe → res.end).\n // Previously only res.json was intercepted, so any other response\n // method would deliver the resource without burning credits and\n // without emitting the payment-response receipt header (#1728).\n const originalEnd = res.end.bind(res) as (...args: Parameters<Response['end']>) => Response\n let settlementStarted = false\n\n // Armed before next() for the same reason as the MPP path: it captures\n // a byte baseline. A route declared `{ planId, credits, mpp: true }`\n // advertises BOTH protocols on the same 402, so without this an MPP\n // buyer who disconnects before delivery is correctly not charged while\n // an x402 buyer doing exactly the same thing is — same route, same\n // disconnect, two different answers.\n const hasBeenDelivered = armDeliveryProbe(res)\n\n const runSettlement = (): Promise<void> => {\n return (\n typeof credits === 'function'\n ? Promise.resolve(credits(req, res))\n : Promise.resolve(creditsToVerify)\n )\n .then((creditsToSettle) => {\n paymentContext.creditsToSettle = creditsToSettle\n return payments.facilitator\n .settlePermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: BigInt(creditsToSettle),\n agentRequestId: paymentContext.agentRequestId,\n })\n .then((settlement) => {\n // Only attach the receipt header if headers haven't flushed\n // yet — streaming responses fire writeHead on the first\n // chunk and may have already sent them by the time we land\n // here.\n if (!res.headersSent) {\n const settlementBase64 = Buffer.from(JSON.stringify(settlement)).toString(\n 'base64',\n )\n res.setHeader(X402_HEADERS.PAYMENT_RESPONSE, settlementBase64)\n } else {\n console.warn(\n '[paymentMiddleware] headers already flushed; payment-response receipt not attached',\n )\n }\n if (onAfterSettle) {\n return Promise.resolve(onAfterSettle(req, creditsToSettle, settlement)).then(\n () => undefined,\n )\n }\n return undefined\n })\n })\n .catch((settleError) => {\n console.error('Payment settlement failed:', settleError)\n })\n }\n\n ;(res as unknown as { end: Response['end'] }).end = function (\n this: Response,\n ...args: Parameters<Response['end']>\n ): Response {\n // Only bill on 2xx success. Skipping 3xx avoids charging when the\n // handler redirects (e.g. `res.redirect(...)`), 304 Not Modified,\n // etc. Skipping 4xx/5xx avoids charging when the handler signals\n // failure — including `sendPaymentRequired`'s 402 which lands here.\n const isSuccess = res.statusCode >= 200 && res.statusCode < 300\n // And only when the response is actually reaching the buyer — an\n // aborted request still carries 200 through this wrapper, so the\n // status alone would charge the card for bytes nobody received.\n const isDelivered = hasBeenDelivered()\n if (settlementStarted || !isSuccess || !isDelivered) {\n if (isSuccess && !isDelivered && !settlementStarted) {\n console.warn(\n '[paymentMiddleware] x402: the buyer disconnected before any of the response reached them; not settling',\n )\n }\n return originalEnd(...args)\n }\n settlementStarted = true\n\n // If the handler streamed before calling end, headers were already\n // flushed. Settle anyway (so we still charge the card) but accept\n // we cannot inject the receipt header.\n if (res.headersSent) {\n void runSettlement()\n return originalEnd(...args)\n }\n\n // Buffered response path: defer the real `end` until settlement\n // finishes so the receipt header makes it into the same response.\n runSettlement().finally(() => {\n originalEnd(...args)\n })\n return res\n } as Response['end']\n\n // Continue to route handler\n next()\n } catch (error) {\n if (onPaymentError) {\n onPaymentError(error as Error, req, res)\n return\n }\n sendPaymentRequired(\n res,\n paymentRequired,\n error instanceof Error ? error.message : 'Payment verification failed',\n )\n }\n }\n\n // Execute async handler with error handling\n handleRequest().catch(next)\n }\n}\n\nexport default paymentMiddleware\n"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seller-edge helpers for MPP.
|
|
3
|
+
*
|
|
4
|
+
* The edge is a thin, secret-free shim: it never mints a challenge itself and
|
|
5
|
+
* never holds the MPP signing secret. It renames headers and forwards opaque
|
|
6
|
+
* strings to the backend.
|
|
7
|
+
*
|
|
8
|
+
* It does read exactly one field out of a credential — `challenge.id`, via
|
|
9
|
+
* {@link mppCredentialId}. That is a deliberate, bounded exception to
|
|
10
|
+
* "forwards opaque strings", and it is forced: enforcing single-use requires a
|
|
11
|
+
* stable identity for a credential, and the header bytes are not one (see
|
|
12
|
+
* {@link extractCredentialChallengeId}). The id is public, unsigned and
|
|
13
|
+
* already the backend's own burn key, so reading it grants the edge nothing
|
|
14
|
+
* it could not already observe; nothing here validates, trusts or acts on any
|
|
15
|
+
* other field, and the credential itself is still forwarded verbatim.
|
|
16
|
+
*/
|
|
17
|
+
import type { Request } from 'express';
|
|
18
|
+
/** MPP HTTP header names, lowercased for `req.headers` lookups. */
|
|
19
|
+
export declare const MPP_HEADERS: {
|
|
20
|
+
/** Server sends the challenge here on the 402. */
|
|
21
|
+
readonly CHALLENGE: "www-authenticate";
|
|
22
|
+
/** Client sends the credential here. */
|
|
23
|
+
readonly CREDENTIAL: "authorization";
|
|
24
|
+
/** Server sends the settlement receipt here on success. */
|
|
25
|
+
readonly RECEIPT: "payment-receipt";
|
|
26
|
+
};
|
|
27
|
+
/** Per-route MPP opt-in. `true` is shorthand for `{ bindBody: false }`. */
|
|
28
|
+
export type MppRouteOption = boolean | {
|
|
29
|
+
bindBody?: boolean;
|
|
30
|
+
};
|
|
31
|
+
export interface ResolvedMppOption {
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
bindBody: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare function resolveMppOption(option: MppRouteOption | undefined): ResolvedMppOption;
|
|
36
|
+
/**
|
|
37
|
+
* The resource the challenge is bound to.
|
|
38
|
+
*
|
|
39
|
+
* Identical to the endpoint value the x402 path already uses, so the scope
|
|
40
|
+
* includes the query string — the buyer reproduces it by retrying the same
|
|
41
|
+
* request.
|
|
42
|
+
*/
|
|
43
|
+
export declare function mppResource(req: Request): string;
|
|
44
|
+
export declare function mppVerb(req: Request): string;
|
|
45
|
+
/**
|
|
46
|
+
* Pulls the `Payment` credential out of `Authorization`, tolerating a header
|
|
47
|
+
* that carries other schemes alongside it (RFC 9110 §11.6.1).
|
|
48
|
+
*/
|
|
49
|
+
export declare function extractCredential(req: Request): string | null;
|
|
50
|
+
/**
|
|
51
|
+
* The key the middleware's single-use and in-flight sets are keyed on: the
|
|
52
|
+
* credential's challenge id, never the header bytes.
|
|
53
|
+
*
|
|
54
|
+
* `null` means the credential carries no usable id, which the middleware
|
|
55
|
+
* treats as a rejection — see {@link extractCredentialChallengeId}.
|
|
56
|
+
*/
|
|
57
|
+
export declare function mppCredentialId(credential: string): string | null;
|
|
58
|
+
//# sourceMappingURL=mpp-support.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mpp-support.d.ts","sourceRoot":"","sources":["../../../src/x402/express/mpp-support.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAGtC,mEAAmE;AACnE,eAAO,MAAM,WAAW;IACtB,kDAAkD;;IAElD,wCAAwC;;IAExC,2DAA2D;;CAEnD,CAAA;AAEV,2EAA2E;AAC3E,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAE7D,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,GAAG,iBAAiB,CAItF;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAEhD;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAE5C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI7D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEjE"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seller-edge helpers for MPP.
|
|
3
|
+
*
|
|
4
|
+
* The edge is a thin, secret-free shim: it never mints a challenge itself and
|
|
5
|
+
* never holds the MPP signing secret. It renames headers and forwards opaque
|
|
6
|
+
* strings to the backend.
|
|
7
|
+
*
|
|
8
|
+
* It does read exactly one field out of a credential — `challenge.id`, via
|
|
9
|
+
* {@link mppCredentialId}. That is a deliberate, bounded exception to
|
|
10
|
+
* "forwards opaque strings", and it is forced: enforcing single-use requires a
|
|
11
|
+
* stable identity for a credential, and the header bytes are not one (see
|
|
12
|
+
* {@link extractCredentialChallengeId}). The id is public, unsigned and
|
|
13
|
+
* already the backend's own burn key, so reading it grants the edge nothing
|
|
14
|
+
* it could not already observe; nothing here validates, trusts or acts on any
|
|
15
|
+
* other field, and the credential itself is still forwarded verbatim.
|
|
16
|
+
*/
|
|
17
|
+
import { extractCredentialChallengeId, extractPaymentScheme } from '../../mpp/codec.js';
|
|
18
|
+
/** MPP HTTP header names, lowercased for `req.headers` lookups. */
|
|
19
|
+
export const MPP_HEADERS = {
|
|
20
|
+
/** Server sends the challenge here on the 402. */
|
|
21
|
+
CHALLENGE: 'www-authenticate',
|
|
22
|
+
/** Client sends the credential here. */
|
|
23
|
+
CREDENTIAL: 'authorization',
|
|
24
|
+
/** Server sends the settlement receipt here on success. */
|
|
25
|
+
RECEIPT: 'payment-receipt',
|
|
26
|
+
};
|
|
27
|
+
export function resolveMppOption(option) {
|
|
28
|
+
if (option === undefined || option === false)
|
|
29
|
+
return { enabled: false, bindBody: false };
|
|
30
|
+
if (option === true)
|
|
31
|
+
return { enabled: true, bindBody: false };
|
|
32
|
+
return { enabled: true, bindBody: option.bindBody === true };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The resource the challenge is bound to.
|
|
36
|
+
*
|
|
37
|
+
* Identical to the endpoint value the x402 path already uses, so the scope
|
|
38
|
+
* includes the query string — the buyer reproduces it by retrying the same
|
|
39
|
+
* request.
|
|
40
|
+
*/
|
|
41
|
+
export function mppResource(req) {
|
|
42
|
+
return req.originalUrl || req.url;
|
|
43
|
+
}
|
|
44
|
+
export function mppVerb(req) {
|
|
45
|
+
return req.method.toUpperCase();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Pulls the `Payment` credential out of `Authorization`, tolerating a header
|
|
49
|
+
* that carries other schemes alongside it (RFC 9110 §11.6.1).
|
|
50
|
+
*/
|
|
51
|
+
export function extractCredential(req) {
|
|
52
|
+
const header = req.headers[MPP_HEADERS.CREDENTIAL];
|
|
53
|
+
if (!header || typeof header !== 'string')
|
|
54
|
+
return null;
|
|
55
|
+
return extractPaymentScheme(header);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The key the middleware's single-use and in-flight sets are keyed on: the
|
|
59
|
+
* credential's challenge id, never the header bytes.
|
|
60
|
+
*
|
|
61
|
+
* `null` means the credential carries no usable id, which the middleware
|
|
62
|
+
* treats as a rejection — see {@link extractCredentialChallengeId}.
|
|
63
|
+
*/
|
|
64
|
+
export function mppCredentialId(credential) {
|
|
65
|
+
return extractCredentialChallengeId(credential);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=mpp-support.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mpp-support.js","sourceRoot":"","sources":["../../../src/x402/express/mpp-support.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,4BAA4B,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAEvF,mEAAmE;AACnE,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,kDAAkD;IAClD,SAAS,EAAE,kBAAkB;IAC7B,wCAAwC;IACxC,UAAU,EAAE,eAAe;IAC3B,2DAA2D;IAC3D,OAAO,EAAE,iBAAiB;CAClB,CAAA;AAUV,MAAM,UAAU,gBAAgB,CAAC,MAAkC;IACjE,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,KAAK;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IACxF,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IAC9D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAA;AAC9D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,OAAO,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,GAAG,CAAA;AACnC,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAY;IAClC,OAAO,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAA;AACjC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC5C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,CAAA;IAClD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACtD,OAAO,oBAAoB,CAAC,MAAM,CAAC,CAAA;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,UAAkB;IAChD,OAAO,4BAA4B,CAAC,UAAU,CAAC,CAAA;AACjD,CAAC","sourcesContent":["/**\n * Seller-edge helpers for MPP.\n *\n * The edge is a thin, secret-free shim: it never mints a challenge itself and\n * never holds the MPP signing secret. It renames headers and forwards opaque\n * strings to the backend.\n *\n * It does read exactly one field out of a credential — `challenge.id`, via\n * {@link mppCredentialId}. That is a deliberate, bounded exception to\n * \"forwards opaque strings\", and it is forced: enforcing single-use requires a\n * stable identity for a credential, and the header bytes are not one (see\n * {@link extractCredentialChallengeId}). The id is public, unsigned and\n * already the backend's own burn key, so reading it grants the edge nothing\n * it could not already observe; nothing here validates, trusts or acts on any\n * other field, and the credential itself is still forwarded verbatim.\n */\n\nimport type { Request } from 'express'\nimport { extractCredentialChallengeId, extractPaymentScheme } from '../../mpp/codec.js'\n\n/** MPP HTTP header names, lowercased for `req.headers` lookups. */\nexport const MPP_HEADERS = {\n /** Server sends the challenge here on the 402. */\n CHALLENGE: 'www-authenticate',\n /** Client sends the credential here. */\n CREDENTIAL: 'authorization',\n /** Server sends the settlement receipt here on success. */\n RECEIPT: 'payment-receipt',\n} as const\n\n/** Per-route MPP opt-in. `true` is shorthand for `{ bindBody: false }`. */\nexport type MppRouteOption = boolean | { bindBody?: boolean }\n\nexport interface ResolvedMppOption {\n enabled: boolean\n bindBody: boolean\n}\n\nexport function resolveMppOption(option: MppRouteOption | undefined): ResolvedMppOption {\n if (option === undefined || option === false) return { enabled: false, bindBody: false }\n if (option === true) return { enabled: true, bindBody: false }\n return { enabled: true, bindBody: option.bindBody === true }\n}\n\n/**\n * The resource the challenge is bound to.\n *\n * Identical to the endpoint value the x402 path already uses, so the scope\n * includes the query string — the buyer reproduces it by retrying the same\n * request.\n */\nexport function mppResource(req: Request): string {\n return req.originalUrl || req.url\n}\n\nexport function mppVerb(req: Request): string {\n return req.method.toUpperCase()\n}\n\n/**\n * Pulls the `Payment` credential out of `Authorization`, tolerating a header\n * that carries other schemes alongside it (RFC 9110 §11.6.1).\n */\nexport function extractCredential(req: Request): string | null {\n const header = req.headers[MPP_HEADERS.CREDENTIAL]\n if (!header || typeof header !== 'string') return null\n return extractPaymentScheme(header)\n}\n\n/**\n * The key the middleware's single-use and in-flight sets are keyed on: the\n * credential's challenge id, never the header bytes.\n *\n * `null` means the credential carries no usable id, which the middleware\n * treats as a rejection — see {@link extractCredentialChallengeId}.\n */\nexport function mppCredentialId(credential: string): string | null {\n return extractCredentialChallengeId(credential)\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw-body capture for MPP body binding.
|
|
3
|
+
*
|
|
4
|
+
* The MPP challenge can be bound to a `sha-256=<base64>` digest of the request
|
|
5
|
+
* body. Express has already parsed the body by the time the middleware runs,
|
|
6
|
+
* and re-serializing `req.body` does not reproduce the bytes the buyer sent —
|
|
7
|
+
* key order, whitespace and number formatting all differ. So the raw buffer has
|
|
8
|
+
* to be kept at parse time.
|
|
9
|
+
*/
|
|
10
|
+
import type { Request, Response } from 'express';
|
|
11
|
+
/**
|
|
12
|
+
* `verify` hook for the body parser.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* app.use(express.json({ verify: captureRawBody }))
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function captureRawBody(req: Request, _res: Response, buf: Buffer): void;
|
|
20
|
+
/** Returns the buffer captured by {@link captureRawBody}, if any. */
|
|
21
|
+
export declare function getRawBody(req: Request): Buffer | undefined;
|
|
22
|
+
/** Computes the RFC 9530 `sha-256=<base64>` digest MPP challenges use. */
|
|
23
|
+
export declare function computeBodyDigest(raw: Buffer): string;
|
|
24
|
+
//# sourceMappingURL=raw-body.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw-body.d.ts","sourceRoot":"","sources":["../../../src/x402/express/raw-body.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAIhD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAK9E;AAED,qEAAqE;AACrE,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE3D;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw-body capture for MPP body binding.
|
|
3
|
+
*
|
|
4
|
+
* The MPP challenge can be bound to a `sha-256=<base64>` digest of the request
|
|
5
|
+
* body. Express has already parsed the body by the time the middleware runs,
|
|
6
|
+
* and re-serializing `req.body` does not reproduce the bytes the buyer sent —
|
|
7
|
+
* key order, whitespace and number formatting all differ. So the raw buffer has
|
|
8
|
+
* to be kept at parse time.
|
|
9
|
+
*/
|
|
10
|
+
import { createHash } from 'crypto';
|
|
11
|
+
const RAW_BODY = Symbol.for('nevermined.rawBody');
|
|
12
|
+
/**
|
|
13
|
+
* `verify` hook for the body parser.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* app.use(express.json({ verify: captureRawBody }))
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function captureRawBody(req, _res, buf) {
|
|
21
|
+
if (buf?.length) {
|
|
22
|
+
const withRawBody = req;
|
|
23
|
+
withRawBody[RAW_BODY] = Buffer.from(buf);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Returns the buffer captured by {@link captureRawBody}, if any. */
|
|
27
|
+
export function getRawBody(req) {
|
|
28
|
+
return req[RAW_BODY];
|
|
29
|
+
}
|
|
30
|
+
/** Computes the RFC 9530 `sha-256=<base64>` digest MPP challenges use. */
|
|
31
|
+
export function computeBodyDigest(raw) {
|
|
32
|
+
return `sha-256=${createHash('sha256').update(raw).digest('base64')}`;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=raw-body.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw-body.js","sourceRoot":"","sources":["../../../src/x402/express/raw-body.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAGnC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;AAEjD;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,GAAY,EAAE,IAAc,EAAE,GAAW;IACtE,IAAI,GAAG,EAAE,MAAM,EAAE,CAAC;QAChB,MAAM,WAAW,GAAG,GAAwC,CAAA;QAC5D,WAAW,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC1C,CAAC;AACH,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,UAAU,CAAC,GAAY;IACrC,OAAQ,GAAyC,CAAC,QAAQ,CAAC,CAAA;AAC7D,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,WAAW,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAA;AACvE,CAAC","sourcesContent":["/**\n * Raw-body capture for MPP body binding.\n *\n * The MPP challenge can be bound to a `sha-256=<base64>` digest of the request\n * body. Express has already parsed the body by the time the middleware runs,\n * and re-serializing `req.body` does not reproduce the bytes the buyer sent —\n * key order, whitespace and number formatting all differ. So the raw buffer has\n * to be kept at parse time.\n */\n\nimport { createHash } from 'crypto'\nimport type { Request, Response } from 'express'\n\nconst RAW_BODY = Symbol.for('nevermined.rawBody')\n\n/**\n * `verify` hook for the body parser.\n *\n * @example\n * ```typescript\n * app.use(express.json({ verify: captureRawBody }))\n * ```\n */\nexport function captureRawBody(req: Request, _res: Response, buf: Buffer): void {\n if (buf?.length) {\n const withRawBody = req as Request & { [RAW_BODY]?: Buffer }\n withRawBody[RAW_BODY] = Buffer.from(buf)\n }\n}\n\n/** Returns the buffer captured by {@link captureRawBody}, if any. */\nexport function getRawBody(req: Request): Buffer | undefined {\n return (req as Request & { [RAW_BODY]?: Buffer })[RAW_BODY]\n}\n\n/** Computes the RFC 9530 `sha-256=<base64>` digest MPP challenges use. */\nexport function computeBodyDigest(raw: Buffer): string {\n return `sha-256=${createHash('sha256').update(raw).digest('base64')}`\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The access-token request body, shared by the x402 and MPP mints.
|
|
3
|
+
*
|
|
4
|
+
* Both routes take identical inputs — only the EIP-712 domain the backend signs
|
|
5
|
+
* under differs — so the body is built in one place to keep them from drifting.
|
|
6
|
+
*/
|
|
7
|
+
import { X402TokenOptions } from '../common/types.js';
|
|
8
|
+
import type { EnvironmentName } from '../environments.js';
|
|
9
|
+
export declare function buildX402TokenRequestBody(params: {
|
|
10
|
+
planId: string;
|
|
11
|
+
agentId?: string;
|
|
12
|
+
tokenOptions?: X402TokenOptions;
|
|
13
|
+
environmentName: EnvironmentName;
|
|
14
|
+
}): Record<string, any>;
|
|
15
|
+
//# sourceMappingURL=token-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-request.d.ts","sourceRoot":"","sources":["../../src/x402/token-request.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,gBAAgB,EAAqB,MAAM,oBAAoB,CAAA;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,gBAAgB,CAAA;IAC/B,eAAe,EAAE,eAAe,CAAA;CACjC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAmEtB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The access-token request body, shared by the x402 and MPP mints.
|
|
3
|
+
*
|
|
4
|
+
* Both routes take identical inputs — only the EIP-712 domain the backend signs
|
|
5
|
+
* under differs — so the body is built in one place to keep them from drifting.
|
|
6
|
+
*/
|
|
7
|
+
import { PaymentsError } from '../common/payments.error.js';
|
|
8
|
+
import { getDefaultNetwork } from '../common/types.js';
|
|
9
|
+
export function buildX402TokenRequestBody(params) {
|
|
10
|
+
const { planId, agentId, tokenOptions, environmentName } = params;
|
|
11
|
+
const scheme = tokenOptions?.scheme ?? 'nvm:erc4337';
|
|
12
|
+
const network = tokenOptions?.network ?? getDefaultNetwork(scheme, environmentName);
|
|
13
|
+
// Validate delegationConfig is provided — the backend requires it for token generation
|
|
14
|
+
if (!tokenOptions?.delegationConfig) {
|
|
15
|
+
throw PaymentsError.validation(`delegationConfig is required for ${scheme} token generation. ` +
|
|
16
|
+
'Create a delegation first with payments.delegation.createDelegation(), ' +
|
|
17
|
+
'then request the token with delegationConfig.delegationId.');
|
|
18
|
+
}
|
|
19
|
+
// Deprecation: the supported flow is create-first — create the delegation
|
|
20
|
+
// with createDelegation(), then request the token with { delegationId }.
|
|
21
|
+
// A delegationConfig that carries an inline-create signal instead of a
|
|
22
|
+
// delegationId triggers inline create-on-the-fly, which the backend has
|
|
23
|
+
// deprecated (auto-select and providerPaymentMethodId/cardId creation).
|
|
24
|
+
// Warn once per call; the { delegationId } (± apiKeyId) path is silent.
|
|
25
|
+
// Predicate mirrors the Python SDK (payments-py#224): no delegationId AND
|
|
26
|
+
// at least one creation field present — a bare/invalid config is left to
|
|
27
|
+
// fail downstream rather than warned.
|
|
28
|
+
const { delegationId, cardId, providerPaymentMethodId, spendingLimitCents, durationSecs } = tokenOptions.delegationConfig;
|
|
29
|
+
// Reject an explicit empty/blank delegationId early — it is neither a valid
|
|
30
|
+
// reuse id nor an inline-create signal, and forwarding `delegationId: ''`
|
|
31
|
+
// would 4xx at the backend. (Symmetric with the Python SDK, payments-py#225.)
|
|
32
|
+
if (delegationId !== undefined && delegationId.trim() === '') {
|
|
33
|
+
throw PaymentsError.validation('delegationConfig.delegationId must not be an empty string. ' +
|
|
34
|
+
'Pass a valid delegation UUID or omit the field.');
|
|
35
|
+
}
|
|
36
|
+
const isInlineCreate = !delegationId &&
|
|
37
|
+
(cardId !== undefined ||
|
|
38
|
+
providerPaymentMethodId !== undefined ||
|
|
39
|
+
spendingLimitCents !== undefined ||
|
|
40
|
+
durationSecs !== undefined);
|
|
41
|
+
if (isInlineCreate) {
|
|
42
|
+
// Neutral wording: this body builder is shared by getX402AccessToken and
|
|
43
|
+
// the MPP mint (payments.mpp.fetch / getMppAccessToken), so the message
|
|
44
|
+
// must not name one caller specifically — an MPP buyer grepping for
|
|
45
|
+
// "getX402AccessToken" after seeing this warning would find nothing.
|
|
46
|
+
console.warn('[DEPRECATED] delegationConfig: inline create-on-the-fly delegationConfig ' +
|
|
47
|
+
'(no delegationId) is deprecated and will be removed in a future release. ' +
|
|
48
|
+
'Create the delegation first with payments.delegation.createDelegation(), ' +
|
|
49
|
+
'then request the token with delegationConfig: { delegationId }.');
|
|
50
|
+
}
|
|
51
|
+
// Build x402-aligned request body
|
|
52
|
+
return {
|
|
53
|
+
accepted: {
|
|
54
|
+
scheme,
|
|
55
|
+
network,
|
|
56
|
+
planId,
|
|
57
|
+
extra: {
|
|
58
|
+
...(agentId && { agentId }),
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
// Add delegation config for both erc4337 and card-delegation schemes.
|
|
62
|
+
// delegationConfig is guaranteed present here (the absence check above throws).
|
|
63
|
+
delegationConfig: tokenOptions.delegationConfig,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=token-request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-request.js","sourceRoot":"","sources":["../../src/x402/token-request.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAC3D,OAAO,EAAoB,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAGxE,MAAM,UAAU,yBAAyB,CAAC,MAKzC;IACC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,GAAG,MAAM,CAAA;IACjE,MAAM,MAAM,GAAG,YAAY,EAAE,MAAM,IAAI,aAAa,CAAA;IACpD,MAAM,OAAO,GAAG,YAAY,EAAE,OAAO,IAAI,iBAAiB,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAEnF,uFAAuF;IACvF,IAAI,CAAC,YAAY,EAAE,gBAAgB,EAAE,CAAC;QACpC,MAAM,aAAa,CAAC,UAAU,CAC5B,oCAAoC,MAAM,qBAAqB;YAC7D,yEAAyE;YACzE,4DAA4D,CAC/D,CAAA;IACH,CAAC;IAED,0EAA0E;IAC1E,yEAAyE;IACzE,uEAAuE;IACvE,wEAAwE;IACxE,wEAAwE;IACxE,wEAAwE;IACxE,0EAA0E;IAC1E,yEAAyE;IACzE,sCAAsC;IACtC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,YAAY,EAAE,GACvF,YAAY,CAAC,gBAAgB,CAAA;IAC/B,4EAA4E;IAC5E,0EAA0E;IAC1E,8EAA8E;IAC9E,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC7D,MAAM,aAAa,CAAC,UAAU,CAC5B,6DAA6D;YAC3D,iDAAiD,CACpD,CAAA;IACH,CAAC;IACD,MAAM,cAAc,GAClB,CAAC,YAAY;QACb,CAAC,MAAM,KAAK,SAAS;YACnB,uBAAuB,KAAK,SAAS;YACrC,kBAAkB,KAAK,SAAS;YAChC,YAAY,KAAK,SAAS,CAAC,CAAA;IAC/B,IAAI,cAAc,EAAE,CAAC;QACnB,yEAAyE;QACzE,wEAAwE;QACxE,oEAAoE;QACpE,qEAAqE;QACrE,OAAO,CAAC,IAAI,CACV,2EAA2E;YACzE,2EAA2E;YAC3E,2EAA2E;YAC3E,iEAAiE,CACpE,CAAA;IACH,CAAC;IAED,kCAAkC;IAClC,OAAO;QACL,QAAQ,EAAE;YACR,MAAM;YACN,OAAO;YACP,MAAM;YACN,KAAK,EAAE;gBACL,GAAG,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,CAAC;aAC5B;SACF;QACD,sEAAsE;QACtE,gFAAgF;QAChF,gBAAgB,EAAE,YAAY,CAAC,gBAAgB;KAChD,CAAA;AACH,CAAC","sourcesContent":["/**\n * The access-token request body, shared by the x402 and MPP mints.\n *\n * Both routes take identical inputs — only the EIP-712 domain the backend signs\n * under differs — so the body is built in one place to keep them from drifting.\n */\n\nimport { PaymentsError } from '../common/payments.error.js'\nimport { X402TokenOptions, getDefaultNetwork } from '../common/types.js'\nimport type { EnvironmentName } from '../environments.js'\n\nexport function buildX402TokenRequestBody(params: {\n planId: string\n agentId?: string\n tokenOptions?: X402TokenOptions\n environmentName: EnvironmentName\n}): Record<string, any> {\n const { planId, agentId, tokenOptions, environmentName } = params\n const scheme = tokenOptions?.scheme ?? 'nvm:erc4337'\n const network = tokenOptions?.network ?? getDefaultNetwork(scheme, environmentName)\n\n // Validate delegationConfig is provided — the backend requires it for token generation\n if (!tokenOptions?.delegationConfig) {\n throw PaymentsError.validation(\n `delegationConfig is required for ${scheme} token generation. ` +\n 'Create a delegation first with payments.delegation.createDelegation(), ' +\n 'then request the token with delegationConfig.delegationId.',\n )\n }\n\n // Deprecation: the supported flow is create-first — create the delegation\n // with createDelegation(), then request the token with { delegationId }.\n // A delegationConfig that carries an inline-create signal instead of a\n // delegationId triggers inline create-on-the-fly, which the backend has\n // deprecated (auto-select and providerPaymentMethodId/cardId creation).\n // Warn once per call; the { delegationId } (± apiKeyId) path is silent.\n // Predicate mirrors the Python SDK (payments-py#224): no delegationId AND\n // at least one creation field present — a bare/invalid config is left to\n // fail downstream rather than warned.\n const { delegationId, cardId, providerPaymentMethodId, spendingLimitCents, durationSecs } =\n tokenOptions.delegationConfig\n // Reject an explicit empty/blank delegationId early — it is neither a valid\n // reuse id nor an inline-create signal, and forwarding `delegationId: ''`\n // would 4xx at the backend. (Symmetric with the Python SDK, payments-py#225.)\n if (delegationId !== undefined && delegationId.trim() === '') {\n throw PaymentsError.validation(\n 'delegationConfig.delegationId must not be an empty string. ' +\n 'Pass a valid delegation UUID or omit the field.',\n )\n }\n const isInlineCreate =\n !delegationId &&\n (cardId !== undefined ||\n providerPaymentMethodId !== undefined ||\n spendingLimitCents !== undefined ||\n durationSecs !== undefined)\n if (isInlineCreate) {\n // Neutral wording: this body builder is shared by getX402AccessToken and\n // the MPP mint (payments.mpp.fetch / getMppAccessToken), so the message\n // must not name one caller specifically — an MPP buyer grepping for\n // \"getX402AccessToken\" after seeing this warning would find nothing.\n console.warn(\n '[DEPRECATED] delegationConfig: inline create-on-the-fly delegationConfig ' +\n '(no delegationId) is deprecated and will be removed in a future release. ' +\n 'Create the delegation first with payments.delegation.createDelegation(), ' +\n 'then request the token with delegationConfig: { delegationId }.',\n )\n }\n\n // Build x402-aligned request body\n return {\n accepted: {\n scheme,\n network,\n planId,\n extra: {\n ...(agentId && { agentId }),\n },\n },\n // Add delegation config for both erc4337 and card-delegation schemes.\n // delegationConfig is guaranteed present here (the absence check above throws).\n delegationConfig: tokenOptions.delegationConfig,\n }\n}\n"]}
|
package/dist/x402/token.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/x402/token.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAGzD,OAAO,EAAE,cAAc,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/x402/token.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAGzD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAGrE;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,eAAe;IAC/C;;;;;OAKG;IACH,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,YAAY;IAIzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACG,kBAAkB,CACtB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,gBAAgB,GAC9B,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;CAmCxD"}
|