@bosonprotocol/x402-server 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,22 @@ var FacilitatorHttpError = class extends Error {
11
11
  }
12
12
  };
13
13
 
14
+ // src/logger.ts
15
+ var noopLogger = {
16
+ debug: () => {
17
+ },
18
+ info: () => {
19
+ },
20
+ warn: () => {
21
+ },
22
+ error: () => {
23
+ }
24
+ };
25
+
14
26
  // src/facilitator/client.ts
27
+ var DEFAULT_TIMEOUT_MS = 3e4;
28
+ var DEFAULT_RETRY = { attempts: 3, backoffMs: 200 };
29
+ var IDEMPOTENCY_KEY_HEADER = "x-x402b-idempotency-key";
15
30
  function createFacilitatorClient(opts) {
16
31
  const fetchImpl = opts.fetch ?? globalThis.fetch;
17
32
  if (fetchImpl === void 0) {
@@ -21,19 +36,49 @@ function createFacilitatorClient(opts) {
21
36
  }
22
37
  const baseUrl = opts.url.replace(/\/+$/, "");
23
38
  const baseHeaders = { "content-type": "application/json", ...opts.headers ?? {} };
24
- const post = async (path, body, validate) => {
39
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
40
+ const retry = opts.retry ?? DEFAULT_RETRY;
41
+ if (!Number.isInteger(retry.attempts) || retry.attempts < 1) {
42
+ throw new Error("createFacilitatorClient: retry.attempts must be an integer >= 1");
43
+ }
44
+ if (!Number.isFinite(retry.backoffMs) || retry.backoffMs < 0) {
45
+ throw new Error("createFacilitatorClient: retry.backoffMs must be a finite number >= 0");
46
+ }
47
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
48
+ throw new Error("createFacilitatorClient: timeoutMs must be a finite number > 0");
49
+ }
50
+ const newIdempotencyKey = opts.idempotencyKey ?? (() => globalThis.crypto.randomUUID());
51
+ const setTimeoutImpl = opts.setTimeout ?? setTimeout;
52
+ const clearTimeoutImpl = opts.clearTimeout ?? clearTimeout;
53
+ const logger = opts.logger ?? noopLogger;
54
+ const postOnce = async (path, body, validate, extraHeaders) => {
55
+ const controller = new AbortController();
56
+ const timer = setTimeoutImpl(() => controller.abort(), timeoutMs);
57
+ const headers = { ...baseHeaders, ...extraHeaders };
25
58
  let res;
26
59
  try {
27
60
  res = await fetchImpl(`${baseUrl}${path}`, {
28
61
  method: "POST",
29
- headers: baseHeaders,
30
- body: JSON.stringify(body)
62
+ headers,
63
+ body: JSON.stringify(body),
64
+ signal: controller.signal
31
65
  });
32
66
  } catch (cause) {
33
- throw new FacilitatorHttpError(`facilitator network error (${path})`, {
34
- code: "NETWORK_ERROR",
35
- cause
67
+ const aborted = controller.signal.aborted;
68
+ logger.warn(aborted ? "facilitator request timed out" : "facilitator network error", {
69
+ path,
70
+ timeoutMs: aborted ? timeoutMs : void 0,
71
+ error: cause instanceof Error ? cause.message : String(cause)
36
72
  });
73
+ throw new FacilitatorHttpError(
74
+ aborted ? `facilitator request timed out after ${timeoutMs}ms (${path})` : `facilitator network error (${path})`,
75
+ {
76
+ code: aborted ? "TIMEOUT" : "NETWORK_ERROR",
77
+ cause
78
+ }
79
+ );
80
+ } finally {
81
+ clearTimeoutImpl(timer);
37
82
  }
38
83
  let text;
39
84
  try {
@@ -60,6 +105,12 @@ function createFacilitatorClient(opts) {
60
105
  return parsed;
61
106
  }
62
107
  const facilitatorCode = extractFacilitatorCode(parsed);
108
+ logger.warn("facilitator HTTP non-2xx", {
109
+ path,
110
+ status: res.status,
111
+ facilitatorCode,
112
+ reason: reasonString(parsed)
113
+ });
63
114
  throw new FacilitatorHttpError(
64
115
  `facilitator HTTP ${res.status} (${path}): ${reasonString(parsed) ?? text}`,
65
116
  {
@@ -77,16 +128,76 @@ function createFacilitatorClient(opts) {
77
128
  }
78
129
  return parsed;
79
130
  };
131
+ const post = async (path, body, validate, extraHeaders = {}) => {
132
+ let lastError;
133
+ for (let attempt = 0; attempt < retry.attempts; attempt += 1) {
134
+ if (attempt > 0) {
135
+ await sleep(retry.backoffMs * attempt, setTimeoutImpl);
136
+ }
137
+ try {
138
+ return await postOnce(path, body, validate, extraHeaders);
139
+ } catch (e) {
140
+ if (e instanceof FacilitatorHttpError && isRetryable(e)) {
141
+ lastError = e;
142
+ continue;
143
+ }
144
+ throw e;
145
+ }
146
+ }
147
+ throw lastError;
148
+ };
80
149
  return {
81
150
  verify: (input) => post("/verify", input, isVerifyResult),
82
- settle: (input) => post("/settle", input, isSettleResult),
151
+ settle: (input) => post("/settle", input, isSettleResult, {
152
+ [IDEMPOTENCY_KEY_HEADER]: newIdempotencyKey()
153
+ }),
83
154
  performAction: (input) => post(
84
155
  `/perform-action?action=${encodeURIComponent(input.action)}`,
85
156
  input,
86
157
  isPerformActionResult
87
- )
158
+ ),
159
+ async healthCheck() {
160
+ const controller = new AbortController();
161
+ const timer = setTimeoutImpl(() => controller.abort(), timeoutMs);
162
+ let res;
163
+ try {
164
+ res = await fetchImpl(`${baseUrl}/healthz`, {
165
+ method: "GET",
166
+ headers: { ...opts.headers ?? {} },
167
+ signal: controller.signal
168
+ });
169
+ } catch (cause) {
170
+ const aborted = controller.signal.aborted;
171
+ throw new FacilitatorHttpError(
172
+ aborted ? `facilitator request timed out after ${timeoutMs}ms (/healthz)` : "facilitator network error (/healthz)",
173
+ {
174
+ code: aborted ? "TIMEOUT" : "NETWORK_ERROR",
175
+ cause
176
+ }
177
+ );
178
+ } finally {
179
+ clearTimeoutImpl(timer);
180
+ }
181
+ if (!res.ok) {
182
+ throw new FacilitatorHttpError(`facilitator HTTP ${res.status} (/healthz)`, {
183
+ code: "BAD_HTTP_STATUS",
184
+ status: res.status
185
+ });
186
+ }
187
+ }
88
188
  };
89
189
  }
190
+ function isRetryable(e) {
191
+ if (e.code === "NETWORK_ERROR" || e.code === "TIMEOUT") return true;
192
+ if (e.code === "BAD_HTTP_STATUS" && e.status !== void 0 && e.status >= 500) return true;
193
+ return false;
194
+ }
195
+ function sleep(ms, setTimeoutImpl) {
196
+ if (ms <= 0) return Promise.resolve();
197
+ return new Promise((resolve) => {
198
+ setTimeoutImpl(resolve, ms);
199
+ });
200
+ }
90
201
  function isObject(v) {
91
202
  return v !== null && typeof v === "object";
92
203
  }
@@ -131,6 +242,7 @@ function reasonString(body) {
131
242
  }
132
243
 
133
244
  exports.FacilitatorHttpError = FacilitatorHttpError;
245
+ exports.IDEMPOTENCY_KEY_HEADER = IDEMPOTENCY_KEY_HEADER;
134
246
  exports.createFacilitatorClient = createFacilitatorClient;
135
247
  //# sourceMappingURL=index.js.map
136
248
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/facilitator/errors.ts","../../../src/facilitator/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Typed error class for HTTP-level facilitator failures. Distinct from\n// the `{ ok: false, code }` shape the facilitator itself returns —\n// those are domain errors and surface in the typed result. This class\n// is thrown only when the HTTP transport fails (network error,\n// non-2xx response with an unparseable body, etc.).\n\nimport type { FacilitatorErrorCode } from \"@bosonprotocol/x402-facilitator\";\n\n/**\n * Stable, x402-server-side codes for HTTP-transport failures.\n * Distinct from the facilitator-side `FacilitatorErrorCode` enum so\n * callers can branch on \"the facilitator answered with X\" vs \"we\n * never reached the facilitator\".\n */\nexport type FacilitatorHttpErrorCode =\n | \"NETWORK_ERROR\"\n | \"BAD_HTTP_STATUS\"\n | \"BAD_RESPONSE_BODY\"\n | \"TIMEOUT\";\n\n/** Network- or transport-layer failure when calling the facilitator. */\nexport class FacilitatorHttpError extends Error {\n readonly code: FacilitatorHttpErrorCode;\n readonly status?: number;\n /** Original facilitator-side error code, if the body parsed cleanly. */\n readonly facilitatorCode?: FacilitatorErrorCode;\n\n constructor(\n message: string,\n init: {\n code: FacilitatorHttpErrorCode;\n status?: number;\n facilitatorCode?: FacilitatorErrorCode;\n cause?: unknown;\n },\n ) {\n super(message, init.cause !== undefined ? { cause: init.cause } : undefined);\n this.name = \"FacilitatorHttpError\";\n this.code = init.code;\n if (init.status !== undefined) this.status = init.status;\n if (init.facilitatorCode !== undefined) this.facilitatorCode = init.facilitatorCode;\n }\n}\n","// HTTP client for the Boson facilitator service. The facilitator runs\n// as a remote process implementing the three endpoints in\n// docs/boson-impl-07-facilitator.md (`/verify`, `/settle`,\n// `/perform-action?action=<action>`); this module wraps `fetch` with the typed\n// request/response shapes exported by `@bosonprotocol/x402-facilitator`.\n//\n// Domain results (`{ ok: true, ... }` or `{ ok: false, code, reason }`)\n// are returned verbatim — both shapes are answers the caller branches\n// on. The facilitator-express adapter emits domain failures over HTTP\n// 400 (so curl users still see a 4xx), but the wire body is still a\n// well-formed result; we recognise that shape on HTTP 400 responses and\n// hand it back rather than throwing. HTTP-transport failures (network\n// down, non-JSON body, schema-mismatched body) throw\n// `FacilitatorHttpError` so the composition layer can distinguish\n// \"facilitator said no\" from \"we couldn't reach the facilitator\".\n\nimport type {\n FacilitatorErrorCode,\n FacilitatorPerformActionInput,\n FacilitatorPerformActionResult,\n FacilitatorSettleInput,\n FacilitatorSettleResult,\n FacilitatorVerifyInput,\n FacilitatorVerifyResult,\n} from \"@bosonprotocol/x402-facilitator\";\n\nimport { FacilitatorHttpError } from \"./errors.js\";\n\nexport type FetchLike = (\n input: string,\n init?: { method?: string; headers?: Record<string, string>; body?: string },\n) => Promise<{\n ok: boolean;\n status: number;\n text: () => Promise<string>;\n json?: () => Promise<unknown>;\n}>;\n\nexport interface CreateFacilitatorClientOptions {\n /** Base URL of the facilitator service (no trailing slash needed). */\n url: string;\n /** Optional `fetch` override — defaults to the global `fetch`. Useful for tests + non-`fetch` runtimes. */\n fetch?: FetchLike;\n /** Optional headers attached to every request — e.g. an `Authorization` for hosted facilitators. */\n headers?: Record<string, string>;\n}\n\nexport interface FacilitatorClient {\n verify(input: FacilitatorVerifyInput): Promise<FacilitatorVerifyResult>;\n settle(input: FacilitatorSettleInput): Promise<FacilitatorSettleResult>;\n performAction(input: FacilitatorPerformActionInput): Promise<FacilitatorPerformActionResult>;\n}\n\n/**\n * Construct a typed HTTP client for the facilitator. The returned\n * methods stringify the input as JSON and POST to the matching path\n * on the configured URL. Bodies matching the well-formed result shape\n * (whether HTTP 2xx success or HTTP 400 domain rejection) are returned\n * verbatim; transport failures (network, non-JSON body,\n * schema-mismatched body) raise `FacilitatorHttpError`.\n */\nexport function createFacilitatorClient(opts: CreateFacilitatorClientOptions): FacilitatorClient {\n const fetchImpl = opts.fetch ?? (globalThis.fetch as FetchLike | undefined);\n if (fetchImpl === undefined) {\n throw new Error(\n \"createFacilitatorClient: no `fetch` implementation available. Pass `opts.fetch` explicitly.\",\n );\n }\n const baseUrl = opts.url.replace(/\\/+$/, \"\");\n const baseHeaders = { \"content-type\": \"application/json\", ...(opts.headers ?? {}) };\n\n const post = async <Req, Res>(\n path: string,\n body: Req,\n validate: (parsed: unknown) => parsed is Res,\n ): Promise<Res> => {\n let res: Awaited<ReturnType<FetchLike>>;\n try {\n res = await fetchImpl(`${baseUrl}${path}`, {\n method: \"POST\",\n headers: baseHeaders,\n body: JSON.stringify(body),\n });\n } catch (cause) {\n throw new FacilitatorHttpError(`facilitator network error (${path})`, {\n code: \"NETWORK_ERROR\",\n cause,\n });\n }\n\n let text: string;\n try {\n text = await res.text();\n } catch (cause) {\n throw new FacilitatorHttpError(`facilitator response body could not be read (${path})`, {\n code: \"BAD_RESPONSE_BODY\",\n status: res.status,\n cause,\n });\n }\n\n let parsed: unknown;\n try {\n parsed = text.length === 0 ? null : JSON.parse(text);\n } catch (cause) {\n throw new FacilitatorHttpError(`facilitator returned non-JSON body (${path})`, {\n code: \"BAD_RESPONSE_BODY\",\n status: res.status,\n cause,\n });\n }\n\n if (!res.ok) {\n // The facilitator-express adapter returns domain failures as\n // HTTP 400 with the `{ok:false, code, reason}` body. Detect that\n // shape and surface it as the typed result — every endpoint's\n // `Res` union includes the same `{ok:false, code, reason}`\n // variant, so the cast through `validate` (which also accepts\n // that shape) preserves type-safety. Any other non-2xx status is\n // a genuine transport failure even if its body happens to look\n // like a facilitator result.\n if (res.status === 400 && isFailureBranch(parsed) && validate(parsed)) {\n return parsed;\n }\n const facilitatorCode = extractFacilitatorCode(parsed);\n throw new FacilitatorHttpError(\n `facilitator HTTP ${res.status} (${path}): ${reasonString(parsed) ?? text}`,\n {\n code: \"BAD_HTTP_STATUS\",\n status: res.status,\n ...(facilitatorCode !== undefined ? { facilitatorCode } : {}),\n },\n );\n }\n\n if (!validate(parsed)) {\n throw new FacilitatorHttpError(`facilitator returned unexpected body shape (${path})`, {\n code: \"BAD_RESPONSE_BODY\",\n status: res.status,\n });\n }\n return parsed;\n };\n\n return {\n verify: (input) =>\n post<FacilitatorVerifyInput, FacilitatorVerifyResult>(\"/verify\", input, isVerifyResult),\n settle: (input) =>\n post<FacilitatorSettleInput, FacilitatorSettleResult>(\"/settle\", input, isSettleResult),\n performAction: (input) =>\n post<FacilitatorPerformActionInput, FacilitatorPerformActionResult>(\n `/perform-action?action=${encodeURIComponent(input.action)}`,\n input,\n isPerformActionResult,\n ),\n };\n}\n\n// --- Response-shape type guards ----------------------------------------\n//\n// A buggy or malicious facilitator could return any 2xx JSON; without a\n// runtime check, `parsed as Res` would silently slip an unexpected shape\n// past the type system and the convenience handlers (PR 4) would\n// dereference fields that aren't there. These guards mirror the\n// discriminated unions in `@bosonprotocol/x402-facilitator`'s types and\n// fail any response that doesn't fit — surfaced as\n// `BAD_RESPONSE_BODY` so the caller treats it as a transport-layer\n// failure rather than a domain answer.\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === \"object\";\n}\n\n/**\n * Failure branch is identical across all three endpoints. Accepts\n * `unknown` so the non-2xx path can pre-check the parsed body without\n * narrowing first.\n */\nfunction isFailureBranch(v: unknown): v is { ok: false; code: string; reason: string } {\n return (\n isObject(v) && v.ok === false && typeof v.code === \"string\" && typeof v.reason === \"string\"\n );\n}\n\nfunction isVerifyResult(parsed: unknown): parsed is FacilitatorVerifyResult {\n if (!isObject(parsed)) return false;\n if (parsed.ok === true) return true; // `{ ok: true }` carries no further fields\n return isFailureBranch(parsed);\n}\n\nfunction isSettleResult(parsed: unknown): parsed is FacilitatorSettleResult {\n if (!isObject(parsed)) return false;\n if (parsed.ok === true) {\n return typeof parsed.exchangeId === \"string\" && typeof parsed.txHash === \"string\";\n }\n return isFailureBranch(parsed);\n}\n\nfunction isPerformActionResult(parsed: unknown): parsed is FacilitatorPerformActionResult {\n if (!isObject(parsed)) return false;\n if (parsed.ok === true) {\n if (typeof parsed.txHash !== \"string\") return false;\n // Entity-keyed actions (e.g. `boson-withdrawFunds`) return just\n // `{ ok: true, txHash }` — no exchange state transition. Accept\n // that shape; only require `newExchangeState` when present.\n if (parsed.newExchangeState !== undefined && typeof parsed.newExchangeState !== \"string\") {\n return false;\n }\n if (parsed.newDisputeState !== undefined && typeof parsed.newDisputeState !== \"string\") {\n return false;\n }\n return true;\n }\n return isFailureBranch(parsed);\n}\n\nfunction extractFacilitatorCode(body: unknown): FacilitatorErrorCode | undefined {\n if (body === null || typeof body !== \"object\") return undefined;\n const code = (body as { code?: unknown }).code;\n return typeof code === \"string\" ? (code as FacilitatorErrorCode) : undefined;\n}\n\nfunction reasonString(body: unknown): string | undefined {\n if (body === null || typeof body !== \"object\") return undefined;\n const reason = (body as { reason?: unknown }).reason;\n return typeof reason === \"string\" ? reason : undefined;\n}\n"]}
1
+ {"version":3,"sources":["../../../src/facilitator/errors.ts","../../../src/logger.ts","../../../src/facilitator/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Typed error class for HTTP-level facilitator failures. Distinct from\n// the `{ ok: false, code }` shape the facilitator itself returns —\n// those are domain errors and surface in the typed result. This class\n// is thrown only when the HTTP transport fails (network error,\n// non-2xx response with an unparseable body, etc.).\n\nimport type { FacilitatorErrorCode } from \"@bosonprotocol/x402-facilitator\";\n\n/**\n * Stable, x402-server-side codes for HTTP-transport failures.\n * Distinct from the facilitator-side `FacilitatorErrorCode` enum so\n * callers can branch on \"the facilitator answered with X\" vs \"we\n * never reached the facilitator\".\n */\nexport type FacilitatorHttpErrorCode =\n | \"NETWORK_ERROR\"\n | \"BAD_HTTP_STATUS\"\n | \"BAD_RESPONSE_BODY\"\n | \"TIMEOUT\";\n\n/** Network- or transport-layer failure when calling the facilitator. */\nexport class FacilitatorHttpError extends Error {\n readonly code: FacilitatorHttpErrorCode;\n readonly status?: number;\n /** Original facilitator-side error code, if the body parsed cleanly. */\n readonly facilitatorCode?: FacilitatorErrorCode;\n\n constructor(\n message: string,\n init: {\n code: FacilitatorHttpErrorCode;\n status?: number;\n facilitatorCode?: FacilitatorErrorCode;\n cause?: unknown;\n },\n ) {\n super(message, init.cause !== undefined ? { cause: init.cause } : undefined);\n this.name = \"FacilitatorHttpError\";\n this.code = init.code;\n if (init.status !== undefined) this.status = init.status;\n if (init.facilitatorCode !== undefined) this.facilitatorCode = init.facilitatorCode;\n }\n}\n","// Minimal four-method logger interface threaded through the server.\n// Matches the de-facto shape every observability library in the JS\n// ecosystem (pino, winston, console, bunyan, …) so hosts adapt their\n// existing logger in one line. The default is `noopLogger`, which\n// discards every event — no overhead, no surprise output, opt-in\n// observability.\n\nexport interface Logger {\n debug(msg: string, meta?: Record<string, unknown>): void;\n info(msg: string, meta?: Record<string, unknown>): void;\n warn(msg: string, meta?: Record<string, unknown>): void;\n error(msg: string, meta?: Record<string, unknown>): void;\n}\n\n/** No-op logger — the default when the host doesn't supply one. */\nexport const noopLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n","// HTTP client for the Boson facilitator service. The facilitator runs\n// as a remote process implementing the three endpoints in\n// docs/boson-impl-07-facilitator.md (`/verify`, `/settle`,\n// `/perform-action?action=<action>`); this module wraps `fetch` with the typed\n// request/response shapes exported by `@bosonprotocol/x402-facilitator`.\n//\n// Domain results (`{ ok: true, ... }` or `{ ok: false, code, reason }`)\n// are returned verbatim — both shapes are answers the caller branches\n// on. The facilitator-express adapter emits domain failures over HTTP\n// 400 (so curl users still see a 4xx), but the wire body is still a\n// well-formed result; we recognise that shape on HTTP 400 responses and\n// hand it back rather than throwing. HTTP-transport failures (network\n// down, non-JSON body, schema-mismatched body) throw\n// `FacilitatorHttpError` so the composition layer can distinguish\n// \"facilitator said no\" from \"we couldn't reach the facilitator\".\n//\n// Transport hardening: every request enforces a per-attempt timeout via\n// `AbortController` and retries `NETWORK_ERROR` / `TIMEOUT` / 5xx with\n// linear backoff. `/settle` calls additionally carry a stable\n// `x-x402b-idempotency-key` header so a facilitator-side dedup table\n// (separate package) can recognise a retried request as the same\n// underlying intent.\n\nimport type {\n FacilitatorErrorCode,\n FacilitatorPerformActionInput,\n FacilitatorPerformActionResult,\n FacilitatorSettleInput,\n FacilitatorSettleResult,\n FacilitatorVerifyInput,\n FacilitatorVerifyResult,\n} from \"@bosonprotocol/x402-facilitator\";\n\nimport { FacilitatorHttpError } from \"./errors.js\";\nimport { noopLogger, type Logger } from \"../logger.js\";\n\nexport type FetchLike = (\n input: string,\n init?: {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n signal?: AbortSignal;\n },\n) => Promise<{\n ok: boolean;\n status: number;\n text: () => Promise<string>;\n json?: () => Promise<unknown>;\n}>;\n\nexport interface FacilitatorRetryOptions {\n /** Total attempt budget (initial try + retries). Default 3. */\n attempts: number;\n /** Linear-backoff multiplier in ms: sleep `backoffMs * attemptIndex` between attempts. Default 200. */\n backoffMs: number;\n}\n\nexport interface CreateFacilitatorClientOptions {\n /** Base URL of the facilitator service (no trailing slash needed). */\n url: string;\n /** Optional `fetch` override — defaults to the global `fetch`. Useful for tests + non-`fetch` runtimes. */\n fetch?: FetchLike;\n /** Optional headers attached to every request — e.g. an `Authorization` for hosted facilitators. */\n headers?: Record<string, string>;\n /**\n * Per-attempt timeout in ms. Default 30_000. The `AbortController`\n * aborts in-flight fetches when this elapses; the error surfaces as\n * `FacilitatorHttpError` with code `TIMEOUT` and is retried under\n * the same policy as `NETWORK_ERROR`.\n */\n timeoutMs?: number;\n /**\n * Retry policy applied to `NETWORK_ERROR`, `TIMEOUT`, and HTTP 5xx\n * responses. Defaults to `{ attempts: 3, backoffMs: 200 }` — three\n * attempts total at 0/200/400 ms. Set `{ attempts: 1, backoffMs: 0 }`\n * to disable.\n */\n retry?: FacilitatorRetryOptions;\n /**\n * Factory for the `x-x402b-idempotency-key` header attached to\n * `/settle` calls. Called once per logical request — *not* once per\n * retry attempt — so the facilitator's dedup table can recognise\n * retries of the same intent. Defaults to `crypto.randomUUID`.\n */\n idempotencyKey?: () => string;\n /** Override for `setTimeout` — tests use this to skip real backoff sleeps. */\n setTimeout?: typeof setTimeout;\n /** Override for `clearTimeout` — paired with `setTimeout`. */\n clearTimeout?: typeof clearTimeout;\n /** Optional structured logger. Defaults to no-op. Receives `warn` on errored requests; `debug` on each call. */\n logger?: Logger;\n}\n\nexport interface FacilitatorClient {\n verify(input: FacilitatorVerifyInput): Promise<FacilitatorVerifyResult>;\n settle(input: FacilitatorSettleInput): Promise<FacilitatorSettleResult>;\n performAction(input: FacilitatorPerformActionInput): Promise<FacilitatorPerformActionResult>;\n /**\n * GET `/healthz` liveness probe. Resolves on any 2xx; throws on\n * network error or non-2xx. Designed for the server SDK's\n * `healthCheck()` helper — hosts mount that one behind whatever\n * `/healthz` route their framework uses.\n */\n healthCheck(): Promise<void>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_RETRY: FacilitatorRetryOptions = { attempts: 3, backoffMs: 200 };\n\n/** Idempotency-key header name. Stable so a facilitator-side dedup table can recognise it. */\nexport const IDEMPOTENCY_KEY_HEADER = \"x-x402b-idempotency-key\";\n\n/**\n * Construct a typed HTTP client for the facilitator. The returned\n * methods stringify the input as JSON and POST to the matching path\n * on the configured URL. Bodies matching the well-formed result shape\n * (whether HTTP 2xx success or HTTP 400 domain rejection) are returned\n * verbatim; transport failures (network, non-JSON body,\n * schema-mismatched body) raise `FacilitatorHttpError`.\n */\nexport function createFacilitatorClient(opts: CreateFacilitatorClientOptions): FacilitatorClient {\n const fetchImpl = opts.fetch ?? (globalThis.fetch as FetchLike | undefined);\n if (fetchImpl === undefined) {\n throw new Error(\n \"createFacilitatorClient: no `fetch` implementation available. Pass `opts.fetch` explicitly.\",\n );\n }\n const baseUrl = opts.url.replace(/\\/+$/, \"\");\n const baseHeaders = { \"content-type\": \"application/json\", ...(opts.headers ?? {}) };\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const retry = opts.retry ?? DEFAULT_RETRY;\n // Defensive validation: `x402bServerConfigSchema` enforces these\n // bounds when the client is built via `createX402bServer`, but\n // `createFacilitatorClient` is also exported for direct use — a bad\n // `attempts` would silently fall through the retry loop and throw\n // `undefined` instead of a `FacilitatorHttpError`.\n if (!Number.isInteger(retry.attempts) || retry.attempts < 1) {\n throw new Error(\"createFacilitatorClient: retry.attempts must be an integer >= 1\");\n }\n if (!Number.isFinite(retry.backoffMs) || retry.backoffMs < 0) {\n throw new Error(\"createFacilitatorClient: retry.backoffMs must be a finite number >= 0\");\n }\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n throw new Error(\"createFacilitatorClient: timeoutMs must be a finite number > 0\");\n }\n const newIdempotencyKey = opts.idempotencyKey ?? (() => globalThis.crypto.randomUUID());\n const setTimeoutImpl = opts.setTimeout ?? setTimeout;\n const clearTimeoutImpl = opts.clearTimeout ?? clearTimeout;\n const logger: Logger = opts.logger ?? noopLogger;\n\n const postOnce = async <Req, Res>(\n path: string,\n body: Req,\n validate: (parsed: unknown) => parsed is Res,\n extraHeaders: Record<string, string>,\n ): Promise<Res> => {\n const controller = new AbortController();\n const timer = setTimeoutImpl(() => controller.abort(), timeoutMs);\n const headers = { ...baseHeaders, ...extraHeaders };\n\n let res: Awaited<ReturnType<FetchLike>>;\n try {\n res = await fetchImpl(`${baseUrl}${path}`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (cause) {\n const aborted = controller.signal.aborted;\n logger.warn(aborted ? \"facilitator request timed out\" : \"facilitator network error\", {\n path,\n timeoutMs: aborted ? timeoutMs : undefined,\n error: cause instanceof Error ? cause.message : String(cause),\n });\n throw new FacilitatorHttpError(\n aborted\n ? `facilitator request timed out after ${timeoutMs}ms (${path})`\n : `facilitator network error (${path})`,\n {\n code: aborted ? \"TIMEOUT\" : \"NETWORK_ERROR\",\n cause,\n },\n );\n } finally {\n clearTimeoutImpl(timer);\n }\n\n let text: string;\n try {\n text = await res.text();\n } catch (cause) {\n throw new FacilitatorHttpError(`facilitator response body could not be read (${path})`, {\n code: \"BAD_RESPONSE_BODY\",\n status: res.status,\n cause,\n });\n }\n\n let parsed: unknown;\n try {\n parsed = text.length === 0 ? null : JSON.parse(text);\n } catch (cause) {\n throw new FacilitatorHttpError(`facilitator returned non-JSON body (${path})`, {\n code: \"BAD_RESPONSE_BODY\",\n status: res.status,\n cause,\n });\n }\n\n if (!res.ok) {\n // The facilitator-express adapter returns domain failures as\n // HTTP 400 with the `{ok:false, code, reason}` body. Detect that\n // shape and surface it as the typed result — every endpoint's\n // `Res` union includes the same `{ok:false, code, reason}`\n // variant, so the cast through `validate` (which also accepts\n // that shape) preserves type-safety. Any other non-2xx status is\n // a genuine transport failure even if its body happens to look\n // like a facilitator result.\n if (res.status === 400 && isFailureBranch(parsed) && validate(parsed)) {\n return parsed;\n }\n const facilitatorCode = extractFacilitatorCode(parsed);\n logger.warn(\"facilitator HTTP non-2xx\", {\n path,\n status: res.status,\n facilitatorCode,\n reason: reasonString(parsed),\n });\n throw new FacilitatorHttpError(\n `facilitator HTTP ${res.status} (${path}): ${reasonString(parsed) ?? text}`,\n {\n code: \"BAD_HTTP_STATUS\",\n status: res.status,\n ...(facilitatorCode !== undefined ? { facilitatorCode } : {}),\n },\n );\n }\n\n if (!validate(parsed)) {\n throw new FacilitatorHttpError(`facilitator returned unexpected body shape (${path})`, {\n code: \"BAD_RESPONSE_BODY\",\n status: res.status,\n });\n }\n return parsed;\n };\n\n const post = async <Req, Res>(\n path: string,\n body: Req,\n validate: (parsed: unknown) => parsed is Res,\n extraHeaders: Record<string, string> = {},\n ): Promise<Res> => {\n let lastError: FacilitatorHttpError | undefined;\n for (let attempt = 0; attempt < retry.attempts; attempt += 1) {\n if (attempt > 0) {\n await sleep(retry.backoffMs * attempt, setTimeoutImpl);\n }\n try {\n return await postOnce(path, body, validate, extraHeaders);\n } catch (e) {\n if (e instanceof FacilitatorHttpError && isRetryable(e)) {\n lastError = e;\n continue;\n }\n throw e;\n }\n }\n // Non-null assertion: the loop above sets `lastError` on every retryable\n // failure and only falls through after the final attempt also failed.\n throw lastError as FacilitatorHttpError;\n };\n\n return {\n verify: (input) =>\n post<FacilitatorVerifyInput, FacilitatorVerifyResult>(\"/verify\", input, isVerifyResult),\n settle: (input) =>\n post<FacilitatorSettleInput, FacilitatorSettleResult>(\"/settle\", input, isSettleResult, {\n [IDEMPOTENCY_KEY_HEADER]: newIdempotencyKey(),\n }),\n performAction: (input) =>\n post<FacilitatorPerformActionInput, FacilitatorPerformActionResult>(\n `/perform-action?action=${encodeURIComponent(input.action)}`,\n input,\n isPerformActionResult,\n ),\n async healthCheck() {\n // GET /healthz — body is ignored. Any 2xx is healthy; anything\n // else (network error, timeout, non-2xx) raises FacilitatorHttpError\n // so the `createHealthCheck` helper maps to \"down\". The abort\n // timer mirrors `postOnce` so a stuck connection can't hang the\n // host's /healthz route indefinitely. `opts.headers` (e.g.\n // `Authorization` for hosted facilitators) must ride along here\n // too — otherwise normal POSTs would succeed while /healthz\n // would always 401 → \"down\".\n const controller = new AbortController();\n const timer = setTimeoutImpl(() => controller.abort(), timeoutMs);\n let res: Awaited<ReturnType<FetchLike>>;\n try {\n res = await fetchImpl(`${baseUrl}/healthz`, {\n method: \"GET\",\n headers: { ...(opts.headers ?? {}) },\n signal: controller.signal,\n });\n } catch (cause) {\n const aborted = controller.signal.aborted;\n throw new FacilitatorHttpError(\n aborted\n ? `facilitator request timed out after ${timeoutMs}ms (/healthz)`\n : \"facilitator network error (/healthz)\",\n {\n code: aborted ? \"TIMEOUT\" : \"NETWORK_ERROR\",\n cause,\n },\n );\n } finally {\n clearTimeoutImpl(timer);\n }\n if (!res.ok) {\n throw new FacilitatorHttpError(`facilitator HTTP ${res.status} (/healthz)`, {\n code: \"BAD_HTTP_STATUS\",\n status: res.status,\n });\n }\n },\n };\n}\n\nfunction isRetryable(e: FacilitatorHttpError): boolean {\n if (e.code === \"NETWORK_ERROR\" || e.code === \"TIMEOUT\") return true;\n if (e.code === \"BAD_HTTP_STATUS\" && e.status !== undefined && e.status >= 500) return true;\n return false;\n}\n\nfunction sleep(ms: number, setTimeoutImpl: typeof setTimeout): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => {\n setTimeoutImpl(resolve, ms);\n });\n}\n\n// --- Response-shape type guards ----------------------------------------\n//\n// A buggy or malicious facilitator could return any 2xx JSON; without a\n// runtime check, `parsed as Res` would silently slip an unexpected shape\n// past the type system and the convenience handlers (PR 4) would\n// dereference fields that aren't there. These guards mirror the\n// discriminated unions in `@bosonprotocol/x402-facilitator`'s types and\n// fail any response that doesn't fit — surfaced as\n// `BAD_RESPONSE_BODY` so the caller treats it as a transport-layer\n// failure rather than a domain answer.\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === \"object\";\n}\n\n/**\n * Failure branch is identical across all three endpoints. Accepts\n * `unknown` so the non-2xx path can pre-check the parsed body without\n * narrowing first.\n */\nfunction isFailureBranch(v: unknown): v is { ok: false; code: string; reason: string } {\n return (\n isObject(v) && v.ok === false && typeof v.code === \"string\" && typeof v.reason === \"string\"\n );\n}\n\nfunction isVerifyResult(parsed: unknown): parsed is FacilitatorVerifyResult {\n if (!isObject(parsed)) return false;\n if (parsed.ok === true) return true; // `{ ok: true }` carries no further fields\n return isFailureBranch(parsed);\n}\n\nfunction isSettleResult(parsed: unknown): parsed is FacilitatorSettleResult {\n if (!isObject(parsed)) return false;\n if (parsed.ok === true) {\n return typeof parsed.exchangeId === \"string\" && typeof parsed.txHash === \"string\";\n }\n return isFailureBranch(parsed);\n}\n\nfunction isPerformActionResult(parsed: unknown): parsed is FacilitatorPerformActionResult {\n if (!isObject(parsed)) return false;\n if (parsed.ok === true) {\n if (typeof parsed.txHash !== \"string\") return false;\n // Entity-keyed actions (e.g. `boson-withdrawFunds`) return just\n // `{ ok: true, txHash }` — no exchange state transition. Accept\n // that shape; only require `newExchangeState` when present.\n if (parsed.newExchangeState !== undefined && typeof parsed.newExchangeState !== \"string\") {\n return false;\n }\n if (parsed.newDisputeState !== undefined && typeof parsed.newDisputeState !== \"string\") {\n return false;\n }\n return true;\n }\n return isFailureBranch(parsed);\n}\n\nfunction extractFacilitatorCode(body: unknown): FacilitatorErrorCode | undefined {\n if (body === null || typeof body !== \"object\") return undefined;\n const code = (body as { code?: unknown }).code;\n return typeof code === \"string\" ? (code as FacilitatorErrorCode) : undefined;\n}\n\nfunction reasonString(body: unknown): string | undefined {\n if (body === null || typeof body !== \"object\") return undefined;\n const reason = (body as { reason?: unknown }).reason;\n return typeof reason === \"string\" ? reason : undefined;\n}\n"]}
@@ -1,267 +1,11 @@
1
- import { EscrowPaymentRequirements, Address } from '@bosonprotocol/x402-core/schemes/escrow';
2
- import { EscrowNextActions, ExchangeState, DisputeState, ChannelRegistry } from '@bosonprotocol/x402-actions';
3
- import { ExchangeReader } from '../onchain/index.js';
4
- import { F as FacilitatorClient } from '../client-tnrpqVMW.js';
5
- import { X as X402bServerConfig, F as FulfillmentRecoveryEntry, b as CoreSdkReadAdapter } from '../config-CBr9qMps.js';
6
- import { ExchangeActionId } from '@bosonprotocol/x402-core/state-machine';
7
- import { Hex } from 'viem';
1
+ export { f as ADDRESS_RE, e as AvailableFundsBody, g as AvailableFundsContext, h as AvailableFundsEntry, A as AvailableFundsQuery, i as CommitHandlerContext, C as CommitHandlerInput, a as CommitOk, D as DECIMAL_UINT_RE, E as EmitNextActionsInput, j as HEX_BYTES_RE, k as HandlerErrorBody, H as HandlerResult, l as HandlerStatus, m as HandlerWarning, n as PerformActionContext, b as PerformActionInput, P as PerformActionOk, c as PlainHandlerResult, o as RedeemHandlerContext, R as RedeemHandlerInput, p as ResolveEntityError, q as ResolveEntityInput, r as ResolveEntityOk, s as ResolveEntityResult, t as WithdrawFundsContext, W as WithdrawFundsInput, d as WithdrawFundsOk, u as emitNextActions, v as handleCommit, w as handleCommitAndRedeem, x as handleComplete, y as handleDisputeEscalate, z as handleDisputeRaise, B as handleDisputeResolve, F as handleDisputeRetract, G as handleGetAvailableFunds, I as handlePerformAction, J as handleRedeem, K as handleWithdrawFunds, L as handlerErr, M as handlerOk, N as plainHandlerOk, O as resolveEntityId } from '../index-COmLTH-U.js';
2
+ import '@bosonprotocol/x402-core/schemes/escrow';
3
+ import '@bosonprotocol/x402-actions';
4
+ import '../onchain/index.js';
5
+ import '../client-B2yejopi.js';
8
6
  import '@bosonprotocol/x402-facilitator';
7
+ import '../config-hnCiZiXF.js';
8
+ import 'viem';
9
9
  import 'zod';
10
10
  import '@bosonprotocol/core-sdk';
11
-
12
- type HandlerStatus = 200 | 400 | 402 | 404 | 409 | 500 | 502;
13
- type HandlerResult<TBody> = {
14
- ok: true;
15
- status: 200;
16
- body: TBody & {
17
- nextActions: EscrowNextActions;
18
- };
19
- } | {
20
- ok: false;
21
- status: Exclude<HandlerStatus, 200>;
22
- body: HandlerErrorBody;
23
- };
24
- /**
25
- * Result shape for handlers whose success body does NOT carry a
26
- * `nextActions` envelope — used by entity-keyed actions
27
- * (`boson-withdrawFunds`) and the read-only `available-funds`
28
- * endpoint, neither of which advance the exchange state machine.
29
- */
30
- type PlainHandlerResult<TBody> = {
31
- ok: true;
32
- status: 200;
33
- body: TBody;
34
- } | {
35
- ok: false;
36
- status: Exclude<HandlerStatus, 200>;
37
- body: HandlerErrorBody;
38
- };
39
- interface HandlerErrorBody {
40
- /** Stable identifier — caller branches on this rather than the human-readable `reason`. */
41
- code: string;
42
- reason: string;
43
- /** Optional rich detail — validator field/expected/got, facilitator code, etc. */
44
- details?: unknown;
45
- }
46
- interface HandlerWarning {
47
- /** Stable identifier — caller branches on this rather than the human-readable `reason`. */
48
- code: string;
49
- reason: string;
50
- /** Optional rich detail — tx hash, exchange id, deferred operation, etc. */
51
- details?: unknown;
52
- }
53
- declare function handlerOk<TBody>(body: TBody & {
54
- nextActions: EscrowNextActions;
55
- }): HandlerResult<TBody>;
56
- declare function plainHandlerOk<TBody>(body: TBody): PlainHandlerResult<TBody>;
57
- declare function handlerErr(status: Exclude<HandlerStatus, 200>, code: string, reason: string, details?: unknown): HandlerResult<never> & PlainHandlerResult<never>;
58
-
59
- interface CommitHandlerInput {
60
- /** Raw `X-PAYMENT` header value (base64'd JSON). */
61
- paymentHeader: string | undefined | null;
62
- /** The 402 `PaymentRequirements` the buyer is responding to. */
63
- requirements: EscrowPaymentRequirements;
64
- }
65
- interface CommitHandlerContext {
66
- config: X402bServerConfig;
67
- facilitator: FacilitatorClient;
68
- exchangeReader: ExchangeReader;
69
- fulfillmentRecoveryStore: Map<string, FulfillmentRecoveryEntry>;
70
- /**
71
- * Per-exchange fulfillment option policy. Flow A writes the ids
72
- * advertised by the original requirements so the redeem-time choice
73
- * is constrained to the offer's own channel set.
74
- */
75
- exchangeFulfillmentOptionStore: Map<string, readonly string[]>;
76
- }
77
- interface CommitOk {
78
- exchangeId: string;
79
- txHash: string;
80
- /**
81
- * Non-fatal post-settle conditions. Today only Flow B uses this slot —
82
- * the on-chain redeem may have succeeded while the configured channel
83
- * adapter's `onCommit(...)` failed (the buyer's funds and voucher are
84
- * already gone; the seller's host needs to recover the delivery target
85
- * out-of-band). The exchange state is the wire-format source of truth;
86
- * warnings are advisory.
87
- */
88
- warnings?: HandlerWarning[];
89
- }
90
- /**
91
- * Flow A — `boson-createOfferAndCommit`. Settles via facilitator,
92
- * expects the resulting exchange in `COMMITTED`, returns 200 with
93
- * `nextActions` advertising the legal post-COMMITTED transitions.
94
- */
95
- declare function handleCommit(input: CommitHandlerInput, ctx: CommitHandlerContext): Promise<HandlerResult<CommitOk>>;
96
- /**
97
- * Flow B — `boson-createOfferCommitAndRedeem`. Same pipeline as
98
- * `handleCommit` but verifies the exchange reached `REDEEMED`.
99
- */
100
- declare function handleCommitAndRedeem(input: CommitHandlerInput, ctx: CommitHandlerContext): Promise<HandlerResult<CommitOk>>;
101
-
102
- /** Per-action inputs accepted by every post-commit convenience handler. */
103
- interface PerformActionInput {
104
- exchangeId: string;
105
- /** ABI-encoded `BosonMetaTx` tuple — see `encodeSignedPayload` in `@bosonprotocol/x402-facilitator`. */
106
- signedPayload: Hex;
107
- }
108
- /**
109
- * Redeem-time variant of `PerformActionInput`. Carries the buyer's
110
- * `fulfillment` selection for Flow A — `data` is the delivery target
111
- * the redeem-time channel adapter persists. Required when the
112
- * original 402 advertised `fulfillment.required = true`; omitted
113
- * otherwise.
114
- */
115
- interface RedeemHandlerInput extends PerformActionInput {
116
- fulfillment?: {
117
- option: string;
118
- data: Record<string, unknown> | null;
119
- };
120
- }
121
- interface PerformActionContext {
122
- config: X402bServerConfig;
123
- facilitator: FacilitatorClient;
124
- exchangeReader: ExchangeReader;
125
- }
126
- interface RedeemHandlerContext extends PerformActionContext {
127
- exchangeFulfillmentOptionStore: Map<string, readonly string[]>;
128
- fulfillmentRecoveryStore: Map<string, FulfillmentRecoveryEntry>;
129
- }
130
- interface PerformActionOk {
131
- txHash: string;
132
- warnings?: HandlerWarning[];
133
- }
134
- /**
135
- * Generic exchange-keyed post-commit handler — wired from each of the
136
- * per-action wrappers below. Entity-keyed actions (e.g. `withdrawFunds`)
137
- * have their own handler in `./withdraw-funds.ts`.
138
- */
139
- declare function handlePerformAction(action: ExchangeActionId, input: PerformActionInput, ctx: PerformActionContext): Promise<HandlerResult<PerformActionOk>>;
140
- /**
141
- * Redeem handler. Validates the buyer's `fulfillment` selection (if
142
- * present) against the offer's advertised option set and the host's
143
- * channel registry, runs the channel's `validate` up-front, then
144
- * forwards to the facilitator. The corresponding
145
- * `onCommit(exchangeId, data)` upsert is deferred until *after* the
146
- * facilitator + state verification confirm the exchange reached
147
- * `REDEEMED`, so a failed redeem leaves the stored delivery target
148
- * unchanged. The voucher NFT is transferable; whichever wallet signs
149
- * `boson-redeem` supplies the delivery data — it's the redeemer's
150
- * choice end-to-end.
151
- */
152
- declare function handleRedeem(input: RedeemHandlerInput, ctx: RedeemHandlerContext): Promise<HandlerResult<PerformActionOk>>;
153
- /** Per-action sugar — preserves the action id at the type level. */
154
- declare const handleComplete: (input: PerformActionInput, ctx: PerformActionContext) => Promise<HandlerResult<PerformActionOk>>;
155
- declare const handleDisputeRaise: (input: PerformActionInput, ctx: PerformActionContext) => Promise<HandlerResult<PerformActionOk>>;
156
- declare const handleDisputeResolve: (input: PerformActionInput, ctx: PerformActionContext) => Promise<HandlerResult<PerformActionOk>>;
157
- declare const handleDisputeRetract: (input: PerformActionInput, ctx: PerformActionContext) => Promise<HandlerResult<PerformActionOk>>;
158
- declare const handleDisputeEscalate: (input: PerformActionInput, ctx: PerformActionContext) => Promise<HandlerResult<PerformActionOk>>;
159
-
160
- interface WithdrawFundsBaseInput {
161
- /** ABI-encoded `BosonMetaTx` tuple — see `encodeSignedPayload` in `@bosonprotocol/x402-evm/codec`. */
162
- signedPayload: Hex;
163
- }
164
- type WithdrawFundsInput = WithdrawFundsBaseInput & ({
165
- entityId: string;
166
- } | {
167
- address: string;
168
- role?: "buyer" | "seller";
169
- });
170
- interface WithdrawFundsContext {
171
- config: X402bServerConfig;
172
- facilitator: FacilitatorClient;
173
- coreSdkRead: CoreSdkReadAdapter;
174
- }
175
- interface WithdrawFundsOk {
176
- txHash: string;
177
- entityId: string;
178
- role?: "buyer" | "seller";
179
- }
180
- declare function handleWithdrawFunds(input: WithdrawFundsInput, ctx: WithdrawFundsContext): Promise<PlainHandlerResult<WithdrawFundsOk>>;
181
-
182
- interface AvailableFundsEntry {
183
- tokenAddress: Address;
184
- tokenSymbol: string;
185
- tokenName: string;
186
- decimals: number;
187
- availableAmount: string;
188
- }
189
- interface AvailableFundsBody {
190
- entityId: string;
191
- /** Present when the caller looked up by `address`; omitted when looked up by `entityId`. */
192
- role?: "buyer" | "seller";
193
- funds: AvailableFundsEntry[];
194
- }
195
- type AvailableFundsQuery = {
196
- entityId: string;
197
- } | {
198
- address: string;
199
- role?: "buyer" | "seller";
200
- };
201
- interface AvailableFundsContext {
202
- coreSdkRead: CoreSdkReadAdapter;
203
- }
204
- declare function handleGetAvailableFunds(query: AvailableFundsQuery, ctx: AvailableFundsContext): Promise<PlainHandlerResult<AvailableFundsBody>>;
205
-
206
- interface ResolveEntityInput {
207
- address: string;
208
- role?: "buyer" | "seller";
209
- }
210
- interface ResolveEntityOk {
211
- ok: true;
212
- entityId: string;
213
- role: "buyer" | "seller";
214
- }
215
- type ResolveEntityError = {
216
- ok: false;
217
- code: "NOT_FOUND";
218
- reason: string;
219
- } | {
220
- ok: false;
221
- code: "AMBIGUOUS";
222
- reason: string;
223
- /** Seller ids matching the address (one or many). Absent when no sellers matched. */
224
- sellerIds?: string[];
225
- /** Buyer ids matching the address (one or many). Absent when no buyers matched. */
226
- buyerIds?: string[];
227
- } | {
228
- ok: false;
229
- code: "SUBGRAPH_FAILURE";
230
- reason: string;
231
- };
232
- type ResolveEntityResult = ResolveEntityOk | ResolveEntityError;
233
- declare function resolveEntityId(coreSdk: CoreSdkReadAdapter, input: ResolveEntityInput): Promise<ResolveEntityResult>;
234
-
235
- /** Boson account `entityId` — uint256 in decimal-string form, no leading zeros. */
236
- declare const DECIMAL_UINT_RE: RegExp;
237
- /** 20-byte EVM address, 0x-prefixed, any letter case (we normalise downstream). */
238
- declare const ADDRESS_RE: RegExp;
239
- /**
240
- * Hex-string check for `signedPayload`. Requires `0x` followed by an
241
- * *even* number of hex digits so the body decodes to whole bytes —
242
- * odd-length payloads like `0xabc` would surface as
243
- * `signedPayload decode failed: …` deep in the facilitator pipeline,
244
- * which is a less precise 502 than the adapter-level 400 this regex
245
- * catches. Stricter than core's `HEX_BYTES` (which permits odd
246
- * lengths) so we keep this one local.
247
- */
248
- declare const HEX_BYTES_RE: RegExp;
249
-
250
- type EmitNextActionsInput = {
251
- exchangeId: string;
252
- } & ({
253
- exchangeState: Exclude<ExchangeState, typeof ExchangeState.DISPUTED>;
254
- disputeState?: never;
255
- } | {
256
- exchangeState: typeof ExchangeState.DISPUTED;
257
- disputeState: DisputeState;
258
- });
259
- /**
260
- * Build the `nextActions` envelope a handler attaches to its 200 body.
261
- * Pure wrapper; the logic beyond `deriveNextActions` is the
262
- * `DISPUTED → disputeState required` narrowing and the optional
263
- * facilitator-endpoint stamp.
264
- */
265
- declare function emitNextActions(input: EmitNextActionsInput, registry: ChannelRegistry, facilitatorUrl?: string): EscrowNextActions;
266
-
267
- export { ADDRESS_RE, type AvailableFundsBody, type AvailableFundsContext, type AvailableFundsEntry, type AvailableFundsQuery, type CommitHandlerContext, type CommitHandlerInput, type CommitOk, DECIMAL_UINT_RE, type EmitNextActionsInput, HEX_BYTES_RE, type HandlerErrorBody, type HandlerResult, type HandlerStatus, type HandlerWarning, type PerformActionContext, type PerformActionInput, type PerformActionOk, type PlainHandlerResult, type RedeemHandlerContext, type RedeemHandlerInput, type ResolveEntityError, type ResolveEntityInput, type ResolveEntityOk, type ResolveEntityResult, type WithdrawFundsContext, type WithdrawFundsInput, type WithdrawFundsOk, emitNextActions, handleCommit, handleCommitAndRedeem, handleComplete, handleDisputeEscalate, handleDisputeRaise, handleDisputeResolve, handleDisputeRetract, handleGetAvailableFunds, handlePerformAction, handleRedeem, handleWithdrawFunds, handlerErr, handlerOk, plainHandlerOk, resolveEntityId };
11
+ import '@bosonprotocol/x402-core/state-machine';