/** * x402 CRDT payment — client-side helpers. * * Creates CRDT payment headers and wraps fetch to auto-handle * 402 responses with CRDT scheme when available. */ export interface CrdtPaymentParams { jwtToken: string; fromDid: string; fromLabel?: string; amount: number; tokenId: string; } /** * Create a base64-encoded X-PAYMENT header for CRDT scheme. */ export function createCrdtPaymentHeader(params: CrdtPaymentParams): string { const payload = { scheme: "crdt" as const, jwtToken: params.jwtToken, fromDid: params.fromDid, fromLabel: params.fromLabel, amount: params.amount, tokenId: params.tokenId, nonce: crypto.randomUUID(), timestamp: Date.now(), }; const json = JSON.stringify(payload); return typeof btoa === "function" ? btoa(json) : Buffer.from(json).toString("base64"); } interface AuthProvider { getToken: () => string | null; getDid: () => string | null; getLabel?: () => string | null; getBalance?: (tokenId: string) => number; } /** * Wrap fetch to auto-handle 402 responses with CRDT payment. * * On 402: checks if "crdt" scheme is offered and user has * sufficient balance → auto-retries with CRDT payment header. * Falls through if no CRDT option or insufficient balance. */ type FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise; export function wrapFetchWithCrdtPayment( baseFetch: FetchFn, auth: AuthProvider, ): FetchFn { return async (input: RequestInfo | URL, init?: RequestInit): Promise => { const response = await baseFetch(input, init); if (response.status !== 402) return response; // Check if we have auth const token = auth.getToken(); const did = auth.getDid(); if (!token || !did) return response; // Parse 402 response for CRDT option let body: any; try { body = await response.clone().json(); } catch { return response; } const requirements = body.paymentRequirements; if (!Array.isArray(requirements)) return response; const crdtReq = requirements.find((r: any) => r.scheme === "crdt"); if (!crdtReq) return response; const amount = parseInt(crdtReq.maxAmountRequired, 10); const tokenId = crdtReq.tokenId; // Check balance if provider available if (auth.getBalance) { const balance = auth.getBalance(tokenId); if (balance < amount) return response; // Insufficient — return original 402 } // Create CRDT payment header and retry const paymentHeader = createCrdtPaymentHeader({ jwtToken: token, fromDid: did, fromLabel: auth.getLabel?.() || undefined, amount, tokenId, }); return baseFetch(input, { ...init, headers: { ...(init?.headers || {}), "X-PAYMENT": paymentHeader, }, }); }; }