UNPKG

@payai/x402

Version:

PayAI-distributed wrapper for @x402/core v2

440 lines (434 loc) 15.9 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/client/index.ts var client_exports = {}; __export(client_exports, { x402Client: () => x402Client, x402HTTPClient: () => x402HTTPClient }); module.exports = __toCommonJS(client_exports); // src/index.ts var x402Version = 2; // src/utils/index.ts var findSchemesByNetwork = (map, network) => { let implementationsByScheme = map.get(network); if (!implementationsByScheme) { for (const [registeredNetworkPattern, implementations] of map.entries()) { const pattern = registeredNetworkPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*"); const regex = new RegExp(`^${pattern}$`); if (regex.test(network)) { implementationsByScheme = implementations; break; } } } return implementationsByScheme; }; var findByNetworkAndScheme = (map, scheme, network) => { return findSchemesByNetwork(map, network)?.get(scheme); }; var Base64EncodedRegex = /^[A-Za-z0-9+/]*={0,2}$/; function safeBase64Encode(data) { if (typeof globalThis !== "undefined" && typeof globalThis.btoa === "function") { const bytes = new TextEncoder().encode(data); const binaryString = Array.from(bytes, (byte) => String.fromCharCode(byte)).join(""); return globalThis.btoa(binaryString); } return Buffer.from(data, "utf8").toString("base64"); } function safeBase64Decode(data) { if (typeof globalThis !== "undefined" && typeof globalThis.atob === "function") { const binaryString = globalThis.atob(data); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } const decoder = new TextDecoder("utf-8"); return decoder.decode(bytes); } return Buffer.from(data, "base64").toString("utf-8"); } // src/client/x402Client.ts var x402Client = class _x402Client { /** * Creates a new x402Client instance. * * @param paymentRequirementsSelector - Function to select payment requirements from available options */ constructor(paymentRequirementsSelector) { this.registeredClientSchemes = /* @__PURE__ */ new Map(); this.policies = []; this.beforePaymentCreationHooks = []; this.afterPaymentCreationHooks = []; this.onPaymentCreationFailureHooks = []; this.paymentRequirementsSelector = paymentRequirementsSelector || ((x402Version2, accepts) => accepts[0]); } /** * Creates a new x402Client instance from a configuration object. * * @param config - The client configuration including schemes, policies, and payment requirements selector * @returns A configured x402Client instance */ static fromConfig(config) { const client = new _x402Client(config.paymentRequirementsSelector); config.schemes.forEach((scheme) => { if (scheme.x402Version === 1) { client.registerV1(scheme.network, scheme.client); } else { client.register(scheme.network, scheme.client); } }); config.policies?.forEach((policy) => { client.registerPolicy(policy); }); return client; } /** * Registers a scheme client for the current x402 version. * * @param network - The network to register the client for * @param client - The scheme network client to register * @returns The x402Client instance for chaining */ register(network, client) { return this._registerScheme(x402Version, network, client); } /** * Registers a scheme client for x402 version 1. * * @param network - The v1 network identifier (e.g., 'base-sepolia', 'solana-devnet') * @param client - The scheme network client to register * @returns The x402Client instance for chaining */ registerV1(network, client) { return this._registerScheme(1, network, client); } /** * Registers a policy to filter or transform payment requirements. * * Policies are applied in order after filtering by registered schemes * and before the selector chooses the final payment requirement. * * @param policy - Function to filter/transform payment requirements * @returns The x402Client instance for chaining * * @example * ```typescript * // Prefer cheaper options * client.registerPolicy((version, reqs) => * reqs.filter(r => BigInt(r.value) < BigInt('1000000')) * ); * * // Prefer specific networks * client.registerPolicy((version, reqs) => * reqs.filter(r => r.network.startsWith('eip155:')) * ); * ``` */ registerPolicy(policy) { this.policies.push(policy); return this; } /** * Register a hook to execute before payment payload creation. * Can abort creation by returning { abort: true, reason: string } * * @param hook - The hook function to register * @returns The x402Client instance for chaining */ onBeforePaymentCreation(hook) { this.beforePaymentCreationHooks.push(hook); return this; } /** * Register a hook to execute after successful payment payload creation. * * @param hook - The hook function to register * @returns The x402Client instance for chaining */ onAfterPaymentCreation(hook) { this.afterPaymentCreationHooks.push(hook); return this; } /** * Register a hook to execute when payment payload creation fails. * Can recover from failure by returning { recovered: true, payload: PaymentPayload } * * @param hook - The hook function to register * @returns The x402Client instance for chaining */ onPaymentCreationFailure(hook) { this.onPaymentCreationFailureHooks.push(hook); return this; } /** * Creates a payment payload based on a PaymentRequired response. * * Automatically extracts x402Version, resource, and extensions from the PaymentRequired * response and constructs a complete PaymentPayload with the accepted requirements. * * @param paymentRequired - The PaymentRequired response from the server * @returns Promise resolving to the complete payment payload */ async createPaymentPayload(paymentRequired) { const clientSchemesByNetwork = this.registeredClientSchemes.get(paymentRequired.x402Version); if (!clientSchemesByNetwork) { throw new Error(`No client registered for x402 version: ${paymentRequired.x402Version}`); } const requirements = this.selectPaymentRequirements(paymentRequired.x402Version, paymentRequired.accepts); const context = { paymentRequired, selectedRequirements: requirements }; for (const hook of this.beforePaymentCreationHooks) { const result = await hook(context); if (result && "abort" in result && result.abort) { throw new Error(`Payment creation aborted: ${result.reason}`); } } try { const schemeNetworkClient = findByNetworkAndScheme(clientSchemesByNetwork, requirements.scheme, requirements.network); if (!schemeNetworkClient) { throw new Error(`No client registered for scheme: ${requirements.scheme} and network: ${requirements.network}`); } const partialPayload = await schemeNetworkClient.createPaymentPayload(paymentRequired.x402Version, requirements); let paymentPayload; if (partialPayload.x402Version == 1) { paymentPayload = partialPayload; } else { paymentPayload = { ...partialPayload, extensions: paymentRequired.extensions, resource: paymentRequired.resource, accepted: requirements }; } const createdContext = { ...context, paymentPayload }; for (const hook of this.afterPaymentCreationHooks) { await hook(createdContext); } return paymentPayload; } catch (error) { const failureContext = { ...context, error }; for (const hook of this.onPaymentCreationFailureHooks) { const result = await hook(failureContext); if (result && "recovered" in result && result.recovered) { return result.payload; } } throw error; } } /** * Selects appropriate payment requirements based on registered clients and policies. * * Selection process: * 1. Filter by registered schemes (network + scheme support) * 2. Apply all registered policies in order * 3. Use selector to choose final requirement * * @param x402Version - The x402 protocol version * @param paymentRequirements - Array of available payment requirements * @returns The selected payment requirements */ selectPaymentRequirements(x402Version2, paymentRequirements) { const clientSchemesByNetwork = this.registeredClientSchemes.get(x402Version2); if (!clientSchemesByNetwork) { throw new Error(`No client registered for x402 version: ${x402Version2}`); } const supportedPaymentRequirements = paymentRequirements.filter((requirement) => { let clientSchemes = findSchemesByNetwork(clientSchemesByNetwork, requirement.network); if (!clientSchemes) { return false; } return clientSchemes.has(requirement.scheme); }); if (supportedPaymentRequirements.length === 0) { throw new Error(`No network/scheme registered for x402 version: ${x402Version2} which comply with the payment requirements. ${JSON.stringify({ x402Version: x402Version2, paymentRequirements, x402Versions: Array.from(this.registeredClientSchemes.keys()), networks: Array.from(clientSchemesByNetwork.keys()), schemes: Array.from(clientSchemesByNetwork.values()).map((schemes) => Array.from(schemes.keys())).flat() })}`); } let filteredRequirements = supportedPaymentRequirements; for (const policy of this.policies) { filteredRequirements = policy(x402Version2, filteredRequirements); if (filteredRequirements.length === 0) { throw new Error(`All payment requirements were filtered out by policies for x402 version: ${x402Version2}`); } } return this.paymentRequirementsSelector(x402Version2, filteredRequirements); } /** * Internal method to register a scheme client. * * @param x402Version - The x402 protocol version * @param network - The network to register the client for * @param client - The scheme network client to register * @returns The x402Client instance for chaining */ _registerScheme(x402Version2, network, client) { if (!this.registeredClientSchemes.has(x402Version2)) { this.registeredClientSchemes.set(x402Version2, /* @__PURE__ */ new Map()); } const clientSchemesByNetwork = this.registeredClientSchemes.get(x402Version2); if (!clientSchemesByNetwork.has(network)) { clientSchemesByNetwork.set(network, /* @__PURE__ */ new Map()); } const clientByScheme = clientSchemesByNetwork.get(network); if (!clientByScheme.has(client.scheme)) { clientByScheme.set(client.scheme, client); } return this; } }; // src/http/index.ts function encodePaymentSignatureHeader(paymentPayload) { return safeBase64Encode(JSON.stringify(paymentPayload)); } function decodePaymentRequiredHeader(paymentRequiredHeader) { if (!Base64EncodedRegex.test(paymentRequiredHeader)) { throw new Error("Invalid payment required header"); } return JSON.parse(safeBase64Decode(paymentRequiredHeader)); } function decodePaymentResponseHeader(paymentResponseHeader) { if (!Base64EncodedRegex.test(paymentResponseHeader)) { throw new Error("Invalid payment response header"); } return JSON.parse(safeBase64Decode(paymentResponseHeader)); } // src/http/x402HTTPClient.ts var x402HTTPClient = class { /** * Creates a new x402HTTPClient instance. * * @param client - The underlying x402Client for payment logic */ constructor(client) { this.client = client; this.paymentRequiredHooks = []; } /** * Register a hook to handle 402 responses before payment. * Hooks run in order; first to return headers wins. * * @param hook - The hook function to register * @returns This instance for chaining */ onPaymentRequired(hook) { this.paymentRequiredHooks.push(hook); return this; } /** * Run hooks and return headers if any hook provides them. * * @param paymentRequired - The payment required response from the server * @returns Headers to use for retry, or null to proceed to payment */ async handlePaymentRequired(paymentRequired) { for (const hook of this.paymentRequiredHooks) { const result = await hook({ paymentRequired }); if (result?.headers) { return result.headers; } } return null; } /** * Encodes a payment payload into appropriate HTTP headers based on version. * * @param paymentPayload - The payment payload to encode * @returns HTTP headers containing the encoded payment signature */ encodePaymentSignatureHeader(paymentPayload) { switch (paymentPayload.x402Version) { case 2: return { "PAYMENT-SIGNATURE": encodePaymentSignatureHeader(paymentPayload) }; case 1: return { "X-PAYMENT": encodePaymentSignatureHeader(paymentPayload) }; default: throw new Error( `Unsupported x402 version: ${paymentPayload.x402Version}` ); } } /** * Extracts payment required information from HTTP response. * * @param getHeader - Function to retrieve header value by name (case-insensitive) * @param body - Optional response body for v1 compatibility * @returns The payment required object */ getPaymentRequiredResponse(getHeader, body) { const paymentRequired = getHeader("PAYMENT-REQUIRED"); if (paymentRequired) { return decodePaymentRequiredHeader(paymentRequired); } if (body && body instanceof Object && "x402Version" in body && body.x402Version === 1) { return body; } throw new Error("Invalid payment required response"); } /** * Extracts payment settlement response from HTTP headers. * * @param getHeader - Function to retrieve header value by name (case-insensitive) * @returns The settlement response object */ getPaymentSettleResponse(getHeader) { const paymentResponse = getHeader("PAYMENT-RESPONSE"); if (paymentResponse) { return decodePaymentResponseHeader(paymentResponse); } const xPaymentResponse = getHeader("X-PAYMENT-RESPONSE"); if (xPaymentResponse) { return decodePaymentResponseHeader(xPaymentResponse); } throw new Error("Payment response header not found"); } /** * Creates a payment payload for the given payment requirements. * Delegates to the underlying x402Client. * * @param paymentRequired - The payment required response from the server * @returns Promise resolving to the payment payload */ async createPaymentPayload(paymentRequired) { return this.client.createPaymentPayload(paymentRequired); } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { x402Client, x402HTTPClient }); //# sourceMappingURL=index.js.map