@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,1182 @@
1
+ 'use strict';
2
+
3
+ var x402Actions = require('@bosonprotocol/x402-actions');
4
+ var escrow = require('@bosonprotocol/x402-core/schemes/escrow');
5
+ var eip712 = require('@bosonprotocol/x402-core/eip712');
6
+ var x402Evm = require('@bosonprotocol/x402-evm');
7
+ var stateMachine = require('@bosonprotocol/x402-core/state-machine');
8
+
9
+ // src/handlers/commit-and-redeem.ts
10
+
11
+ // src/internal/facilitator-endpoints.ts
12
+ var COMMIT_ACTION_IDS = /* @__PURE__ */ new Set([
13
+ "boson-createOfferAndCommit",
14
+ "boson-createOfferCommitAndRedeem"
15
+ ]);
16
+ function facilitatorEndpointFor(actionId, facilitatorUrl) {
17
+ const base = facilitatorUrl.replace(/\/+$/, "");
18
+ if (COMMIT_ACTION_IDS.has(actionId)) {
19
+ return `${base}/settle`;
20
+ }
21
+ return `${base}/perform-action?action=${encodeURIComponent(actionId)}`;
22
+ }
23
+ function stampFacilitatorEndpoints(next, facilitatorUrl) {
24
+ return next.map((entry) => {
25
+ if (!entry.channels.includes("facilitator")) {
26
+ return entry;
27
+ }
28
+ if (entry.endpoints?.facilitator !== void 0) {
29
+ return entry;
30
+ }
31
+ return {
32
+ ...entry,
33
+ endpoints: {
34
+ ...entry.endpoints,
35
+ facilitator: facilitatorEndpointFor(entry.id, facilitatorUrl)
36
+ }
37
+ };
38
+ });
39
+ }
40
+
41
+ // src/handlers/next-actions.ts
42
+ function emitNextActions(input, registry, facilitatorUrl) {
43
+ const derived = input.exchangeState === x402Actions.ExchangeState.DISPUTED ? x402Actions.deriveNextActions(
44
+ {
45
+ exchangeId: input.exchangeId,
46
+ exchangeState: x402Actions.ExchangeState.DISPUTED,
47
+ disputeState: input.disputeState
48
+ },
49
+ registry
50
+ ) : x402Actions.deriveNextActions(
51
+ { exchangeId: input.exchangeId, exchangeState: input.exchangeState },
52
+ registry
53
+ );
54
+ if (facilitatorUrl === void 0) {
55
+ return derived;
56
+ }
57
+ return {
58
+ ...derived,
59
+ next: stampFacilitatorEndpoints(derived.next, facilitatorUrl)
60
+ };
61
+ }
62
+
63
+ // src/handlers/types.ts
64
+ function handlerOk(body) {
65
+ return { ok: true, status: 200, body };
66
+ }
67
+ function plainHandlerOk(body) {
68
+ return { ok: true, status: 200, body };
69
+ }
70
+ function handlerErr(status, code, reason, details) {
71
+ const body = { code, reason };
72
+ if (details !== void 0) body.details = details;
73
+ return { ok: false, status, body };
74
+ }
75
+ function decodeXPaymentHeader(header) {
76
+ if (header === void 0 || header === null || header.length === 0) {
77
+ return { ok: false, code: "MISSING_HEADER", reason: "X-PAYMENT header is missing" };
78
+ }
79
+ const padded = base64UrlToBase64(header);
80
+ let decoded;
81
+ try {
82
+ if (typeof atob === "function") {
83
+ const binary = atob(padded);
84
+ const bytes = new Uint8Array(binary.length);
85
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
86
+ decoded = new TextDecoder().decode(bytes);
87
+ } else {
88
+ decoded = Buffer.from(padded, "base64").toString("utf8");
89
+ }
90
+ } catch (e) {
91
+ return { ok: false, code: "INVALID_BASE64", reason: e.message };
92
+ }
93
+ let parsed;
94
+ try {
95
+ parsed = JSON.parse(decoded);
96
+ } catch (e) {
97
+ return { ok: false, code: "INVALID_JSON", reason: e.message };
98
+ }
99
+ try {
100
+ const payload = escrow.parseEscrowPaymentPayload(parsed);
101
+ return { ok: true, payload };
102
+ } catch (e) {
103
+ return { ok: false, code: "INVALID_PAYLOAD", reason: e.message };
104
+ }
105
+ }
106
+ function base64UrlToBase64(input) {
107
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
108
+ const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
109
+ return normalized + padding;
110
+ }
111
+ async function validatePaymentPayload(args) {
112
+ const { payload, requirements } = args;
113
+ if (payload.scheme !== "escrow" || requirements.scheme !== "escrow") {
114
+ return failure(1, "SCHEME_MISMATCH", "scheme", "escrow", payload.scheme);
115
+ }
116
+ if (payload.network !== requirements.network) {
117
+ return failure(2, "NETWORK_MISMATCH", "network", requirements.network, payload.network);
118
+ }
119
+ if (!deepEqual(payload.payload.offerRef.fullOffer, requirements.offer.fullOffer)) {
120
+ return failure(
121
+ 3,
122
+ "FULL_OFFER_MISMATCH",
123
+ "payload.offerRef.fullOffer",
124
+ "<requirements.offer.fullOffer>",
125
+ "<payload.offerRef.fullOffer>"
126
+ );
127
+ }
128
+ if (payload.payload.offerRef.sellerSig !== requirements.offer.sellerSig) {
129
+ return failure(
130
+ 4,
131
+ "SELLER_SIG_MISMATCH",
132
+ "payload.offerRef.sellerSig",
133
+ requirements.offer.sellerSig,
134
+ payload.payload.offerRef.sellerSig
135
+ );
136
+ }
137
+ const advertisedActions = requirements.actions.next.map((entry) => entry.id);
138
+ if (!advertisedActions.includes(payload.payload.action)) {
139
+ return failure(
140
+ 5,
141
+ "ACTION_NOT_IN_REQUIREMENTS",
142
+ "payload.action",
143
+ advertisedActions,
144
+ payload.payload.action
145
+ );
146
+ }
147
+ if (!requirements.tokenAuthStrategies.includes(payload.payload.tokenAuthStrategy)) {
148
+ return failure(
149
+ 6,
150
+ "TOKEN_AUTH_NOT_IN_REQUIREMENTS",
151
+ "payload.tokenAuthStrategy",
152
+ requirements.tokenAuthStrategies,
153
+ payload.payload.tokenAuthStrategy
154
+ );
155
+ }
156
+ const calldataResult = await checkFunctionSignatureAndCalldataEquality(payload);
157
+ if (calldataResult !== null) return calldataResult;
158
+ if (payload.payload.buyer.toLowerCase() !== payload.payload.metaTx.from.toLowerCase()) {
159
+ return failure(
160
+ 8,
161
+ "BAD_META_TX_SIGNATURE",
162
+ "payload.metaTx.from",
163
+ payload.payload.buyer,
164
+ payload.payload.metaTx.from
165
+ );
166
+ }
167
+ let recovered;
168
+ try {
169
+ recovered = await eip712.recoverMetaTransactionSigner({
170
+ chainId: args.chainId,
171
+ verifyingContract: requirements.escrowAddress,
172
+ message: {
173
+ nonce: BigInt(payload.payload.metaTx.nonce),
174
+ from: payload.payload.metaTx.from,
175
+ contractAddress: requirements.escrowAddress,
176
+ functionName: payload.payload.metaTx.functionName,
177
+ functionSignature: payload.payload.metaTx.functionSignature
178
+ },
179
+ signature: encodeRsv(payload.payload.metaTx.sig)
180
+ });
181
+ } catch (e) {
182
+ return failure(
183
+ 8,
184
+ "BAD_META_TX_SIGNATURE",
185
+ "payload.metaTx.sig",
186
+ "recoverable ECDSA signature",
187
+ payload.payload.metaTx.sig,
188
+ e.message
189
+ );
190
+ }
191
+ if (recovered.toLowerCase() !== payload.payload.buyer.toLowerCase()) {
192
+ return failure(
193
+ 8,
194
+ "BAD_META_TX_SIGNATURE",
195
+ "payload.metaTx.sig",
196
+ payload.payload.buyer,
197
+ recovered
198
+ );
199
+ }
200
+ const strategyResult = checkTokenAuthRules(payload, requirements, args.now);
201
+ if (strategyResult !== null) return strategyResult;
202
+ const fulfillmentResult = checkFulfillment(payload, requirements, args.validateFulfillmentData);
203
+ if (fulfillmentResult !== null) return fulfillmentResult;
204
+ return { ok: true };
205
+ }
206
+ async function checkFunctionSignatureAndCalldataEquality(payload) {
207
+ const action = payload.payload.action;
208
+ const buildCalldata = action === "boson-createOfferAndCommit" ? x402Evm.buildCreateOfferAndCommitCalldata : action === "boson-createOfferCommitAndRedeem" ? x402Evm.buildCreateOfferCommitAndRedeemCalldata : void 0;
209
+ if (!buildCalldata) {
210
+ return failure(
211
+ 7,
212
+ "CALLDATA_MISMATCH",
213
+ "payload.action",
214
+ "boson-createOfferAndCommit | boson-createOfferCommitAndRedeem",
215
+ action,
216
+ "rule-7 calldata reconstruction is only defined for commit-time actions"
217
+ );
218
+ }
219
+ const fullOfferWithSig = {
220
+ ...payload.payload.offerRef.fullOffer,
221
+ committer: payload.payload.buyer,
222
+ signature: payload.payload.offerRef.sellerSig
223
+ };
224
+ let expected;
225
+ try {
226
+ expected = await buildCalldata({ fullOffer: fullOfferWithSig });
227
+ } catch (e) {
228
+ return failure(
229
+ 7,
230
+ "CALLDATA_MISMATCH",
231
+ "payload.metaTx.functionSignature",
232
+ void 0,
233
+ void 0,
234
+ e.message
235
+ );
236
+ }
237
+ if (expected.functionName !== payload.payload.metaTx.functionName) {
238
+ return failure(
239
+ 7,
240
+ "CALLDATA_MISMATCH",
241
+ "payload.metaTx.functionName",
242
+ expected.functionName,
243
+ payload.payload.metaTx.functionName
244
+ );
245
+ }
246
+ if (expected.functionSignature.toLowerCase() !== payload.payload.metaTx.functionSignature.toLowerCase()) {
247
+ return failure(
248
+ 7,
249
+ "CALLDATA_MISMATCH",
250
+ "payload.metaTx.functionSignature",
251
+ expected.functionSignature,
252
+ payload.payload.metaTx.functionSignature
253
+ );
254
+ }
255
+ return null;
256
+ }
257
+ function checkTokenAuthRules(payload, requirements, nowSec) {
258
+ const strategy = payload.payload.tokenAuthStrategy;
259
+ const tokenAuth = payload.payload.tokenAuth;
260
+ const now = nowSec ?? Math.floor(Date.now() / 1e3);
261
+ const horizon = now + requirements.maxTimeoutSeconds;
262
+ if (strategy === "none") {
263
+ if (tokenAuth !== void 0) {
264
+ return failure(
265
+ 9,
266
+ "TOKEN_AUTH_UNEXPECTED",
267
+ "payload.tokenAuth",
268
+ "undefined (strategy=none)",
269
+ tokenAuth.kind
270
+ );
271
+ }
272
+ return null;
273
+ }
274
+ if (tokenAuth === void 0) {
275
+ return failure(
276
+ 9,
277
+ "TOKEN_AUTH_MISSING",
278
+ "payload.tokenAuth",
279
+ `<${strategy} authorization>`,
280
+ "undefined"
281
+ );
282
+ }
283
+ if (tokenAuth.kind !== strategy) {
284
+ return failure(9, "TOKEN_AUTH_MISSING", "payload.tokenAuth.kind", strategy, tokenAuth.kind);
285
+ }
286
+ switch (tokenAuth.kind) {
287
+ case "erc3009": {
288
+ if (tokenAuth.data.value !== requirements.amount) {
289
+ return failure(
290
+ 9,
291
+ "TOKEN_AUTH_AMOUNT_MISMATCH",
292
+ "payload.tokenAuth.data.value",
293
+ requirements.amount,
294
+ tokenAuth.data.value
295
+ );
296
+ }
297
+ if (tokenAuth.data.to.toLowerCase() !== requirements.escrowAddress.toLowerCase()) {
298
+ return failure(
299
+ 9,
300
+ "TOKEN_AUTH_RECIPIENT_MISMATCH",
301
+ "payload.tokenAuth.data.to",
302
+ requirements.escrowAddress,
303
+ tokenAuth.data.to
304
+ );
305
+ }
306
+ if (tokenAuth.data.validBefore > horizon) {
307
+ return failure(
308
+ 9,
309
+ "TOKEN_AUTH_DEADLINE_EXCEEDED",
310
+ "payload.tokenAuth.data.validBefore",
311
+ `<= ${horizon}`,
312
+ tokenAuth.data.validBefore
313
+ );
314
+ }
315
+ return null;
316
+ }
317
+ case "permit": {
318
+ if (tokenAuth.data.value !== requirements.amount) {
319
+ return failure(
320
+ 10,
321
+ "TOKEN_AUTH_AMOUNT_MISMATCH",
322
+ "payload.tokenAuth.data.value",
323
+ requirements.amount,
324
+ tokenAuth.data.value
325
+ );
326
+ }
327
+ if (tokenAuth.data.spender.toLowerCase() !== requirements.escrowAddress.toLowerCase()) {
328
+ return failure(
329
+ 10,
330
+ "TOKEN_AUTH_SPENDER_MISMATCH",
331
+ "payload.tokenAuth.data.spender",
332
+ requirements.escrowAddress,
333
+ tokenAuth.data.spender
334
+ );
335
+ }
336
+ if (tokenAuth.data.deadline > horizon) {
337
+ return failure(
338
+ 10,
339
+ "TOKEN_AUTH_DEADLINE_EXCEEDED",
340
+ "payload.tokenAuth.data.deadline",
341
+ `<= ${horizon}`,
342
+ tokenAuth.data.deadline
343
+ );
344
+ }
345
+ return null;
346
+ }
347
+ case "permit2": {
348
+ if (tokenAuth.data.permitted.amount !== requirements.amount) {
349
+ return failure(
350
+ 11,
351
+ "TOKEN_AUTH_AMOUNT_MISMATCH",
352
+ "payload.tokenAuth.data.permitted.amount",
353
+ requirements.amount,
354
+ tokenAuth.data.permitted.amount
355
+ );
356
+ }
357
+ if (tokenAuth.data.permitted.token.toLowerCase() !== requirements.asset.toLowerCase()) {
358
+ return failure(
359
+ 11,
360
+ "TOKEN_AUTH_TOKEN_MISMATCH",
361
+ "payload.tokenAuth.data.permitted.token",
362
+ requirements.asset,
363
+ tokenAuth.data.permitted.token
364
+ );
365
+ }
366
+ if (tokenAuth.data.spender.toLowerCase() !== requirements.escrowAddress.toLowerCase()) {
367
+ return failure(
368
+ 11,
369
+ "TOKEN_AUTH_SPENDER_MISMATCH",
370
+ "payload.tokenAuth.data.spender",
371
+ requirements.escrowAddress,
372
+ tokenAuth.data.spender
373
+ );
374
+ }
375
+ if (tokenAuth.data.deadline > horizon) {
376
+ return failure(
377
+ 11,
378
+ "TOKEN_AUTH_DEADLINE_EXCEEDED",
379
+ "payload.tokenAuth.data.deadline",
380
+ `<= ${horizon}`,
381
+ tokenAuth.data.deadline
382
+ );
383
+ }
384
+ return null;
385
+ }
386
+ default:
387
+ return failure(
388
+ 9,
389
+ "TOKEN_AUTH_UNKNOWN_STRATEGY",
390
+ "payload.tokenAuth.kind",
391
+ "erc3009 | permit | permit2",
392
+ tokenAuth.kind
393
+ );
394
+ }
395
+ }
396
+ function checkFulfillment(payload, requirements, validator) {
397
+ const required = requirements.fulfillment?.required === true;
398
+ const carried = payload.fulfillment;
399
+ if (carried === void 0) {
400
+ return required ? failure(13, "FULFILLMENT_REQUIRED", "payload.fulfillment", "<option>", "undefined") : null;
401
+ }
402
+ const advertised = requirements.fulfillment?.options ?? [];
403
+ const matched = advertised.find((option) => option.id === carried.option);
404
+ if (!matched) {
405
+ return failure(
406
+ 13,
407
+ "FULFILLMENT_OPTION_NOT_ADVERTISED",
408
+ "payload.fulfillment.option",
409
+ advertised.map((o) => o.id),
410
+ carried.option
411
+ );
412
+ }
413
+ const action = payload.payload.action;
414
+ if (action === "boson-createOfferCommitAndRedeem") {
415
+ if (carried.data === void 0) {
416
+ return failure(
417
+ 13,
418
+ "FULFILLMENT_DATA_REQUIRED",
419
+ "payload.fulfillment.data",
420
+ matched.schema ?? "null",
421
+ "undefined",
422
+ "atomic createOfferCommitAndRedeem requires fulfillment.data at commit time"
423
+ );
424
+ }
425
+ if (validator !== void 0) {
426
+ const result = validator(matched.id, carried.data);
427
+ if (!result.ok) {
428
+ return failure(
429
+ 13,
430
+ "FULFILLMENT_DATA_INVALID",
431
+ "payload.fulfillment.data",
432
+ matched.schema,
433
+ carried.data,
434
+ result.reason
435
+ );
436
+ }
437
+ }
438
+ return null;
439
+ }
440
+ if (carried.data !== void 0) {
441
+ return failure(
442
+ 13,
443
+ "FULFILLMENT_DATA_UNEXPECTED",
444
+ "payload.fulfillment.data",
445
+ "undefined",
446
+ carried.data,
447
+ "two-step boson-createOfferAndCommit defers fulfillment.data to the redeem POST body"
448
+ );
449
+ }
450
+ return null;
451
+ }
452
+ function deepEqual(a, b) {
453
+ if (a === b) return true;
454
+ if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
455
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
456
+ if (Array.isArray(a) && Array.isArray(b)) {
457
+ if (a.length !== b.length) return false;
458
+ for (let i = 0; i < a.length; i += 1) {
459
+ if (!deepEqual(a[i], b[i])) return false;
460
+ }
461
+ return true;
462
+ }
463
+ const ao = a;
464
+ const bo = b;
465
+ const aKeys = Object.keys(ao);
466
+ const bKeys = Object.keys(bo);
467
+ if (aKeys.length !== bKeys.length) return false;
468
+ for (const k of aKeys) {
469
+ if (!Object.prototype.hasOwnProperty.call(bo, k)) return false;
470
+ if (!deepEqual(ao[k], bo[k])) return false;
471
+ }
472
+ return true;
473
+ }
474
+ function encodeRsv(sig) {
475
+ if (sig.v !== 0 && sig.v !== 1 && sig.v !== 27 && sig.v !== 28) {
476
+ throw new Error("signature v must be 0, 1, 27, or 28");
477
+ }
478
+ const r = sig.r.replace(/^0x/, "").padStart(64, "0");
479
+ const s = sig.s.replace(/^0x/, "").padStart(64, "0");
480
+ const normalizedV = sig.v === 0 || sig.v === 1 ? sig.v + 27 : sig.v;
481
+ const v = normalizedV.toString(16).padStart(2, "0");
482
+ return `0x${r}${s}${v}`;
483
+ }
484
+ function failure(rule, code, field, expected, got, reason) {
485
+ const out = { ok: false, rule, code };
486
+ if (field !== void 0) out.field = field;
487
+ if (expected !== void 0) out.expected = expected;
488
+ if (got !== void 0) out.got = got;
489
+ if (reason !== void 0) out.reason = reason;
490
+ return out;
491
+ }
492
+ function verifyExchangeSnapshot(snapshot, expected) {
493
+ if (snapshot === null) {
494
+ return { ok: false, code: "EXCHANGE_NOT_FOUND" };
495
+ }
496
+ if (snapshot.state !== expected.state) {
497
+ return {
498
+ ok: false,
499
+ code: "STATE_MISMATCH",
500
+ field: "state",
501
+ expected: expected.state,
502
+ got: snapshot.state
503
+ };
504
+ }
505
+ if (expected.state === x402Actions.ExchangeState.DISPUTED) {
506
+ if (snapshot.disputeState !== expected.disputeState) {
507
+ return {
508
+ ok: false,
509
+ code: "DISPUTE_STATE_MISMATCH",
510
+ field: "disputeState",
511
+ expected: expected.disputeState,
512
+ got: snapshot.disputeState
513
+ };
514
+ }
515
+ }
516
+ if (snapshot.seller.toLowerCase() !== expected.seller.toLowerCase()) {
517
+ return {
518
+ ok: false,
519
+ code: "SELLER_MISMATCH",
520
+ field: "seller",
521
+ expected: expected.seller,
522
+ got: snapshot.seller
523
+ };
524
+ }
525
+ if (snapshot.exchangeToken.toLowerCase() !== expected.exchangeToken.toLowerCase()) {
526
+ return {
527
+ ok: false,
528
+ code: "TOKEN_MISMATCH",
529
+ field: "exchangeToken",
530
+ expected: expected.exchangeToken,
531
+ got: snapshot.exchangeToken
532
+ };
533
+ }
534
+ if (snapshot.price !== expected.price) {
535
+ return {
536
+ ok: false,
537
+ code: "PRICE_MISMATCH",
538
+ field: "price",
539
+ expected: expected.price,
540
+ got: snapshot.price
541
+ };
542
+ }
543
+ return { ok: true };
544
+ }
545
+ async function verifyExchange(reader, exchangeId, expected, options = {}) {
546
+ const attempts = Math.max(1, Math.floor(options.attempts ?? 3));
547
+ const delayMs = Math.max(0, Math.floor(options.delayMs ?? 50));
548
+ let result = { ok: false, code: "EXCHANGE_NOT_FOUND" };
549
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
550
+ const snapshot = await reader.read(exchangeId);
551
+ result = verifyExchangeSnapshot(snapshot, expected);
552
+ if (result.ok || !isRetryableVerifyResult(result) || attempt === attempts) {
553
+ return result;
554
+ }
555
+ if (delayMs > 0) {
556
+ await delay(delayMs);
557
+ }
558
+ }
559
+ return result;
560
+ }
561
+ function isRetryableVerifyResult(result) {
562
+ return !result.ok && (result.code === "EXCHANGE_NOT_FOUND" || result.code === "STATE_MISMATCH" || result.code === "DISPUTE_STATE_MISMATCH");
563
+ }
564
+ async function delay(ms) {
565
+ await new Promise((resolve) => {
566
+ setTimeout(resolve, ms);
567
+ });
568
+ }
569
+
570
+ // src/facilitator/errors.ts
571
+ var FacilitatorHttpError = class extends Error {
572
+ constructor(message, init) {
573
+ super(message, init.cause !== void 0 ? { cause: init.cause } : void 0);
574
+ this.name = "FacilitatorHttpError";
575
+ this.code = init.code;
576
+ if (init.status !== void 0) this.status = init.status;
577
+ if (init.facilitatorCode !== void 0) this.facilitatorCode = init.facilitatorCode;
578
+ }
579
+ };
580
+
581
+ // src/handlers/commit-and-redeem.ts
582
+ async function handleCommit(input, ctx) {
583
+ return await handleCommitImpl(input, ctx, {
584
+ expectedAction: "boson-createOfferAndCommit",
585
+ expectedState: x402Actions.ExchangeState.COMMITTED
586
+ });
587
+ }
588
+ async function handleCommitAndRedeem(input, ctx) {
589
+ return await handleCommitImpl(input, ctx, {
590
+ expectedAction: "boson-createOfferCommitAndRedeem",
591
+ expectedState: x402Actions.ExchangeState.REDEEMED
592
+ });
593
+ }
594
+ async function handleCommitImpl(input, ctx, expected) {
595
+ const decoded = decodeXPaymentHeader(input.paymentHeader);
596
+ if (!decoded.ok) {
597
+ const status = decoded.code === "MISSING_HEADER" ? 402 : 400;
598
+ return handlerErr(status, decoded.code, decoded.reason);
599
+ }
600
+ if (decoded.payload.payload.action !== expected.expectedAction) {
601
+ return handlerErr(
602
+ 400,
603
+ "ACTION_ROUTE_MISMATCH",
604
+ `handler expected action ${expected.expectedAction}, got ${decoded.payload.payload.action}`,
605
+ {
606
+ expected: expected.expectedAction,
607
+ got: decoded.payload.payload.action
608
+ }
609
+ );
610
+ }
611
+ const channels = ctx.config.fulfillmentChannels ?? [];
612
+ const channelById = new Map(channels.map((c) => [c.id, c]));
613
+ const validation = await validatePaymentPayload({
614
+ payload: decoded.payload,
615
+ requirements: input.requirements,
616
+ chainId: ctx.config.chainId,
617
+ validateFulfillmentData: (option, data) => {
618
+ const channel = channelById.get(option);
619
+ if (channel === void 0) {
620
+ return {
621
+ ok: false,
622
+ reason: `fulfillment.option '${option}' has no registered channel adapter on this server`
623
+ };
624
+ }
625
+ try {
626
+ return channel.validate(data);
627
+ } catch (e) {
628
+ return { ok: false, reason: e instanceof Error ? e.message : String(e) };
629
+ }
630
+ }
631
+ });
632
+ if (!validation.ok) {
633
+ return handlerErr(400, validation.code, validation.reason ?? `rule ${validation.rule} failed`, {
634
+ rule: validation.rule,
635
+ field: validation.field,
636
+ expected: validation.expected,
637
+ got: validation.got
638
+ });
639
+ }
640
+ let settleResult;
641
+ try {
642
+ settleResult = await ctx.facilitator.settle({
643
+ scheme: "escrow",
644
+ network: input.requirements.network,
645
+ payload: decoded.payload,
646
+ requirements: input.requirements
647
+ });
648
+ } catch (e) {
649
+ if (e instanceof FacilitatorHttpError) {
650
+ return handlerErr(502, "FACILITATOR_UNREACHABLE", e.message, {
651
+ code: e.code,
652
+ status: e.status,
653
+ facilitatorCode: e.facilitatorCode
654
+ });
655
+ }
656
+ throw e;
657
+ }
658
+ if (!settleResult.ok) {
659
+ return handlerErr(502, "FACILITATOR_REJECTED", settleResult.reason, {
660
+ facilitatorCode: settleResult.code
661
+ });
662
+ }
663
+ const verifyResult = await verifyExchange(
664
+ ctx.exchangeReader,
665
+ settleResult.exchangeId,
666
+ buildExpectedFromRequirements(input.requirements, expected.expectedState)
667
+ );
668
+ if (!verifyResult.ok) {
669
+ return handlerErr(
670
+ 502,
671
+ `STATE_VERIFY_${verifyResult.code}`,
672
+ "post-settle state verification failed",
673
+ {
674
+ exchangeId: settleResult.exchangeId,
675
+ txHash: settleResult.txHash,
676
+ field: verifyResult.field,
677
+ expected: verifyResult.expected,
678
+ got: verifyResult.got
679
+ }
680
+ );
681
+ }
682
+ if (expected.expectedState === x402Actions.ExchangeState.COMMITTED) {
683
+ ctx.exchangeFulfillmentOptionStore.set(
684
+ settleResult.exchangeId,
685
+ input.requirements.fulfillment?.options.map((option) => option.id) ?? []
686
+ );
687
+ }
688
+ const warnings = [];
689
+ if (expected.expectedState === x402Actions.ExchangeState.REDEEMED && decoded.payload.fulfillment !== void 0 && decoded.payload.fulfillment.data !== void 0) {
690
+ const pending = {
691
+ exchangeId: settleResult.exchangeId,
692
+ option: decoded.payload.fulfillment.option,
693
+ data: decoded.payload.fulfillment.data,
694
+ redeemer: decoded.payload.payload.buyer,
695
+ recordedAt: Date.now()
696
+ };
697
+ ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, pending);
698
+ const channel = channelById.get(decoded.payload.fulfillment.option);
699
+ if (channel === void 0) {
700
+ const reason = "no channel adapter is registered";
701
+ ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
702
+ ...pending,
703
+ error: reason
704
+ });
705
+ warnings.push({
706
+ code: "FULFILLMENT_COMMIT_DEFERRED",
707
+ reason: "atomic redeem succeeded on-chain, but no channel adapter is registered",
708
+ details: {
709
+ exchangeId: settleResult.exchangeId,
710
+ option: decoded.payload.fulfillment.option,
711
+ error: reason
712
+ }
713
+ });
714
+ } else {
715
+ try {
716
+ await channel.onCommit(settleResult.exchangeId, decoded.payload.fulfillment.data);
717
+ ctx.fulfillmentRecoveryStore.delete(settleResult.exchangeId);
718
+ } catch (e) {
719
+ const reason = e instanceof Error ? e.message : String(e);
720
+ ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
721
+ ...pending,
722
+ error: reason
723
+ });
724
+ warnings.push({
725
+ code: "FULFILLMENT_COMMIT_DEFERRED",
726
+ reason: "atomic redeem succeeded on-chain, but the channel adapter rejected the data",
727
+ details: {
728
+ exchangeId: settleResult.exchangeId,
729
+ option: decoded.payload.fulfillment.option,
730
+ error: reason
731
+ }
732
+ });
733
+ }
734
+ }
735
+ }
736
+ const nextActions = emitNextActions(
737
+ {
738
+ exchangeId: settleResult.exchangeId,
739
+ exchangeState: expected.expectedState
740
+ },
741
+ ctx.config.channelRegistry,
742
+ ctx.config.facilitator.url
743
+ );
744
+ return handlerOk({
745
+ exchangeId: settleResult.exchangeId,
746
+ txHash: settleResult.txHash,
747
+ nextActions,
748
+ ...warnings.length > 0 ? { warnings } : {}
749
+ });
750
+ }
751
+ function buildExpectedFromRequirements(requirements, state) {
752
+ return {
753
+ state,
754
+ seller: requirements.offer.creator,
755
+ exchangeToken: requirements.asset,
756
+ price: requirements.amount
757
+ };
758
+ }
759
+ async function handlePerformAction(action, input, ctx) {
760
+ const reference = await ctx.exchangeReader.read(input.exchangeId);
761
+ if (reference === null) {
762
+ return handlerErr(
763
+ 502,
764
+ "STATE_VERIFY_EXCHANGE_NOT_FOUND",
765
+ "pre-action exchange reference could not be read",
766
+ { action, exchangeId: input.exchangeId }
767
+ );
768
+ }
769
+ let result;
770
+ try {
771
+ result = await ctx.facilitator.performAction({
772
+ network: ctx.config.network,
773
+ escrowAddress: ctx.config.escrow,
774
+ exchangeId: input.exchangeId,
775
+ action,
776
+ signedPayload: input.signedPayload
777
+ });
778
+ } catch (e) {
779
+ if (e instanceof FacilitatorHttpError) {
780
+ return handlerErr(502, "FACILITATOR_UNREACHABLE", e.message, {
781
+ code: e.code,
782
+ status: e.status,
783
+ facilitatorCode: e.facilitatorCode
784
+ });
785
+ }
786
+ throw e;
787
+ }
788
+ if (!result.ok) {
789
+ return handlerErr(502, "FACILITATOR_REJECTED", result.reason, {
790
+ facilitatorCode: result.code
791
+ });
792
+ }
793
+ const postState = stateMachine.ACTION_POST_STATE[action];
794
+ const expected = {
795
+ state: postState.exchange,
796
+ ...postState.dispute !== void 0 ? { disputeState: postState.dispute } : {},
797
+ seller: reference.seller,
798
+ exchangeToken: reference.exchangeToken,
799
+ price: reference.price
800
+ };
801
+ const verifyResult = await verifyExchange(ctx.exchangeReader, input.exchangeId, expected);
802
+ if (!verifyResult.ok) {
803
+ return handlerErr(
804
+ 502,
805
+ `STATE_VERIFY_${verifyResult.code}`,
806
+ "post-action state verification failed",
807
+ {
808
+ action,
809
+ exchangeId: input.exchangeId,
810
+ txHash: result.txHash,
811
+ field: verifyResult.field,
812
+ expected: verifyResult.expected,
813
+ got: verifyResult.got
814
+ }
815
+ );
816
+ }
817
+ const nextActions = postState.exchange === x402Actions.ExchangeState.DISPUTED && postState.dispute !== void 0 ? emitNextActions(
818
+ {
819
+ exchangeId: input.exchangeId,
820
+ exchangeState: x402Actions.ExchangeState.DISPUTED,
821
+ disputeState: postState.dispute
822
+ },
823
+ ctx.config.channelRegistry,
824
+ ctx.config.facilitator.url
825
+ ) : emitNextActions(
826
+ {
827
+ exchangeId: input.exchangeId,
828
+ exchangeState: postState.exchange
829
+ },
830
+ ctx.config.channelRegistry,
831
+ ctx.config.facilitator.url
832
+ );
833
+ return handlerOk({ txHash: result.txHash, nextActions });
834
+ }
835
+ async function handleRedeem(input, ctx) {
836
+ let redeemer;
837
+ try {
838
+ redeemer = x402Evm.decodeSignedPayload(input.signedPayload).from;
839
+ } catch (e) {
840
+ return handlerErr(
841
+ 400,
842
+ "SIGNED_PAYLOAD_DECODE_FAILED",
843
+ e instanceof Error ? e.message : "signedPayload could not be decoded"
844
+ );
845
+ }
846
+ let resolvedChannel;
847
+ if (input.fulfillment !== void 0) {
848
+ const advertisedOptions = ctx.exchangeFulfillmentOptionStore.get(input.exchangeId);
849
+ if (advertisedOptions !== void 0 && !advertisedOptions.includes(input.fulfillment.option)) {
850
+ return handlerErr(
851
+ 400,
852
+ "FULFILLMENT_OPTION_NOT_ADVERTISED",
853
+ `fulfillment.option '${input.fulfillment.option}' was not advertised for this exchange`,
854
+ { option: input.fulfillment.option, advertised: advertisedOptions }
855
+ );
856
+ }
857
+ const channels = ctx.config.fulfillmentChannels;
858
+ if (channels === void 0) {
859
+ return handlerErr(
860
+ 400,
861
+ "FULFILLMENT_CHANNELS_NOT_CONFIGURED",
862
+ "server received redeem-time fulfillment data but has no fulfillmentChannels registered"
863
+ );
864
+ }
865
+ const channel = channels.find((c) => c.id === input.fulfillment.option);
866
+ if (channel === void 0) {
867
+ return handlerErr(
868
+ 400,
869
+ "FULFILLMENT_OPTION_UNKNOWN",
870
+ `fulfillment.option '${input.fulfillment.option}' is not registered with the server`,
871
+ { option: input.fulfillment.option, registered: channels.map((c) => c.id) }
872
+ );
873
+ }
874
+ let validation;
875
+ try {
876
+ validation = channel.validate(input.fulfillment.data);
877
+ } catch (e) {
878
+ return handlerErr(400, "FULFILLMENT_DATA_INVALID", errorMessage(e), {
879
+ option: input.fulfillment.option
880
+ });
881
+ }
882
+ if (!validation.ok) {
883
+ return handlerErr(400, "FULFILLMENT_DATA_INVALID", validation.reason, {
884
+ option: input.fulfillment.option
885
+ });
886
+ }
887
+ resolvedChannel = channel;
888
+ }
889
+ const result = await handlePerformAction("boson-redeem", input, ctx);
890
+ if (!result.ok) return result;
891
+ let warning;
892
+ if (resolvedChannel !== void 0 && input.fulfillment !== void 0) {
893
+ const pending = {
894
+ exchangeId: input.exchangeId,
895
+ option: input.fulfillment.option,
896
+ data: input.fulfillment.data,
897
+ redeemer,
898
+ recordedAt: Date.now()
899
+ };
900
+ ctx.fulfillmentRecoveryStore.set(input.exchangeId, pending);
901
+ try {
902
+ await resolvedChannel.onCommit(input.exchangeId, input.fulfillment.data);
903
+ ctx.fulfillmentRecoveryStore.delete(input.exchangeId);
904
+ } catch (e) {
905
+ const reason = errorMessage(e);
906
+ ctx.fulfillmentRecoveryStore.set(input.exchangeId, { ...pending, error: reason });
907
+ warning = {
908
+ code: "FULFILLMENT_UPDATE_DEFERRED",
909
+ reason: "redeem succeeded on-chain, but the server could not persist the fulfillment update",
910
+ details: {
911
+ exchangeId: input.exchangeId,
912
+ option: input.fulfillment.option,
913
+ error: reason
914
+ }
915
+ };
916
+ }
917
+ }
918
+ ctx.exchangeFulfillmentOptionStore.delete(input.exchangeId);
919
+ if (warning !== void 0) {
920
+ return {
921
+ ...result,
922
+ body: {
923
+ ...result.body,
924
+ warnings: [...result.body.warnings ?? [], warning]
925
+ }
926
+ };
927
+ }
928
+ return result;
929
+ }
930
+ function errorMessage(e) {
931
+ return e instanceof Error ? e.message : String(e);
932
+ }
933
+ var handleComplete = (input, ctx) => handlePerformAction("boson-completeExchange", input, ctx);
934
+ var handleDisputeRaise = (input, ctx) => handlePerformAction("boson-raiseDispute", input, ctx);
935
+ var handleDisputeResolve = (input, ctx) => handlePerformAction("boson-resolveDispute", input, ctx);
936
+ var handleDisputeRetract = (input, ctx) => handlePerformAction("boson-retractDispute", input, ctx);
937
+ var handleDisputeEscalate = (input, ctx) => handlePerformAction("boson-escalateDispute", input, ctx);
938
+ var DECIMAL_UINT_RE = escrow.DECIMAL_UINT;
939
+ var ADDRESS_RE = escrow.ADDRESS;
940
+ var HEX_BYTES_RE = /^0x([0-9a-fA-F]{2})*$/;
941
+
942
+ // src/handlers/resolve-entity.ts
943
+ async function resolveEntityId(coreSdk, input) {
944
+ const address = input.address.toLowerCase();
945
+ let sellers = [];
946
+ let buyers = [];
947
+ try {
948
+ [sellers, buyers] = await Promise.all([
949
+ input.role === "buyer" ? Promise.resolve([]) : coreSdk.getSellersByAddress(address),
950
+ input.role === "seller" ? Promise.resolve([]) : coreSdk.getBuyers({ buyersFilter: { wallet: address } })
951
+ ]);
952
+ } catch (e) {
953
+ return {
954
+ ok: false,
955
+ code: "SUBGRAPH_FAILURE",
956
+ reason: e instanceof Error ? e.message : "subgraph lookup failed"
957
+ };
958
+ }
959
+ const sellerIds = sellers.map((s) => s.id);
960
+ const buyerIds = buyers.map((b) => b.id);
961
+ if (input.role === "seller") {
962
+ if (sellerIds.length === 0) {
963
+ return {
964
+ ok: false,
965
+ code: "NOT_FOUND",
966
+ reason: `no seller entity found for address ${address}`
967
+ };
968
+ }
969
+ if (sellerIds.length > 1) {
970
+ return {
971
+ ok: false,
972
+ code: "AMBIGUOUS",
973
+ reason: `address ${address} resolves to multiple seller entities (ids ${sellerIds.join(", ")}); pass entityId to disambiguate`,
974
+ sellerIds
975
+ };
976
+ }
977
+ return { ok: true, entityId: sellerIds[0], role: "seller" };
978
+ }
979
+ if (input.role === "buyer") {
980
+ if (buyerIds.length === 0) {
981
+ return {
982
+ ok: false,
983
+ code: "NOT_FOUND",
984
+ reason: `no buyer entity found for address ${address}`
985
+ };
986
+ }
987
+ if (buyerIds.length > 1) {
988
+ return {
989
+ ok: false,
990
+ code: "AMBIGUOUS",
991
+ reason: `address ${address} resolves to multiple buyer entities (ids ${buyerIds.join(", ")}); pass entityId to disambiguate`,
992
+ buyerIds
993
+ };
994
+ }
995
+ return { ok: true, entityId: buyerIds[0], role: "buyer" };
996
+ }
997
+ const totalMatches = sellerIds.length + buyerIds.length;
998
+ if (totalMatches === 0) {
999
+ return {
1000
+ ok: false,
1001
+ code: "NOT_FOUND",
1002
+ reason: `no seller or buyer entity found for address ${address}`
1003
+ };
1004
+ }
1005
+ if (totalMatches > 1) {
1006
+ return {
1007
+ ok: false,
1008
+ code: "AMBIGUOUS",
1009
+ reason: `address ${address} resolves to ${describeMatches(sellerIds, buyerIds)}; pass role and/or entityId to disambiguate`,
1010
+ ...sellerIds.length > 0 ? { sellerIds } : {},
1011
+ ...buyerIds.length > 0 ? { buyerIds } : {}
1012
+ };
1013
+ }
1014
+ if (sellerIds.length === 1) return { ok: true, entityId: sellerIds[0], role: "seller" };
1015
+ return { ok: true, entityId: buyerIds[0], role: "buyer" };
1016
+ }
1017
+ function describeMatches(sellerIds, buyerIds) {
1018
+ const parts = [];
1019
+ if (sellerIds.length > 0) {
1020
+ parts.push(
1021
+ sellerIds.length === 1 ? `seller (id ${sellerIds[0]})` : `${sellerIds.length} sellers (ids ${sellerIds.join(", ")})`
1022
+ );
1023
+ }
1024
+ if (buyerIds.length > 0) {
1025
+ parts.push(
1026
+ buyerIds.length === 1 ? `buyer (id ${buyerIds[0]})` : `${buyerIds.length} buyers (ids ${buyerIds.join(", ")})`
1027
+ );
1028
+ }
1029
+ return parts.join(" and ");
1030
+ }
1031
+
1032
+ // src/handlers/withdraw-funds.ts
1033
+ async function handleWithdrawFunds(input, ctx) {
1034
+ let entityId;
1035
+ let role;
1036
+ if ("entityId" in input) {
1037
+ if (!DECIMAL_UINT_RE.test(input.entityId)) {
1038
+ return handlerErr(
1039
+ 400,
1040
+ "INVALID_ENTITY_ID",
1041
+ `entityId must be a decimal uint256 string, got "${input.entityId}"`
1042
+ );
1043
+ }
1044
+ entityId = input.entityId;
1045
+ } else {
1046
+ if (!ADDRESS_RE.test(input.address)) {
1047
+ return handlerErr(
1048
+ 400,
1049
+ "INVALID_ADDRESS",
1050
+ `address must be a 20-byte 0x-prefixed hex string, got "${input.address}"`
1051
+ );
1052
+ }
1053
+ const resolved = await resolveEntityId(ctx.coreSdkRead, {
1054
+ address: input.address,
1055
+ ...input.role !== void 0 ? { role: input.role } : {}
1056
+ });
1057
+ if (!resolved.ok) {
1058
+ const status = resolved.code === "NOT_FOUND" ? 404 : resolved.code === "AMBIGUOUS" ? 409 : 502;
1059
+ const details = resolved.code === "AMBIGUOUS" ? {
1060
+ ...resolved.sellerIds !== void 0 ? { sellerIds: resolved.sellerIds } : {},
1061
+ ...resolved.buyerIds !== void 0 ? { buyerIds: resolved.buyerIds } : {}
1062
+ } : void 0;
1063
+ return handlerErr(status, resolved.code, resolved.reason, details);
1064
+ }
1065
+ entityId = resolved.entityId;
1066
+ role = resolved.role;
1067
+ }
1068
+ let result;
1069
+ try {
1070
+ result = await ctx.facilitator.performAction({
1071
+ network: ctx.config.network,
1072
+ escrowAddress: ctx.config.escrow,
1073
+ entityId,
1074
+ action: "boson-withdrawFunds",
1075
+ signedPayload: input.signedPayload
1076
+ });
1077
+ } catch (e) {
1078
+ if (e instanceof FacilitatorHttpError) {
1079
+ return handlerErr(502, "FACILITATOR_UNREACHABLE", e.message, {
1080
+ code: e.code,
1081
+ status: e.status,
1082
+ facilitatorCode: e.facilitatorCode
1083
+ });
1084
+ }
1085
+ throw e;
1086
+ }
1087
+ if (!result.ok) {
1088
+ return handlerErr(502, "FACILITATOR_REJECTED", result.reason, {
1089
+ facilitatorCode: result.code
1090
+ });
1091
+ }
1092
+ return plainHandlerOk({
1093
+ txHash: result.txHash,
1094
+ entityId,
1095
+ ...role !== void 0 ? { role } : {}
1096
+ });
1097
+ }
1098
+
1099
+ // src/handlers/available-funds.ts
1100
+ async function handleGetAvailableFunds(query, ctx) {
1101
+ let entityId;
1102
+ let role;
1103
+ if ("entityId" in query) {
1104
+ if (!DECIMAL_UINT_RE.test(query.entityId)) {
1105
+ return handlerErr(
1106
+ 400,
1107
+ "INVALID_ENTITY_ID",
1108
+ `entityId must be a decimal uint256 string, got "${query.entityId}"`
1109
+ );
1110
+ }
1111
+ entityId = query.entityId;
1112
+ } else {
1113
+ if (!ADDRESS_RE.test(query.address)) {
1114
+ return handlerErr(
1115
+ 400,
1116
+ "INVALID_ADDRESS",
1117
+ `address must be a 20-byte 0x-prefixed hex string, got "${query.address}"`
1118
+ );
1119
+ }
1120
+ const resolved = await resolveEntityId(ctx.coreSdkRead, {
1121
+ address: query.address,
1122
+ ...query.role !== void 0 ? { role: query.role } : {}
1123
+ });
1124
+ if (!resolved.ok) {
1125
+ const status = resolved.code === "NOT_FOUND" ? 404 : resolved.code === "AMBIGUOUS" ? 409 : 502;
1126
+ return handlerErr(status, resolved.code, resolved.reason, omitErrorMetadata(resolved));
1127
+ }
1128
+ entityId = resolved.entityId;
1129
+ role = resolved.role;
1130
+ }
1131
+ let raw;
1132
+ try {
1133
+ raw = await ctx.coreSdkRead.getFunds({ fundsFilter: { accountId: entityId } });
1134
+ } catch (e) {
1135
+ return handlerErr(
1136
+ 502,
1137
+ "SUBGRAPH_FAILURE",
1138
+ e instanceof Error ? e.message : "subgraph getFunds lookup failed"
1139
+ );
1140
+ }
1141
+ const funds = raw.map((entry) => ({
1142
+ tokenAddress: entry.token.address,
1143
+ tokenSymbol: entry.token.symbol,
1144
+ tokenName: entry.token.name,
1145
+ decimals: Number(entry.token.decimals),
1146
+ availableAmount: entry.availableAmount
1147
+ }));
1148
+ return plainHandlerOk({
1149
+ entityId,
1150
+ ...role !== void 0 ? { role } : {},
1151
+ funds
1152
+ });
1153
+ }
1154
+ function omitErrorMetadata(resolved) {
1155
+ if (resolved.code !== "AMBIGUOUS") return void 0;
1156
+ const details = {};
1157
+ if (resolved.sellerIds !== void 0) details.sellerIds = resolved.sellerIds;
1158
+ if (resolved.buyerIds !== void 0) details.buyerIds = resolved.buyerIds;
1159
+ return Object.keys(details).length > 0 ? details : void 0;
1160
+ }
1161
+
1162
+ exports.ADDRESS_RE = ADDRESS_RE;
1163
+ exports.DECIMAL_UINT_RE = DECIMAL_UINT_RE;
1164
+ exports.HEX_BYTES_RE = HEX_BYTES_RE;
1165
+ exports.emitNextActions = emitNextActions;
1166
+ exports.handleCommit = handleCommit;
1167
+ exports.handleCommitAndRedeem = handleCommitAndRedeem;
1168
+ exports.handleComplete = handleComplete;
1169
+ exports.handleDisputeEscalate = handleDisputeEscalate;
1170
+ exports.handleDisputeRaise = handleDisputeRaise;
1171
+ exports.handleDisputeResolve = handleDisputeResolve;
1172
+ exports.handleDisputeRetract = handleDisputeRetract;
1173
+ exports.handleGetAvailableFunds = handleGetAvailableFunds;
1174
+ exports.handlePerformAction = handlePerformAction;
1175
+ exports.handleRedeem = handleRedeem;
1176
+ exports.handleWithdrawFunds = handleWithdrawFunds;
1177
+ exports.handlerErr = handlerErr;
1178
+ exports.handlerOk = handlerOk;
1179
+ exports.plainHandlerOk = plainHandlerOk;
1180
+ exports.resolveEntityId = resolveEntityId;
1181
+ //# sourceMappingURL=index.js.map
1182
+ //# sourceMappingURL=index.js.map