@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,657 @@
1
+ import { FacilitatorHttpError } from './chunk-PU5Y7FZG.js';
2
+ import { verifyExchange } from './chunk-4NQ3VKC3.js';
3
+ import { decodeXPaymentHeader, validatePaymentPayload } from './chunk-CGVXQX5V.js';
4
+ import { ExchangeState, deriveNextActions } from '@bosonprotocol/x402-actions';
5
+ import { ACTION_POST_STATE } from '@bosonprotocol/x402-core/state-machine';
6
+ import { decodeSignedPayload } from '@bosonprotocol/x402-evm';
7
+ import { ADDRESS, DECIMAL_UINT } from '@bosonprotocol/x402-core/schemes/escrow';
8
+
9
+ // src/internal/facilitator-endpoints.ts
10
+ var COMMIT_ACTION_IDS = /* @__PURE__ */ new Set([
11
+ "boson-createOfferAndCommit",
12
+ "boson-createOfferCommitAndRedeem"
13
+ ]);
14
+ function facilitatorEndpointFor(actionId, facilitatorUrl) {
15
+ const base = facilitatorUrl.replace(/\/+$/, "");
16
+ if (COMMIT_ACTION_IDS.has(actionId)) {
17
+ return `${base}/settle`;
18
+ }
19
+ return `${base}/perform-action?action=${encodeURIComponent(actionId)}`;
20
+ }
21
+ function stampFacilitatorEndpoints(next, facilitatorUrl) {
22
+ return next.map((entry) => {
23
+ if (!entry.channels.includes("facilitator")) {
24
+ return entry;
25
+ }
26
+ if (entry.endpoints?.facilitator !== void 0) {
27
+ return entry;
28
+ }
29
+ return {
30
+ ...entry,
31
+ endpoints: {
32
+ ...entry.endpoints,
33
+ facilitator: facilitatorEndpointFor(entry.id, facilitatorUrl)
34
+ }
35
+ };
36
+ });
37
+ }
38
+
39
+ // src/handlers/next-actions.ts
40
+ function emitNextActions(input, registry, facilitatorUrl) {
41
+ const derived = input.exchangeState === ExchangeState.DISPUTED ? deriveNextActions(
42
+ {
43
+ exchangeId: input.exchangeId,
44
+ exchangeState: ExchangeState.DISPUTED,
45
+ disputeState: input.disputeState
46
+ },
47
+ registry
48
+ ) : deriveNextActions(
49
+ { exchangeId: input.exchangeId, exchangeState: input.exchangeState },
50
+ registry
51
+ );
52
+ if (facilitatorUrl === void 0) {
53
+ return derived;
54
+ }
55
+ return {
56
+ ...derived,
57
+ next: stampFacilitatorEndpoints(derived.next, facilitatorUrl)
58
+ };
59
+ }
60
+
61
+ // src/handlers/types.ts
62
+ function handlerOk(body) {
63
+ return { ok: true, status: 200, body };
64
+ }
65
+ function plainHandlerOk(body) {
66
+ return { ok: true, status: 200, body };
67
+ }
68
+ function handlerErr(status, code, reason, details) {
69
+ const body = { code, reason };
70
+ if (details !== void 0) body.details = details;
71
+ return { ok: false, status, body };
72
+ }
73
+
74
+ // src/handlers/commit-and-redeem.ts
75
+ async function handleCommit(input, ctx) {
76
+ return await handleCommitImpl(input, ctx, {
77
+ expectedAction: "boson-createOfferAndCommit",
78
+ expectedState: ExchangeState.COMMITTED
79
+ });
80
+ }
81
+ async function handleCommitAndRedeem(input, ctx) {
82
+ return await handleCommitImpl(input, ctx, {
83
+ expectedAction: "boson-createOfferCommitAndRedeem",
84
+ expectedState: ExchangeState.REDEEMED
85
+ });
86
+ }
87
+ async function handleCommitImpl(input, ctx, expected) {
88
+ const decoded = decodeXPaymentHeader(input.paymentHeader);
89
+ if (!decoded.ok) {
90
+ const status = decoded.code === "MISSING_HEADER" ? 402 : 400;
91
+ return handlerErr(status, decoded.code, decoded.reason);
92
+ }
93
+ if (decoded.payload.payload.action !== expected.expectedAction) {
94
+ return handlerErr(
95
+ 400,
96
+ "ACTION_ROUTE_MISMATCH",
97
+ `handler expected action ${expected.expectedAction}, got ${decoded.payload.payload.action}`,
98
+ {
99
+ expected: expected.expectedAction,
100
+ got: decoded.payload.payload.action
101
+ }
102
+ );
103
+ }
104
+ const channels = ctx.config.fulfillmentChannels ?? [];
105
+ const channelById = new Map(channels.map((c) => [c.id, c]));
106
+ const validation = await validatePaymentPayload({
107
+ payload: decoded.payload,
108
+ requirements: input.requirements,
109
+ chainId: ctx.config.chainId,
110
+ validateFulfillmentData: (option, data) => {
111
+ const channel = channelById.get(option);
112
+ if (channel === void 0) {
113
+ return {
114
+ ok: false,
115
+ reason: `fulfillment.option '${option}' has no registered channel adapter on this server`
116
+ };
117
+ }
118
+ try {
119
+ return channel.validate(data);
120
+ } catch (e) {
121
+ return { ok: false, reason: e instanceof Error ? e.message : String(e) };
122
+ }
123
+ }
124
+ });
125
+ if (!validation.ok) {
126
+ return handlerErr(400, validation.code, validation.reason ?? `rule ${validation.rule} failed`, {
127
+ rule: validation.rule,
128
+ field: validation.field,
129
+ expected: validation.expected,
130
+ got: validation.got
131
+ });
132
+ }
133
+ let settleResult;
134
+ try {
135
+ settleResult = await ctx.facilitator.settle({
136
+ scheme: "escrow",
137
+ network: input.requirements.network,
138
+ payload: decoded.payload,
139
+ requirements: input.requirements
140
+ });
141
+ } catch (e) {
142
+ if (e instanceof FacilitatorHttpError) {
143
+ return handlerErr(502, "FACILITATOR_UNREACHABLE", e.message, {
144
+ code: e.code,
145
+ status: e.status,
146
+ facilitatorCode: e.facilitatorCode
147
+ });
148
+ }
149
+ throw e;
150
+ }
151
+ if (!settleResult.ok) {
152
+ return handlerErr(502, "FACILITATOR_REJECTED", settleResult.reason, {
153
+ facilitatorCode: settleResult.code
154
+ });
155
+ }
156
+ const verifyResult = await verifyExchange(
157
+ ctx.exchangeReader,
158
+ settleResult.exchangeId,
159
+ buildExpectedFromRequirements(input.requirements, expected.expectedState)
160
+ );
161
+ if (!verifyResult.ok) {
162
+ return handlerErr(
163
+ 502,
164
+ `STATE_VERIFY_${verifyResult.code}`,
165
+ "post-settle state verification failed",
166
+ {
167
+ exchangeId: settleResult.exchangeId,
168
+ txHash: settleResult.txHash,
169
+ field: verifyResult.field,
170
+ expected: verifyResult.expected,
171
+ got: verifyResult.got
172
+ }
173
+ );
174
+ }
175
+ if (expected.expectedState === ExchangeState.COMMITTED) {
176
+ ctx.exchangeFulfillmentOptionStore.set(
177
+ settleResult.exchangeId,
178
+ input.requirements.fulfillment?.options.map((option) => option.id) ?? []
179
+ );
180
+ }
181
+ const warnings = [];
182
+ if (expected.expectedState === ExchangeState.REDEEMED && decoded.payload.fulfillment !== void 0 && decoded.payload.fulfillment.data !== void 0) {
183
+ const pending = {
184
+ exchangeId: settleResult.exchangeId,
185
+ option: decoded.payload.fulfillment.option,
186
+ data: decoded.payload.fulfillment.data,
187
+ redeemer: decoded.payload.payload.buyer,
188
+ recordedAt: Date.now()
189
+ };
190
+ ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, pending);
191
+ const channel = channelById.get(decoded.payload.fulfillment.option);
192
+ if (channel === void 0) {
193
+ const reason = "no channel adapter is registered";
194
+ ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
195
+ ...pending,
196
+ error: reason
197
+ });
198
+ warnings.push({
199
+ code: "FULFILLMENT_COMMIT_DEFERRED",
200
+ reason: "atomic redeem succeeded on-chain, but no channel adapter is registered",
201
+ details: {
202
+ exchangeId: settleResult.exchangeId,
203
+ option: decoded.payload.fulfillment.option,
204
+ error: reason
205
+ }
206
+ });
207
+ } else {
208
+ try {
209
+ await channel.onCommit(settleResult.exchangeId, decoded.payload.fulfillment.data);
210
+ ctx.fulfillmentRecoveryStore.delete(settleResult.exchangeId);
211
+ } catch (e) {
212
+ const reason = e instanceof Error ? e.message : String(e);
213
+ ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
214
+ ...pending,
215
+ error: reason
216
+ });
217
+ warnings.push({
218
+ code: "FULFILLMENT_COMMIT_DEFERRED",
219
+ reason: "atomic redeem succeeded on-chain, but the channel adapter rejected the data",
220
+ details: {
221
+ exchangeId: settleResult.exchangeId,
222
+ option: decoded.payload.fulfillment.option,
223
+ error: reason
224
+ }
225
+ });
226
+ }
227
+ }
228
+ }
229
+ const nextActions = emitNextActions(
230
+ {
231
+ exchangeId: settleResult.exchangeId,
232
+ exchangeState: expected.expectedState
233
+ },
234
+ ctx.config.channelRegistry,
235
+ ctx.config.facilitator.url
236
+ );
237
+ return handlerOk({
238
+ exchangeId: settleResult.exchangeId,
239
+ txHash: settleResult.txHash,
240
+ nextActions,
241
+ ...warnings.length > 0 ? { warnings } : {}
242
+ });
243
+ }
244
+ function buildExpectedFromRequirements(requirements, state) {
245
+ return {
246
+ state,
247
+ seller: requirements.offer.creator,
248
+ exchangeToken: requirements.asset,
249
+ price: requirements.amount
250
+ };
251
+ }
252
+ async function handlePerformAction(action, input, ctx) {
253
+ const reference = await ctx.exchangeReader.read(input.exchangeId);
254
+ if (reference === null) {
255
+ return handlerErr(
256
+ 502,
257
+ "STATE_VERIFY_EXCHANGE_NOT_FOUND",
258
+ "pre-action exchange reference could not be read",
259
+ { action, exchangeId: input.exchangeId }
260
+ );
261
+ }
262
+ let result;
263
+ try {
264
+ result = await ctx.facilitator.performAction({
265
+ network: ctx.config.network,
266
+ escrowAddress: ctx.config.escrow,
267
+ exchangeId: input.exchangeId,
268
+ action,
269
+ signedPayload: input.signedPayload
270
+ });
271
+ } catch (e) {
272
+ if (e instanceof FacilitatorHttpError) {
273
+ return handlerErr(502, "FACILITATOR_UNREACHABLE", e.message, {
274
+ code: e.code,
275
+ status: e.status,
276
+ facilitatorCode: e.facilitatorCode
277
+ });
278
+ }
279
+ throw e;
280
+ }
281
+ if (!result.ok) {
282
+ return handlerErr(502, "FACILITATOR_REJECTED", result.reason, {
283
+ facilitatorCode: result.code
284
+ });
285
+ }
286
+ const postState = ACTION_POST_STATE[action];
287
+ const expected = {
288
+ state: postState.exchange,
289
+ ...postState.dispute !== void 0 ? { disputeState: postState.dispute } : {},
290
+ seller: reference.seller,
291
+ exchangeToken: reference.exchangeToken,
292
+ price: reference.price
293
+ };
294
+ const verifyResult = await verifyExchange(ctx.exchangeReader, input.exchangeId, expected);
295
+ if (!verifyResult.ok) {
296
+ return handlerErr(
297
+ 502,
298
+ `STATE_VERIFY_${verifyResult.code}`,
299
+ "post-action state verification failed",
300
+ {
301
+ action,
302
+ exchangeId: input.exchangeId,
303
+ txHash: result.txHash,
304
+ field: verifyResult.field,
305
+ expected: verifyResult.expected,
306
+ got: verifyResult.got
307
+ }
308
+ );
309
+ }
310
+ const nextActions = postState.exchange === ExchangeState.DISPUTED && postState.dispute !== void 0 ? emitNextActions(
311
+ {
312
+ exchangeId: input.exchangeId,
313
+ exchangeState: ExchangeState.DISPUTED,
314
+ disputeState: postState.dispute
315
+ },
316
+ ctx.config.channelRegistry,
317
+ ctx.config.facilitator.url
318
+ ) : emitNextActions(
319
+ {
320
+ exchangeId: input.exchangeId,
321
+ exchangeState: postState.exchange
322
+ },
323
+ ctx.config.channelRegistry,
324
+ ctx.config.facilitator.url
325
+ );
326
+ return handlerOk({ txHash: result.txHash, nextActions });
327
+ }
328
+ async function handleRedeem(input, ctx) {
329
+ let redeemer;
330
+ try {
331
+ redeemer = decodeSignedPayload(input.signedPayload).from;
332
+ } catch (e) {
333
+ return handlerErr(
334
+ 400,
335
+ "SIGNED_PAYLOAD_DECODE_FAILED",
336
+ e instanceof Error ? e.message : "signedPayload could not be decoded"
337
+ );
338
+ }
339
+ let resolvedChannel;
340
+ if (input.fulfillment !== void 0) {
341
+ const advertisedOptions = ctx.exchangeFulfillmentOptionStore.get(input.exchangeId);
342
+ if (advertisedOptions !== void 0 && !advertisedOptions.includes(input.fulfillment.option)) {
343
+ return handlerErr(
344
+ 400,
345
+ "FULFILLMENT_OPTION_NOT_ADVERTISED",
346
+ `fulfillment.option '${input.fulfillment.option}' was not advertised for this exchange`,
347
+ { option: input.fulfillment.option, advertised: advertisedOptions }
348
+ );
349
+ }
350
+ const channels = ctx.config.fulfillmentChannels;
351
+ if (channels === void 0) {
352
+ return handlerErr(
353
+ 400,
354
+ "FULFILLMENT_CHANNELS_NOT_CONFIGURED",
355
+ "server received redeem-time fulfillment data but has no fulfillmentChannels registered"
356
+ );
357
+ }
358
+ const channel = channels.find((c) => c.id === input.fulfillment.option);
359
+ if (channel === void 0) {
360
+ return handlerErr(
361
+ 400,
362
+ "FULFILLMENT_OPTION_UNKNOWN",
363
+ `fulfillment.option '${input.fulfillment.option}' is not registered with the server`,
364
+ { option: input.fulfillment.option, registered: channels.map((c) => c.id) }
365
+ );
366
+ }
367
+ let validation;
368
+ try {
369
+ validation = channel.validate(input.fulfillment.data);
370
+ } catch (e) {
371
+ return handlerErr(400, "FULFILLMENT_DATA_INVALID", errorMessage(e), {
372
+ option: input.fulfillment.option
373
+ });
374
+ }
375
+ if (!validation.ok) {
376
+ return handlerErr(400, "FULFILLMENT_DATA_INVALID", validation.reason, {
377
+ option: input.fulfillment.option
378
+ });
379
+ }
380
+ resolvedChannel = channel;
381
+ }
382
+ const result = await handlePerformAction("boson-redeem", input, ctx);
383
+ if (!result.ok) return result;
384
+ let warning;
385
+ if (resolvedChannel !== void 0 && input.fulfillment !== void 0) {
386
+ const pending = {
387
+ exchangeId: input.exchangeId,
388
+ option: input.fulfillment.option,
389
+ data: input.fulfillment.data,
390
+ redeemer,
391
+ recordedAt: Date.now()
392
+ };
393
+ ctx.fulfillmentRecoveryStore.set(input.exchangeId, pending);
394
+ try {
395
+ await resolvedChannel.onCommit(input.exchangeId, input.fulfillment.data);
396
+ ctx.fulfillmentRecoveryStore.delete(input.exchangeId);
397
+ } catch (e) {
398
+ const reason = errorMessage(e);
399
+ ctx.fulfillmentRecoveryStore.set(input.exchangeId, { ...pending, error: reason });
400
+ warning = {
401
+ code: "FULFILLMENT_UPDATE_DEFERRED",
402
+ reason: "redeem succeeded on-chain, but the server could not persist the fulfillment update",
403
+ details: {
404
+ exchangeId: input.exchangeId,
405
+ option: input.fulfillment.option,
406
+ error: reason
407
+ }
408
+ };
409
+ }
410
+ }
411
+ ctx.exchangeFulfillmentOptionStore.delete(input.exchangeId);
412
+ if (warning !== void 0) {
413
+ return {
414
+ ...result,
415
+ body: {
416
+ ...result.body,
417
+ warnings: [...result.body.warnings ?? [], warning]
418
+ }
419
+ };
420
+ }
421
+ return result;
422
+ }
423
+ function errorMessage(e) {
424
+ return e instanceof Error ? e.message : String(e);
425
+ }
426
+ var handleComplete = (input, ctx) => handlePerformAction("boson-completeExchange", input, ctx);
427
+ var handleDisputeRaise = (input, ctx) => handlePerformAction("boson-raiseDispute", input, ctx);
428
+ var handleDisputeResolve = (input, ctx) => handlePerformAction("boson-resolveDispute", input, ctx);
429
+ var handleDisputeRetract = (input, ctx) => handlePerformAction("boson-retractDispute", input, ctx);
430
+ var handleDisputeEscalate = (input, ctx) => handlePerformAction("boson-escalateDispute", input, ctx);
431
+ var DECIMAL_UINT_RE = DECIMAL_UINT;
432
+ var ADDRESS_RE = ADDRESS;
433
+ var HEX_BYTES_RE = /^0x([0-9a-fA-F]{2})*$/;
434
+
435
+ // src/handlers/resolve-entity.ts
436
+ async function resolveEntityId(coreSdk, input) {
437
+ const address = input.address.toLowerCase();
438
+ let sellers = [];
439
+ let buyers = [];
440
+ try {
441
+ [sellers, buyers] = await Promise.all([
442
+ input.role === "buyer" ? Promise.resolve([]) : coreSdk.getSellersByAddress(address),
443
+ input.role === "seller" ? Promise.resolve([]) : coreSdk.getBuyers({ buyersFilter: { wallet: address } })
444
+ ]);
445
+ } catch (e) {
446
+ return {
447
+ ok: false,
448
+ code: "SUBGRAPH_FAILURE",
449
+ reason: e instanceof Error ? e.message : "subgraph lookup failed"
450
+ };
451
+ }
452
+ const sellerIds = sellers.map((s) => s.id);
453
+ const buyerIds = buyers.map((b) => b.id);
454
+ if (input.role === "seller") {
455
+ if (sellerIds.length === 0) {
456
+ return {
457
+ ok: false,
458
+ code: "NOT_FOUND",
459
+ reason: `no seller entity found for address ${address}`
460
+ };
461
+ }
462
+ if (sellerIds.length > 1) {
463
+ return {
464
+ ok: false,
465
+ code: "AMBIGUOUS",
466
+ reason: `address ${address} resolves to multiple seller entities (ids ${sellerIds.join(", ")}); pass entityId to disambiguate`,
467
+ sellerIds
468
+ };
469
+ }
470
+ return { ok: true, entityId: sellerIds[0], role: "seller" };
471
+ }
472
+ if (input.role === "buyer") {
473
+ if (buyerIds.length === 0) {
474
+ return {
475
+ ok: false,
476
+ code: "NOT_FOUND",
477
+ reason: `no buyer entity found for address ${address}`
478
+ };
479
+ }
480
+ if (buyerIds.length > 1) {
481
+ return {
482
+ ok: false,
483
+ code: "AMBIGUOUS",
484
+ reason: `address ${address} resolves to multiple buyer entities (ids ${buyerIds.join(", ")}); pass entityId to disambiguate`,
485
+ buyerIds
486
+ };
487
+ }
488
+ return { ok: true, entityId: buyerIds[0], role: "buyer" };
489
+ }
490
+ const totalMatches = sellerIds.length + buyerIds.length;
491
+ if (totalMatches === 0) {
492
+ return {
493
+ ok: false,
494
+ code: "NOT_FOUND",
495
+ reason: `no seller or buyer entity found for address ${address}`
496
+ };
497
+ }
498
+ if (totalMatches > 1) {
499
+ return {
500
+ ok: false,
501
+ code: "AMBIGUOUS",
502
+ reason: `address ${address} resolves to ${describeMatches(sellerIds, buyerIds)}; pass role and/or entityId to disambiguate`,
503
+ ...sellerIds.length > 0 ? { sellerIds } : {},
504
+ ...buyerIds.length > 0 ? { buyerIds } : {}
505
+ };
506
+ }
507
+ if (sellerIds.length === 1) return { ok: true, entityId: sellerIds[0], role: "seller" };
508
+ return { ok: true, entityId: buyerIds[0], role: "buyer" };
509
+ }
510
+ function describeMatches(sellerIds, buyerIds) {
511
+ const parts = [];
512
+ if (sellerIds.length > 0) {
513
+ parts.push(
514
+ sellerIds.length === 1 ? `seller (id ${sellerIds[0]})` : `${sellerIds.length} sellers (ids ${sellerIds.join(", ")})`
515
+ );
516
+ }
517
+ if (buyerIds.length > 0) {
518
+ parts.push(
519
+ buyerIds.length === 1 ? `buyer (id ${buyerIds[0]})` : `${buyerIds.length} buyers (ids ${buyerIds.join(", ")})`
520
+ );
521
+ }
522
+ return parts.join(" and ");
523
+ }
524
+
525
+ // src/handlers/withdraw-funds.ts
526
+ async function handleWithdrawFunds(input, ctx) {
527
+ let entityId;
528
+ let role;
529
+ if ("entityId" in input) {
530
+ if (!DECIMAL_UINT_RE.test(input.entityId)) {
531
+ return handlerErr(
532
+ 400,
533
+ "INVALID_ENTITY_ID",
534
+ `entityId must be a decimal uint256 string, got "${input.entityId}"`
535
+ );
536
+ }
537
+ entityId = input.entityId;
538
+ } else {
539
+ if (!ADDRESS_RE.test(input.address)) {
540
+ return handlerErr(
541
+ 400,
542
+ "INVALID_ADDRESS",
543
+ `address must be a 20-byte 0x-prefixed hex string, got "${input.address}"`
544
+ );
545
+ }
546
+ const resolved = await resolveEntityId(ctx.coreSdkRead, {
547
+ address: input.address,
548
+ ...input.role !== void 0 ? { role: input.role } : {}
549
+ });
550
+ if (!resolved.ok) {
551
+ const status = resolved.code === "NOT_FOUND" ? 404 : resolved.code === "AMBIGUOUS" ? 409 : 502;
552
+ const details = resolved.code === "AMBIGUOUS" ? {
553
+ ...resolved.sellerIds !== void 0 ? { sellerIds: resolved.sellerIds } : {},
554
+ ...resolved.buyerIds !== void 0 ? { buyerIds: resolved.buyerIds } : {}
555
+ } : void 0;
556
+ return handlerErr(status, resolved.code, resolved.reason, details);
557
+ }
558
+ entityId = resolved.entityId;
559
+ role = resolved.role;
560
+ }
561
+ let result;
562
+ try {
563
+ result = await ctx.facilitator.performAction({
564
+ network: ctx.config.network,
565
+ escrowAddress: ctx.config.escrow,
566
+ entityId,
567
+ action: "boson-withdrawFunds",
568
+ signedPayload: input.signedPayload
569
+ });
570
+ } catch (e) {
571
+ if (e instanceof FacilitatorHttpError) {
572
+ return handlerErr(502, "FACILITATOR_UNREACHABLE", e.message, {
573
+ code: e.code,
574
+ status: e.status,
575
+ facilitatorCode: e.facilitatorCode
576
+ });
577
+ }
578
+ throw e;
579
+ }
580
+ if (!result.ok) {
581
+ return handlerErr(502, "FACILITATOR_REJECTED", result.reason, {
582
+ facilitatorCode: result.code
583
+ });
584
+ }
585
+ return plainHandlerOk({
586
+ txHash: result.txHash,
587
+ entityId,
588
+ ...role !== void 0 ? { role } : {}
589
+ });
590
+ }
591
+
592
+ // src/handlers/available-funds.ts
593
+ async function handleGetAvailableFunds(query, ctx) {
594
+ let entityId;
595
+ let role;
596
+ if ("entityId" in query) {
597
+ if (!DECIMAL_UINT_RE.test(query.entityId)) {
598
+ return handlerErr(
599
+ 400,
600
+ "INVALID_ENTITY_ID",
601
+ `entityId must be a decimal uint256 string, got "${query.entityId}"`
602
+ );
603
+ }
604
+ entityId = query.entityId;
605
+ } else {
606
+ if (!ADDRESS_RE.test(query.address)) {
607
+ return handlerErr(
608
+ 400,
609
+ "INVALID_ADDRESS",
610
+ `address must be a 20-byte 0x-prefixed hex string, got "${query.address}"`
611
+ );
612
+ }
613
+ const resolved = await resolveEntityId(ctx.coreSdkRead, {
614
+ address: query.address,
615
+ ...query.role !== void 0 ? { role: query.role } : {}
616
+ });
617
+ if (!resolved.ok) {
618
+ const status = resolved.code === "NOT_FOUND" ? 404 : resolved.code === "AMBIGUOUS" ? 409 : 502;
619
+ return handlerErr(status, resolved.code, resolved.reason, omitErrorMetadata(resolved));
620
+ }
621
+ entityId = resolved.entityId;
622
+ role = resolved.role;
623
+ }
624
+ let raw;
625
+ try {
626
+ raw = await ctx.coreSdkRead.getFunds({ fundsFilter: { accountId: entityId } });
627
+ } catch (e) {
628
+ return handlerErr(
629
+ 502,
630
+ "SUBGRAPH_FAILURE",
631
+ e instanceof Error ? e.message : "subgraph getFunds lookup failed"
632
+ );
633
+ }
634
+ const funds = raw.map((entry) => ({
635
+ tokenAddress: entry.token.address,
636
+ tokenSymbol: entry.token.symbol,
637
+ tokenName: entry.token.name,
638
+ decimals: Number(entry.token.decimals),
639
+ availableAmount: entry.availableAmount
640
+ }));
641
+ return plainHandlerOk({
642
+ entityId,
643
+ ...role !== void 0 ? { role } : {},
644
+ funds
645
+ });
646
+ }
647
+ function omitErrorMetadata(resolved) {
648
+ if (resolved.code !== "AMBIGUOUS") return void 0;
649
+ const details = {};
650
+ if (resolved.sellerIds !== void 0) details.sellerIds = resolved.sellerIds;
651
+ if (resolved.buyerIds !== void 0) details.buyerIds = resolved.buyerIds;
652
+ return Object.keys(details).length > 0 ? details : void 0;
653
+ }
654
+
655
+ export { ADDRESS_RE, DECIMAL_UINT_RE, HEX_BYTES_RE, emitNextActions, handleCommit, handleCommitAndRedeem, handleComplete, handleDisputeEscalate, handleDisputeRaise, handleDisputeResolve, handleDisputeRetract, handleGetAvailableFunds, handlePerformAction, handleRedeem, handleWithdrawFunds, handlerErr, handlerOk, plainHandlerOk, resolveEntityId, stampFacilitatorEndpoints };
656
+ //# sourceMappingURL=chunk-Y5HFLCAT.js.map
657
+ //# sourceMappingURL=chunk-Y5HFLCAT.js.map