@nevermined-io/payments 1.11.2 → 1.13.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/README.md +33 -0
- package/dist/a2a/clientRegistry.d.ts +10 -1
- package/dist/a2a/clientRegistry.d.ts.map +1 -1
- package/dist/a2a/clientRegistry.js +13 -4
- package/dist/a2a/clientRegistry.js.map +1 -1
- package/dist/a2a/paymentsClient.d.ts +60 -3
- package/dist/a2a/paymentsClient.d.ts.map +1 -1
- package/dist/a2a/paymentsClient.js +129 -8
- package/dist/a2a/paymentsClient.js.map +1 -1
- package/dist/a2a/types.d.ts +5 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/api/nvm-api.d.ts +2 -0
- package/dist/api/nvm-api.d.ts.map +1 -1
- package/dist/api/nvm-api.js +4 -0
- package/dist/api/nvm-api.js.map +1 -1
- package/dist/api/orders-api.d.ts +146 -0
- package/dist/api/orders-api.d.ts.map +1 -0
- package/dist/api/orders-api.js +107 -0
- package/dist/api/orders-api.js.map +1 -0
- package/dist/common/types.d.ts +87 -0
- package/dist/common/types.d.ts.map +1 -1
- package/dist/common/types.js.map +1 -1
- package/dist/index.d.ts +4 -1
- 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/paywall.d.ts.map +1 -1
- package/dist/mcp/core/paywall.js +10 -0
- package/dist/mcp/core/paywall.js.map +1 -1
- package/dist/mpp/fetch.d.ts +8 -3
- package/dist/mpp/fetch.d.ts.map +1 -1
- package/dist/mpp/fetch.js +6 -0
- package/dist/mpp/fetch.js.map +1 -1
- package/dist/mpp/mpp-api.d.ts +11 -5
- package/dist/mpp/mpp-api.d.ts.map +1 -1
- package/dist/mpp/mpp-api.js +10 -3
- package/dist/mpp/mpp-api.js.map +1 -1
- package/dist/payments.d.ts +3 -0
- package/dist/payments.d.ts.map +1 -1
- package/dist/payments.js +4 -0
- package/dist/payments.js.map +1 -1
- package/dist/x402/express/middleware.d.ts.map +1 -1
- package/dist/x402/express/middleware.js +98 -1
- package/dist/x402/express/middleware.js.map +1 -1
- package/dist/x402/facilitator-api.d.ts +90 -7
- package/dist/x402/facilitator-api.d.ts.map +1 -1
- package/dist/x402/facilitator-api.js +64 -3
- package/dist/x402/facilitator-api.js.map +1 -1
- package/dist/x402/index.d.ts +3 -2
- package/dist/x402/index.d.ts.map +1 -1
- package/dist/x402/index.js +1 -0
- package/dist/x402/index.js.map +1 -1
- package/dist/x402/token-request.d.ts +8 -0
- package/dist/x402/token-request.d.ts.map +1 -1
- package/dist/x402/token-request.js +101 -1
- package/dist/x402/token-request.js.map +1 -1
- package/dist/x402/token-version.d.ts +97 -0
- package/dist/x402/token-version.d.ts.map +1 -0
- package/dist/x402/token-version.js +127 -0
- package/dist/x402/token-version.js.map +1 -0
- package/dist/x402/token.d.ts +33 -2
- package/dist/x402/token.d.ts.map +1 -1
- package/dist/x402/token.js +47 -2
- package/dist/x402/token.js.map +1 -1
- package/package.json +1 -1
|
@@ -30,13 +30,22 @@
|
|
|
30
30
|
* })
|
|
31
31
|
*
|
|
32
32
|
* if (verification.isValid) {
|
|
33
|
-
* // Settle (burn) the credits
|
|
34
33
|
* const settlement = await payments.facilitator.settlePermissions({
|
|
35
34
|
* paymentRequired,
|
|
36
35
|
* x402AccessToken: x402Token,
|
|
37
36
|
* maxAmount: 2n
|
|
38
37
|
* })
|
|
39
|
-
*
|
|
38
|
+
*
|
|
39
|
+
* // Read `billingModel` before reading the credit fields: on a pay-as-you-go
|
|
40
|
+
* // plan there is no balance, so `creditsRedeemed` is always the string "0"
|
|
41
|
+
* // even on a charge that succeeded. See `SettlePermissionsResult`.
|
|
42
|
+
* if (settlement.success) {
|
|
43
|
+
* if (settlement.billingModel === 'pay-as-you-go') {
|
|
44
|
+
* console.log(`Charged, reference: ${settlement.orderTx || settlement.transaction}`)
|
|
45
|
+
* } else {
|
|
46
|
+
* console.log(`Credits redeemed: ${settlement.creditsRedeemed}`)
|
|
47
|
+
* }
|
|
48
|
+
* }
|
|
40
49
|
* }
|
|
41
50
|
* ```
|
|
42
51
|
*/
|
|
@@ -44,6 +53,7 @@ import { BasePaymentsAPI } from '../api/base-payments.js';
|
|
|
44
53
|
import { API_URL_SETTLE_PERMISSIONS, API_URL_VERIFY_PERMISSIONS } from '../api/nvm-api.js';
|
|
45
54
|
import { PaymentsError } from '../common/payments.error.js';
|
|
46
55
|
import { getDefaultNetwork, } from '../common/types.js';
|
|
56
|
+
import { X402_TOKEN_ALREADY_USED_CODE } from './token-version.js';
|
|
47
57
|
/**
|
|
48
58
|
* Build an X402PaymentRequired object for verify/settle operations.
|
|
49
59
|
*
|
|
@@ -324,12 +334,63 @@ export class FacilitatorAPI extends BasePaymentsAPI {
|
|
|
324
334
|
catch {
|
|
325
335
|
// Use default error message
|
|
326
336
|
}
|
|
337
|
+
// A spent single-use (v3) token is not a decline and not a forgery: the
|
|
338
|
+
// token was valid and has already been settled once. Say what to do
|
|
339
|
+
// about it, because the wrong reaction — retrying with the same token —
|
|
340
|
+
// is the one a generic settlement failure invites.
|
|
341
|
+
//
|
|
342
|
+
// This branch covers the THROWN shape, which is the one the backend
|
|
343
|
+
// uses here: `BCK.X402.0059` is catalogued as `httpStatus: 402`, so it
|
|
344
|
+
// arrives as a non-2xx and `isAccessTokenAlreadyUsed` fires on the
|
|
345
|
+
// PaymentsError. Verified end-to-end against staging: a second settle
|
|
346
|
+
// of the same v3 token rejects with that code. Settle can also report
|
|
347
|
+
// failure as 200 + `success: false` (e.g. `errorReason: "Cannot order
|
|
348
|
+
// plan"`), but a spent token is not one of those cases — read
|
|
349
|
+
// `success`/`errorReason` on the returned result for those.
|
|
350
|
+
if (errorCode === X402_TOKEN_ALREADY_USED_CODE) {
|
|
351
|
+
throw PaymentsError.fromBackend(errorMessage, {
|
|
352
|
+
message: 'this x402 access token was already used. Single-use (v3) tokens are consumed by ' +
|
|
353
|
+
'their first settlement — mint a new token for this request instead of retrying ' +
|
|
354
|
+
'with the same one.',
|
|
355
|
+
code: errorCode,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
327
358
|
throw PaymentsError.fromBackend(errorMessage, {
|
|
328
359
|
message: errorMessage,
|
|
329
360
|
code: errorCode,
|
|
330
361
|
});
|
|
331
362
|
}
|
|
332
|
-
|
|
363
|
+
const result = (await response.json());
|
|
364
|
+
// Settlement failure has two shapes. A refused settle (a spent v3 token,
|
|
365
|
+
// a forged one) arrives as a non-2xx and throws above. A settle the
|
|
366
|
+
// backend accepted but could not complete — no credits available and the
|
|
367
|
+
// auto-order reverting, say — arrives as 200 with `success: false` and an
|
|
368
|
+
// `errorReason`, and is returned verbatim because the reason, the billing
|
|
369
|
+
// model and the credit fields are what the caller needs to react.
|
|
370
|
+
//
|
|
371
|
+
// Returned, but not unremarked. The settlement object does reach callers
|
|
372
|
+
// (the Express middleware base64s it into `payment-response` and hands it
|
|
373
|
+
// to `onAfterSettle`; the LangChain decorator stores it as
|
|
374
|
+
// `lastSettlement()`), but of the in-tree consumers only the MCP paywall
|
|
375
|
+
// actually branches on `success` — so a caller that forgets to check it
|
|
376
|
+
// serves a request it was never paid for with nothing in the log.
|
|
377
|
+
//
|
|
378
|
+
// `!== true` rather than `=== false`: a 200 whose body has no `success`
|
|
379
|
+
// at all is not a settlement either, and reading an absent field as a
|
|
380
|
+
// success is the failure this warning exists to prevent.
|
|
381
|
+
if (result?.success !== true) {
|
|
382
|
+
// Deliberately claims nothing about what was charged. This layer reads
|
|
383
|
+
// `success` and nothing else — it never inspects `creditsRedeemed` or
|
|
384
|
+
// `orderTx` — and on a card rail an auto-order can have charged the
|
|
385
|
+
// buyer before the burn failed. Saying "no credits were burned" here
|
|
386
|
+
// would invite exactly the retry the SettlePermissionsResult docblock
|
|
387
|
+
// warns against.
|
|
388
|
+
console.warn(`[x402] settlePermissions did not settle (${result?.errorReason ?? 'no reason given'}). ` +
|
|
389
|
+
'Do not treat the request as paid. ' +
|
|
390
|
+
`creditsRedeemed=${result?.creditsRedeemed ?? 'unset'}, orderTx=${result?.orderTx ?? 'none'}, ` +
|
|
391
|
+
`billingModel=${result?.billingModel ?? 'unset'} — read those before assuming nothing was charged.`);
|
|
392
|
+
}
|
|
393
|
+
return result;
|
|
333
394
|
}
|
|
334
395
|
catch (error) {
|
|
335
396
|
if (error instanceof PaymentsError) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facilitator-api.js","sourceRoot":"","sources":["../../src/x402/facilitator-api.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The FacilitatorAPI class provides methods to verify and settle AI agent permissions using X402 access tokens.\n * This allows AI agents to act as facilitators, verifying and settling credits on behalf of subscribers.\n *\n * @example\n * ```typescript\n * import { Payments, X402PaymentRequired } from '@nevermined-io/payments'\n *\n * // Initialize the Payments instance\n * const payments = Payments.getInstance({\n * nvmApiKey: 'your-nvm-api-key',\n * environment: 'sandbox'\n * })\n *\n * // The server's 402 PaymentRequired response\n * const paymentRequired: X402PaymentRequired = buildPaymentRequired('123456789', {\n * endpoint: '/api/v1/agents/task',\n * agentId: '987654321',\n * httpVerb: 'POST'\n * })\n *\n * // Get X402 access token from subscriber (x402 v2: payment-signature header)\n * const x402Token = req.headers['payment-signature'] as string\n *\n * // Verify if subscriber has sufficient permissions/credits\n * const verification = await payments.facilitator.verifyPermissions({\n * paymentRequired,\n * x402AccessToken: x402Token,\n * maxAmount: 2n\n * })\n *\n * if (verification.isValid) {\n * // Settle (burn) the credits\n * const settlement = await payments.facilitator.settlePermissions({\n * paymentRequired,\n * x402AccessToken: x402Token,\n * maxAmount: 2n\n * })\n * console.log(`Credits redeemed: ${settlement.creditsRedeemed}`)\n * }\n * ```\n */\n\nimport { BasePaymentsAPI } from '../api/base-payments.js'\nimport { API_URL_SETTLE_PERMISSIONS, API_URL_VERIFY_PERMISSIONS } from '../api/nvm-api.js'\nimport { PaymentsError } from '../common/payments.error.js'\nimport {\n PaymentOptions,\n StartAgentRequest,\n X402SchemeType,\n getDefaultNetwork,\n} from '../common/types.js'\nimport type { EnvironmentName } from '../environments.js'\nimport type { Payments } from '../payments.js'\n\n/**\n * x402 Resource information\n */\nexport interface X402Resource {\n /** The protected resource URL */\n url: string\n /** Human-readable description */\n description?: string\n /** Expected response MIME type (e.g., \"application/json\") */\n mimeType?: string\n}\n\n/**\n * x402 Scheme extra fields for nvm:erc4337\n */\nexport interface X402SchemeExtra {\n /** Scheme version (e.g., \"1\") */\n version?: string\n /** Agent identifier */\n agentId?: string\n /** HTTP method for the endpoint */\n httpVerb?: string\n}\n\n/**\n * x402 Scheme definition (nvm:erc4337)\n */\nexport interface X402Scheme {\n /** Payment scheme identifier (e.g., \"nvm:erc4337\") */\n scheme: string\n /** Blockchain network in CAIP-2 format (e.g., \"eip155:84532\") */\n network: string\n /** 256-bit plan identifier */\n planId: string\n /** Scheme-specific extra fields */\n extra?: X402SchemeExtra\n}\n\n/**\n * x402 PaymentRequired response (402 response from server)\n */\nexport interface X402PaymentRequired {\n /** x402 protocol version (always 2) */\n x402Version: number\n /** Human-readable error message */\n error?: string\n /** Protected resource information */\n resource: X402Resource\n /** Array of accepted payment schemes */\n accepts: X402Scheme[]\n /** Extensions object (empty object for nvm:erc4337) */\n extensions: Record<string, unknown>\n}\n\n/**\n * x402 PaymentAccepted response (accepted payment scheme)\n */\nexport interface X402PaymentAccepted {\n /** The x402 version */\n x402Version: number\n /** The accepted payment scheme (nvm:erc4337) */\n accepted: X402Scheme\n /** The payload of the payment accepted */\n payload: {\n signature: string\n authorization: {\n from: string\n sessionKeysProvider: string\n sessionKeys: string[]\n }\n }\n extensions: Record<string, unknown>\n}\n\n/**\n * Parameters for verifying permissions\n */\nexport interface VerifyPermissionsParams {\n /** The server's 402 PaymentRequired response */\n paymentRequired: X402PaymentRequired\n /** The X402 access token (base64-encoded) */\n x402AccessToken: string\n /** Maximum credits to verify (optional) */\n maxAmount?: bigint\n}\n\n/**\n * x402 Verify Response - per x402 facilitator spec\n * @see https://github.com/coinbase/x402/blob/main/specs/x402-specification-v2.md\n */\nexport interface VerifyPermissionsResult {\n /** Whether the payment authorization is valid */\n isValid: boolean\n /** Reason for invalidity (only present if isValid is false) */\n invalidReason?: string\n /** Address of the payer's wallet */\n payer?: string\n /** Network identifier (e.g., 'stripe', 'braintree', 'visa', 'eip155:84532') */\n network?: string\n /** Agent request ID for observability tracking (Nevermined extension) */\n agentRequestId?: string\n /** URL pattern that matched the endpoint (Nevermined extension) */\n urlMatching?: string\n /** Agent request context for observability (Nevermined extension) */\n agentRequest?: StartAgentRequest\n}\n\n/**\n * Parameters for settling permissions\n */\nexport interface SettlePermissionsParams {\n /** The server's 402 PaymentRequired response */\n paymentRequired: X402PaymentRequired\n /** The X402 access token (base64-encoded) */\n x402AccessToken: string\n /** Number of credits to burn (optional) */\n maxAmount?: bigint\n /** Agent request ID for observability tracking. Returned by verifyPermissions. */\n agentRequestId?: string\n /** Whether this is a batch request (multiple LLM calls under one agentRequestId) */\n batch?: boolean\n /** Margin percentage (0-10) for credit calculation. Mutually exclusive with maxAmount when agentRequestId provided. */\n marginPercent?: number\n}\n\n/**\n * x402 Settle Response - per x402 facilitator spec\n * @see https://github.com/coinbase/x402/blob/main/specs/x402-specification-v2.md\n */\nexport interface SettlePermissionsResult {\n /** Whether settlement was successful */\n success: boolean\n /** Reason for settlement failure (only present if success is false) */\n errorReason?: string\n /** Address of the payer's wallet */\n payer?: string\n /** Blockchain transaction hash (empty string if settlement failed) */\n transaction: string\n /** Blockchain network identifier in CAIP-2 format */\n network: string\n /** Number of credits redeemed (Nevermined extension) */\n creditsRedeemed?: string\n /** Subscriber's remaining balance (Nevermined extension) */\n remainingBalance?: string\n /** Transaction hash of the order operation if auto top-up occurred (Nevermined extension) */\n orderTx?: string\n}\n\n/**\n * Build an X402PaymentRequired object for verify/settle operations.\n *\n * This helper simplifies the creation of payment requirement objects\n * that are needed for the facilitator API.\n *\n * @param planId - The Nevermined plan identifier (required)\n * @param options - Optional configuration with endpoint, agentId, httpVerb, network, description\n * @returns X402PaymentRequired object ready to use with verifyPermissions/settlePermissions\n *\n * @example\n * ```typescript\n * import { buildPaymentRequired } from '@nevermined-io/payments'\n *\n * const paymentRequired = buildPaymentRequired('123456789', {\n * endpoint: '/api/v1/agents/task',\n * agentId: '987654321',\n * httpVerb: 'POST'\n * })\n *\n * const result = await payments.facilitator.verifyPermissions({\n * paymentRequired,\n * x402AccessToken: token,\n * maxAmount: 2n\n * })\n * ```\n */\nexport function buildPaymentRequired(\n planId: string,\n options?: {\n endpoint?: string\n agentId?: string\n httpVerb?: string\n network?: string\n description?: string\n mimeType?: string\n scheme?: X402SchemeType\n environment?: EnvironmentName\n },\n): X402PaymentRequired {\n const {\n endpoint,\n agentId,\n httpVerb,\n scheme = 'nvm:erc4337',\n network,\n description,\n mimeType,\n environment,\n } = options || {}\n const resolvedNetwork = network ?? getDefaultNetwork(scheme, environment)\n\n // Build extra fields — always include version for scheme versioning\n const extra: X402SchemeExtra = {\n version: '1',\n ...(agentId && { agentId }),\n ...(httpVerb && { httpVerb }),\n }\n\n return {\n x402Version: 2,\n resource: {\n url: endpoint || '',\n ...(description && { description }),\n ...(mimeType && { mimeType }),\n },\n accepts: [\n {\n scheme,\n network: resolvedNetwork,\n planId,\n extra,\n },\n ],\n extensions: {},\n }\n}\n\n/**\n * Build an X402PaymentRequired object advertising one or more plans.\n *\n * Like {@link buildPaymentRequired} but produces one `accepts[]` entry per plan\n * id, so a payment-required response can advertise every plan that unlocks the\n * resource. For a single plan this is equivalent to {@link buildPaymentRequired}.\n *\n * @param planIds - The Nevermined plan identifiers (falls back to `['']` if empty)\n * @param options - Same options as {@link buildPaymentRequired}\n * @returns X402PaymentRequired object with one accepts entry per plan\n */\nexport function buildPaymentRequiredForPlans(\n planIds: string[],\n options?: {\n endpoint?: string\n agentId?: string\n httpVerb?: string\n network?: string\n description?: string\n mimeType?: string\n scheme?: X402SchemeType\n environment?: EnvironmentName\n },\n): X402PaymentRequired {\n const ids = planIds.length > 0 ? planIds : ['']\n if (ids.length === 1) {\n return buildPaymentRequired(ids[0], options)\n }\n\n const {\n endpoint,\n agentId,\n httpVerb,\n scheme = 'nvm:erc4337',\n network,\n description,\n mimeType,\n environment,\n } = options || {}\n const resolvedNetwork = network ?? getDefaultNetwork(scheme, environment)\n\n const extra: X402SchemeExtra = {\n version: '1',\n ...(agentId && { agentId }),\n ...(httpVerb && { httpVerb }),\n }\n\n return {\n x402Version: 2,\n resource: {\n url: endpoint || '',\n ...(description && { description }),\n ...(mimeType && { mimeType }),\n },\n accepts: ids.map((planId) => ({\n scheme,\n network: resolvedNetwork,\n planId,\n extra,\n })),\n extensions: {},\n }\n}\n\ninterface CachedPlanMetadata {\n scheme: X402SchemeType\n fiatProvider?: string\n cachedAt: number\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes\nconst planMetadataCache = new Map<string, CachedPlanMetadata>()\n\nasync function fetchPlanMetadata(\n payments: Payments,\n planId: string,\n): Promise<{ scheme: X402SchemeType }> {\n const cached = planMetadataCache.get(planId)\n if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {\n return { scheme: cached.scheme }\n }\n try {\n const plan = await payments.plans.getPlan(planId)\n const isCrypto = plan.registry?.price?.isCrypto\n // fiatPaymentProvider is in plan.metadata.plan, not in registry.price\n const fiatProvider = (plan as any).metadata?.plan?.fiatPaymentProvider\n const scheme: X402SchemeType = isCrypto === false ? 'nvm:card-delegation' : 'nvm:erc4337'\n planMetadataCache.set(planId, { scheme, fiatProvider, cachedAt: Date.now() })\n return { scheme }\n } catch {\n return { scheme: 'nvm:erc4337' }\n }\n}\n\n/**\n * Resolve the network for a plan from its fiatPaymentProvider metadata.\n * For card-delegation plans, returns the provider ('stripe' or 'braintree').\n * Returns undefined for crypto plans.\n */\nexport async function resolveNetwork(\n payments: Payments,\n planId: string,\n explicitNetwork?: string,\n): Promise<string | undefined> {\n if (explicitNetwork) return explicitNetwork\n await fetchPlanMetadata(payments, planId)\n const cached = planMetadataCache.get(planId)\n return cached?.fiatProvider\n}\n\n/**\n * Resolve the x402 scheme for a plan by fetching plan metadata (cached).\n * Used in callsites that don't have a token to extract scheme from\n * (402 responses and token generation).\n *\n * @param payments - The Payments instance for API access\n * @param planId - The plan identifier\n * @param explicitScheme - Optional explicit override; returned immediately if provided\n * @returns The resolved scheme type\n */\nexport async function resolveScheme(\n payments: Payments,\n planId: string,\n explicitScheme?: X402SchemeType,\n): Promise<X402SchemeType> {\n if (explicitScheme) return explicitScheme\n const metadata = await fetchPlanMetadata(payments, planId)\n return metadata.scheme\n}\n\n/**\n * The FacilitatorAPI class provides methods to verify and settle AI agent permissions.\n * It enables AI agents to act as facilitators, managing credit verification and settlement\n * for subscribers using X402 access tokens.\n */\nexport class FacilitatorAPI extends BasePaymentsAPI {\n /**\n * Get a singleton instance of the FacilitatorAPI class.\n *\n * @param options - The options to initialize the payments class\n * @returns The instance of the FacilitatorAPI class\n */\n static getInstance(options: PaymentOptions): FacilitatorAPI {\n return new FacilitatorAPI(options)\n }\n\n /**\n * Verify if a subscriber has permission to use credits from a payment plan.\n * This method simulates the credit usage without actually burning credits,\n * checking if the subscriber has sufficient balance and permissions.\n *\n * The planId and subscriberAddress are extracted from the x402AccessToken.\n *\n * @param params - Verification parameters (see {@link VerifyPermissionsParams}).\n * - paymentRequired: x402 PaymentRequired from 402 response (required, for validation)\n * - x402AccessToken: X402 access token (contains planId, subscriberAddress, agentId)\n * - maxAmount: maximum credits to verify (optional, bigint)\n * @returns A promise that resolves to a verification result with 'isValid' boolean\n *\n * @throws PaymentsError if verification fails\n */\n async verifyPermissions(params: VerifyPermissionsParams): Promise<VerifyPermissionsResult> {\n const { paymentRequired, x402AccessToken, maxAmount } = params\n\n const url = new URL(API_URL_VERIFY_PERMISSIONS, this.environment.backend)\n\n const body: Record<string, unknown> = {\n paymentRequired,\n x402AccessToken,\n }\n\n if (maxAmount !== undefined) {\n body.maxAmount = maxAmount.toString()\n }\n\n // Send the NVM API-key auth header (Authorization: Bearer <nvmApiKey>).\n // The backend /verify endpoint runs an OPTIONAL guard that tolerates the\n // header's absence today, so this is non-breaking; it pre-positions for\n // the later strict-guard flip. See nevermined-io/nvm-monorepo#1570.\n const options = this.getBackendHTTPOptions('POST', body)\n\n try {\n const response = await fetch(url, options)\n if (!response.ok) {\n let errorMessage = 'Permission verification failed'\n let errorCode = `http_${response.status}`\n try {\n const errorData = await response.json()\n if (errorData.message) errorMessage = errorData.message\n if (errorData.code) errorCode = errorData.code\n if (errorData.hint) errorMessage = `${errorMessage} — ${errorData.hint}`\n } catch {\n // Use default error message\n }\n throw PaymentsError.fromBackend(errorMessage, {\n message: errorMessage,\n code: errorCode,\n })\n }\n return await response.json()\n } catch (error) {\n if (error instanceof PaymentsError) {\n throw error\n }\n throw PaymentsError.fromBackend('Network error during permission verification', {\n message: error instanceof Error ? error.message : String(error),\n code: 'network_error',\n })\n }\n }\n\n /**\n * Settle (burn) credits from a subscriber's payment plan.\n * This method executes the actual credit consumption, burning the specified\n * number of credits from the subscriber's balance. If the subscriber doesn't\n * have enough credits, it will attempt to order more before settling.\n *\n * The planId and subscriberAddress are extracted from the x402AccessToken.\n *\n * @param params - Settlement parameters (see {@link SettlePermissionsParams}).\n * - paymentRequired: x402 PaymentRequired from 402 response (required, for validation)\n * - x402AccessToken: X402 access token (contains planId, subscriberAddress, agentId)\n * - maxAmount: number of credits to burn (optional, bigint)\n * - agentRequestId: Agent request ID for observability tracking (optional)\n * - batch: Whether this is a batch request (optional)\n * - marginPercent: Margin percentage for credit calculation (optional)\n * @returns A promise that resolves to a settlement result with transaction details\n *\n * @throws PaymentsError if settlement fails\n */\n async settlePermissions(params: SettlePermissionsParams): Promise<SettlePermissionsResult> {\n const { paymentRequired, x402AccessToken, maxAmount, agentRequestId, batch, marginPercent } =\n params\n\n const url = new URL(API_URL_SETTLE_PERMISSIONS, this.environment.backend)\n\n const body: Record<string, unknown> = {\n paymentRequired,\n x402AccessToken,\n }\n\n if (maxAmount !== undefined) {\n body.maxAmount = maxAmount.toString()\n }\n if (agentRequestId !== undefined) {\n body.agentRequestId = agentRequestId\n }\n if (batch !== undefined) {\n body.batch = batch\n }\n if (marginPercent !== undefined) {\n body.marginPercent = marginPercent\n }\n\n // Send the NVM API-key auth header (Authorization: Bearer <nvmApiKey>).\n // The backend /settle endpoint runs an OPTIONAL guard that tolerates the\n // header's absence today, so this is non-breaking; it pre-positions for\n // the later strict-guard flip. See nevermined-io/nvm-monorepo#1570.\n const options = this.getBackendHTTPOptions('POST', body)\n\n try {\n const response = await fetch(url, options)\n if (!response.ok) {\n let errorMessage = 'Permission settlement failed'\n let errorCode = `http_${response.status}`\n try {\n const errorData = await response.json()\n if (errorData.message) errorMessage = errorData.message\n if (errorData.code) errorCode = errorData.code\n if (errorData.hint) errorMessage = `${errorMessage} — ${errorData.hint}`\n } catch {\n // Use default error message\n }\n throw PaymentsError.fromBackend(errorMessage, {\n message: errorMessage,\n code: errorCode,\n })\n }\n return await response.json()\n } catch (error) {\n if (error instanceof PaymentsError) {\n throw error\n }\n throw PaymentsError.fromBackend('Network error during permission settlement', {\n message: error instanceof Error ? error.message : String(error),\n code: 'network_error',\n })\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"facilitator-api.js","sourceRoot":"","sources":["../../src/x402/facilitator-api.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The FacilitatorAPI class provides methods to verify and settle AI agent permissions using X402 access tokens.\n * This allows AI agents to act as facilitators, verifying and settling credits on behalf of subscribers.\n *\n * @example\n * ```typescript\n * import { Payments, X402PaymentRequired } from '@nevermined-io/payments'\n *\n * // Initialize the Payments instance\n * const payments = Payments.getInstance({\n * nvmApiKey: 'your-nvm-api-key',\n * environment: 'sandbox'\n * })\n *\n * // The server's 402 PaymentRequired response\n * const paymentRequired: X402PaymentRequired = buildPaymentRequired('123456789', {\n * endpoint: '/api/v1/agents/task',\n * agentId: '987654321',\n * httpVerb: 'POST'\n * })\n *\n * // Get X402 access token from subscriber (x402 v2: payment-signature header)\n * const x402Token = req.headers['payment-signature'] as string\n *\n * // Verify if subscriber has sufficient permissions/credits\n * const verification = await payments.facilitator.verifyPermissions({\n * paymentRequired,\n * x402AccessToken: x402Token,\n * maxAmount: 2n\n * })\n *\n * if (verification.isValid) {\n * const settlement = await payments.facilitator.settlePermissions({\n * paymentRequired,\n * x402AccessToken: x402Token,\n * maxAmount: 2n\n * })\n *\n * // Read `billingModel` before reading the credit fields: on a pay-as-you-go\n * // plan there is no balance, so `creditsRedeemed` is always the string \"0\"\n * // even on a charge that succeeded. See `SettlePermissionsResult`.\n * if (settlement.success) {\n * if (settlement.billingModel === 'pay-as-you-go') {\n * console.log(`Charged, reference: ${settlement.orderTx || settlement.transaction}`)\n * } else {\n * console.log(`Credits redeemed: ${settlement.creditsRedeemed}`)\n * }\n * }\n * }\n * ```\n */\n\nimport { BasePaymentsAPI } from '../api/base-payments.js'\nimport { API_URL_SETTLE_PERMISSIONS, API_URL_VERIFY_PERMISSIONS } from '../api/nvm-api.js'\nimport { PaymentsError } from '../common/payments.error.js'\nimport {\n PaymentOptions,\n StartAgentRequest,\n X402SchemeType,\n getDefaultNetwork,\n} from '../common/types.js'\nimport type { EnvironmentName } from '../environments.js'\nimport type { Payments } from '../payments.js'\nimport { X402_TOKEN_ALREADY_USED_CODE } from './token-version.js'\n\n/**\n * x402 Resource information\n */\nexport interface X402Resource {\n /** The protected resource URL */\n url: string\n /** Human-readable description */\n description?: string\n /** Expected response MIME type (e.g., \"application/json\") */\n mimeType?: string\n}\n\n/**\n * x402 Scheme extra fields for nvm:erc4337\n */\nexport interface X402SchemeExtra {\n /** Scheme version (e.g., \"1\") */\n version?: string\n /** Agent identifier */\n agentId?: string\n /** HTTP method for the endpoint */\n httpVerb?: string\n}\n\n/**\n * x402 Scheme definition (nvm:erc4337)\n */\nexport interface X402Scheme {\n /** Payment scheme identifier (e.g., \"nvm:erc4337\") */\n scheme: string\n /** Blockchain network in CAIP-2 format (e.g., \"eip155:84532\") */\n network: string\n /** 256-bit plan identifier */\n planId: string\n /** Scheme-specific extra fields */\n extra?: X402SchemeExtra\n}\n\n/**\n * x402 PaymentRequired response (402 response from server)\n */\nexport interface X402PaymentRequired {\n /** x402 protocol version (always 2) */\n x402Version: number\n /** Human-readable error message */\n error?: string\n /** Protected resource information */\n resource: X402Resource\n /** Array of accepted payment schemes */\n accepts: X402Scheme[]\n /** Extensions object (empty object for nvm:erc4337) */\n extensions: Record<string, unknown>\n}\n\n/**\n * x402 PaymentAccepted response (accepted payment scheme)\n */\nexport interface X402PaymentAccepted {\n /** The x402 version */\n x402Version: number\n /** The accepted payment scheme (nvm:erc4337) */\n accepted: X402Scheme\n /** The payload of the payment accepted */\n payload: {\n signature: string\n authorization: {\n from: string\n sessionKeysProvider: string\n sessionKeys: string[]\n }\n }\n extensions: Record<string, unknown>\n}\n\n/**\n * Parameters for verifying permissions\n */\nexport interface VerifyPermissionsParams {\n /** The server's 402 PaymentRequired response */\n paymentRequired: X402PaymentRequired\n /** The X402 access token (base64-encoded) */\n x402AccessToken: string\n /** Maximum credits to verify (optional) */\n maxAmount?: bigint\n}\n\n/**\n * x402 Verify Response - per x402 facilitator spec\n * @see https://github.com/coinbase/x402/blob/main/specs/x402-specification-v2.md\n */\nexport interface VerifyPermissionsResult {\n /** Whether the payment authorization is valid */\n isValid: boolean\n /** Reason for invalidity (only present if isValid is false) */\n invalidReason?: string\n /** Address of the payer's wallet */\n payer?: string\n /** Network identifier (e.g., 'stripe', 'braintree', 'visa', 'eip155:84532') */\n network?: string\n /** Agent request ID for observability tracking (Nevermined extension) */\n agentRequestId?: string\n /** URL pattern that matched the endpoint (Nevermined extension) */\n urlMatching?: string\n /** Agent request context for observability (Nevermined extension) */\n agentRequest?: StartAgentRequest\n}\n\n/**\n * Parameters for settling permissions\n */\nexport interface SettlePermissionsParams {\n /** The server's 402 PaymentRequired response */\n paymentRequired: X402PaymentRequired\n /** The X402 access token (base64-encoded) */\n x402AccessToken: string\n /** Number of credits to burn (optional) */\n maxAmount?: bigint\n /** Agent request ID for observability tracking. Returned by verifyPermissions. */\n agentRequestId?: string\n /** Whether this is a batch request (multiple LLM calls under one agentRequestId) */\n batch?: boolean\n /** Margin percentage (0-10) for credit calculation. Mutually exclusive with maxAmount when agentRequestId provided. */\n marginPercent?: number\n}\n\n/**\n * How the settled request was priced (Nevermined extension).\n *\n * - `credits` — the plan holds a credit balance and this settle redeemed from it.\n * - `pay-as-you-go` — the plan holds no balance; the request is priced individually\n * and charged directly (a card PSP on fiat rails, an on-chain order on crypto\n * rails).\n */\nexport type X402BillingModel = 'credits' | 'pay-as-you-go'\n\n/**\n * x402 Settle Response - per x402 facilitator spec\n * @see https://github.com/coinbase/x402/blob/main/specs/x402-specification-v2.md\n *\n * ### Deciding whether the buyer was charged\n *\n * `success` alone tells you the settle worked. What to check *in addition*\n * depends on {@link SettlePermissionsResult.billingModel}:\n *\n * - `credits` — `success === true && Number(creditsRedeemed) > 0`.\n * - `pay-as-you-go` — `success === true` plus a non-empty `orderTx` (fiat rails)\n * or `transaction` (crypto rails). `creditsRedeemed` and `remainingBalance` are\n * always the string `'0'` on this billing model and carry no information.\n *\n * Do not gate on `creditsRedeemed` without reading `billingModel` first: on a\n * pay-as-you-go plan `creditsRedeemed > 0` can never hold, so a real charge reads\n * as a decline — and retrying a card charge that already succeeded is the one\n * thing to avoid, because repeated attempts feed issuer fraud scoring.\n *\n * Mind the type as well: these fields are **strings**. `'0'` is truthy while\n * `Number('0') > 0` is false, so two plausible-looking checks disagree.\n *\n * If `billingModel` is **absent**, you are talking to a Nevermined API that\n * predates the discriminator: apply the `credits` rule, and never read a missing\n * discriminator as pay-as-you-go.\n *\n * @example\n * ```typescript\n * const settled =\n * settlement.success &&\n * (settlement.billingModel === 'pay-as-you-go'\n * ? Boolean(settlement.orderTx || settlement.transaction)\n * : Number(settlement.creditsRedeemed ?? '0') > 0)\n * ```\n */\nexport interface SettlePermissionsResult {\n /** Whether settlement was successful */\n success: boolean\n /** Reason for settlement failure (only present if success is false) */\n errorReason?: string\n /** Address of the payer's wallet */\n payer?: string\n /**\n * Blockchain transaction hash (empty string if settlement failed). On crypto\n * `pay-as-you-go` plans this is also the reference for the per-request charge.\n */\n transaction: string\n /**\n * Network identifier. The discriminator is the rail, not the billing model: for\n * crypto rails (`nvm:erc4337`) it is the CAIP-2 chain id (`eip155:84532`) under\n * both billing models; for fiat card-delegation rails it is the settling payment\n * provider (`stripe`, `braintree`, `visa`), not a CAIP-2 value.\n */\n network: string\n /**\n * Which billing model this settle was priced under (Nevermined extension).\n *\n * Reported whether or not the settle succeeded — so check `success` before\n * treating it as evidence of a charge. It is absent entirely against a\n * Nevermined API that predates the discriminator, which is why it is optional;\n * treat that case as `credits`. Read it before `creditsRedeemed` /\n * `remainingBalance`: see the interface docs above for the per-model criterion.\n */\n billingModel?: X402BillingModel\n /**\n * Number of credits redeemed (Nevermined extension). Always the string `'0'` for\n * `billingModel: 'pay-as-you-go'` plans, which hold no credit balance — including\n * on a settle that charged the buyer successfully.\n */\n creditsRedeemed?: string\n /**\n * Subscriber's remaining credit balance (Nevermined extension). Always the string\n * `'0'` for `billingModel: 'pay-as-you-go'` plans — the per-request charge is\n * referenced by `orderTx` (fiat) or `transaction` (crypto), not here.\n */\n remainingBalance?: string\n /**\n * Reference for the order or per-request charge, if one occurred (Nevermined\n * extension). On fiat `pay-as-you-go` this is the per-request charge — the PSP\n * transaction id (a Stripe PaymentIntent `pi_…`, a Braintree transaction id);\n * crypto `pay-as-you-go` reports its on-chain order in `transaction` instead. On\n * `credits` plans it is set only when the settle had to order credits first\n * (auto top-up). Treat it as an opaque string and disambiguate by prefix if you\n * need to.\n */\n orderTx?: string\n}\n\n/**\n * Build an X402PaymentRequired object for verify/settle operations.\n *\n * This helper simplifies the creation of payment requirement objects\n * that are needed for the facilitator API.\n *\n * @param planId - The Nevermined plan identifier (required)\n * @param options - Optional configuration with endpoint, agentId, httpVerb, network, description\n * @returns X402PaymentRequired object ready to use with verifyPermissions/settlePermissions\n *\n * @example\n * ```typescript\n * import { buildPaymentRequired } from '@nevermined-io/payments'\n *\n * const paymentRequired = buildPaymentRequired('123456789', {\n * endpoint: '/api/v1/agents/task',\n * agentId: '987654321',\n * httpVerb: 'POST'\n * })\n *\n * const result = await payments.facilitator.verifyPermissions({\n * paymentRequired,\n * x402AccessToken: token,\n * maxAmount: 2n\n * })\n * ```\n */\nexport function buildPaymentRequired(\n planId: string,\n options?: {\n endpoint?: string\n agentId?: string\n httpVerb?: string\n network?: string\n description?: string\n mimeType?: string\n scheme?: X402SchemeType\n environment?: EnvironmentName\n },\n): X402PaymentRequired {\n const {\n endpoint,\n agentId,\n httpVerb,\n scheme = 'nvm:erc4337',\n network,\n description,\n mimeType,\n environment,\n } = options || {}\n const resolvedNetwork = network ?? getDefaultNetwork(scheme, environment)\n\n // Build extra fields — always include version for scheme versioning\n const extra: X402SchemeExtra = {\n version: '1',\n ...(agentId && { agentId }),\n ...(httpVerb && { httpVerb }),\n }\n\n return {\n x402Version: 2,\n resource: {\n url: endpoint || '',\n ...(description && { description }),\n ...(mimeType && { mimeType }),\n },\n accepts: [\n {\n scheme,\n network: resolvedNetwork,\n planId,\n extra,\n },\n ],\n extensions: {},\n }\n}\n\n/**\n * Build an X402PaymentRequired object advertising one or more plans.\n *\n * Like {@link buildPaymentRequired} but produces one `accepts[]` entry per plan\n * id, so a payment-required response can advertise every plan that unlocks the\n * resource. For a single plan this is equivalent to {@link buildPaymentRequired}.\n *\n * @param planIds - The Nevermined plan identifiers (falls back to `['']` if empty)\n * @param options - Same options as {@link buildPaymentRequired}\n * @returns X402PaymentRequired object with one accepts entry per plan\n */\nexport function buildPaymentRequiredForPlans(\n planIds: string[],\n options?: {\n endpoint?: string\n agentId?: string\n httpVerb?: string\n network?: string\n description?: string\n mimeType?: string\n scheme?: X402SchemeType\n environment?: EnvironmentName\n },\n): X402PaymentRequired {\n const ids = planIds.length > 0 ? planIds : ['']\n if (ids.length === 1) {\n return buildPaymentRequired(ids[0], options)\n }\n\n const {\n endpoint,\n agentId,\n httpVerb,\n scheme = 'nvm:erc4337',\n network,\n description,\n mimeType,\n environment,\n } = options || {}\n const resolvedNetwork = network ?? getDefaultNetwork(scheme, environment)\n\n const extra: X402SchemeExtra = {\n version: '1',\n ...(agentId && { agentId }),\n ...(httpVerb && { httpVerb }),\n }\n\n return {\n x402Version: 2,\n resource: {\n url: endpoint || '',\n ...(description && { description }),\n ...(mimeType && { mimeType }),\n },\n accepts: ids.map((planId) => ({\n scheme,\n network: resolvedNetwork,\n planId,\n extra,\n })),\n extensions: {},\n }\n}\n\ninterface CachedPlanMetadata {\n scheme: X402SchemeType\n fiatProvider?: string\n cachedAt: number\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes\nconst planMetadataCache = new Map<string, CachedPlanMetadata>()\n\nasync function fetchPlanMetadata(\n payments: Payments,\n planId: string,\n): Promise<{ scheme: X402SchemeType }> {\n const cached = planMetadataCache.get(planId)\n if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {\n return { scheme: cached.scheme }\n }\n try {\n const plan = await payments.plans.getPlan(planId)\n const isCrypto = plan.registry?.price?.isCrypto\n // fiatPaymentProvider is in plan.metadata.plan, not in registry.price\n const fiatProvider = (plan as any).metadata?.plan?.fiatPaymentProvider\n const scheme: X402SchemeType = isCrypto === false ? 'nvm:card-delegation' : 'nvm:erc4337'\n planMetadataCache.set(planId, { scheme, fiatProvider, cachedAt: Date.now() })\n return { scheme }\n } catch {\n return { scheme: 'nvm:erc4337' }\n }\n}\n\n/**\n * Resolve the network for a plan from its fiatPaymentProvider metadata.\n * For card-delegation plans, returns the provider ('stripe' or 'braintree').\n * Returns undefined for crypto plans.\n */\nexport async function resolveNetwork(\n payments: Payments,\n planId: string,\n explicitNetwork?: string,\n): Promise<string | undefined> {\n if (explicitNetwork) return explicitNetwork\n await fetchPlanMetadata(payments, planId)\n const cached = planMetadataCache.get(planId)\n return cached?.fiatProvider\n}\n\n/**\n * Resolve the x402 scheme for a plan by fetching plan metadata (cached).\n * Used in callsites that don't have a token to extract scheme from\n * (402 responses and token generation).\n *\n * @param payments - The Payments instance for API access\n * @param planId - The plan identifier\n * @param explicitScheme - Optional explicit override; returned immediately if provided\n * @returns The resolved scheme type\n */\nexport async function resolveScheme(\n payments: Payments,\n planId: string,\n explicitScheme?: X402SchemeType,\n): Promise<X402SchemeType> {\n if (explicitScheme) return explicitScheme\n const metadata = await fetchPlanMetadata(payments, planId)\n return metadata.scheme\n}\n\n/**\n * The FacilitatorAPI class provides methods to verify and settle AI agent permissions.\n * It enables AI agents to act as facilitators, managing credit verification and settlement\n * for subscribers using X402 access tokens.\n */\nexport class FacilitatorAPI extends BasePaymentsAPI {\n /**\n * Get a singleton instance of the FacilitatorAPI class.\n *\n * @param options - The options to initialize the payments class\n * @returns The instance of the FacilitatorAPI class\n */\n static getInstance(options: PaymentOptions): FacilitatorAPI {\n return new FacilitatorAPI(options)\n }\n\n /**\n * Verify if a subscriber has permission to use credits from a payment plan.\n * This method simulates the credit usage without actually burning credits,\n * checking if the subscriber has sufficient balance and permissions.\n *\n * The planId and subscriberAddress are extracted from the x402AccessToken.\n *\n * @param params - Verification parameters (see {@link VerifyPermissionsParams}).\n * - paymentRequired: x402 PaymentRequired from 402 response (required, for validation)\n * - x402AccessToken: X402 access token (contains planId, subscriberAddress, agentId)\n * - maxAmount: maximum credits to verify (optional, bigint)\n * @returns A promise that resolves to a verification result with 'isValid' boolean\n *\n * @throws PaymentsError if verification fails\n */\n async verifyPermissions(params: VerifyPermissionsParams): Promise<VerifyPermissionsResult> {\n const { paymentRequired, x402AccessToken, maxAmount } = params\n\n const url = new URL(API_URL_VERIFY_PERMISSIONS, this.environment.backend)\n\n const body: Record<string, unknown> = {\n paymentRequired,\n x402AccessToken,\n }\n\n if (maxAmount !== undefined) {\n body.maxAmount = maxAmount.toString()\n }\n\n // Send the NVM API-key auth header (Authorization: Bearer <nvmApiKey>).\n // The backend /verify endpoint runs an OPTIONAL guard that tolerates the\n // header's absence today, so this is non-breaking; it pre-positions for\n // the later strict-guard flip. See nevermined-io/nvm-monorepo#1570.\n const options = this.getBackendHTTPOptions('POST', body)\n\n try {\n const response = await fetch(url, options)\n if (!response.ok) {\n let errorMessage = 'Permission verification failed'\n let errorCode = `http_${response.status}`\n try {\n const errorData = await response.json()\n if (errorData.message) errorMessage = errorData.message\n if (errorData.code) errorCode = errorData.code\n if (errorData.hint) errorMessage = `${errorMessage} — ${errorData.hint}`\n } catch {\n // Use default error message\n }\n throw PaymentsError.fromBackend(errorMessage, {\n message: errorMessage,\n code: errorCode,\n })\n }\n return await response.json()\n } catch (error) {\n if (error instanceof PaymentsError) {\n throw error\n }\n throw PaymentsError.fromBackend('Network error during permission verification', {\n message: error instanceof Error ? error.message : String(error),\n code: 'network_error',\n })\n }\n }\n\n /**\n * Settle (burn) credits from a subscriber's payment plan.\n * This method executes the actual credit consumption, burning the specified\n * number of credits from the subscriber's balance. If the subscriber doesn't\n * have enough credits, it will attempt to order more before settling.\n *\n * The planId and subscriberAddress are extracted from the x402AccessToken.\n *\n * @param params - Settlement parameters (see {@link SettlePermissionsParams}).\n * - paymentRequired: x402 PaymentRequired from 402 response (required, for validation)\n * - x402AccessToken: X402 access token (contains planId, subscriberAddress, agentId)\n * - maxAmount: number of credits to burn (optional, bigint)\n * - agentRequestId: Agent request ID for observability tracking (optional)\n * - batch: Whether this is a batch request (optional)\n * - marginPercent: Margin percentage for credit calculation (optional)\n * @returns A promise that resolves to a settlement result with transaction details\n *\n * @throws PaymentsError if settlement fails\n */\n async settlePermissions(params: SettlePermissionsParams): Promise<SettlePermissionsResult> {\n const { paymentRequired, x402AccessToken, maxAmount, agentRequestId, batch, marginPercent } =\n params\n\n const url = new URL(API_URL_SETTLE_PERMISSIONS, this.environment.backend)\n\n const body: Record<string, unknown> = {\n paymentRequired,\n x402AccessToken,\n }\n\n if (maxAmount !== undefined) {\n body.maxAmount = maxAmount.toString()\n }\n if (agentRequestId !== undefined) {\n body.agentRequestId = agentRequestId\n }\n if (batch !== undefined) {\n body.batch = batch\n }\n if (marginPercent !== undefined) {\n body.marginPercent = marginPercent\n }\n\n // Send the NVM API-key auth header (Authorization: Bearer <nvmApiKey>).\n // The backend /settle endpoint runs an OPTIONAL guard that tolerates the\n // header's absence today, so this is non-breaking; it pre-positions for\n // the later strict-guard flip. See nevermined-io/nvm-monorepo#1570.\n const options = this.getBackendHTTPOptions('POST', body)\n\n try {\n const response = await fetch(url, options)\n if (!response.ok) {\n let errorMessage = 'Permission settlement failed'\n let errorCode = `http_${response.status}`\n try {\n const errorData = await response.json()\n if (errorData.message) errorMessage = errorData.message\n if (errorData.code) errorCode = errorData.code\n if (errorData.hint) errorMessage = `${errorMessage} — ${errorData.hint}`\n } catch {\n // Use default error message\n }\n // A spent single-use (v3) token is not a decline and not a forgery: the\n // token was valid and has already been settled once. Say what to do\n // about it, because the wrong reaction — retrying with the same token —\n // is the one a generic settlement failure invites.\n //\n // This branch covers the THROWN shape, which is the one the backend\n // uses here: `BCK.X402.0059` is catalogued as `httpStatus: 402`, so it\n // arrives as a non-2xx and `isAccessTokenAlreadyUsed` fires on the\n // PaymentsError. Verified end-to-end against staging: a second settle\n // of the same v3 token rejects with that code. Settle can also report\n // failure as 200 + `success: false` (e.g. `errorReason: \"Cannot order\n // plan\"`), but a spent token is not one of those cases — read\n // `success`/`errorReason` on the returned result for those.\n if (errorCode === X402_TOKEN_ALREADY_USED_CODE) {\n throw PaymentsError.fromBackend(errorMessage, {\n message:\n 'this x402 access token was already used. Single-use (v3) tokens are consumed by ' +\n 'their first settlement — mint a new token for this request instead of retrying ' +\n 'with the same one.',\n code: errorCode,\n })\n }\n throw PaymentsError.fromBackend(errorMessage, {\n message: errorMessage,\n code: errorCode,\n })\n }\n const result = (await response.json()) as SettlePermissionsResult\n // Settlement failure has two shapes. A refused settle (a spent v3 token,\n // a forged one) arrives as a non-2xx and throws above. A settle the\n // backend accepted but could not complete — no credits available and the\n // auto-order reverting, say — arrives as 200 with `success: false` and an\n // `errorReason`, and is returned verbatim because the reason, the billing\n // model and the credit fields are what the caller needs to react.\n //\n // Returned, but not unremarked. The settlement object does reach callers\n // (the Express middleware base64s it into `payment-response` and hands it\n // to `onAfterSettle`; the LangChain decorator stores it as\n // `lastSettlement()`), but of the in-tree consumers only the MCP paywall\n // actually branches on `success` — so a caller that forgets to check it\n // serves a request it was never paid for with nothing in the log.\n //\n // `!== true` rather than `=== false`: a 200 whose body has no `success`\n // at all is not a settlement either, and reading an absent field as a\n // success is the failure this warning exists to prevent.\n if (result?.success !== true) {\n // Deliberately claims nothing about what was charged. This layer reads\n // `success` and nothing else — it never inspects `creditsRedeemed` or\n // `orderTx` — and on a card rail an auto-order can have charged the\n // buyer before the burn failed. Saying \"no credits were burned\" here\n // would invite exactly the retry the SettlePermissionsResult docblock\n // warns against.\n console.warn(\n `[x402] settlePermissions did not settle (${result?.errorReason ?? 'no reason given'}). ` +\n 'Do not treat the request as paid. ' +\n `creditsRedeemed=${result?.creditsRedeemed ?? 'unset'}, orderTx=${result?.orderTx ?? 'none'}, ` +\n `billingModel=${result?.billingModel ?? 'unset'} — read those before assuming nothing was charged.`,\n )\n }\n return result\n } catch (error) {\n if (error instanceof PaymentsError) {\n throw error\n }\n throw PaymentsError.fromBackend('Network error during permission settlement', {\n message: error instanceof Error ? error.message : String(error),\n code: 'network_error',\n })\n }\n }\n}\n"]}
|
package/dist/x402/index.d.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* X402 API module for token generation and facilitator operations.
|
|
3
3
|
*/
|
|
4
4
|
export { X402TokenAPI } from './token.js';
|
|
5
|
+
export { detectAccessTokenVersion, isSingleUseAccessToken, isAccessTokenAlreadyUsed, X402_TOKEN_ALREADY_USED_CODE, } from './token-version.js';
|
|
5
6
|
export { FacilitatorAPI, buildPaymentRequired, resolveNetwork, resolveScheme, } from './facilitator-api.js';
|
|
6
|
-
export type { X402Resource, X402SchemeExtra, X402Scheme, X402PaymentRequired, X402PaymentAccepted, VerifyPermissionsParams, VerifyPermissionsResult, SettlePermissionsParams, SettlePermissionsResult, } from './facilitator-api.js';
|
|
7
|
+
export type { X402Resource, X402SchemeExtra, X402Scheme, X402PaymentRequired, X402PaymentAccepted, VerifyPermissionsParams, VerifyPermissionsResult, SettlePermissionsParams, SettlePermissionsResult, X402BillingModel, } from './facilitator-api.js';
|
|
7
8
|
export { DelegationAPI } from './delegation-api.js';
|
|
8
9
|
export type { CardProvider, DelegationProvider, PaymentMethodSummary, UpdatePaymentMethodDto, DelegationSummary, DelegationListResponse, PurchasingPower, ListOptions, } from './delegation-api.js';
|
|
9
|
-
export type { X402SchemeType, DelegationConfig, DelegationCurrency, CreateDelegationPayload, CreateDelegationResponse, X402TokenOptions, } from '../common/types.js';
|
|
10
|
+
export type { X402SchemeType, DelegationConfig, DelegationCurrency, CreateDelegationPayload, CreateDelegationResponse, X402TokenOptions, MppTokenOptions, X402TokenResource, X402TokenVersion, } from '../common/types.js';
|
|
10
11
|
export { X402_SCHEME_NETWORKS, getDefaultNetwork, isValidScheme } from '../common/types.js';
|
|
11
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/x402/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/x402/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,aAAa,GACd,MAAM,sBAAsB,CAAA;AAC7B,YAAY,EAEV,YAAY,EACZ,eAAe,EACf,UAAU,EACV,mBAAmB,EACnB,mBAAmB,EAEnB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/x402/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,aAAa,GACd,MAAM,sBAAsB,CAAA;AAC7B,YAAY,EAEV,YAAY,EACZ,eAAe,EACf,UAAU,EACV,mBAAmB,EACnB,mBAAmB,EAEnB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,iBAAiB,EACjB,sBAAsB,EACtB,eAAe,EACf,WAAW,GACZ,MAAM,qBAAqB,CAAA;AAG5B,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
|
package/dist/x402/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* X402 API module for token generation and facilitator operations.
|
|
3
3
|
*/
|
|
4
4
|
export { X402TokenAPI } from './token.js';
|
|
5
|
+
export { detectAccessTokenVersion, isSingleUseAccessToken, isAccessTokenAlreadyUsed, X402_TOKEN_ALREADY_USED_CODE, } from './token-version.js';
|
|
5
6
|
export { FacilitatorAPI, buildPaymentRequired, resolveNetwork, resolveScheme, } from './facilitator-api.js';
|
|
6
7
|
// Delegation exports
|
|
7
8
|
export { DelegationAPI } from './delegation-api.js';
|
package/dist/x402/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/x402/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,aAAa,GACd,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/x402/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,aAAa,GACd,MAAM,sBAAsB,CAAA;AAgB7B,qBAAqB;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAwBnD,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA","sourcesContent":["/**\n * X402 API module for token generation and facilitator operations.\n */\n\nexport { X402TokenAPI } from './token.js'\nexport {\n detectAccessTokenVersion,\n isSingleUseAccessToken,\n isAccessTokenAlreadyUsed,\n X402_TOKEN_ALREADY_USED_CODE,\n} from './token-version.js'\nexport {\n FacilitatorAPI,\n buildPaymentRequired,\n resolveNetwork,\n resolveScheme,\n} from './facilitator-api.js'\nexport type {\n // x402 types\n X402Resource,\n X402SchemeExtra,\n X402Scheme,\n X402PaymentRequired,\n X402PaymentAccepted,\n // Facilitator params and results\n VerifyPermissionsParams,\n VerifyPermissionsResult,\n SettlePermissionsParams,\n SettlePermissionsResult,\n X402BillingModel,\n} from './facilitator-api.js'\n\n// Delegation exports\nexport { DelegationAPI } from './delegation-api.js'\nexport type {\n CardProvider,\n DelegationProvider,\n PaymentMethodSummary,\n UpdatePaymentMethodDto,\n DelegationSummary,\n DelegationListResponse,\n PurchasingPower,\n ListOptions,\n} from './delegation-api.js'\n\n// Scheme and delegation types\nexport type {\n X402SchemeType,\n DelegationConfig,\n DelegationCurrency,\n CreateDelegationPayload,\n CreateDelegationResponse,\n X402TokenOptions,\n MppTokenOptions,\n X402TokenResource,\n X402TokenVersion,\n} from '../common/types.js'\nexport { X402_SCHEME_NETWORKS, getDefaultNetwork, isValidScheme } from '../common/types.js'\n"]}
|
|
@@ -11,5 +11,13 @@ export declare function buildX402TokenRequestBody(params: {
|
|
|
11
11
|
agentId?: string;
|
|
12
12
|
tokenOptions?: X402TokenOptions;
|
|
13
13
|
environmentName: EnvironmentName;
|
|
14
|
+
/**
|
|
15
|
+
* Which mint this body is for. MPP has no token version at all — it is not
|
|
16
|
+
* "x402 v2 by another name": the two protocols stopped sharing a version
|
|
17
|
+
* ladder (nvm-monorepo#3266), and `MppService.createPermission` refuses ANY
|
|
18
|
+
* `tokenVersion` with `BCK.MPP.0007`, `2` included. Sending one would 400 the
|
|
19
|
+
* mint, so this builder refuses it here, where the caller can be told why.
|
|
20
|
+
*/
|
|
21
|
+
protocol?: 'x402' | 'mpp';
|
|
14
22
|
}): Record<string, any>;
|
|
15
23
|
//# sourceMappingURL=token-request.d.ts.map
|
|
@@ -1 +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;
|
|
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;IAChC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CAC1B,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAgLtB"}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { PaymentsError } from '../common/payments.error.js';
|
|
8
8
|
import { getDefaultNetwork } from '../common/types.js';
|
|
9
9
|
export function buildX402TokenRequestBody(params) {
|
|
10
|
-
const { planId, agentId, tokenOptions, environmentName } = params;
|
|
10
|
+
const { planId, agentId, tokenOptions, environmentName, protocol = 'x402' } = params;
|
|
11
11
|
const scheme = tokenOptions?.scheme ?? 'nvm:erc4337';
|
|
12
12
|
const network = tokenOptions?.network ?? getDefaultNetwork(scheme, environmentName);
|
|
13
13
|
// Validate delegationConfig is provided — the backend requires it for token generation
|
|
@@ -48,19 +48,119 @@ export function buildX402TokenRequestBody(params) {
|
|
|
48
48
|
'Create the delegation first with payments.delegation.createDelegation(), ' +
|
|
49
49
|
'then request the token with delegationConfig: { delegationId }.');
|
|
50
50
|
}
|
|
51
|
+
// `resource` and `httpVerb` are what a v3 token is bound to: they go inside
|
|
52
|
+
// the EIP-712 signature next to `agentId` and the one-time nonce, so a v3
|
|
53
|
+
// token minted for one seller endpoint cannot be presented to another.
|
|
54
|
+
//
|
|
55
|
+
// On a v1/v2 token they are outside the signature — but NOT inert. The
|
|
56
|
+
// backend compares a token's `resource.url` against the seller's
|
|
57
|
+
// `paymentRequired.resource.url` (origin + path, exact string when it cannot
|
|
58
|
+
// parse either as a URL) for ANY token that carries one, so a v2 token bound
|
|
59
|
+
// to a URL the seller does not advertise verbatim fails verification with
|
|
60
|
+
// `BCK.X402.0013`. Measured against staging, not inferred.
|
|
61
|
+
//
|
|
62
|
+
// They are still forwarded when the caller supplies them — this is the
|
|
63
|
+
// low-level builder, and a caller that names a resource means it (binding a
|
|
64
|
+
// v2 token to the URL the seller really advertises is valid, and once the
|
|
65
|
+
// backend default flips to v3 the binding is needed without anyone asking for
|
|
66
|
+
// a version). What the builder will not do is let that happen silently: see
|
|
67
|
+
// the warning below.
|
|
68
|
+
//
|
|
69
|
+
// Omitted rather than sent empty when the caller did not supply them: a field
|
|
70
|
+
// absent at mint is signed as the empty string AND must stay absent from the
|
|
71
|
+
// unsigned envelope, so sending `{ url: '' }` and sending nothing must not
|
|
72
|
+
// diverge — which is why the guard below tests `resource?.url`, not the
|
|
73
|
+
// object.
|
|
74
|
+
const { resource, tokenVersion } = tokenOptions;
|
|
75
|
+
// Normalized here, once, rather than in each consumer: sellers advertise
|
|
76
|
+
// `req.method`, i.e. upper case, and `httpVerb: 'post'` mints a token that
|
|
77
|
+
// can never match. Both in-tree consumers already upper-cased it themselves;
|
|
78
|
+
// a direct SDK caller had no such defence.
|
|
79
|
+
const httpVerb = tokenOptions.httpVerb?.toUpperCase();
|
|
80
|
+
const boundUrl = resource?.url;
|
|
81
|
+
// A binding on a token that is not v3 is legal but rarely intended, and its
|
|
82
|
+
// failure mode (verify rejecting with BCK.X402.0013 at the seller) points
|
|
83
|
+
// nowhere near this call. Say so once, here, where the cause is visible.
|
|
84
|
+
//
|
|
85
|
+
// MPP gets the same warning for the same reason: the MPP mint accepts
|
|
86
|
+
// `resource`/`httpVerb` (its DTO is `OmitType(GenerateX402TokenDto,
|
|
87
|
+
// ['tokenVersion'])`, so only the version is refused) and forwards them into
|
|
88
|
+
// the same comparison — but MPP has no v3 to opt into, so a binding there is
|
|
89
|
+
// never signed and can only narrow what the credential verifies against.
|
|
90
|
+
if (boundUrl || httpVerb) {
|
|
91
|
+
// A binding on anything but a v3 mint has no good outcome, so it is refused
|
|
92
|
+
// rather than warned about — the same answer payments-py gives
|
|
93
|
+
// (`token_request.py`). The presence of `resource.url` is what arms the
|
|
94
|
+
// backend's endpoint allowlist (`erc4337-scheme.handler.ts`; without it the
|
|
95
|
+
// backend logs "resource.url not provided in token … skipping endpoint
|
|
96
|
+
// validation"), while a v2 signature binds nothing — so the caller either
|
|
97
|
+
// arms a check they did not configure (`BCK.PROTOCOL.0031` for an agent
|
|
98
|
+
// registered with `endpoints`) or fails verification later with
|
|
99
|
+
// `BCK.X402.0013`. Neither is worth a log line nobody reads at mint time.
|
|
100
|
+
//
|
|
101
|
+
// This covers an OMITTED `tokenVersion` as well as an explicit `2`, because
|
|
102
|
+
// today they mint the same thing: `core-kit`'s minter is
|
|
103
|
+
// `(options.tokenVersion ?? 2) === 3`, the handler adds no default, and no
|
|
104
|
+
// versioning gate fills the field in — verified against a live mint, which
|
|
105
|
+
// comes back v2. Once nvm-monorepo#3292 makes v3 the default (it is open at
|
|
106
|
+
// the time of writing), the omitted case becomes the shape the backend is
|
|
107
|
+
// moving to and this guard should narrow to `tokenVersion === 2`.
|
|
108
|
+
if (protocol === 'x402' && tokenVersion !== 3) {
|
|
109
|
+
throw PaymentsError.validation(`resource/httpVerb were supplied with tokenVersion ${tokenVersion ?? '(omitted, defaults to 2)'}. ` +
|
|
110
|
+
'Only a v3 token signs them, so the binding cannot hold — but the backend still ' +
|
|
111
|
+
'compares the resource against what the seller advertises, and arms its endpoint ' +
|
|
112
|
+
'allowlist on it. Pass tokenVersion: 3 to bind the token, or drop resource/httpVerb.');
|
|
113
|
+
}
|
|
114
|
+
if (protocol === 'mpp') {
|
|
115
|
+
// MPP is warned, not refused: it has no v3 to opt into, so there is no
|
|
116
|
+
// "correct" version to point the caller at, and the binding still narrows
|
|
117
|
+
// what the credential verifies against rather than arming a signature
|
|
118
|
+
// that cannot hold.
|
|
119
|
+
console.warn('[mpp] resource/httpVerb were supplied on an MPP mint. MPP tokens carry no version and ' +
|
|
120
|
+
'nothing is signed over them, but the backend still compares the resource against what ' +
|
|
121
|
+
'the seller advertises — a mismatch fails the credential. Omit them unless the string ' +
|
|
122
|
+
'matches the seller exactly.');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// MPP's single-use unit is the CHALLENGE, not the token: one MPP access token
|
|
126
|
+
// is presented across many challenges by design, so the x402 v3 per-token
|
|
127
|
+
// nonce would kill every buyer's second challenge. The backend enforces this
|
|
128
|
+
// by refusing the field outright; refuse it here too rather than let the
|
|
129
|
+
// caller discover it as a 400 whose cause is a field they set two layers up.
|
|
130
|
+
if (protocol === 'mpp' && tokenVersion !== undefined) {
|
|
131
|
+
throw PaymentsError.validation('tokenVersion is not supported on MPP access tokens: MPP and x402 no longer share a ' +
|
|
132
|
+
'token version ladder, and the backend refuses any tokenVersion on an MPP mint ' +
|
|
133
|
+
'(BCK.MPP.0007). Omit the field — an MPP token is reusable across challenges, and the ' +
|
|
134
|
+
'challenge is what is single-use.');
|
|
135
|
+
}
|
|
51
136
|
// Build x402-aligned request body
|
|
52
137
|
return {
|
|
138
|
+
...(boundUrl && { resource }),
|
|
53
139
|
accepted: {
|
|
54
140
|
scheme,
|
|
55
141
|
network,
|
|
56
142
|
planId,
|
|
57
143
|
extra: {
|
|
58
144
|
...(agentId && { agentId }),
|
|
145
|
+
...(httpVerb && { httpVerb }),
|
|
59
146
|
},
|
|
60
147
|
},
|
|
61
148
|
// Add delegation config for both erc4337 and card-delegation schemes.
|
|
62
149
|
// delegationConfig is guaranteed present here (the absence check above throws).
|
|
63
150
|
delegationConfig: tokenOptions.delegationConfig,
|
|
151
|
+
// Opt-in only, and x402-only (the MPP guard above has already thrown).
|
|
152
|
+
// Left out entirely when unset so a v2 mint stays byte-identical to what it
|
|
153
|
+
// was before v3 existed. A backend that predates the v3 struct drops this
|
|
154
|
+
// field silently (ValidationPipe whitelists without forbidNonWhitelisted)
|
|
155
|
+
// and returns v2 — which is why no caller may infer the version from what
|
|
156
|
+
// it asked for. See detectAccessTokenVersion().
|
|
157
|
+
//
|
|
158
|
+
// `tokenVersion: 3` WITHOUT a resource is deliberately allowed, not an
|
|
159
|
+
// oversight: the backend signs an absent binding member as the empty
|
|
160
|
+
// string, so the result is a single-use token bound to nothing. That is a
|
|
161
|
+
// supported mode — single-use without seller binding — and the only one
|
|
162
|
+
// available when the caller does not know the seller's advertised URL.
|
|
163
|
+
...(tokenVersion !== undefined && { tokenVersion }),
|
|
64
164
|
};
|
|
65
165
|
}
|
|
66
166
|
//# sourceMappingURL=token-request.js.map
|
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"file":"token-request.js","sourceRoot":"","sources":["../../src/x402/token-request.ts"],"names":[],"mappings":"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","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 /**\n * Which mint this body is for. MPP has no token version at all — it is not\n * \"x402 v2 by another name\": the two protocols stopped sharing a version\n * ladder (nvm-monorepo#3266), and `MppService.createPermission` refuses ANY\n * `tokenVersion` with `BCK.MPP.0007`, `2` included. Sending one would 400 the\n * mint, so this builder refuses it here, where the caller can be told why.\n */\n protocol?: 'x402' | 'mpp'\n}): Record<string, any> {\n const { planId, agentId, tokenOptions, environmentName, protocol = 'x402' } = 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 // `resource` and `httpVerb` are what a v3 token is bound to: they go inside\n // the EIP-712 signature next to `agentId` and the one-time nonce, so a v3\n // token minted for one seller endpoint cannot be presented to another.\n //\n // On a v1/v2 token they are outside the signature — but NOT inert. The\n // backend compares a token's `resource.url` against the seller's\n // `paymentRequired.resource.url` (origin + path, exact string when it cannot\n // parse either as a URL) for ANY token that carries one, so a v2 token bound\n // to a URL the seller does not advertise verbatim fails verification with\n // `BCK.X402.0013`. Measured against staging, not inferred.\n //\n // They are still forwarded when the caller supplies them — this is the\n // low-level builder, and a caller that names a resource means it (binding a\n // v2 token to the URL the seller really advertises is valid, and once the\n // backend default flips to v3 the binding is needed without anyone asking for\n // a version). What the builder will not do is let that happen silently: see\n // the warning below.\n //\n // Omitted rather than sent empty when the caller did not supply them: a field\n // absent at mint is signed as the empty string AND must stay absent from the\n // unsigned envelope, so sending `{ url: '' }` and sending nothing must not\n // diverge — which is why the guard below tests `resource?.url`, not the\n // object.\n const { resource, tokenVersion } = tokenOptions\n // Normalized here, once, rather than in each consumer: sellers advertise\n // `req.method`, i.e. upper case, and `httpVerb: 'post'` mints a token that\n // can never match. Both in-tree consumers already upper-cased it themselves;\n // a direct SDK caller had no such defence.\n const httpVerb = tokenOptions.httpVerb?.toUpperCase()\n const boundUrl = resource?.url\n\n // A binding on a token that is not v3 is legal but rarely intended, and its\n // failure mode (verify rejecting with BCK.X402.0013 at the seller) points\n // nowhere near this call. Say so once, here, where the cause is visible.\n //\n // MPP gets the same warning for the same reason: the MPP mint accepts\n // `resource`/`httpVerb` (its DTO is `OmitType(GenerateX402TokenDto,\n // ['tokenVersion'])`, so only the version is refused) and forwards them into\n // the same comparison — but MPP has no v3 to opt into, so a binding there is\n // never signed and can only narrow what the credential verifies against.\n if (boundUrl || httpVerb) {\n // A binding on anything but a v3 mint has no good outcome, so it is refused\n // rather than warned about — the same answer payments-py gives\n // (`token_request.py`). The presence of `resource.url` is what arms the\n // backend's endpoint allowlist (`erc4337-scheme.handler.ts`; without it the\n // backend logs \"resource.url not provided in token … skipping endpoint\n // validation\"), while a v2 signature binds nothing — so the caller either\n // arms a check they did not configure (`BCK.PROTOCOL.0031` for an agent\n // registered with `endpoints`) or fails verification later with\n // `BCK.X402.0013`. Neither is worth a log line nobody reads at mint time.\n //\n // This covers an OMITTED `tokenVersion` as well as an explicit `2`, because\n // today they mint the same thing: `core-kit`'s minter is\n // `(options.tokenVersion ?? 2) === 3`, the handler adds no default, and no\n // versioning gate fills the field in — verified against a live mint, which\n // comes back v2. Once nvm-monorepo#3292 makes v3 the default (it is open at\n // the time of writing), the omitted case becomes the shape the backend is\n // moving to and this guard should narrow to `tokenVersion === 2`.\n if (protocol === 'x402' && tokenVersion !== 3) {\n throw PaymentsError.validation(\n `resource/httpVerb were supplied with tokenVersion ${tokenVersion ?? '(omitted, defaults to 2)'}. ` +\n 'Only a v3 token signs them, so the binding cannot hold — but the backend still ' +\n 'compares the resource against what the seller advertises, and arms its endpoint ' +\n 'allowlist on it. Pass tokenVersion: 3 to bind the token, or drop resource/httpVerb.',\n )\n }\n if (protocol === 'mpp') {\n // MPP is warned, not refused: it has no v3 to opt into, so there is no\n // \"correct\" version to point the caller at, and the binding still narrows\n // what the credential verifies against rather than arming a signature\n // that cannot hold.\n console.warn(\n '[mpp] resource/httpVerb were supplied on an MPP mint. MPP tokens carry no version and ' +\n 'nothing is signed over them, but the backend still compares the resource against what ' +\n 'the seller advertises — a mismatch fails the credential. Omit them unless the string ' +\n 'matches the seller exactly.',\n )\n }\n }\n\n // MPP's single-use unit is the CHALLENGE, not the token: one MPP access token\n // is presented across many challenges by design, so the x402 v3 per-token\n // nonce would kill every buyer's second challenge. The backend enforces this\n // by refusing the field outright; refuse it here too rather than let the\n // caller discover it as a 400 whose cause is a field they set two layers up.\n if (protocol === 'mpp' && tokenVersion !== undefined) {\n throw PaymentsError.validation(\n 'tokenVersion is not supported on MPP access tokens: MPP and x402 no longer share a ' +\n 'token version ladder, and the backend refuses any tokenVersion on an MPP mint ' +\n '(BCK.MPP.0007). Omit the field — an MPP token is reusable across challenges, and the ' +\n 'challenge is what is single-use.',\n )\n }\n\n // Build x402-aligned request body\n return {\n ...(boundUrl && { resource }),\n accepted: {\n scheme,\n network,\n planId,\n extra: {\n ...(agentId && { agentId }),\n ...(httpVerb && { httpVerb }),\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 // Opt-in only, and x402-only (the MPP guard above has already thrown).\n // Left out entirely when unset so a v2 mint stays byte-identical to what it\n // was before v3 existed. A backend that predates the v3 struct drops this\n // field silently (ValidationPipe whitelists without forbidNonWhitelisted)\n // and returns v2 — which is why no caller may infer the version from what\n // it asked for. See detectAccessTokenVersion().\n //\n // `tokenVersion: 3` WITHOUT a resource is deliberately allowed, not an\n // oversight: the backend signs an absent binding member as the empty\n // string, so the result is a single-use token bound to nothing. That is a\n // supported mode — single-use without seller binding — and the only one\n // available when the caller does not know the seller's advertised URL.\n ...(tokenVersion !== undefined && { tokenVersion }),\n }\n}\n"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detecting the EIP-712 version of a minted x402 access token.
|
|
3
|
+
*
|
|
4
|
+
* The version is a property of the token you were handed, never of the one you
|
|
5
|
+
* asked for. The backend's `ValidationPipe` runs with `whitelist: true` and
|
|
6
|
+
* without `forbidNonWhitelisted`, so a `tokenVersion: 3` sent to a deployment
|
|
7
|
+
* that predates the v3 struct is dropped **without an error** and a v2 token
|
|
8
|
+
* comes back. Branching on the requested version would then silently treat a
|
|
9
|
+
* reusable v2 token as single-use (harmless) or — far worse, once the default
|
|
10
|
+
* flips — a single-use v3 token as reusable, replaying a spent nonce on every
|
|
11
|
+
* call after the first.
|
|
12
|
+
*
|
|
13
|
+
* The discriminator is `payload.authorization.nonce`: v3 is the first version
|
|
14
|
+
* to carry one, and it is exactly what makes the token single-use.
|
|
15
|
+
*/
|
|
16
|
+
import type { X402TokenVersion } from '../common/types.js';
|
|
17
|
+
/**
|
|
18
|
+
* The EIP-712 version the given access token was actually signed under.
|
|
19
|
+
*
|
|
20
|
+
* `3` when `payload.authorization.nonce` is a non-empty string, `2` otherwise.
|
|
21
|
+
*
|
|
22
|
+
* A token that cannot be decoded also reads as `2`. That is the conservative
|
|
23
|
+
* answer for a *version* question — v2 is what every pre-v3 token and every
|
|
24
|
+
* non-Nevermined blob already is — but note it is the permissive answer for the
|
|
25
|
+
* *reuse* question a caller asks next: `2` means "safe to cache and replay".
|
|
26
|
+
* The gap is unreachable today, since an undecodable token is unusable anyway
|
|
27
|
+
* (the backend rejects it on the first presentation, so there is nothing to
|
|
28
|
+
* replay), but a future change to the envelope format would turn every v3 token
|
|
29
|
+
* into a cached one here. Any such change must revisit this function first.
|
|
30
|
+
*
|
|
31
|
+
* Note this is deliberately NOT the same as trusting the token: nothing here
|
|
32
|
+
* is verified. It answers "which settle semantics does this token have", which
|
|
33
|
+
* only the backend's signature check ultimately enforces.
|
|
34
|
+
*
|
|
35
|
+
* @param accessToken - The base64-encoded x402 access token
|
|
36
|
+
* @returns `3` for a single-use, seller/resource-bound token, else `2`
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const { accessToken } = await payments.x402.getX402AccessToken(planId, agentId, {
|
|
41
|
+
* delegationConfig: { delegationId },
|
|
42
|
+
* // The string the seller advertises, not the URL you fetch.
|
|
43
|
+
* resource: { url: '/api/v1/tasks' },
|
|
44
|
+
* httpVerb: 'POST',
|
|
45
|
+
* tokenVersion: 3,
|
|
46
|
+
* })
|
|
47
|
+
* if (detectAccessTokenVersion(accessToken) === 3) {
|
|
48
|
+
* // Single-use: mint a new one for the next paid request.
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function detectAccessTokenVersion(accessToken: string): X402TokenVersion;
|
|
53
|
+
/**
|
|
54
|
+
* Whether the token is consumed by its first successful settlement.
|
|
55
|
+
*
|
|
56
|
+
* True exactly for v3 tokens. A caller that caches an access token must test
|
|
57
|
+
* this before reusing one: settling a spent v3 token fails with
|
|
58
|
+
* `BCK.X402.0059` (see {@link isAccessTokenAlreadyUsed}).
|
|
59
|
+
*
|
|
60
|
+
* @param accessToken - The base64-encoded x402 access token
|
|
61
|
+
*/
|
|
62
|
+
export declare function isSingleUseAccessToken(accessToken: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Backend code for "this access token was already used".
|
|
65
|
+
*
|
|
66
|
+
* Returned by `POST /x402/settle` when a v3 token is settled a second time.
|
|
67
|
+
* Deliberately not forgery-gated on the backend: it says plainly that the
|
|
68
|
+
* token was spent, so a client can tell a **spent** token apart from a
|
|
69
|
+
* declined one (insufficient credits, wrong plan) and from a **forged** one
|
|
70
|
+
* (`BCK.X402.0005`, an envelope edited to disagree with the signature).
|
|
71
|
+
*/
|
|
72
|
+
export declare const X402_TOKEN_ALREADY_USED_CODE = "BCK.X402.0059";
|
|
73
|
+
/**
|
|
74
|
+
* Whether an error is the backend refusing a second settlement of the same
|
|
75
|
+
* single-use token.
|
|
76
|
+
*
|
|
77
|
+
* The one correct response is to **mint a new token** — a retry that replays
|
|
78
|
+
* the same token fails identically forever. Never treat this as a transient
|
|
79
|
+
* settlement failure.
|
|
80
|
+
*
|
|
81
|
+
* @param error - Any thrown value; typically a `PaymentsError` from
|
|
82
|
+
* `payments.facilitator.settlePermissions`
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* try {
|
|
87
|
+
* await payments.facilitator.settlePermissions({ paymentRequired, x402AccessToken })
|
|
88
|
+
* } catch (error) {
|
|
89
|
+
* if (isAccessTokenAlreadyUsed(error)) {
|
|
90
|
+
* // Re-mint, do not replay.
|
|
91
|
+
* }
|
|
92
|
+
* throw error
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export declare function isAccessTokenAlreadyUsed(error: unknown): boolean;
|
|
97
|
+
//# sourceMappingURL=token-version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-version.d.ts","sourceRoot":"","sources":["../../src/x402/token-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAgB1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAU9E;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,kBAAkB,CAAA;AAE3D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGhE"}
|