@bosonprotocol/x402-server 0.1.0-alpha-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.
Files changed (53) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +65 -0
  3. package/dist/cjs/challenge/index.d.ts +64 -0
  4. package/dist/cjs/challenge/index.js +96 -0
  5. package/dist/cjs/challenge/index.js.map +1 -0
  6. package/dist/cjs/client-tnrpqVMW.d.ts +36 -0
  7. package/dist/cjs/config-CBr9qMps.d.ts +419 -0
  8. package/dist/cjs/facilitator/index.d.ts +26 -0
  9. package/dist/cjs/facilitator/index.js +136 -0
  10. package/dist/cjs/facilitator/index.js.map +1 -0
  11. package/dist/cjs/handlers/index.d.ts +267 -0
  12. package/dist/cjs/handlers/index.js +1182 -0
  13. package/dist/cjs/handlers/index.js.map +1 -0
  14. package/dist/cjs/index.d.ts +72 -0
  15. package/dist/cjs/index.js +1643 -0
  16. package/dist/cjs/index.js.map +1 -0
  17. package/dist/cjs/onchain/index.d.ts +73 -0
  18. package/dist/cjs/onchain/index.js +87 -0
  19. package/dist/cjs/onchain/index.js.map +1 -0
  20. package/dist/cjs/package.json +3 -0
  21. package/dist/cjs/validate/index.d.ts +67 -0
  22. package/dist/cjs/validate/index.js +429 -0
  23. package/dist/cjs/validate/index.js.map +1 -0
  24. package/dist/esm/challenge/index.js +3 -0
  25. package/dist/esm/challenge/index.js.map +1 -0
  26. package/dist/esm/chunk-4NQ3VKC3.js +84 -0
  27. package/dist/esm/chunk-4NQ3VKC3.js.map +1 -0
  28. package/dist/esm/chunk-CGVXQX5V.js +426 -0
  29. package/dist/esm/chunk-CGVXQX5V.js.map +1 -0
  30. package/dist/esm/chunk-DJMCSCBE.js +3 -0
  31. package/dist/esm/chunk-DJMCSCBE.js.map +1 -0
  32. package/dist/esm/chunk-EAKQ4EFZ.js +124 -0
  33. package/dist/esm/chunk-EAKQ4EFZ.js.map +1 -0
  34. package/dist/esm/chunk-GBPU373M.js +93 -0
  35. package/dist/esm/chunk-GBPU373M.js.map +1 -0
  36. package/dist/esm/chunk-PU5Y7FZG.js +14 -0
  37. package/dist/esm/chunk-PU5Y7FZG.js.map +1 -0
  38. package/dist/esm/chunk-WU7QJ7YP.js +3 -0
  39. package/dist/esm/chunk-WU7QJ7YP.js.map +1 -0
  40. package/dist/esm/chunk-Y5HFLCAT.js +657 -0
  41. package/dist/esm/chunk-Y5HFLCAT.js.map +1 -0
  42. package/dist/esm/facilitator/index.js +4 -0
  43. package/dist/esm/facilitator/index.js.map +1 -0
  44. package/dist/esm/handlers/index.js +6 -0
  45. package/dist/esm/handlers/index.js.map +1 -0
  46. package/dist/esm/index.js +261 -0
  47. package/dist/esm/index.js.map +1 -0
  48. package/dist/esm/onchain/index.js +4 -0
  49. package/dist/esm/onchain/index.js.map +1 -0
  50. package/dist/esm/package.json +3 -0
  51. package/dist/esm/validate/index.js +4 -0
  52. package/dist/esm/validate/index.js.map +1 -0
  53. package/package.json +88 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/onchain/verify-exchange.ts"],"names":[],"mappings":"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","file":"chunk-4NQ3VKC3.js","sourcesContent":["// On-chain (or off-chain — subgraph) verification of an exchange's\n// state after a facilitator settle / perform-action. The reader is\n// pluggable so callers can wire up a subgraph query, a viem RPC\n// read, or core-sdk's `exchanges.handler.getExchange` without\n// x402-server taking a hard dependency on any one mechanism.\n//\n// The verifier itself is pure: given a snapshot + the four-field\n// expectation from boson-impl-02-flows.md Flow A/B \"Verify\" step\n// (state, seller, exchangeToken, price), it returns a structured\n// pass / fail result.\n\nimport { DisputeState, ExchangeState } from \"@bosonprotocol/x402-actions\";\nimport type { Address } from \"@bosonprotocol/x402-core/schemes/escrow\";\n\n/**\n * Minimal protocol-level snapshot of an exchange the verifier needs.\n *\n * Sourced by `ExchangeReader.read` — typical implementations:\n *\n * - `@bosonprotocol/core-sdk`'s `coreSDK.getExchangeById` (subgraph\n * round-trip; tolerant of indexer lag).\n * - A viem `publicClient.readContract` against\n * `IBosonExchangeHandler.getExchange(exchangeId)` plus a follow-up\n * `getOffer(offerId)` for `exchangeToken` + `price` (no indexer\n * lag, costs an extra RPC call).\n */\nexport interface ExchangeSnapshot {\n state: ExchangeState;\n /** Present when `state === ExchangeState.DISPUTED`. */\n disputeState?: DisputeState;\n seller: Address;\n exchangeToken: Address;\n /** Atomic units, decimal string. */\n price: string;\n}\n\n/**\n * Pluggable reader for the post-settle / post-perform-action state\n * verification step. The reader returns `null` iff the exchange id\n * doesn't yet exist in the data source (subgraph not yet indexed,\n * `getExchange` reverts on a non-existent id, etc.). `verifyExchange`\n * retries transient not-found / stale-state results with a bounded wait.\n */\nexport interface ExchangeReader {\n read(exchangeId: string): Promise<ExchangeSnapshot | null>;\n}\n\nexport interface VerifyExchangeExpected {\n state: ExchangeState;\n /** Present iff `state === ExchangeState.DISPUTED`. */\n disputeState?: DisputeState;\n seller: Address;\n exchangeToken: Address;\n price: string;\n}\n\nexport type VerifyExchangeResult =\n | { ok: true }\n | { ok: false; code: VerifyExchangeErrorCode; field?: string; expected?: unknown; got?: unknown };\n\nexport type VerifyExchangeErrorCode =\n | \"EXCHANGE_NOT_FOUND\"\n | \"STATE_MISMATCH\"\n | \"DISPUTE_STATE_MISMATCH\"\n | \"SELLER_MISMATCH\"\n | \"TOKEN_MISMATCH\"\n | \"PRICE_MISMATCH\";\n\nexport interface VerifyExchangeOptions {\n /** Maximum read/compare attempts. Defaults to 3. */\n attempts?: number;\n /** Delay between retryable attempts in milliseconds. Defaults to 50. */\n delayMs?: number;\n}\n\n/**\n * Compare a snapshot against the expected post-state. Short-circuits\n * at the first mismatch. Pure; the reader does the I/O.\n */\nexport function verifyExchangeSnapshot(\n snapshot: ExchangeSnapshot | null,\n expected: VerifyExchangeExpected,\n): VerifyExchangeResult {\n if (snapshot === null) {\n return { ok: false, code: \"EXCHANGE_NOT_FOUND\" };\n }\n if (snapshot.state !== expected.state) {\n return {\n ok: false,\n code: \"STATE_MISMATCH\",\n field: \"state\",\n expected: expected.state,\n got: snapshot.state,\n };\n }\n if (expected.state === ExchangeState.DISPUTED) {\n if (snapshot.disputeState !== expected.disputeState) {\n return {\n ok: false,\n code: \"DISPUTE_STATE_MISMATCH\",\n field: \"disputeState\",\n expected: expected.disputeState,\n got: snapshot.disputeState,\n };\n }\n }\n if (snapshot.seller.toLowerCase() !== expected.seller.toLowerCase()) {\n return {\n ok: false,\n code: \"SELLER_MISMATCH\",\n field: \"seller\",\n expected: expected.seller,\n got: snapshot.seller,\n };\n }\n if (snapshot.exchangeToken.toLowerCase() !== expected.exchangeToken.toLowerCase()) {\n return {\n ok: false,\n code: \"TOKEN_MISMATCH\",\n field: \"exchangeToken\",\n expected: expected.exchangeToken,\n got: snapshot.exchangeToken,\n };\n }\n if (snapshot.price !== expected.price) {\n return {\n ok: false,\n code: \"PRICE_MISMATCH\",\n field: \"price\",\n expected: expected.price,\n got: snapshot.price,\n };\n }\n return { ok: true };\n}\n\n/**\n * High-level convenience — read via the configured `ExchangeReader`\n * and apply `verifyExchangeSnapshot`. Retries boundedly when a reader\n * may be behind the just-mined transaction (`EXCHANGE_NOT_FOUND`,\n * `STATE_MISMATCH`, or `DISPUTE_STATE_MISMATCH`), then returns the\n * last comparison result.\n */\nexport async function verifyExchange(\n reader: ExchangeReader,\n exchangeId: string,\n expected: VerifyExchangeExpected,\n options: VerifyExchangeOptions = {},\n): Promise<VerifyExchangeResult> {\n const attempts = Math.max(1, Math.floor(options.attempts ?? 3));\n const delayMs = Math.max(0, Math.floor(options.delayMs ?? 50));\n let result: VerifyExchangeResult = { ok: false, code: \"EXCHANGE_NOT_FOUND\" };\n\n for (let attempt = 1; attempt <= attempts; attempt += 1) {\n const snapshot = await reader.read(exchangeId);\n result = verifyExchangeSnapshot(snapshot, expected);\n if (result.ok || !isRetryableVerifyResult(result) || attempt === attempts) {\n return result;\n }\n if (delayMs > 0) {\n await delay(delayMs);\n }\n }\n\n return result;\n}\n\nfunction isRetryableVerifyResult(result: VerifyExchangeResult): boolean {\n return (\n !result.ok &&\n (result.code === \"EXCHANGE_NOT_FOUND\" ||\n result.code === \"STATE_MISMATCH\" ||\n result.code === \"DISPUTE_STATE_MISMATCH\")\n );\n}\n\nasync function delay(ms: number): Promise<void> {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n"]}
