UNPKG

@payai/x402

Version:

PayAI-distributed wrapper for @x402/core v2

660 lines (659 loc) 22.1 kB
import { HTTPFacilitatorClient, RouteConfigurationError, x402HTTPResourceServer } from "../chunk-CWIHZ5RE.mjs"; import { x402Version } from "../chunk-VE37GDG2.mjs"; import { SettleError, VerifyError } from "../chunk-L2RJI3BI.mjs"; import { deepEqual, findByNetworkAndScheme } from "../chunk-TDLQZ6MP.mjs"; import "../chunk-BJTO5JO5.mjs"; // src/server/x402ResourceServer.ts var x402ResourceServer = class { /** * Creates a new x402ResourceServer instance. * * @param facilitatorClients - Optional facilitator client(s) for payment processing */ constructor(facilitatorClients) { this.registeredServerSchemes = /* @__PURE__ */ new Map(); this.supportedResponsesMap = /* @__PURE__ */ new Map(); this.facilitatorClientsMap = /* @__PURE__ */ new Map(); this.registeredExtensions = /* @__PURE__ */ new Map(); this.beforeVerifyHooks = []; this.afterVerifyHooks = []; this.onVerifyFailureHooks = []; this.beforeSettleHooks = []; this.afterSettleHooks = []; this.onSettleFailureHooks = []; if (!facilitatorClients) { this.facilitatorClients = [new HTTPFacilitatorClient()]; } else if (Array.isArray(facilitatorClients)) { this.facilitatorClients = facilitatorClients.length > 0 ? facilitatorClients : [new HTTPFacilitatorClient()]; } else { this.facilitatorClients = [facilitatorClients]; } } /** * Register a scheme/network server implementation. * * @param network - The network identifier * @param server - The scheme/network server implementation * @returns The x402ResourceServer instance for chaining */ register(network, server) { if (!this.registeredServerSchemes.has(network)) { this.registeredServerSchemes.set(network, /* @__PURE__ */ new Map()); } const serverByScheme = this.registeredServerSchemes.get(network); if (!serverByScheme.has(server.scheme)) { serverByScheme.set(server.scheme, server); } return this; } /** * Check if a scheme is registered for a given network. * * @param network - The network identifier * @param scheme - The payment scheme name * @returns True if the scheme is registered for the network, false otherwise */ hasRegisteredScheme(network, scheme) { return !!findByNetworkAndScheme(this.registeredServerSchemes, scheme, network); } /** * Registers a resource service extension that can enrich extension declarations. * * @param extension - The extension to register * @returns The x402ResourceServer instance for chaining */ registerExtension(extension) { this.registeredExtensions.set(extension.key, extension); return this; } /** * Check if an extension is registered. * * @param key - The extension key * @returns True if the extension is registered */ hasExtension(key) { return this.registeredExtensions.has(key); } /** * Get all registered extensions. * * @returns Array of registered extensions */ getExtensions() { return Array.from(this.registeredExtensions.values()); } /** * Enriches declared extensions using registered extension hooks. * * @param declaredExtensions - Extensions declared on the route * @param transportContext - Transport-specific context (HTTP, A2A, MCP, etc.) * @returns Enriched extensions map */ enrichExtensions(declaredExtensions, transportContext) { const enriched = {}; for (const [key, declaration] of Object.entries(declaredExtensions)) { const extension = this.registeredExtensions.get(key); if (extension?.enrichDeclaration) { enriched[key] = extension.enrichDeclaration(declaration, transportContext); } else { enriched[key] = declaration; } } return enriched; } /** * Register a hook to execute before payment verification. * Can abort verification by returning { abort: true, reason: string } * * @param hook - The hook function to register * @returns The x402ResourceServer instance for chaining */ onBeforeVerify(hook) { this.beforeVerifyHooks.push(hook); return this; } /** * Register a hook to execute after successful payment verification. * * @param hook - The hook function to register * @returns The x402ResourceServer instance for chaining */ onAfterVerify(hook) { this.afterVerifyHooks.push(hook); return this; } /** * Register a hook to execute when payment verification fails. * Can recover from failure by returning { recovered: true, result: VerifyResponse } * * @param hook - The hook function to register * @returns The x402ResourceServer instance for chaining */ onVerifyFailure(hook) { this.onVerifyFailureHooks.push(hook); return this; } /** * Register a hook to execute before payment settlement. * Can abort settlement by returning { abort: true, reason: string } * * @param hook - The hook function to register * @returns The x402ResourceServer instance for chaining */ onBeforeSettle(hook) { this.beforeSettleHooks.push(hook); return this; } /** * Register a hook to execute after successful payment settlement. * * @param hook - The hook function to register * @returns The x402ResourceServer instance for chaining */ onAfterSettle(hook) { this.afterSettleHooks.push(hook); return this; } /** * Register a hook to execute when payment settlement fails. * Can recover from failure by returning { recovered: true, result: SettleResponse } * * @param hook - The hook function to register * @returns The x402ResourceServer instance for chaining */ onSettleFailure(hook) { this.onSettleFailureHooks.push(hook); return this; } /** * Initialize by fetching supported kinds from all facilitators * Creates mappings for supported responses and facilitator clients * Earlier facilitators in the array get precedence */ async initialize() { this.supportedResponsesMap.clear(); this.facilitatorClientsMap.clear(); for (const facilitatorClient of this.facilitatorClients) { try { const supported = await facilitatorClient.getSupported(); for (const kind of supported.kinds) { const x402Version2 = kind.x402Version; if (!this.supportedResponsesMap.has(x402Version2)) { this.supportedResponsesMap.set(x402Version2, /* @__PURE__ */ new Map()); } const responseVersionMap = this.supportedResponsesMap.get(x402Version2); if (!this.facilitatorClientsMap.has(x402Version2)) { this.facilitatorClientsMap.set(x402Version2, /* @__PURE__ */ new Map()); } const clientVersionMap = this.facilitatorClientsMap.get(x402Version2); if (!responseVersionMap.has(kind.network)) { responseVersionMap.set(kind.network, /* @__PURE__ */ new Map()); } const responseNetworkMap = responseVersionMap.get(kind.network); if (!clientVersionMap.has(kind.network)) { clientVersionMap.set(kind.network, /* @__PURE__ */ new Map()); } const clientNetworkMap = clientVersionMap.get(kind.network); if (!responseNetworkMap.has(kind.scheme)) { responseNetworkMap.set(kind.scheme, supported); clientNetworkMap.set(kind.scheme, facilitatorClient); } } } catch (error) { console.warn(`Failed to fetch supported kinds from facilitator: ${error}`); } } } /** * Get supported kind for a specific version, network, and scheme * * @param x402Version - The x402 version * @param network - The network identifier * @param scheme - The payment scheme * @returns The supported kind or undefined if not found */ getSupportedKind(x402Version2, network, scheme) { const versionMap = this.supportedResponsesMap.get(x402Version2); if (!versionMap) return void 0; const supportedResponse = findByNetworkAndScheme(versionMap, scheme, network); if (!supportedResponse) return void 0; return supportedResponse.kinds.find( (kind) => kind.x402Version === x402Version2 && kind.network === network && kind.scheme === scheme ); } /** * Get facilitator extensions for a specific version, network, and scheme * * @param x402Version - The x402 version * @param network - The network identifier * @param scheme - The payment scheme * @returns The facilitator extensions or empty array if not found */ getFacilitatorExtensions(x402Version2, network, scheme) { const versionMap = this.supportedResponsesMap.get(x402Version2); if (!versionMap) return []; const supportedResponse = findByNetworkAndScheme(versionMap, scheme, network); return supportedResponse?.extensions || []; } /** * Build payment requirements for a protected resource * * @param resourceConfig - Configuration for the protected resource * @returns Array of payment requirements */ async buildPaymentRequirements(resourceConfig) { const requirements = []; const scheme = resourceConfig.scheme; const SchemeNetworkServer = findByNetworkAndScheme( this.registeredServerSchemes, scheme, resourceConfig.network ); if (!SchemeNetworkServer) { console.warn( `No server implementation registered for scheme: ${scheme}, network: ${resourceConfig.network}` ); return requirements; } const supportedKind = this.getSupportedKind( x402Version, resourceConfig.network, SchemeNetworkServer.scheme ); if (!supportedKind) { throw new Error( `Facilitator does not support ${SchemeNetworkServer.scheme} on ${resourceConfig.network}. Make sure to call initialize() to fetch supported kinds from facilitators.` ); } const facilitatorExtensions = this.getFacilitatorExtensions( x402Version, resourceConfig.network, SchemeNetworkServer.scheme ); const parsedPrice = await SchemeNetworkServer.parsePrice( resourceConfig.price, resourceConfig.network ); const baseRequirements = { scheme: SchemeNetworkServer.scheme, network: resourceConfig.network, amount: parsedPrice.amount, asset: parsedPrice.asset, payTo: resourceConfig.payTo, maxTimeoutSeconds: resourceConfig.maxTimeoutSeconds || 300, // Default 5 minutes extra: { ...parsedPrice.extra } }; const requirement = await SchemeNetworkServer.enhancePaymentRequirements( baseRequirements, { ...supportedKind, x402Version }, facilitatorExtensions ); requirements.push(requirement); return requirements; } /** * Build payment requirements from multiple payment options * This method handles resolving dynamic payTo/price functions and builds requirements for each option * * @param paymentOptions - Array of payment options to convert * @param context - HTTP request context for resolving dynamic functions * @returns Array of payment requirements (one per option) */ async buildPaymentRequirementsFromOptions(paymentOptions, context) { const allRequirements = []; for (const option of paymentOptions) { const resolvedPayTo = typeof option.payTo === "function" ? await option.payTo(context) : option.payTo; const resolvedPrice = typeof option.price === "function" ? await option.price(context) : option.price; const resourceConfig = { scheme: option.scheme, payTo: resolvedPayTo, price: resolvedPrice, network: option.network, maxTimeoutSeconds: option.maxTimeoutSeconds }; const requirements = await this.buildPaymentRequirements(resourceConfig); allRequirements.push(...requirements); } return allRequirements; } /** * Create a payment required response * * @param requirements - Payment requirements * @param resourceInfo - Resource information * @param error - Error message * @param extensions - Optional declared extensions (for per-key enrichment) * @returns Payment required response object */ async createPaymentRequiredResponse(requirements, resourceInfo, error, extensions) { let response = { x402Version: 2, error, resource: resourceInfo, accepts: requirements }; if (extensions && Object.keys(extensions).length > 0) { response.extensions = extensions; } if (extensions) { for (const [key, declaration] of Object.entries(extensions)) { const extension = this.registeredExtensions.get(key); if (extension?.enrichPaymentRequiredResponse) { try { const context = { requirements, resourceInfo, error, paymentRequiredResponse: response }; const extensionData = await extension.enrichPaymentRequiredResponse( declaration, context ); if (extensionData !== void 0) { if (!response.extensions) { response.extensions = {}; } response.extensions[key] = extensionData; } } catch (error2) { console.error( `Error in enrichPaymentRequiredResponse hook for extension ${key}:`, error2 ); } } } } return response; } /** * Verify a payment against requirements * * @param paymentPayload - The payment payload to verify * @param requirements - The payment requirements * @returns Verification response */ async verifyPayment(paymentPayload, requirements) { const context = { paymentPayload, requirements }; for (const hook of this.beforeVerifyHooks) { try { const result = await hook(context); if (result && "abort" in result && result.abort) { return { isValid: false, invalidReason: result.reason, invalidMessage: result.message }; } } catch (error) { throw new VerifyError(400, { isValid: false, invalidReason: "before_verify_hook_error", invalidMessage: error instanceof Error ? error.message : "" }); } } try { const facilitatorClient = this.getFacilitatorClient( paymentPayload.x402Version, requirements.network, requirements.scheme ); let verifyResult; if (!facilitatorClient) { let lastError; for (const client of this.facilitatorClients) { try { verifyResult = await client.verify(paymentPayload, requirements); break; } catch (error) { lastError = error; } } if (!verifyResult) { throw lastError || new Error( `No facilitator supports ${requirements.scheme} on ${requirements.network} for v${paymentPayload.x402Version}` ); } } else { verifyResult = await facilitatorClient.verify(paymentPayload, requirements); } const resultContext = { ...context, result: verifyResult }; for (const hook of this.afterVerifyHooks) { await hook(resultContext); } return verifyResult; } catch (error) { const failureContext = { ...context, error }; for (const hook of this.onVerifyFailureHooks) { const result = await hook(failureContext); if (result && "recovered" in result && result.recovered) { return result.result; } } throw error; } } /** * Settle a verified payment * * @param paymentPayload - The payment payload to settle * @param requirements - The payment requirements * @param declaredExtensions - Optional declared extensions (for per-key enrichment) * @returns Settlement response */ async settlePayment(paymentPayload, requirements, declaredExtensions) { const context = { paymentPayload, requirements }; for (const hook of this.beforeSettleHooks) { try { const result = await hook(context); if (result && "abort" in result && result.abort) { throw new SettleError(400, { success: false, errorReason: result.reason, errorMessage: result.message, transaction: "", network: requirements.network }); } } catch (error) { throw new SettleError(400, { success: false, errorReason: "before_settle_hook_error", errorMessage: error instanceof Error ? error.message : "", transaction: "", network: requirements.network }); } } try { const facilitatorClient = this.getFacilitatorClient( paymentPayload.x402Version, requirements.network, requirements.scheme ); let settleResult; if (!facilitatorClient) { let lastError; for (const client of this.facilitatorClients) { try { settleResult = await client.settle(paymentPayload, requirements); break; } catch (error) { lastError = error; } } if (!settleResult) { throw lastError || new Error( `No facilitator supports ${requirements.scheme} on ${requirements.network} for v${paymentPayload.x402Version}` ); } } else { settleResult = await facilitatorClient.settle(paymentPayload, requirements); } const resultContext = { ...context, result: settleResult }; for (const hook of this.afterSettleHooks) { await hook(resultContext); } if (declaredExtensions) { for (const [key, declaration] of Object.entries(declaredExtensions)) { const extension = this.registeredExtensions.get(key); if (extension?.enrichSettlementResponse) { try { const extensionData = await extension.enrichSettlementResponse( declaration, resultContext ); if (extensionData !== void 0) { if (!settleResult.extensions) { settleResult.extensions = {}; } settleResult.extensions[key] = extensionData; } } catch (error) { console.error(`Error in enrichSettlementResponse hook for extension ${key}:`, error); } } } } return settleResult; } catch (error) { const failureContext = { ...context, error }; for (const hook of this.onSettleFailureHooks) { const result = await hook(failureContext); if (result && "recovered" in result && result.recovered) { return result.result; } } throw error; } } /** * Find matching payment requirements for a payment * * @param availableRequirements - Array of available payment requirements * @param paymentPayload - The payment payload * @returns Matching payment requirements or undefined */ findMatchingRequirements(availableRequirements, paymentPayload) { switch (paymentPayload.x402Version) { case 2: return availableRequirements.find( (paymentRequirements) => deepEqual(paymentRequirements, paymentPayload.accepted) ); case 1: return availableRequirements.find( (req) => req.scheme === paymentPayload.accepted.scheme && req.network === paymentPayload.accepted.network ); default: throw new Error( `Unsupported x402 version: ${paymentPayload.x402Version}` ); } } /** * Process a payment request * * @param paymentPayload - Optional payment payload if provided * @param resourceConfig - Configuration for the protected resource * @param resourceInfo - Information about the resource being accessed * @param extensions - Optional extensions to include in the response * @returns Processing result */ async processPaymentRequest(paymentPayload, resourceConfig, resourceInfo, extensions) { const requirements = await this.buildPaymentRequirements(resourceConfig); if (!paymentPayload) { return { success: false, requiresPayment: await this.createPaymentRequiredResponse( requirements, resourceInfo, "Payment required", extensions ) }; } const matchingRequirements = this.findMatchingRequirements(requirements, paymentPayload); if (!matchingRequirements) { return { success: false, requiresPayment: await this.createPaymentRequiredResponse( requirements, resourceInfo, "No matching payment requirements found", extensions ) }; } const verificationResult = await this.verifyPayment(paymentPayload, matchingRequirements); if (!verificationResult.isValid) { return { success: false, error: verificationResult.invalidReason, verificationResult }; } return { success: true, verificationResult }; } /** * Get facilitator client for a specific version, network, and scheme * * @param x402Version - The x402 version * @param network - The network identifier * @param scheme - The payment scheme * @returns The facilitator client or undefined if not found */ getFacilitatorClient(x402Version2, network, scheme) { const versionMap = this.facilitatorClientsMap.get(x402Version2); if (!versionMap) return void 0; return findByNetworkAndScheme(versionMap, scheme, network); } }; export { HTTPFacilitatorClient, RouteConfigurationError, x402HTTPResourceServer, x402ResourceServer }; //# sourceMappingURL=index.mjs.map