import type { ExactEvmPayload } from "x402/types"; import { cachedTextDecoder } from "../utils/text-decoder.js"; import { cachedTextEncoder } from "../utils/text-encoder.js"; import type { RequestedPaymentPayload, RequestedPaymentRequirements, } from "./schemas.js"; /** * Encodes a payment payload into a base64 string, ensuring bigint values are properly stringified * * @param payment - The payment payload to encode * @returns A base64 encoded string representation of the payment payload */ export function encodePayment(payment: RequestedPaymentPayload): string { let safe: RequestedPaymentPayload; // evm const evmPayload = payment.payload as ExactEvmPayload; safe = { ...payment, payload: { ...evmPayload, authorization: Object.fromEntries( Object.entries(evmPayload.authorization).map(([key, value]) => [ key, typeof value === "bigint" ? (value as bigint).toString() : value, ]), ) as ExactEvmPayload["authorization"], }, }; return base64EncodeUtf8(JSON.stringify(safe)); } /** * Decodes a base64 encoded payment string back into a PaymentPayload object * * @param payment - The base64 encoded payment string to decode * @returns The decoded and validated PaymentPayload object */ export function decodePayment(payment: string): RequestedPaymentPayload { const decoded = base64DecodeUtf8(payment); const parsed = JSON.parse(decoded); const obj: RequestedPaymentPayload = { ...parsed, payload: parsed.payload as ExactEvmPayload, }; return obj; } /** * Encodes a payment required object into a base64 string for the PAYMENT-REQUIRED header (x402 v2) * * @param paymentRequired - The payment required object to encode * @returns A base64 encoded string representation of the payment required object */ export function encodePaymentRequired(paymentRequired: { x402Version: number; error?: string; accepts: RequestedPaymentRequirements[]; resource?: { url: string; description?: string; mimeType?: string }; }): string { return safeBase64Encode(JSON.stringify(paymentRequired)); } /** * Encodes a string to base64 format * * @param data - The string to be encoded to base64 * @returns The base64 encoded string */ export function safeBase64Encode(data: string): string { if ( typeof globalThis !== "undefined" && typeof globalThis.btoa === "function" ) { return globalThis.btoa(data); } return Buffer.from(data).toString("base64"); } /** * Decodes a base64 string back to its original format * * @param data - The base64 encoded string to be decoded * @returns The decoded string in UTF-8 format */ export function safeBase64Decode(data: string): string { if ( typeof globalThis !== "undefined" && typeof globalThis.atob === "function" ) { return globalThis.atob(data); } return Buffer.from(data, "base64").toString("utf-8"); } /** * Encodes a string as UTF-8 and then to base64 * * @param data - The string to encode * @returns The base64 encoded UTF-8 bytes */ function base64EncodeUtf8(data: string): string { if ( typeof globalThis !== "undefined" && typeof globalThis.btoa === "function" ) { const bytes = cachedTextEncoder().encode(data); let binary = ""; for (const byte of bytes) { binary += String.fromCharCode(byte); } return globalThis.btoa(binary); } return Buffer.from(data, "utf-8").toString("base64"); } /** * Decodes a base64 string and interprets the bytes as UTF-8 * * @param data - The base64 encoded string * @returns The decoded string */ export function base64DecodeUtf8(data: string): string { if ( typeof globalThis !== "undefined" && typeof globalThis.atob === "function" ) { const binary = globalThis.atob(data); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return cachedTextDecoder().decode(bytes); } return Buffer.from(data, "base64").toString("utf-8"); }