@@ -0,0 +1,426 @@
1
+ import { parseEscrowPaymentPayload } from '@bosonprotocol/x402-core/schemes/escrow';
2
+ import { recoverMetaTransactionSigner } from '@bosonprotocol/x402-core/eip712';
3
+ import { buildCreateOfferAndCommitCalldata, buildCreateOfferCommitAndRedeemCalldata } from '@bosonprotocol/x402-evm';
4
+
5
+ // src/validate/decode.ts
6
+ function decodeXPaymentHeader(header) {
7
+ if (header === void 0 || header === null || header.length === 0) {
8
+ return { ok: false, code: "MISSING_HEADER", reason: "X-PAYMENT header is missing" };
9
+ }
10
+ const padded = base64UrlToBase64(header);
11
+ let decoded;
12
+ try {
13
+ if (typeof atob === "function") {
14
+ const binary = atob(padded);
15
+ const bytes = new Uint8Array(binary.length);
16
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
17
+ decoded = new TextDecoder().decode(bytes);
18
+ } else {
19
+ decoded = Buffer.from(padded, "base64").toString("utf8");
20
+ }
21
+ } catch (e) {
22
+ return { ok: false, code: "INVALID_BASE64", reason: e.message };
23
+ }
24
+ let parsed;
25
+ try {
26
+ parsed = JSON.parse(decoded);
27
+ } catch (e) {
28
+ return { ok: false, code: "INVALID_JSON", reason: e.message };
29
+ }
30
+ try {
31
+ const payload = parseEscrowPaymentPayload(parsed);
32
+ return { ok: true, payload };
33
+ } catch (e) {
34
+ return { ok: false, code: "INVALID_PAYLOAD", reason: e.message };
35
+ }
36
+ }
37
+ function base64UrlToBase64(input) {
38
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
39
+ const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
40
+ return normalized + padding;
41
+ }
42
+ async function validatePaymentPayload(args) {
43
+ const { payload, requirements } = args;
44
+ if (payload.scheme !== "escrow" || requirements.scheme !== "escrow") {
45
+ return failure(1, "SCHEME_MISMATCH", "scheme", "escrow", payload.scheme);
46
+ }
47
+ if (payload.network !== requirements.network) {
48
+ return failure(2, "NETWORK_MISMATCH", "network", requirements.network, payload.network);
49
+ }
50
+ if (!deepEqual(payload.payload.offerRef.fullOffer, requirements.offer.fullOffer)) {
51
+ return failure(
52
+ 3,
53
+ "FULL_OFFER_MISMATCH",
54
+ "payload.offerRef.fullOffer",
55
+ "<requirements.offer.fullOffer>",
56
+ "<payload.offerRef.fullOffer>"
57
+ );
58
+ }
59
+ if (payload.payload.offerRef.sellerSig !== requirements.offer.sellerSig) {
60
+ return failure(
61
+ 4,
62
+ "SELLER_SIG_MISMATCH",
63
+ "payload.offerRef.sellerSig",
64
+ requirements.offer.sellerSig,
65
+ payload.payload.offerRef.sellerSig
66
+ );
67
+ }
68
+ const advertisedActions = requirements.actions.next.map((entry) => entry.id);
69
+ if (!advertisedActions.includes(payload.payload.action)) {
70
+ return failure(
71
+ 5,
72
+ "ACTION_NOT_IN_REQUIREMENTS",
73
+ "payload.action",
74
+ advertisedActions,
75
+ payload.payload.action
76
+ );
77
+ }
78
+ if (!requirements.tokenAuthStrategies.includes(payload.payload.tokenAuthStrategy)) {
79
+ return failure(
80
+ 6,
81
+ "TOKEN_AUTH_NOT_IN_REQUIREMENTS",
82
+ "payload.tokenAuthStrategy",
83
+ requirements.tokenAuthStrategies,
84
+ payload.payload.tokenAuthStrategy
85
+ );
86
+ }
87
+ const calldataResult = await checkFunctionSignatureAndCalldataEquality(payload);
88
+ if (calldataResult !== null) return calldataResult;
89
+ if (payload.payload.buyer.toLowerCase() !== payload.payload.metaTx.from.toLowerCase()) {
90
+ return failure(
91
+ 8,
92
+ "BAD_META_TX_SIGNATURE",
93
+ "payload.metaTx.from",
94
+ payload.payload.buyer,
95
+ payload.payload.metaTx.from
96
+ );
97
+ }
98
+ let recovered;
99
+ try {
100
+ recovered = await recoverMetaTransactionSigner({
101
+ chainId: args.chainId,
102
+ verifyingContract: requirements.escrowAddress,
103
+ message: {
104
+ nonce: BigInt(payload.payload.metaTx.nonce),
105
+ from: payload.payload.metaTx.from,
106
+ contractAddress: requirements.escrowAddress,
107
+ functionName: payload.payload.metaTx.functionName,
108
+ functionSignature: payload.payload.metaTx.functionSignature
109
+ },
110
+ signature: encodeRsv(payload.payload.metaTx.sig)
111
+ });
112
+ } catch (e) {
113
+ return failure(
114
+ 8,
115
+ "BAD_META_TX_SIGNATURE",
116
+ "payload.metaTx.sig",
117
+ "recoverable ECDSA signature",
118
+ payload.payload.metaTx.sig,
119
+ e.message
120
+ );
121
+ }
122
+ if (recovered.toLowerCase() !== payload.payload.buyer.toLowerCase()) {
123
+ return failure(
124
+ 8,
125
+ "BAD_META_TX_SIGNATURE",
126
+ "payload.metaTx.sig",
127
+ payload.payload.buyer,
128
+ recovered
129
+ );
130
+ }
131
+ const strategyResult = checkTokenAuthRules(payload, requirements, args.now);
132
+ if (strategyResult !== null) return strategyResult;
133
+ const fulfillmentResult = checkFulfillment(payload, requirements, args.validateFulfillmentData);
134
+ if (fulfillmentResult !== null) return fulfillmentResult;
135
+ return { ok: true };
136
+ }
137
+ async function checkFunctionSignatureAndCalldataEquality(payload) {
138
+ const action = payload.payload.action;
139
+ const buildCalldata = action === "boson-createOfferAndCommit" ? buildCreateOfferAndCommitCalldata : action === "boson-createOfferCommitAndRedeem" ? buildCreateOfferCommitAndRedeemCalldata : void 0;
140
+ if (!buildCalldata) {
141
+ return failure(
142
+ 7,
143
+ "CALLDATA_MISMATCH",
144
+ "payload.action",
145
+ "boson-createOfferAndCommit | boson-createOfferCommitAndRedeem",
146
+ action,
147
+ "rule-7 calldata reconstruction is only defined for commit-time actions"
148
+ );
149
+ }
150
+ const fullOfferWithSig = {
151
+ ...payload.payload.offerRef.fullOffer,
152
+ committer: payload.payload.buyer,
153
+ signature: payload.payload.offerRef.sellerSig
154
+ };
155
+ let expected;
156
+ try {
157
+ expected = await buildCalldata({ fullOffer: fullOfferWithSig });
158
+ } catch (e) {
159
+ return failure(
160
+ 7,
161
+ "CALLDATA_MISMATCH",
162
+ "payload.metaTx.functionSignature",
163
+ void 0,
164
+ void 0,
165
+ e.message
166
+ );
167
+ }
168
+ if (expected.functionName !== payload.payload.metaTx.functionName) {
169
+ return failure(
170
+ 7,
171
+ "CALLDATA_MISMATCH",
172
+ "payload.metaTx.functionName",
173
+ expected.functionName,
174
+ payload.payload.metaTx.functionName
175
+ );
176
+ }
177
+ if (expected.functionSignature.toLowerCase() !== payload.payload.metaTx.functionSignature.toLowerCase()) {
178
+ return failure(
179
+ 7,
180
+ "CALLDATA_MISMATCH",
181
+ "payload.metaTx.functionSignature",
182
+ expected.functionSignature,
183
+ payload.payload.metaTx.functionSignature
184
+ );
185
+ }
186
+ return null;
187
+ }
188
+ function checkTokenAuthRules(payload, requirements, nowSec) {
189
+ const strategy = payload.payload.tokenAuthStrategy;
190
+ const tokenAuth = payload.payload.tokenAuth;
191
+ const now = nowSec ?? Math.floor(Date.now() / 1e3);
192
+ const horizon = now + requirements.maxTimeoutSeconds;
193
+ if (strategy === "none") {
194
+ if (tokenAuth !== void 0) {
195
+ return failure(
196
+ 9,
197
+ "TOKEN_AUTH_UNEXPECTED",
198
+ "payload.tokenAuth",
199
+ "undefined (strategy=none)",
200
+ tokenAuth.kind
201
+ );
202
+ }
203
+ return null;
204
+ }
205
+ if (tokenAuth === void 0) {
206
+ return failure(
207
+ 9,
208
+ "TOKEN_AUTH_MISSING",
209
+ "payload.tokenAuth",
210
+ `<${strategy} authorization>`,
211
+ "undefined"
212
+ );
213
+ }
214
+ if (tokenAuth.kind !== strategy) {
215
+ return failure(9, "TOKEN_AUTH_MISSING", "payload.tokenAuth.kind", strategy, tokenAuth.kind);
216
+ }
217
+ switch (tokenAuth.kind) {
218
+ case "erc3009": {
219
+ if (tokenAuth.data.value !== requirements.amount) {
220
+ return failure(
221
+ 9,
222
+ "TOKEN_AUTH_AMOUNT_MISMATCH",
223
+ "payload.tokenAuth.data.value",
224
+ requirements.amount,
225
+ tokenAuth.data.value
226
+ );
227
+ }
228
+ if (tokenAuth.data.to.toLowerCase() !== requirements.escrowAddress.toLowerCase()) {
229
+ return failure(
230
+ 9,
231
+ "TOKEN_AUTH_RECIPIENT_MISMATCH",
232
+ "payload.tokenAuth.data.to",
233
+ requirements.escrowAddress,
234
+ tokenAuth.data.to
235
+ );
236
+ }
237
+ if (tokenAuth.data.validBefore > horizon) {
238
+ return failure(
239
+ 9,
240
+ "TOKEN_AUTH_DEADLINE_EXCEEDED",
241
+ "payload.tokenAuth.data.validBefore",
242
+ `<= ${horizon}`,
243
+ tokenAuth.data.validBefore
244
+ );
245
+ }
246
+ return null;
247
+ }
248
+ case "permit": {
249
+ if (tokenAuth.data.value !== requirements.amount) {
250
+ return failure(
251
+ 10,
252
+ "TOKEN_AUTH_AMOUNT_MISMATCH",
253
+ "payload.tokenAuth.data.value",
254
+ requirements.amount,
255
+ tokenAuth.data.value
256
+ );
257
+ }
258
+ if (tokenAuth.data.spender.toLowerCase() !== requirements.escrowAddress.toLowerCase()) {
259
+ return failure(
260
+ 10,
261
+ "TOKEN_AUTH_SPENDER_MISMATCH",
262
+ "payload.tokenAuth.data.spender",
263
+ requirements.escrowAddress,
264
+ tokenAuth.data.spender
265
+ );
266
+ }
267
+ if (tokenAuth.data.deadline > horizon) {
268
+ return failure(
269
+ 10,
270
+ "TOKEN_AUTH_DEADLINE_EXCEEDED",
271
+ "payload.tokenAuth.data.deadline",
272
+ `<= ${horizon}`,
273
+ tokenAuth.data.deadline
274
+ );
275
+ }
276
+ return null;
277
+ }
278
+ case "permit2": {
279
+ if (tokenAuth.data.permitted.amount !== requirements.amount) {
280
+ return failure(
281
+ 11,
282
+ "TOKEN_AUTH_AMOUNT_MISMATCH",
283
+ "payload.tokenAuth.data.permitted.amount",
284
+ requirements.amount,
285
+ tokenAuth.data.permitted.amount
286
+ );
287
+ }
288
+ if (tokenAuth.data.permitted.token.toLowerCase() !== requirements.asset.toLowerCase()) {
289
+ return failure(
290
+ 11,
291
+ "TOKEN_AUTH_TOKEN_MISMATCH",
292
+ "payload.tokenAuth.data.permitted.token",
293
+ requirements.asset,
294
+ tokenAuth.data.permitted.token
295
+ );
296
+ }
297
+ if (tokenAuth.data.spender.toLowerCase() !== requirements.escrowAddress.toLowerCase()) {
298
+ return failure(
299
+ 11,
300
+ "TOKEN_AUTH_SPENDER_MISMATCH",
301
+ "payload.tokenAuth.data.spender",
302
+ requirements.escrowAddress,
303
+ tokenAuth.data.spender
304
+ );
305
+ }
306
+ if (tokenAuth.data.deadline > horizon) {
307
+ return failure(
308
+ 11,
309
+ "TOKEN_AUTH_DEADLINE_EXCEEDED",
310
+ "payload.tokenAuth.data.deadline",
311
+ `<= ${horizon}`,
312
+ tokenAuth.data.deadline
313
+ );
314
+ }
315
+ return null;
316
+ }
317
+ default:
318
+ return failure(
319
+ 9,
320
+ "TOKEN_AUTH_UNKNOWN_STRATEGY",
321
+ "payload.tokenAuth.kind",
322
+ "erc3009 | permit | permit2",
323
+ tokenAuth.kind
324
+ );
325
+ }
326
+ }
327
+ function checkFulfillment(payload, requirements, validator) {
328
+ const required = requirements.fulfillment?.required === true;
329
+ const carried = payload.fulfillment;
330
+ if (carried === void 0) {
331
+ return required ? failure(13, "FULFILLMENT_REQUIRED", "payload.fulfillment", "<option>", "undefined") : null;
332
+ }
333
+ const advertised = requirements.fulfillment?.options ?? [];
334
+ const matched = advertised.find((option) => option.id === carried.option);
335
+ if (!matched) {
336
+ return failure(
337
+ 13,
338
+ "FULFILLMENT_OPTION_NOT_ADVERTISED",
339
+ "payload.fulfillment.option",
340
+ advertised.map((o) => o.id),
341
+ carried.option
342
+ );
343
+ }
344
+ const action = payload.payload.action;
345
+ if (action === "boson-createOfferCommitAndRedeem") {
346
+ if (carried.data === void 0) {
347
+ return failure(
348
+ 13,
349
+ "FULFILLMENT_DATA_REQUIRED",
350
+ "payload.fulfillment.data",
351
+ matched.schema ?? "null",
352
+ "undefined",
353
+ "atomic createOfferCommitAndRedeem requires fulfillment.data at commit time"
354
+ );
355
+ }
356
+ if (validator !== void 0) {
357
+ const result = validator(matched.id, carried.data);
358
+ if (!result.ok) {
359
+ return failure(
360
+ 13,
361
+ "FULFILLMENT_DATA_INVALID",
362
+ "payload.fulfillment.data",
363
+ matched.schema,
364
+ carried.data,
365
+ result.reason
366
+ );
367
+ }
368
+ }
369
+ return null;
370
+ }
371
+ if (carried.data !== void 0) {
372
+ return failure(
373
+ 13,
374
+ "FULFILLMENT_DATA_UNEXPECTED",
375
+ "payload.fulfillment.data",
376
+ "undefined",
377
+ carried.data,
378
+ "two-step boson-createOfferAndCommit defers fulfillment.data to the redeem POST body"
379
+ );
380
+ }
381
+ return null;
382
+ }
383
+ function deepEqual(a, b) {
384
+ if (a === b) return true;
385
+ if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
386
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
387
+ if (Array.isArray(a) && Array.isArray(b)) {
388
+ if (a.length !== b.length) return false;
389
+ for (let i = 0; i < a.length; i += 1) {
390
+ if (!deepEqual(a[i], b[i])) return false;
391
+ }
392
+ return true;
393
+ }
394
+ const ao = a;
395
+ const bo = b;
396
+ const aKeys = Object.keys(ao);
397
+ const bKeys = Object.keys(bo);
398
+ if (aKeys.length !== bKeys.length) return false;
399
+ for (const k of aKeys) {
400
+ if (!Object.prototype.hasOwnProperty.call(bo, k)) return false;
401
+ if (!deepEqual(ao[k], bo[k])) return false;
402
+ }
403
+ return true;
404
+ }
405
+ function encodeRsv(sig) {
406
+ if (sig.v !== 0 && sig.v !== 1 && sig.v !== 27 && sig.v !== 28) {
407
+ throw new Error("signature v must be 0, 1, 27, or 28");
408
+ }
409
+ const r = sig.r.replace(/^0x/, "").padStart(64, "0");
410
+ const s = sig.s.replace(/^0x/, "").padStart(64, "0");
411
+ const normalizedV = sig.v === 0 || sig.v === 1 ? sig.v + 27 : sig.v;
412
+ const v = normalizedV.toString(16).padStart(2, "0");
413
+ return `0x${r}${s}${v}`;
414
+ }
415
+ function failure(rule, code, field, expected, got, reason) {
416
+ const out = { ok: false, rule, code };
417
+ if (field !== void 0) out.field = field;
418
+ if (expected !== void 0) out.expected = expected;
419
+ if (got !== void 0) out.got = got;
420
+ if (reason !== void 0) out.reason = reason;
421
+ return out;
422
+ }
423
+
424
+ export { decodeXPaymentHeader, validatePaymentPayload };
425
+ //# sourceMappingURL=chunk-CGVXQX5V.js.map
426
+ //# sourceMappingURL=chunk-CGVXQX5V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/validate/decode.ts","../../src/validate/payment-payload.ts"],"names":[],"mappings":"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","file":"chunk-CGVXQX5V.js","sourcesContent":["// Decode the `X-PAYMENT` header — base64 → JSON → zod-parse.\n//\n// `parseEscrowPaymentPayload` from `@bosonprotocol/x402-core` handles\n// the wire-format zod validation; this module's job is the base64 +\n// JSON layer plus the obvious \"not a string\" / \"bad base64\" / \"bad\n// JSON\" failure cases, each mapped to a structured error code so the\n// caller can surface a useful 400 to the buyer.\n\nimport {\n parseEscrowPaymentPayload,\n type EscrowPaymentPayload,\n} from \"@bosonprotocol/x402-core/schemes/escrow\";\n\nexport type DecodeXPaymentResult =\n | { ok: true; payload: EscrowPaymentPayload }\n | { ok: false; code: DecodeErrorCode; reason: string };\n\nexport type DecodeErrorCode =\n | \"MISSING_HEADER\"\n | \"INVALID_BASE64\"\n | \"INVALID_JSON\"\n | \"INVALID_PAYLOAD\";\n\n/**\n * Decode the raw `X-PAYMENT` header value. Returns the parsed\n * `EscrowPaymentPayload` on success or a `{ ok: false }` discriminated\n * error on failure. Accepts both base64 and base64url variants (RFC\n * 4648 §5) — buyers in the wild use both.\n */\nexport function decodeXPaymentHeader(header: string | undefined | null): DecodeXPaymentResult {\n if (header === undefined || header === null || header.length === 0) {\n return { ok: false, code: \"MISSING_HEADER\", reason: \"X-PAYMENT header is missing\" };\n }\n\n // base64url → base64 (RFC 4648 §5) then decode. We prefer `atob` so\n // the validator stays edge-runtime compatible; `atob` returns a\n // Latin-1 binary string though, so we re-encode through `TextDecoder`\n // to recover the original UTF-8 (atomic Flow B's `fulfillment.data`\n // can carry non-ASCII bytes — names, addresses, free-form notes —\n // and so can other buyer-supplied inner fields).\n const padded = base64UrlToBase64(header);\n let decoded: string;\n try {\n if (typeof atob === \"function\") {\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n decoded = new TextDecoder().decode(bytes);\n } else {\n decoded = Buffer.from(padded, \"base64\").toString(\"utf8\");\n }\n } catch (e) {\n return { ok: false, code: \"INVALID_BASE64\", reason: (e as Error).message };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(decoded);\n } catch (e) {\n return { ok: false, code: \"INVALID_JSON\", reason: (e as Error).message };\n }\n\n try {\n const payload = parseEscrowPaymentPayload(parsed);\n return { ok: true, payload };\n } catch (e) {\n return { ok: false, code: \"INVALID_PAYLOAD\", reason: (e as Error).message };\n }\n}\n\nfunction base64UrlToBase64(input: string): string {\n const normalized = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padding = normalized.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (normalized.length % 4));\n return normalized + padding;\n}\n","// `validatePaymentPayload` — the 13-rule X-PAYMENT validator from\n// docs/boson-impl-01-escrow-scheme.md §5. Pure function; no I/O.\n//\n// The validator returns a discriminated `{ ok: true } | { ok: false, … }`\n// result. On failure, the result carries the spec's rule number, a\n// stable string code, and the field/expected/got triple from the spec\n// (so callers can serialise it directly into a 400 body).\n//\n// Rule 12 (`none` strategy → server SHOULD pre-flight allowance check)\n// is RPC-dependent and intentionally NOT enforced here — the\n// composition layer (PR 4) runs it as part of `verifyExchange`.\n//\n// Rule 7 byte-compares `payload.metaTx.functionSignature` against\n// calldata reconstructed from `payload.offerRef`. Both commit-time\n// actions are supported: `boson-createOfferAndCommit` (Flow A) and\n// `boson-createOfferCommitAndRedeem` (Flow B).\n\nimport { recoverMetaTransactionSigner } from \"@bosonprotocol/x402-core/eip712\";\nimport type {\n EscrowPaymentPayload,\n EscrowPaymentRequirements,\n FulfillmentOption,\n} from \"@bosonprotocol/x402-core/schemes/escrow\";\nimport {\n buildCreateOfferAndCommitCalldata,\n buildCreateOfferCommitAndRedeemCalldata,\n} from \"@bosonprotocol/x402-evm\";\nimport type { Address as ViemAddress, Hex as ViemHex } from \"viem\";\n\ntype BuilderFullOffer = Parameters<typeof buildCreateOfferAndCommitCalldata>[0][\"fullOffer\"];\n\nexport type ValidatePaymentPayloadResult =\n | { ok: true; warnings?: ValidationWarning[] }\n | {\n ok: false;\n rule: number;\n code: ValidationErrorCode;\n field?: string;\n expected?: unknown;\n got?: unknown;\n reason?: string;\n };\n\nexport type ValidationErrorCode =\n | \"SCHEME_MISMATCH\"\n | \"NETWORK_MISMATCH\"\n | \"FULL_OFFER_MISMATCH\"\n | \"SELLER_SIG_MISMATCH\"\n | \"ACTION_NOT_IN_REQUIREMENTS\"\n | \"TOKEN_AUTH_NOT_IN_REQUIREMENTS\"\n | \"CALLDATA_MISMATCH\"\n | \"BAD_META_TX_SIGNATURE\"\n | \"TOKEN_AUTH_AMOUNT_MISMATCH\"\n | \"TOKEN_AUTH_RECIPIENT_MISMATCH\"\n | \"TOKEN_AUTH_SPENDER_MISMATCH\"\n | \"TOKEN_AUTH_TOKEN_MISMATCH\"\n | \"TOKEN_AUTH_DEADLINE_EXCEEDED\"\n | \"TOKEN_AUTH_MISSING\"\n | \"TOKEN_AUTH_UNEXPECTED\"\n | \"TOKEN_AUTH_UNKNOWN_STRATEGY\"\n | \"FULFILLMENT_REQUIRED\"\n | \"FULFILLMENT_OPTION_NOT_ADVERTISED\"\n | \"FULFILLMENT_DATA_REQUIRED\"\n | \"FULFILLMENT_DATA_UNEXPECTED\"\n | \"FULFILLMENT_DATA_INVALID\";\n\nexport interface ValidationWarning {\n rule: number;\n code: \"RULE_SKIPPED\";\n reason: string;\n}\n\nexport interface ValidatePaymentPayloadArgs {\n payload: EscrowPaymentPayload;\n requirements: EscrowPaymentRequirements;\n /** EVM chain id matching `requirements.network` — drives meta-tx EIP-712 signer recovery (rule 8). */\n chainId: number;\n /** Reference time in seconds since epoch for rules 9–11. Defaults to `Math.floor(Date.now() / 1000)`. */\n now?: number;\n /**\n * Per-fulfillment-option data validator (typically the\n * `FulfillmentRegistry`'s per-channel `validate`). Only consulted for\n * atomic Flow B (`boson-createOfferCommitAndRedeem`) where the buyer\n * must carry `fulfillment.data` in the commit-time payload — Flow A\n * defers data to the redeem POST body and rule 13 rejects any\n * commit-time `data` there outright. Without this validator, Flow B\n * still rejects missing data but does not deep-validate the data\n * against the option's schema.\n */\n validateFulfillmentData?: (\n option: string,\n data: Record<string, unknown> | null,\n ) => { ok: true } | { ok: false; reason: string };\n}\n\n/**\n * Run all 13 rules. Short-circuits at the first failure.\n */\nexport async function validatePaymentPayload(\n args: ValidatePaymentPayloadArgs,\n): Promise<ValidatePaymentPayloadResult> {\n const { payload, requirements } = args;\n // Rule 1 — scheme equality on both sides.\n if (payload.scheme !== \"escrow\" || requirements.scheme !== \"escrow\") {\n return failure(1, \"SCHEME_MISMATCH\", \"scheme\", \"escrow\", payload.scheme);\n }\n\n // Rule 2 — network equality.\n if (payload.network !== requirements.network) {\n return failure(2, \"NETWORK_MISMATCH\", \"network\", requirements.network, payload.network);\n }\n\n // Rule 3 — fullOffer deep equality (byte-equals in canonical form).\n if (!deepEqual(payload.payload.offerRef.fullOffer, requirements.offer.fullOffer)) {\n return failure(\n 3,\n \"FULL_OFFER_MISMATCH\",\n \"payload.offerRef.fullOffer\",\n \"<requirements.offer.fullOffer>\",\n \"<payload.offerRef.fullOffer>\",\n );\n }\n\n // Rule 4 — sellerSig equality.\n if (payload.payload.offerRef.sellerSig !== requirements.offer.sellerSig) {\n return failure(\n 4,\n \"SELLER_SIG_MISMATCH\",\n \"payload.offerRef.sellerSig\",\n requirements.offer.sellerSig,\n payload.payload.offerRef.sellerSig,\n );\n }\n\n // Rule 5 — action advertised in requirements.actions.next.\n const advertisedActions = requirements.actions.next.map((entry) => entry.id);\n if (!advertisedActions.includes(payload.payload.action)) {\n return failure(\n 5,\n \"ACTION_NOT_IN_REQUIREMENTS\",\n \"payload.action\",\n advertisedActions,\n payload.payload.action,\n );\n }\n\n // Rule 6 — tokenAuthStrategy advertised in requirements.tokenAuthStrategies.\n if (!requirements.tokenAuthStrategies.includes(payload.payload.tokenAuthStrategy)) {\n return failure(\n 6,\n \"TOKEN_AUTH_NOT_IN_REQUIREMENTS\",\n \"payload.tokenAuthStrategy\",\n requirements.tokenAuthStrategies,\n payload.payload.tokenAuthStrategy,\n );\n }\n\n // Rule 7 — functionSignature byte-equals the calldata reconstructed\n // from offerRef. Both commit-time actions are validated.\n const calldataResult = await checkFunctionSignatureAndCalldataEquality(payload);\n if (calldataResult !== null) return calldataResult;\n\n // Rule 8 — meta-tx signer recovers to payload.buyer === metaTx.from.\n if (payload.payload.buyer.toLowerCase() !== payload.payload.metaTx.from.toLowerCase()) {\n return failure(\n 8,\n \"BAD_META_TX_SIGNATURE\",\n \"payload.metaTx.from\",\n payload.payload.buyer,\n payload.payload.metaTx.from,\n );\n }\n let recovered: ViemAddress;\n try {\n recovered = await recoverMetaTransactionSigner({\n chainId: args.chainId,\n verifyingContract: requirements.escrowAddress as ViemAddress,\n message: {\n nonce: BigInt(payload.payload.metaTx.nonce),\n from: payload.payload.metaTx.from as ViemAddress,\n contractAddress: requirements.escrowAddress as ViemAddress,\n functionName: payload.payload.metaTx.functionName,\n functionSignature: payload.payload.metaTx.functionSignature as ViemHex,\n },\n signature: encodeRsv(payload.payload.metaTx.sig) as ViemHex,\n });\n } catch (e) {\n return failure(\n 8,\n \"BAD_META_TX_SIGNATURE\",\n \"payload.metaTx.sig\",\n \"recoverable ECDSA signature\",\n payload.payload.metaTx.sig,\n (e as Error).message,\n );\n }\n if (recovered.toLowerCase() !== payload.payload.buyer.toLowerCase()) {\n return failure(\n 8,\n \"BAD_META_TX_SIGNATURE\",\n \"payload.metaTx.sig\",\n payload.payload.buyer,\n recovered,\n );\n }\n\n // Rules 9–11 — strategy-specific token-auth structural checks.\n const strategyResult = checkTokenAuthRules(payload, requirements, args.now);\n if (strategyResult !== null) return strategyResult;\n\n // Rule 12 — `none` strategy SHOULD pre-flight `IERC20.allowance` on\n // chain. Intentionally not enforced here; runs as part of\n // `verifyExchange` in PR 4 (RPC dependency).\n\n // Rule 13 — fulfillment option + action-conditional data presence.\n // Flow A (`boson-createOfferAndCommit`) defers buyer-supplied data\n // to the redeem POST body; `fulfillment.data` must NOT appear in the\n // commit-time payload. Flow B (`boson-createOfferCommitAndRedeem`)\n // has only one round trip, so `fulfillment.data` MUST be present\n // (validated against the option's JSON Schema when a per-channel\n // validator is configured).\n const fulfillmentResult = checkFulfillment(payload, requirements, args.validateFulfillmentData);\n if (fulfillmentResult !== null) return fulfillmentResult;\n\n return { ok: true };\n}\n\n/**\n * Validate that `payload.metaTx.functionName` + `functionSignature`\n * byte-equal the calldata reconstructed from the payload's\n * `offerRef.fullOffer` + `sellerSig`. Tracks spec rule 7 today;\n * named for what it checks rather than its rule number so the\n * function survives any future renumbering of the spec.\n */\nasync function checkFunctionSignatureAndCalldataEquality(\n payload: EscrowPaymentPayload,\n): Promise<ValidatePaymentPayloadResult | null> {\n const action = payload.payload.action;\n const buildCalldata =\n action === \"boson-createOfferAndCommit\"\n ? buildCreateOfferAndCommitCalldata\n : action === \"boson-createOfferCommitAndRedeem\"\n ? buildCreateOfferCommitAndRedeemCalldata\n : undefined;\n\n if (!buildCalldata) {\n // Other actions (boson-redeem, dispute/*, etc.) aren't commit-time;\n // they have their own meta-tx encodings outside this scheme's\n // payment-payload validator. If we ever see one here it's a config\n // bug — the action wouldn't have been listed in\n // requirements.actions.next at commit time.\n return failure(\n 7,\n \"CALLDATA_MISMATCH\",\n \"payload.action\",\n \"boson-createOfferAndCommit | boson-createOfferCommitAndRedeem\",\n action,\n \"rule-7 calldata reconstruction is only defined for commit-time actions\",\n );\n }\n\n // `committer` is an outer argument of `createOfferAndCommit(...)`,\n // not a field in the seller's FullOffer EIP-712 typed-data (see\n // `signFullOffer` in `@bosonprotocol/core-sdk`'s\n // `exchanges/handler.js`). The seller's signature is valid for any\n // committer value, so the buyer's client splices `committer = buyer`\n // into the meta-tx calldata before signing (see\n // `@bosonprotocol/x402-client`'s `pre-commit.ts:94`). Rule 3 already\n // enforces that `offerRef.fullOffer` matches\n // `requirements.offer.fullOffer` byte-for-byte, so the offerRef\n // carries whatever seed value the application chose at challenge\n // time. Here we mirror the buyer-side splice when rebuilding the\n // expected calldata — without it every valid payment trips rule 7\n // on the committer slot. Regression: x402B#73.\n const fullOfferWithSig = {\n ...payload.payload.offerRef.fullOffer,\n committer: payload.payload.buyer,\n signature: payload.payload.offerRef.sellerSig,\n } as unknown as BuilderFullOffer;\n\n let expected: { functionName: string; functionSignature: string };\n try {\n expected = await buildCalldata({ fullOffer: fullOfferWithSig });\n } catch (e) {\n return failure(\n 7,\n \"CALLDATA_MISMATCH\",\n \"payload.metaTx.functionSignature\",\n undefined,\n undefined,\n (e as Error).message,\n );\n }\n\n if (expected.functionName !== payload.payload.metaTx.functionName) {\n return failure(\n 7,\n \"CALLDATA_MISMATCH\",\n \"payload.metaTx.functionName\",\n expected.functionName,\n payload.payload.metaTx.functionName,\n );\n }\n if (\n expected.functionSignature.toLowerCase() !==\n payload.payload.metaTx.functionSignature.toLowerCase()\n ) {\n return failure(\n 7,\n \"CALLDATA_MISMATCH\",\n \"payload.metaTx.functionSignature\",\n expected.functionSignature,\n payload.payload.metaTx.functionSignature,\n );\n }\n return null;\n}\n\nfunction checkTokenAuthRules(\n payload: EscrowPaymentPayload,\n requirements: EscrowPaymentRequirements,\n nowSec?: number,\n): ValidatePaymentPayloadResult | null {\n const strategy = payload.payload.tokenAuthStrategy;\n const tokenAuth = payload.payload.tokenAuth;\n const now = nowSec ?? Math.floor(Date.now() / 1000);\n const horizon = now + requirements.maxTimeoutSeconds;\n\n if (strategy === \"none\") {\n if (tokenAuth !== undefined) {\n return failure(\n 9,\n \"TOKEN_AUTH_UNEXPECTED\",\n \"payload.tokenAuth\",\n \"undefined (strategy=none)\",\n tokenAuth.kind,\n );\n }\n return null;\n }\n\n // strategy is one of erc3009 / permit / permit2 — token-auth required.\n if (tokenAuth === undefined) {\n return failure(\n 9,\n \"TOKEN_AUTH_MISSING\",\n \"payload.tokenAuth\",\n `<${strategy} authorization>`,\n \"undefined\",\n );\n }\n if (tokenAuth.kind !== strategy) {\n return failure(9, \"TOKEN_AUTH_MISSING\", \"payload.tokenAuth.kind\", strategy, tokenAuth.kind);\n }\n\n switch (tokenAuth.kind) {\n case \"erc3009\": {\n if (tokenAuth.data.value !== requirements.amount) {\n return failure(\n 9,\n \"TOKEN_AUTH_AMOUNT_MISMATCH\",\n \"payload.tokenAuth.data.value\",\n requirements.amount,\n tokenAuth.data.value,\n );\n }\n if (tokenAuth.data.to.toLowerCase() !== requirements.escrowAddress.toLowerCase()) {\n return failure(\n 9,\n \"TOKEN_AUTH_RECIPIENT_MISMATCH\",\n \"payload.tokenAuth.data.to\",\n requirements.escrowAddress,\n tokenAuth.data.to,\n );\n }\n if (tokenAuth.data.validBefore > horizon) {\n return failure(\n 9,\n \"TOKEN_AUTH_DEADLINE_EXCEEDED\",\n \"payload.tokenAuth.data.validBefore\",\n `<= ${horizon}`,\n tokenAuth.data.validBefore,\n );\n }\n return null;\n }\n case \"permit\": {\n if (tokenAuth.data.value !== requirements.amount) {\n return failure(\n 10,\n \"TOKEN_AUTH_AMOUNT_MISMATCH\",\n \"payload.tokenAuth.data.value\",\n requirements.amount,\n tokenAuth.data.value,\n );\n }\n if (tokenAuth.data.spender.toLowerCase() !== requirements.escrowAddress.toLowerCase()) {\n return failure(\n 10,\n \"TOKEN_AUTH_SPENDER_MISMATCH\",\n \"payload.tokenAuth.data.spender\",\n requirements.escrowAddress,\n tokenAuth.data.spender,\n );\n }\n if (tokenAuth.data.deadline > horizon) {\n return failure(\n 10,\n \"TOKEN_AUTH_DEADLINE_EXCEEDED\",\n \"payload.tokenAuth.data.deadline\",\n `<= ${horizon}`,\n tokenAuth.data.deadline,\n );\n }\n return null;\n }\n case \"permit2\": {\n if (tokenAuth.data.permitted.amount !== requirements.amount) {\n return failure(\n 11,\n \"TOKEN_AUTH_AMOUNT_MISMATCH\",\n \"payload.tokenAuth.data.permitted.amount\",\n requirements.amount,\n tokenAuth.data.permitted.amount,\n );\n }\n if (tokenAuth.data.permitted.token.toLowerCase() !== requirements.asset.toLowerCase()) {\n return failure(\n 11,\n \"TOKEN_AUTH_TOKEN_MISMATCH\",\n \"payload.tokenAuth.data.permitted.token\",\n requirements.asset,\n tokenAuth.data.permitted.token,\n );\n }\n if (tokenAuth.data.spender.toLowerCase() !== requirements.escrowAddress.toLowerCase()) {\n return failure(\n 11,\n \"TOKEN_AUTH_SPENDER_MISMATCH\",\n \"payload.tokenAuth.data.spender\",\n requirements.escrowAddress,\n tokenAuth.data.spender,\n );\n }\n if (tokenAuth.data.deadline > horizon) {\n return failure(\n 11,\n \"TOKEN_AUTH_DEADLINE_EXCEEDED\",\n \"payload.tokenAuth.data.deadline\",\n `<= ${horizon}`,\n tokenAuth.data.deadline,\n );\n }\n return null;\n }\n default:\n // Defensive: zod narrows `tokenAuth.kind` to the discriminated\n // union above, so the runtime should never reach this branch.\n // Fail closed if a malformed payload bypasses the parser rather\n // than implicitly returning `undefined` (which the caller treats\n // as `=== null` and would silently pass through to rule 13).\n return failure(\n 9,\n \"TOKEN_AUTH_UNKNOWN_STRATEGY\",\n \"payload.tokenAuth.kind\",\n \"erc3009 | permit | permit2\",\n (tokenAuth as { kind: string }).kind,\n );\n }\n}\n\nfunction checkFulfillment(\n payload: EscrowPaymentPayload,\n requirements: EscrowPaymentRequirements,\n validator?: ValidatePaymentPayloadArgs[\"validateFulfillmentData\"],\n): ValidatePaymentPayloadResult | null {\n const required = requirements.fulfillment?.required === true;\n const carried = payload.fulfillment;\n\n if (carried === undefined) {\n return required\n ? failure(13, \"FULFILLMENT_REQUIRED\", \"payload.fulfillment\", \"<option>\", \"undefined\")\n : null;\n }\n\n const advertised: FulfillmentOption[] = requirements.fulfillment?.options ?? [];\n const matched = advertised.find((option) => option.id === carried.option);\n if (!matched) {\n return failure(\n 13,\n \"FULFILLMENT_OPTION_NOT_ADVERTISED\",\n \"payload.fulfillment.option\",\n advertised.map((o) => o.id),\n carried.option,\n );\n }\n\n // Atomic Flow B carries buyer-supplied delivery data in the\n // commit-time payload; Flow A defers it to the redeem POST body.\n const action = payload.payload.action;\n if (action === \"boson-createOfferCommitAndRedeem\") {\n if (carried.data === undefined) {\n return failure(\n 13,\n \"FULFILLMENT_DATA_REQUIRED\",\n \"payload.fulfillment.data\",\n matched.schema ?? \"null\",\n \"undefined\",\n \"atomic createOfferCommitAndRedeem requires fulfillment.data at commit time\",\n );\n }\n if (validator !== undefined) {\n const result = validator(matched.id, carried.data);\n if (!result.ok) {\n return failure(\n 13,\n \"FULFILLMENT_DATA_INVALID\",\n \"payload.fulfillment.data\",\n matched.schema,\n carried.data,\n result.reason,\n );\n }\n }\n return null;\n }\n\n // Flow A and any other commit-time action: data is forbidden here.\n if (carried.data !== undefined) {\n return failure(\n 13,\n \"FULFILLMENT_DATA_UNEXPECTED\",\n \"payload.fulfillment.data\",\n \"undefined\",\n carried.data,\n \"two-step boson-createOfferAndCommit defers fulfillment.data to the redeem POST body\",\n );\n }\n return null;\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null || typeof a !== \"object\" || typeof b !== \"object\") return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i += 1) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n const ao = a as Record<string, unknown>;\n const bo = b as Record<string, unknown>;\n const aKeys = Object.keys(ao);\n const bKeys = Object.keys(bo);\n if (aKeys.length !== bKeys.length) return false;\n for (const k of aKeys) {\n if (!Object.prototype.hasOwnProperty.call(bo, k)) return false;\n if (!deepEqual(ao[k], bo[k])) return false;\n }\n return true;\n}\n\n/**\n * Concatenate the buyer's split-form `(v, r, s)` triple into the\n * 65-byte signature shape viem's `recoverTypedDataAddress` accepts.\n * `v` is normalised to the high byte; `r` and `s` are zero-padded to\n * 32 bytes (they're already that in the wire-format schema, but we\n * defend in depth).\n */\nfunction encodeRsv(sig: { v: number; r: string; s: string }): string {\n if (sig.v !== 0 && sig.v !== 1 && sig.v !== 27 && sig.v !== 28) {\n throw new Error(\"signature v must be 0, 1, 27, or 28\");\n }\n const r = sig.r.replace(/^0x/, \"\").padStart(64, \"0\");\n const s = sig.s.replace(/^0x/, \"\").padStart(64, \"0\");\n const normalizedV = sig.v === 0 || sig.v === 1 ? sig.v + 27 : sig.v;\n const v = normalizedV.toString(16).padStart(2, \"0\");\n return `0x${r}${s}${v}`;\n}\n\nfunction failure(\n rule: number,\n code: ValidationErrorCode,\n field?: string,\n expected?: unknown,\n got?: unknown,\n reason?: string,\n): ValidatePaymentPayloadResult {\n const out: ValidatePaymentPayloadResult = { ok: false, rule, code };\n if (field !== undefined) (out as { field?: string }).field = field;\n if (expected !== undefined) (out as { expected?: unknown }).expected = expected;\n if (got !== undefined) (out as { got?: unknown }).got = got;\n if (reason !== undefined) (out as { reason?: string }).reason = reason;\n return out;\n}\n"]}
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=chunk-DJMCSCBE.js.map
3
+ //# sourceMappingURL=chunk-DJMCSCBE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-DJMCSCBE.js"}
@@ -0,0 +1,124 @@
1
+ import { FacilitatorHttpError } from './chunk-PU5Y7FZG.js';
2
+
3
+ // src/facilitator/client.ts
4
+ function createFacilitatorClient(opts) {
5
+ const fetchImpl = opts.fetch ?? globalThis.fetch;
6
+ if (fetchImpl === void 0) {
7
+ throw new Error(
8
+ "createFacilitatorClient: no `fetch` implementation available. Pass `opts.fetch` explicitly."
9
+ );
10
+ }
11
+ const baseUrl = opts.url.replace(/\/+$/, "");
12
+ const baseHeaders = { "content-type": "application/json", ...opts.headers ?? {} };
13
+ const post = async (path, body, validate) => {
14
+ let res;
15
+ try {
16
+ res = await fetchImpl(`${baseUrl}${path}`, {
17
+ method: "POST",
18
+ headers: baseHeaders,
19
+ body: JSON.stringify(body)
20
+ });
21
+ } catch (cause) {
22
+ throw new FacilitatorHttpError(`facilitator network error (${path})`, {
23
+ code: "NETWORK_ERROR",
24
+ cause
25
+ });
26
+ }
27
+ let text;
28
+ try {
29
+ text = await res.text();
30
+ } catch (cause) {
31
+ throw new FacilitatorHttpError(`facilitator response body could not be read (${path})`, {
32
+ code: "BAD_RESPONSE_BODY",
33
+ status: res.status,
34
+ cause
35
+ });
36
+ }
37
+ let parsed;
38
+ try {
39
+ parsed = text.length === 0 ? null : JSON.parse(text);
40
+ } catch (cause) {
41
+ throw new FacilitatorHttpError(`facilitator returned non-JSON body (${path})`, {
42
+ code: "BAD_RESPONSE_BODY",
43
+ status: res.status,
44
+ cause
45
+ });
46
+ }
47
+ if (!res.ok) {
48
+ if (res.status === 400 && isFailureBranch(parsed) && validate(parsed)) {
49
+ return parsed;
50
+ }
51
+ const facilitatorCode = extractFacilitatorCode(parsed);
52
+ throw new FacilitatorHttpError(
53
+ `facilitator HTTP ${res.status} (${path}): ${reasonString(parsed) ?? text}`,
54
+ {
55
+ code: "BAD_HTTP_STATUS",
56
+ status: res.status,
57
+ ...facilitatorCode !== void 0 ? { facilitatorCode } : {}
58
+ }
59
+ );
60
+ }
61
+ if (!validate(parsed)) {
62
+ throw new FacilitatorHttpError(`facilitator returned unexpected body shape (${path})`, {
63
+ code: "BAD_RESPONSE_BODY",
64
+ status: res.status
65
+ });
66
+ }
67
+ return parsed;
68
+ };
69
+ return {
70
+ verify: (input) => post("/verify", input, isVerifyResult),
71
+ settle: (input) => post("/settle", input, isSettleResult),
72
+ performAction: (input) => post(
73
+ `/perform-action?action=${encodeURIComponent(input.action)}`,
74
+ input,
75
+ isPerformActionResult
76
+ )
77
+ };
78
+ }
79
+ function isObject(v) {
80
+ return v !== null && typeof v === "object";
81
+ }
82
+ function isFailureBranch(v) {
83
+ return isObject(v) && v.ok === false && typeof v.code === "string" && typeof v.reason === "string";
84
+ }
85
+ function isVerifyResult(parsed) {
86
+ if (!isObject(parsed)) return false;
87
+ if (parsed.ok === true) return true;
88
+ return isFailureBranch(parsed);
89
+ }
90
+ function isSettleResult(parsed) {
91
+ if (!isObject(parsed)) return false;
92
+ if (parsed.ok === true) {
93
+ return typeof parsed.exchangeId === "string" && typeof parsed.txHash === "string";
94
+ }
95
+ return isFailureBranch(parsed);
96
+ }
97
+ function isPerformActionResult(parsed) {
98
+ if (!isObject(parsed)) return false;
99
+ if (parsed.ok === true) {
100
+ if (typeof parsed.txHash !== "string") return false;
101
+ if (parsed.newExchangeState !== void 0 && typeof parsed.newExchangeState !== "string") {
102
+ return false;
103
+ }
104
+ if (parsed.newDisputeState !== void 0 && typeof parsed.newDisputeState !== "string") {
105
+ return false;
106
+ }
107
+ return true;
108
+ }
109
+ return isFailureBranch(parsed);
110
+ }
111
+ function extractFacilitatorCode(body) {
112
+ if (body === null || typeof body !== "object") return void 0;
113
+ const code = body.code;
114
+ return typeof code === "string" ? code : void 0;
115
+ }
116
+ function reasonString(body) {
117
+ if (body === null || typeof body !== "object") return void 0;
118
+ const reason = body.reason;
119
+ return typeof reason === "string" ? reason : void 0;
120
+ }
121
+
122
+ export { createFacilitatorClient };
123
+ //# sourceMappingURL=chunk-EAKQ4EFZ.js.map
124
+ //# sourceMappingURL=chunk-EAKQ4EFZ.js.map