@payai/x402
Version:
PayAI-distributed wrapper for @x402/core v2
618 lines (610 loc) • 23 kB
JavaScript
;
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.registeredExtensions = /* @__PURE__ */ new Map();
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;
}
/**
* Registers a client extension that can enrich payment payloads.
*
* Extensions are invoked after the scheme creates the base payload and the
* payload is wrapped with extensions/resource/accepted data. If the extension's
* key is present in `paymentRequired.extensions`, the extension's
* `enrichPaymentPayload` hook is called to modify the payload.
*
* @param extension - The client extension to register
* @returns The x402Client instance for chaining
*/
registerExtension(extension) {
this.registeredExtensions.set(extension.key, extension);
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,
{ extensions: paymentRequired.extensions }
);
let paymentPayload;
if (partialPayload.x402Version == 1) {
paymentPayload = partialPayload;
} else {
const mergedExtensions = this.mergeExtensions(
paymentRequired.extensions,
partialPayload.extensions
);
paymentPayload = {
x402Version: partialPayload.x402Version,
payload: partialPayload.payload,
extensions: mergedExtensions,
resource: paymentRequired.resource,
accepted: requirements
};
}
paymentPayload = await this.enrichPaymentPayloadWithExtensions(paymentPayload, paymentRequired);
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;
}
}
/**
* Merges server-declared extensions with scheme-provided extensions.
* Scheme extensions overlay on top of server extensions at each key,
* preserving server-provided schema while overlaying scheme-provided info.
*
* @param serverExtensions - Extensions declared by the server in the 402 response
* @param schemeExtensions - Extensions provided by the scheme client (e.g. EIP-2612)
* @returns The merged extensions object, or undefined if both inputs are undefined
*/
mergeExtensions(serverExtensions, schemeExtensions) {
if (!schemeExtensions) return serverExtensions;
if (!serverExtensions) return schemeExtensions;
const merged = { ...serverExtensions };
for (const [key, schemeValue] of Object.entries(schemeExtensions)) {
const serverValue = merged[key];
if (serverValue && typeof serverValue === "object" && schemeValue && typeof schemeValue === "object") {
merged[key] = { ...serverValue, ...schemeValue };
} else {
merged[key] = schemeValue;
}
}
return merged;
}
/**
* Enriches a payment payload by calling registered extension hooks.
* For each extension key present in the PaymentRequired response,
* invokes the corresponding extension's enrichPaymentPayload callback.
*
* @param paymentPayload - The payment payload to enrich with extension data
* @param paymentRequired - The PaymentRequired response containing extension declarations
* @returns The enriched payment payload with extension data applied
*/
async enrichPaymentPayloadWithExtensions(paymentPayload, paymentRequired) {
if (!paymentRequired.extensions || this.registeredExtensions.size === 0) {
return paymentPayload;
}
let enriched = paymentPayload;
for (const [key, extension] of this.registeredExtensions) {
if (key in paymentRequired.extensions && extension.enrichPaymentPayload) {
enriched = await extension.enrichPaymentPayload(enriched, paymentRequired);
}
}
return enriched;
}
/**
* 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/schemas/index.ts
var import_zod = require("zod");
var import_zod2 = require("zod");
var NonEmptyString = import_zod.z.string().min(1);
var Any = import_zod.z.record(import_zod.z.unknown());
var OptionalAny = import_zod.z.record(import_zod.z.unknown()).optional().nullable();
var NetworkSchemaV1 = NonEmptyString;
var NetworkSchemaV2 = import_zod.z.string().min(3).refine((val) => val.includes(":"), {
message: "Network must be in CAIP-2 format (e.g., 'eip155:84532')"
});
var NetworkSchema = import_zod.z.union([NetworkSchemaV1, NetworkSchemaV2]);
var ResourceInfoSchema = import_zod.z.object({
url: NonEmptyString,
description: import_zod.z.string().optional(),
mimeType: import_zod.z.string().optional()
});
var PaymentRequirementsV1Schema = import_zod.z.object({
scheme: NonEmptyString,
network: NetworkSchemaV1,
maxAmountRequired: NonEmptyString,
resource: NonEmptyString,
// URL string in V1
description: import_zod.z.string(),
mimeType: import_zod.z.string().optional(),
outputSchema: Any.optional().nullable(),
payTo: NonEmptyString,
maxTimeoutSeconds: import_zod.z.number().positive(),
asset: NonEmptyString,
extra: OptionalAny
});
var PaymentRequiredV1Schema = import_zod.z.object({
x402Version: import_zod.z.literal(1),
error: import_zod.z.string().optional(),
accepts: import_zod.z.array(PaymentRequirementsV1Schema).min(1)
});
var PaymentPayloadV1Schema = import_zod.z.object({
x402Version: import_zod.z.literal(1),
scheme: NonEmptyString,
network: NetworkSchemaV1,
payload: Any
});
var PaymentRequirementsV2Schema = import_zod.z.object({
scheme: NonEmptyString,
network: NetworkSchemaV2,
amount: NonEmptyString,
asset: NonEmptyString,
payTo: NonEmptyString,
maxTimeoutSeconds: import_zod.z.number().positive(),
extra: OptionalAny
});
var PaymentRequiredV2Schema = import_zod.z.object({
x402Version: import_zod.z.literal(2),
error: import_zod.z.string().optional(),
resource: ResourceInfoSchema,
accepts: import_zod.z.array(PaymentRequirementsV2Schema).min(1),
extensions: OptionalAny
});
var PaymentPayloadV2Schema = import_zod.z.object({
x402Version: import_zod.z.literal(2),
resource: ResourceInfoSchema.optional(),
accepted: PaymentRequirementsV2Schema,
payload: Any,
extensions: OptionalAny
});
var PaymentRequirementsSchema = import_zod.z.union([
PaymentRequirementsV1Schema,
PaymentRequirementsV2Schema
]);
var PaymentRequiredSchema = import_zod.z.discriminatedUnion("x402Version", [
PaymentRequiredV1Schema,
PaymentRequiredV2Schema
]);
var PaymentPayloadSchema = import_zod.z.discriminatedUnion("x402Version", [
PaymentPayloadV1Schema,
PaymentPayloadV2Schema
]);
// src/http/httpFacilitatorClient.ts
var verifyResponseSchema = import_zod2.z.object({
isValid: import_zod2.z.boolean(),
invalidReason: import_zod2.z.string().optional(),
invalidMessage: import_zod2.z.string().optional(),
payer: import_zod2.z.string().optional(),
extensions: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional()
});
var settleResponseSchema = import_zod2.z.object({
success: import_zod2.z.boolean(),
errorReason: import_zod2.z.string().optional(),
errorMessage: import_zod2.z.string().optional(),
payer: import_zod2.z.string().optional(),
transaction: import_zod2.z.string(),
network: import_zod2.z.custom((value) => typeof value === "string"),
extensions: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional()
});
var supportedKindSchema = import_zod2.z.object({
x402Version: import_zod2.z.number(),
scheme: import_zod2.z.string(),
network: import_zod2.z.custom(
(value) => typeof value === "string"
),
extra: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional()
});
var supportedResponseSchema = import_zod2.z.object({
kinds: import_zod2.z.array(supportedKindSchema),
extensions: import_zod2.z.array(import_zod2.z.string()).default([]),
signers: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.array(import_zod2.z.string())).default({})
});
// 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