From 91e7c0095fccb23c64300154d17e01f8c0dbcd89 Mon Sep 17 00:00:00 2001 From: Rishab Luthra Date: Sat, 14 Feb 2026 16:59:55 -0800 Subject: [PATCH 1/6] Add x402 payment protocol support for HTTP and WebSocket connections Extends the Fern-generated SDK with wrapper classes that automatically handle x402 (402 Payment Required) flows when an x402Client is passed. HTTP requests use wrapped fetch; WebSocket connections probe for payment requirements and connect with signed payment headers. Co-Authored-By: Claude Opus 4.6 --- .fernignore | 3 ++ src/index.ts | 2 +- src/wrapper/AgentMailClient.ts | 63 +++++++++++++++++++++++++++++ src/wrapper/X402WebsocketsClient.ts | 35 ++++++++++++++++ src/wrapper/index.ts | 1 + src/wrapper/x402-modules.d.ts | 13 ++++++ src/wrapper/x402.ts | 60 +++++++++++++++++++++++++++ 7 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 src/wrapper/AgentMailClient.ts create mode 100644 src/wrapper/X402WebsocketsClient.ts create mode 100644 src/wrapper/index.ts create mode 100644 src/wrapper/x402-modules.d.ts create mode 100644 src/wrapper/x402.ts diff --git a/.fernignore b/.fernignore index 084a8eb..5919867 100644 --- a/.fernignore +++ b/.fernignore @@ -1 +1,4 @@ # Specify files that shouldn't be modified by Fern +src/wrapper/ +src/index.ts +tests/custom.test.ts diff --git a/src/index.ts b/src/index.ts index 82a86f4..aa95d2d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ export * as AgentMail from "./api/index.js"; export type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; -export { AgentMailClient } from "./Client.js"; +export { AgentMailClient } from "./wrapper/AgentMailClient.js"; export { AgentMailEnvironment, type AgentMailEnvironmentUrls } from "./environments.js"; export { AgentMailError, AgentMailTimeoutError } from "./errors/index.js"; export * from "./exports.js"; diff --git a/src/wrapper/AgentMailClient.ts b/src/wrapper/AgentMailClient.ts new file mode 100644 index 0000000..6218b52 --- /dev/null +++ b/src/wrapper/AgentMailClient.ts @@ -0,0 +1,63 @@ +import { AgentMailClient as FernAgentMailClient } from "../Client.js"; +import type { BaseClientOptions, BaseRequestOptions } from "../BaseClient.js"; +import { WebsocketsClient } from "../api/resources/websockets/client/Client.js"; +import { X402WebsocketsClient } from "./X402WebsocketsClient.js"; +import { initX402, type X402Initialized } from "./x402.js"; + +export declare namespace AgentMailClient { + export interface Options extends BaseClientOptions { + /** + * An x402Client instance for automatic payment handling on all HTTP and WebSocket calls. + * + * @example + * ```typescript + * import { x402Client } from "@x402/fetch"; + * import { registerExactEvmScheme } from "@x402/evm/exact/client"; + * import { privateKeyToAccount } from "viem/accounts"; + * + * const x402 = new x402Client(); + * registerExactEvmScheme(x402, { signer: privateKeyToAccount("0x...") }); + * + * const client = new AgentMailClient({ + * environment: AgentMailEnvironment.ProdX402, + * x402, + * }); + * ``` + */ + x402?: unknown; + } + + export type RequestOptions = BaseRequestOptions; +} + +export class AgentMailClient extends FernAgentMailClient { + private readonly _x402Init?: Promise; + + constructor(options: AgentMailClient.Options = {}) { + if (options.x402) { + const x402Init = initX402(options.x402); + let resolvedFetch: typeof fetch | undefined; + + super({ + ...options, + fetch: (async (input: RequestInfo | URL, init?: RequestInit) => { + resolvedFetch ??= (await x402Init).fetch; + return resolvedFetch(input, init); + }) as typeof fetch, + }); + + this._x402Init = x402Init; + } else { + super(options); + } + } + + public get websockets(): WebsocketsClient { + if (!this._websockets) { + this._websockets = this._x402Init + ? new X402WebsocketsClient(this._options, this._x402Init) + : new WebsocketsClient(this._options); + } + return this._websockets; + } +} diff --git a/src/wrapper/X402WebsocketsClient.ts b/src/wrapper/X402WebsocketsClient.ts new file mode 100644 index 0000000..cfab570 --- /dev/null +++ b/src/wrapper/X402WebsocketsClient.ts @@ -0,0 +1,35 @@ +import { WebsocketsClient } from "../api/resources/websockets/client/Client.js"; +import type { WebsocketsSocket } from "../api/resources/websockets/client/Socket.js"; +import * as core from "../core/index.js"; +import * as environments from "../environments.js"; +import { type X402Initialized, getPaymentHeaders } from "./x402.js"; + +export class X402WebsocketsClient extends WebsocketsClient { + private readonly _x402Init: Promise; + + constructor(options: WebsocketsClient.Options, x402Init: Promise) { + super(options); + this._x402Init = x402Init; + } + + public async connect(args: WebsocketsClient.ConnectArgs = {}): Promise { + const x402 = await this._x402Init; + + const wsUrl = core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + ((await core.Supplier.get(this._options.environment)) ?? environments.AgentMailEnvironment.Prod) + .websockets, + "/v0", + ); + + console.log("[x402] Probing WS endpoint:", wsUrl); + const paymentHeaders = await getPaymentHeaders(wsUrl, x402); + console.log("[x402] Connecting WS with payment headers"); + + return super.connect({ + ...args, + headers: { ...paymentHeaders, ...args.headers }, + debug: true, + }); + } +} diff --git a/src/wrapper/index.ts b/src/wrapper/index.ts new file mode 100644 index 0000000..a839795 --- /dev/null +++ b/src/wrapper/index.ts @@ -0,0 +1 @@ +export { AgentMailClient } from "./AgentMailClient.js"; diff --git a/src/wrapper/x402-modules.d.ts b/src/wrapper/x402-modules.d.ts new file mode 100644 index 0000000..21a80e8 --- /dev/null +++ b/src/wrapper/x402-modules.d.ts @@ -0,0 +1,13 @@ +// Type declarations for @x402/fetch (optional peer dependency, dynamically imported) + +declare module "@x402/fetch" { + export function wrapFetchWithPayment( + fetch: typeof globalThis.fetch, + client: unknown, + ): typeof globalThis.fetch; + export class x402HTTPClient { + constructor(client: unknown); + getPaymentRequiredResponse(getHeader: (name: string) => string | null, body: unknown): unknown; + encodePaymentSignatureHeader(payload: unknown): Record; + } +} diff --git a/src/wrapper/x402.ts b/src/wrapper/x402.ts new file mode 100644 index 0000000..f35ac44 --- /dev/null +++ b/src/wrapper/x402.ts @@ -0,0 +1,60 @@ +export interface X402Initialized { + fetch: typeof fetch; + httpClient: { + getPaymentRequiredResponse(getHeader: (name: string) => string | null, body: unknown): unknown; + encodePaymentSignatureHeader(payload: unknown): Record; + }; + payClient: { + createPaymentPayload(paymentRequired: unknown): Promise; + }; +} + +/** + * Initializes x402 fetch wrapper and HTTP client from an x402Client instance. + * Dynamically imports @x402/fetch so it's only needed when x402 is used. + */ +export async function initX402(x402Client: unknown): Promise { + let x402Fetch: typeof import("@x402/fetch"); + try { + x402Fetch = await import("@x402/fetch"); + } catch { + throw new Error( + 'The x402 option requires @x402/fetch to be installed. Run: npm install @x402/fetch', + ); + } + const wrappedFetch = x402Fetch.wrapFetchWithPayment(fetch, x402Client as never); + const httpClient = new x402Fetch.x402HTTPClient(x402Client as never); + return { + fetch: wrappedFetch as typeof fetch, + httpClient, + payClient: x402Client as X402Initialized["payClient"], + }; +} + +/** + * Probes a WebSocket endpoint over HTTP to get a 402 response, + * then signs a payment and returns the headers needed for the WS handshake. + */ +export async function getPaymentHeaders(wsUrl: string, x402: X402Initialized): Promise> { + const httpUrl = wsUrl.replace(/^wss:\/\//, "https://").replace(/^ws:\/\//, "http://"); + + const response = await fetch(httpUrl); + if (response.status !== 402) { + const body = await response.text(); + throw new Error(`x402: expected 402 from ${httpUrl} but got ${response.status}: ${body || "(empty)"}`); + } + + let body: unknown; + try { + body = JSON.parse(await response.text()); + } catch { + body = undefined; + } + + const getHeader = (name: string): string | null => response.headers.get(name); + const paymentRequired = x402.httpClient.getPaymentRequiredResponse(getHeader, body); + const paymentPayload = await x402.payClient.createPaymentPayload(paymentRequired); + const headers = x402.httpClient.encodePaymentSignatureHeader(paymentPayload); + console.log("[x402] Payment headers obtained:", Object.keys(headers)); + return headers; +} From 6d3df2add1b4010cd0c99e650a3a1de5adf3cdc5 Mon Sep 17 00:00:00 2001 From: Rishab Luthra Date: Sat, 14 Feb 2026 17:02:49 -0800 Subject: [PATCH 2/6] Remove debug logging from x402 wrapper Co-Authored-By: Claude Opus 4.6 --- src/wrapper/X402WebsocketsClient.ts | 3 --- src/wrapper/x402.ts | 1 - 2 files changed, 4 deletions(-) diff --git a/src/wrapper/X402WebsocketsClient.ts b/src/wrapper/X402WebsocketsClient.ts index cfab570..8113e2a 100644 --- a/src/wrapper/X402WebsocketsClient.ts +++ b/src/wrapper/X402WebsocketsClient.ts @@ -22,14 +22,11 @@ export class X402WebsocketsClient extends WebsocketsClient { "/v0", ); - console.log("[x402] Probing WS endpoint:", wsUrl); const paymentHeaders = await getPaymentHeaders(wsUrl, x402); - console.log("[x402] Connecting WS with payment headers"); return super.connect({ ...args, headers: { ...paymentHeaders, ...args.headers }, - debug: true, }); } } diff --git a/src/wrapper/x402.ts b/src/wrapper/x402.ts index f35ac44..ffc2d40 100644 --- a/src/wrapper/x402.ts +++ b/src/wrapper/x402.ts @@ -55,6 +55,5 @@ export async function getPaymentHeaders(wsUrl: string, x402: X402Initialized): P const paymentRequired = x402.httpClient.getPaymentRequiredResponse(getHeader, body); const paymentPayload = await x402.payClient.createPaymentPayload(paymentRequired); const headers = x402.httpClient.encodePaymentSignatureHeader(paymentPayload); - console.log("[x402] Payment headers obtained:", Object.keys(headers)); return headers; } From 881e6d79e65a9d8af8d807fb1fbb221aca06a2d7 Mon Sep 17 00:00:00 2001 From: Rishab Luthra Date: Sat, 14 Feb 2026 17:12:12 -0800 Subject: [PATCH 3/6] =?UTF-8?q?Simplify=20x402=20wrapper=20=E2=80=94=20del?= =?UTF-8?q?egate=20HTTP=20to=20fetch=20override?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Users pass `wrapFetchWithPayment(fetch, x402)` via the existing `fetch` option for HTTP. The SDK wrapper now only handles WebSocket x402 probing, removing the lazy fetch init and X402Initialized abstraction. Co-Authored-By: Claude Opus 4.6 --- src/wrapper/AgentMailClient.ts | 29 +++++++-------------- src/wrapper/X402WebsocketsClient.ts | 12 ++++----- src/wrapper/x402.ts | 40 +++++++---------------------- 3 files changed, 23 insertions(+), 58 deletions(-) diff --git a/src/wrapper/AgentMailClient.ts b/src/wrapper/AgentMailClient.ts index 6218b52..db5cfa2 100644 --- a/src/wrapper/AgentMailClient.ts +++ b/src/wrapper/AgentMailClient.ts @@ -2,16 +2,16 @@ import { AgentMailClient as FernAgentMailClient } from "../Client.js"; import type { BaseClientOptions, BaseRequestOptions } from "../BaseClient.js"; import { WebsocketsClient } from "../api/resources/websockets/client/Client.js"; import { X402WebsocketsClient } from "./X402WebsocketsClient.js"; -import { initX402, type X402Initialized } from "./x402.js"; export declare namespace AgentMailClient { export interface Options extends BaseClientOptions { /** - * An x402Client instance for automatic payment handling on all HTTP and WebSocket calls. + * An x402Client instance for automatic payment handling on WebSocket connections. + * For HTTP, pass a wrapped fetch via the `fetch` option instead. * * @example * ```typescript - * import { x402Client } from "@x402/fetch"; + * import { x402Client, wrapFetchWithPayment } from "@x402/fetch"; * import { registerExactEvmScheme } from "@x402/evm/exact/client"; * import { privateKeyToAccount } from "viem/accounts"; * @@ -20,6 +20,7 @@ export declare namespace AgentMailClient { * * const client = new AgentMailClient({ * environment: AgentMailEnvironment.ProdX402, + * fetch: wrapFetchWithPayment(fetch, x402), * x402, * }); * ``` @@ -31,31 +32,19 @@ export declare namespace AgentMailClient { } export class AgentMailClient extends FernAgentMailClient { - private readonly _x402Init?: Promise; + private readonly _x402Client?: unknown; constructor(options: AgentMailClient.Options = {}) { + super(options); if (options.x402) { - const x402Init = initX402(options.x402); - let resolvedFetch: typeof fetch | undefined; - - super({ - ...options, - fetch: (async (input: RequestInfo | URL, init?: RequestInit) => { - resolvedFetch ??= (await x402Init).fetch; - return resolvedFetch(input, init); - }) as typeof fetch, - }); - - this._x402Init = x402Init; - } else { - super(options); + this._x402Client = options.x402; } } public get websockets(): WebsocketsClient { if (!this._websockets) { - this._websockets = this._x402Init - ? new X402WebsocketsClient(this._options, this._x402Init) + this._websockets = this._x402Client + ? new X402WebsocketsClient(this._options, this._x402Client) : new WebsocketsClient(this._options); } return this._websockets; diff --git a/src/wrapper/X402WebsocketsClient.ts b/src/wrapper/X402WebsocketsClient.ts index 8113e2a..dd7ebde 100644 --- a/src/wrapper/X402WebsocketsClient.ts +++ b/src/wrapper/X402WebsocketsClient.ts @@ -2,19 +2,17 @@ import { WebsocketsClient } from "../api/resources/websockets/client/Client.js"; import type { WebsocketsSocket } from "../api/resources/websockets/client/Socket.js"; import * as core from "../core/index.js"; import * as environments from "../environments.js"; -import { type X402Initialized, getPaymentHeaders } from "./x402.js"; +import { getPaymentHeaders } from "./x402.js"; export class X402WebsocketsClient extends WebsocketsClient { - private readonly _x402Init: Promise; + private readonly _x402Client: unknown; - constructor(options: WebsocketsClient.Options, x402Init: Promise) { + constructor(options: WebsocketsClient.Options, x402Client: unknown) { super(options); - this._x402Init = x402Init; + this._x402Client = x402Client; } public async connect(args: WebsocketsClient.ConnectArgs = {}): Promise { - const x402 = await this._x402Init; - const wsUrl = core.url.join( (await core.Supplier.get(this._options.baseUrl)) ?? ((await core.Supplier.get(this._options.environment)) ?? environments.AgentMailEnvironment.Prod) @@ -22,7 +20,7 @@ export class X402WebsocketsClient extends WebsocketsClient { "/v0", ); - const paymentHeaders = await getPaymentHeaders(wsUrl, x402); + const paymentHeaders = await getPaymentHeaders(wsUrl, this._x402Client); return super.connect({ ...args, diff --git a/src/wrapper/x402.ts b/src/wrapper/x402.ts index ffc2d40..2782988 100644 --- a/src/wrapper/x402.ts +++ b/src/wrapper/x402.ts @@ -1,41 +1,20 @@ -export interface X402Initialized { - fetch: typeof fetch; - httpClient: { - getPaymentRequiredResponse(getHeader: (name: string) => string | null, body: unknown): unknown; - encodePaymentSignatureHeader(payload: unknown): Record; - }; - payClient: { - createPaymentPayload(paymentRequired: unknown): Promise; - }; -} - /** - * Initializes x402 fetch wrapper and HTTP client from an x402Client instance. - * Dynamically imports @x402/fetch so it's only needed when x402 is used. + * Probes a WebSocket endpoint over HTTP to get a 402 response, + * then signs a payment and returns the headers needed for the WS handshake. */ -export async function initX402(x402Client: unknown): Promise { +export async function getPaymentHeaders(wsUrl: string, x402Client: unknown): Promise> { let x402Fetch: typeof import("@x402/fetch"); try { x402Fetch = await import("@x402/fetch"); } catch { throw new Error( - 'The x402 option requires @x402/fetch to be installed. Run: npm install @x402/fetch', + 'x402 WebSocket support requires @x402/fetch to be installed. Run: npm install @x402/fetch', ); } - const wrappedFetch = x402Fetch.wrapFetchWithPayment(fetch, x402Client as never); + const httpClient = new x402Fetch.x402HTTPClient(x402Client as never); - return { - fetch: wrappedFetch as typeof fetch, - httpClient, - payClient: x402Client as X402Initialized["payClient"], - }; -} + const payClient = x402Client as { createPaymentPayload(req: unknown): Promise }; -/** - * Probes a WebSocket endpoint over HTTP to get a 402 response, - * then signs a payment and returns the headers needed for the WS handshake. - */ -export async function getPaymentHeaders(wsUrl: string, x402: X402Initialized): Promise> { const httpUrl = wsUrl.replace(/^wss:\/\//, "https://").replace(/^ws:\/\//, "http://"); const response = await fetch(httpUrl); @@ -52,8 +31,7 @@ export async function getPaymentHeaders(wsUrl: string, x402: X402Initialized): P } const getHeader = (name: string): string | null => response.headers.get(name); - const paymentRequired = x402.httpClient.getPaymentRequiredResponse(getHeader, body); - const paymentPayload = await x402.payClient.createPaymentPayload(paymentRequired); - const headers = x402.httpClient.encodePaymentSignatureHeader(paymentPayload); - return headers; + const paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body); + const paymentPayload = await payClient.createPaymentPayload(paymentRequired); + return httpClient.encodePaymentSignatureHeader(paymentPayload); } From e308c166190fb9f9045b89aa11cdb0a724e82e40 Mon Sep 17 00:00:00 2001 From: Rishab Luthra Date: Sat, 14 Feb 2026 17:20:20 -0800 Subject: [PATCH 4/6] =?UTF-8?q?Handle=20HTTP=20x402=20internally=20?= =?UTF-8?q?=E2=80=94=20single=20x402=20option=20for=20everything?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wrapper now calls wrapFetchWithPayment internally so users only need to pass x402 once. No separate fetch override needed. Co-Authored-By: Claude Opus 4.6 --- src/wrapper/AgentMailClient.ts | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/wrapper/AgentMailClient.ts b/src/wrapper/AgentMailClient.ts index db5cfa2..a787084 100644 --- a/src/wrapper/AgentMailClient.ts +++ b/src/wrapper/AgentMailClient.ts @@ -6,12 +6,11 @@ import { X402WebsocketsClient } from "./X402WebsocketsClient.js"; export declare namespace AgentMailClient { export interface Options extends BaseClientOptions { /** - * An x402Client instance for automatic payment handling on WebSocket connections. - * For HTTP, pass a wrapped fetch via the `fetch` option instead. + * An x402Client instance for automatic payment handling on HTTP and WebSocket calls. * * @example * ```typescript - * import { x402Client, wrapFetchWithPayment } from "@x402/fetch"; + * import { x402Client } from "@x402/fetch"; * import { registerExactEvmScheme } from "@x402/evm/exact/client"; * import { privateKeyToAccount } from "viem/accounts"; * @@ -20,7 +19,6 @@ export declare namespace AgentMailClient { * * const client = new AgentMailClient({ * environment: AgentMailEnvironment.ProdX402, - * fetch: wrapFetchWithPayment(fetch, x402), * x402, * }); * ``` @@ -35,9 +33,24 @@ export class AgentMailClient extends FernAgentMailClient { private readonly _x402Client?: unknown; constructor(options: AgentMailClient.Options = {}) { - super(options); if (options.x402) { - this._x402Client = options.x402; + let wrappedFetch: typeof fetch | undefined; + const x402Client = options.x402; + + super({ + ...options, + fetch: (async (input: RequestInfo | URL, init?: RequestInit) => { + if (!wrappedFetch) { + const { wrapFetchWithPayment } = await import("@x402/fetch"); + wrappedFetch = wrapFetchWithPayment(fetch, x402Client as never); + } + return wrappedFetch(input, init); + }) as typeof fetch, + }); + + this._x402Client = x402Client; + } else { + super(options); } } From d957f2cdb236e6a7a2bc8971380c3d1cdef70053 Mon Sep 17 00:00:00 2001 From: Rishab Luthra Date: Sat, 14 Feb 2026 17:30:18 -0800 Subject: [PATCH 5/6] Default apiKey when x402 is provided Users no longer need to pass apiKey: "" when using x402. Co-Authored-By: Claude Opus 4.6 --- src/wrapper/AgentMailClient.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wrapper/AgentMailClient.ts b/src/wrapper/AgentMailClient.ts index a787084..346aae0 100644 --- a/src/wrapper/AgentMailClient.ts +++ b/src/wrapper/AgentMailClient.ts @@ -38,6 +38,7 @@ export class AgentMailClient extends FernAgentMailClient { const x402Client = options.x402; super({ + apiKey: "", ...options, fetch: (async (input: RequestInfo | URL, init?: RequestInit) => { if (!wrappedFetch) { From 8aa3fabefbbf6c0021793cd88ca12dd2d7307a0b Mon Sep 17 00:00:00 2001 From: Rishab Luthra Date: Thu, 26 Feb 2026 15:25:13 -0800 Subject: [PATCH 6/6] Use query params instead of headers for x402 WebSocket auth Many WebSocket clients (including browser WebSocket) don't support custom headers on the upgrade request. Send payment-signature as a query parameter instead. Co-Authored-By: Claude Opus 4.6 --- src/wrapper/X402WebsocketsClient.ts | 6 +++--- src/wrapper/x402.ts | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/wrapper/X402WebsocketsClient.ts b/src/wrapper/X402WebsocketsClient.ts index dd7ebde..f143e4e 100644 --- a/src/wrapper/X402WebsocketsClient.ts +++ b/src/wrapper/X402WebsocketsClient.ts @@ -2,7 +2,7 @@ import { WebsocketsClient } from "../api/resources/websockets/client/Client.js"; import type { WebsocketsSocket } from "../api/resources/websockets/client/Socket.js"; import * as core from "../core/index.js"; import * as environments from "../environments.js"; -import { getPaymentHeaders } from "./x402.js"; +import { getPaymentQueryParams } from "./x402.js"; export class X402WebsocketsClient extends WebsocketsClient { private readonly _x402Client: unknown; @@ -20,11 +20,11 @@ export class X402WebsocketsClient extends WebsocketsClient { "/v0", ); - const paymentHeaders = await getPaymentHeaders(wsUrl, this._x402Client); + const paymentQueryParams = await getPaymentQueryParams(wsUrl, this._x402Client); return super.connect({ ...args, - headers: { ...paymentHeaders, ...args.headers }, + queryParams: { ...paymentQueryParams, ...args.queryParams }, }); } } diff --git a/src/wrapper/x402.ts b/src/wrapper/x402.ts index 2782988..28ebed8 100644 --- a/src/wrapper/x402.ts +++ b/src/wrapper/x402.ts @@ -1,8 +1,11 @@ /** * Probes a WebSocket endpoint over HTTP to get a 402 response, - * then signs a payment and returns the headers needed for the WS handshake. + * then signs a payment and returns the payment-signature as a query parameter. + * + * Uses query params instead of headers because many WebSocket clients + * (including browser WebSocket) don't support custom headers on the upgrade request. */ -export async function getPaymentHeaders(wsUrl: string, x402Client: unknown): Promise> { +export async function getPaymentQueryParams(wsUrl: string, x402Client: unknown): Promise> { let x402Fetch: typeof import("@x402/fetch"); try { x402Fetch = await import("@x402/fetch"); @@ -33,5 +36,12 @@ export async function getPaymentHeaders(wsUrl: string, x402Client: unknown): Pro const getHeader = (name: string): string | null => response.headers.get(name); const paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body); const paymentPayload = await payClient.createPaymentPayload(paymentRequired); - return httpClient.encodePaymentSignatureHeader(paymentPayload); + const headers = httpClient.encodePaymentSignatureHeader(paymentPayload); + + const paymentSignature = headers["PAYMENT-SIGNATURE"] ?? headers["payment-signature"]; + if (!paymentSignature) { + throw new Error("x402: encodePaymentSignatureHeader did not return a payment-signature"); + } + + return { "payment-signature": paymentSignature }; }