@bosonprotocol/x402-server 0.2.0 → 0.3.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.
- package/dist/cjs/challenge/index.d.ts +2 -1
- package/dist/cjs/client-B2yejopi.d.ts +88 -0
- package/dist/cjs/{config-CBr9qMps.d.ts → config-hnCiZiXF.d.ts} +241 -22
- package/dist/cjs/facilitator/index.d.ts +1 -1
- package/dist/cjs/facilitator/index.js +120 -8
- package/dist/cjs/facilitator/index.js.map +1 -1
- package/dist/cjs/handlers/index.d.ts +8 -264
- package/dist/cjs/handlers/index.js +147 -18
- package/dist/cjs/handlers/index.js.map +1 -1
- package/dist/cjs/index-COmLTH-U.d.ts +292 -0
- package/dist/cjs/index.d.ts +91 -7
- package/dist/cjs/index.js +559 -73
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/chunk-OMJEQEOF.js +223 -0
- package/dist/esm/chunk-OMJEQEOF.js.map +1 -0
- package/dist/esm/{chunk-PU5Y7FZG.js → chunk-RAESVNQG.js} +15 -3
- package/dist/esm/chunk-RAESVNQG.js.map +1 -0
- package/dist/esm/{chunk-Y5HFLCAT.js → chunk-SHYOIKYU.js} +138 -21
- package/dist/esm/chunk-SHYOIKYU.js.map +1 -0
- package/dist/esm/facilitator/index.js +2 -2
- package/dist/esm/handlers/index.js +2 -2
- package/dist/esm/index.js +306 -53
- package/dist/esm/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/cjs/client-tnrpqVMW.d.ts +0 -36
- package/dist/esm/chunk-EAKQ4EFZ.js +0 -124
- package/dist/esm/chunk-EAKQ4EFZ.js.map +0 -1
- package/dist/esm/chunk-PU5Y7FZG.js.map +0 -1
- package/dist/esm/chunk-Y5HFLCAT.js.map +0 -1
package/dist/cjs/index.js
CHANGED
|
@@ -116,6 +116,31 @@ function buildForwardingAdapter(signer, chainId) {
|
|
|
116
116
|
getCurrentTimeMs: () => unreachable2("getCurrentTimeMs")
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
|
+
|
|
120
|
+
// src/store.ts
|
|
121
|
+
function mapAsStore(m) {
|
|
122
|
+
return {
|
|
123
|
+
async get(key) {
|
|
124
|
+
return m.get(key);
|
|
125
|
+
},
|
|
126
|
+
async set(key, value) {
|
|
127
|
+
m.set(key, value);
|
|
128
|
+
},
|
|
129
|
+
async delete(key) {
|
|
130
|
+
m.delete(key);
|
|
131
|
+
},
|
|
132
|
+
async *entries() {
|
|
133
|
+
for (const e of m.entries()) yield e;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function isStore(value) {
|
|
138
|
+
if (value === null || typeof value !== "object") return false;
|
|
139
|
+
const v = value;
|
|
140
|
+
return typeof v.get === "function" && typeof v.set === "function" && typeof v.delete === "function" && typeof v.entries === "function";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/config.ts
|
|
119
144
|
var httpUrlSchema = zod.z.string().url().refine((url) => url.startsWith("http://") || url.startsWith("https://"), {
|
|
120
145
|
message: "must be an http(s) URL"
|
|
121
146
|
});
|
|
@@ -138,22 +163,40 @@ var coreSdkReadShallowSchema = zod.z.object({
|
|
|
138
163
|
var fulfillmentChannelShallowSchema = zod.z.object({
|
|
139
164
|
id: zod.z.string().min(1),
|
|
140
165
|
validate: zod.z.function(),
|
|
141
|
-
onCommit: zod.z.function()
|
|
166
|
+
onCommit: zod.z.function(),
|
|
167
|
+
// Optional delivery dispatch — present on real channels, omitted by
|
|
168
|
+
// hosts that deliver out-of-band.
|
|
169
|
+
onFulfill: zod.z.function().optional()
|
|
142
170
|
}).passthrough();
|
|
171
|
+
var asyncStoreSchema = () => zod.z.custom(isStore, {
|
|
172
|
+
message: "must implement the async Store<V> interface (get/set/delete/entries)"
|
|
173
|
+
});
|
|
143
174
|
var x402bServerConfigSchema = zod.z.object({
|
|
144
175
|
network: escrow.evmNetworkSchema,
|
|
145
176
|
chainId: zod.z.number().int().positive(),
|
|
146
177
|
escrow: escrow.addressSchema,
|
|
147
178
|
signer: sellerSignerSchema,
|
|
148
179
|
facilitator: zod.z.object({
|
|
149
|
-
url: httpUrlSchema
|
|
180
|
+
url: httpUrlSchema,
|
|
181
|
+
timeoutMs: zod.z.number().int().positive().optional(),
|
|
182
|
+
retry: zod.z.object({
|
|
183
|
+
attempts: zod.z.number().int().positive(),
|
|
184
|
+
backoffMs: zod.z.number().int().nonnegative()
|
|
185
|
+
}).strict().optional(),
|
|
186
|
+
idempotencyKey: zod.z.function().args().returns(zod.z.string()).optional()
|
|
150
187
|
}).strict(),
|
|
151
188
|
channelRegistry: x402Actions.channelRegistryZodSchema,
|
|
152
189
|
exchangeReader: exchangeReaderShallowSchema.optional(),
|
|
153
190
|
subgraphUrl: httpUrlSchema.optional(),
|
|
154
191
|
coreSdkRead: coreSdkReadShallowSchema.optional(),
|
|
155
|
-
exchangeFulfillmentOptionStore:
|
|
156
|
-
fulfillmentRecoveryStore:
|
|
192
|
+
exchangeFulfillmentOptionStore: asyncStoreSchema().optional(),
|
|
193
|
+
fulfillmentRecoveryStore: asyncStoreSchema().optional(),
|
|
194
|
+
logger: zod.z.object({
|
|
195
|
+
debug: zod.z.function(),
|
|
196
|
+
info: zod.z.function(),
|
|
197
|
+
warn: zod.z.function(),
|
|
198
|
+
error: zod.z.function()
|
|
199
|
+
}).passthrough().optional(),
|
|
157
200
|
fulfillmentChannels: zod.z.array(fulfillmentChannelShallowSchema).superRefine((channels, ctx) => {
|
|
158
201
|
const seen = /* @__PURE__ */ new Set();
|
|
159
202
|
const duplicates = /* @__PURE__ */ new Set();
|
|
@@ -167,7 +210,8 @@ var x402bServerConfigSchema = zod.z.object({
|
|
|
167
210
|
message: `fulfillmentChannels has duplicate id(s): ${[...duplicates].join(", ")}`
|
|
168
211
|
});
|
|
169
212
|
}
|
|
170
|
-
}).optional()
|
|
213
|
+
}).optional(),
|
|
214
|
+
mode: zod.z.enum(["development", "production"]).optional()
|
|
171
215
|
}).strict().superRefine((cfg, ctx) => {
|
|
172
216
|
const networkChainId = Number(cfg.network.split(":")[1]);
|
|
173
217
|
if (networkChainId !== cfg.chainId) {
|
|
@@ -177,6 +221,36 @@ var x402bServerConfigSchema = zod.z.object({
|
|
|
177
221
|
message: `chainId (${cfg.chainId}) must match network (${cfg.network})`
|
|
178
222
|
});
|
|
179
223
|
}
|
|
224
|
+
if (cfg.mode === "production") {
|
|
225
|
+
if (cfg.exchangeReader === void 0) {
|
|
226
|
+
ctx.addIssue({
|
|
227
|
+
code: zod.z.ZodIssueCode.custom,
|
|
228
|
+
path: ["exchangeReader"],
|
|
229
|
+
message: "required when mode is 'production' (post-settle state verification)"
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (cfg.coreSdkRead === void 0 && cfg.subgraphUrl === void 0) {
|
|
233
|
+
ctx.addIssue({
|
|
234
|
+
code: zod.z.ZodIssueCode.custom,
|
|
235
|
+
path: ["subgraphUrl"],
|
|
236
|
+
message: "one of `coreSdkRead` or `subgraphUrl` is required when mode is 'production' (read client for withdraw / available-funds)"
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (cfg.exchangeFulfillmentOptionStore === void 0) {
|
|
240
|
+
ctx.addIssue({
|
|
241
|
+
code: zod.z.ZodIssueCode.custom,
|
|
242
|
+
path: ["exchangeFulfillmentOptionStore"],
|
|
243
|
+
message: "required when mode is 'production'; without a persistent store the Flow A redeem-time option gate silently relaxes after a restart"
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (cfg.fulfillmentRecoveryStore === void 0) {
|
|
247
|
+
ctx.addIssue({
|
|
248
|
+
code: zod.z.ZodIssueCode.custom,
|
|
249
|
+
path: ["fulfillmentRecoveryStore"],
|
|
250
|
+
message: "required when mode is 'production'; without a persistent store, post-settle channel-onCommit failures lose their replay handle"
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
180
254
|
});
|
|
181
255
|
function assertChannelRegistryEscrowMatch(config) {
|
|
182
256
|
if (config.escrow.toLowerCase() !== config.channelRegistry.escrow.toLowerCase()) {
|
|
@@ -197,7 +271,22 @@ var FacilitatorHttpError = class extends Error {
|
|
|
197
271
|
}
|
|
198
272
|
};
|
|
199
273
|
|
|
274
|
+
// src/logger.ts
|
|
275
|
+
var noopLogger = {
|
|
276
|
+
debug: () => {
|
|
277
|
+
},
|
|
278
|
+
info: () => {
|
|
279
|
+
},
|
|
280
|
+
warn: () => {
|
|
281
|
+
},
|
|
282
|
+
error: () => {
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
200
286
|
// src/facilitator/client.ts
|
|
287
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
288
|
+
var DEFAULT_RETRY = { attempts: 3, backoffMs: 200 };
|
|
289
|
+
var IDEMPOTENCY_KEY_HEADER = "x-x402b-idempotency-key";
|
|
201
290
|
function createFacilitatorClient(opts) {
|
|
202
291
|
const fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
203
292
|
if (fetchImpl === void 0) {
|
|
@@ -207,19 +296,49 @@ function createFacilitatorClient(opts) {
|
|
|
207
296
|
}
|
|
208
297
|
const baseUrl = opts.url.replace(/\/+$/, "");
|
|
209
298
|
const baseHeaders = { "content-type": "application/json", ...opts.headers ?? {} };
|
|
210
|
-
const
|
|
299
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
300
|
+
const retry = opts.retry ?? DEFAULT_RETRY;
|
|
301
|
+
if (!Number.isInteger(retry.attempts) || retry.attempts < 1) {
|
|
302
|
+
throw new Error("createFacilitatorClient: retry.attempts must be an integer >= 1");
|
|
303
|
+
}
|
|
304
|
+
if (!Number.isFinite(retry.backoffMs) || retry.backoffMs < 0) {
|
|
305
|
+
throw new Error("createFacilitatorClient: retry.backoffMs must be a finite number >= 0");
|
|
306
|
+
}
|
|
307
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
308
|
+
throw new Error("createFacilitatorClient: timeoutMs must be a finite number > 0");
|
|
309
|
+
}
|
|
310
|
+
const newIdempotencyKey = opts.idempotencyKey ?? (() => globalThis.crypto.randomUUID());
|
|
311
|
+
const setTimeoutImpl = opts.setTimeout ?? setTimeout;
|
|
312
|
+
const clearTimeoutImpl = opts.clearTimeout ?? clearTimeout;
|
|
313
|
+
const logger = opts.logger ?? noopLogger;
|
|
314
|
+
const postOnce = async (path, body, validate, extraHeaders) => {
|
|
315
|
+
const controller = new AbortController();
|
|
316
|
+
const timer = setTimeoutImpl(() => controller.abort(), timeoutMs);
|
|
317
|
+
const headers = { ...baseHeaders, ...extraHeaders };
|
|
211
318
|
let res;
|
|
212
319
|
try {
|
|
213
320
|
res = await fetchImpl(`${baseUrl}${path}`, {
|
|
214
321
|
method: "POST",
|
|
215
|
-
headers
|
|
216
|
-
body: JSON.stringify(body)
|
|
322
|
+
headers,
|
|
323
|
+
body: JSON.stringify(body),
|
|
324
|
+
signal: controller.signal
|
|
217
325
|
});
|
|
218
326
|
} catch (cause) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
327
|
+
const aborted = controller.signal.aborted;
|
|
328
|
+
logger.warn(aborted ? "facilitator request timed out" : "facilitator network error", {
|
|
329
|
+
path,
|
|
330
|
+
timeoutMs: aborted ? timeoutMs : void 0,
|
|
331
|
+
error: cause instanceof Error ? cause.message : String(cause)
|
|
222
332
|
});
|
|
333
|
+
throw new FacilitatorHttpError(
|
|
334
|
+
aborted ? `facilitator request timed out after ${timeoutMs}ms (${path})` : `facilitator network error (${path})`,
|
|
335
|
+
{
|
|
336
|
+
code: aborted ? "TIMEOUT" : "NETWORK_ERROR",
|
|
337
|
+
cause
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
} finally {
|
|
341
|
+
clearTimeoutImpl(timer);
|
|
223
342
|
}
|
|
224
343
|
let text;
|
|
225
344
|
try {
|
|
@@ -246,6 +365,12 @@ function createFacilitatorClient(opts) {
|
|
|
246
365
|
return parsed;
|
|
247
366
|
}
|
|
248
367
|
const facilitatorCode = extractFacilitatorCode(parsed);
|
|
368
|
+
logger.warn("facilitator HTTP non-2xx", {
|
|
369
|
+
path,
|
|
370
|
+
status: res.status,
|
|
371
|
+
facilitatorCode,
|
|
372
|
+
reason: reasonString(parsed)
|
|
373
|
+
});
|
|
249
374
|
throw new FacilitatorHttpError(
|
|
250
375
|
`facilitator HTTP ${res.status} (${path}): ${reasonString(parsed) ?? text}`,
|
|
251
376
|
{
|
|
@@ -263,16 +388,76 @@ function createFacilitatorClient(opts) {
|
|
|
263
388
|
}
|
|
264
389
|
return parsed;
|
|
265
390
|
};
|
|
391
|
+
const post = async (path, body, validate, extraHeaders = {}) => {
|
|
392
|
+
let lastError;
|
|
393
|
+
for (let attempt = 0; attempt < retry.attempts; attempt += 1) {
|
|
394
|
+
if (attempt > 0) {
|
|
395
|
+
await sleep(retry.backoffMs * attempt, setTimeoutImpl);
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
return await postOnce(path, body, validate, extraHeaders);
|
|
399
|
+
} catch (e) {
|
|
400
|
+
if (e instanceof FacilitatorHttpError && isRetryable(e)) {
|
|
401
|
+
lastError = e;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
throw e;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
throw lastError;
|
|
408
|
+
};
|
|
266
409
|
return {
|
|
267
410
|
verify: (input) => post("/verify", input, isVerifyResult),
|
|
268
|
-
settle: (input) => post("/settle", input, isSettleResult
|
|
411
|
+
settle: (input) => post("/settle", input, isSettleResult, {
|
|
412
|
+
[IDEMPOTENCY_KEY_HEADER]: newIdempotencyKey()
|
|
413
|
+
}),
|
|
269
414
|
performAction: (input) => post(
|
|
270
415
|
`/perform-action?action=${encodeURIComponent(input.action)}`,
|
|
271
416
|
input,
|
|
272
417
|
isPerformActionResult
|
|
273
|
-
)
|
|
418
|
+
),
|
|
419
|
+
async healthCheck() {
|
|
420
|
+
const controller = new AbortController();
|
|
421
|
+
const timer = setTimeoutImpl(() => controller.abort(), timeoutMs);
|
|
422
|
+
let res;
|
|
423
|
+
try {
|
|
424
|
+
res = await fetchImpl(`${baseUrl}/healthz`, {
|
|
425
|
+
method: "GET",
|
|
426
|
+
headers: { ...opts.headers ?? {} },
|
|
427
|
+
signal: controller.signal
|
|
428
|
+
});
|
|
429
|
+
} catch (cause) {
|
|
430
|
+
const aborted = controller.signal.aborted;
|
|
431
|
+
throw new FacilitatorHttpError(
|
|
432
|
+
aborted ? `facilitator request timed out after ${timeoutMs}ms (/healthz)` : "facilitator network error (/healthz)",
|
|
433
|
+
{
|
|
434
|
+
code: aborted ? "TIMEOUT" : "NETWORK_ERROR",
|
|
435
|
+
cause
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
} finally {
|
|
439
|
+
clearTimeoutImpl(timer);
|
|
440
|
+
}
|
|
441
|
+
if (!res.ok) {
|
|
442
|
+
throw new FacilitatorHttpError(`facilitator HTTP ${res.status} (/healthz)`, {
|
|
443
|
+
code: "BAD_HTTP_STATUS",
|
|
444
|
+
status: res.status
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
274
448
|
};
|
|
275
449
|
}
|
|
450
|
+
function isRetryable(e) {
|
|
451
|
+
if (e.code === "NETWORK_ERROR" || e.code === "TIMEOUT") return true;
|
|
452
|
+
if (e.code === "BAD_HTTP_STATUS" && e.status !== void 0 && e.status >= 500) return true;
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
function sleep(ms, setTimeoutImpl) {
|
|
456
|
+
if (ms <= 0) return Promise.resolve();
|
|
457
|
+
return new Promise((resolve) => {
|
|
458
|
+
setTimeoutImpl(resolve, ms);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
276
461
|
function isObject(v) {
|
|
277
462
|
return v !== null && typeof v === "object";
|
|
278
463
|
}
|
|
@@ -368,6 +553,21 @@ function emitNextActions(input, registry, facilitatorUrl) {
|
|
|
368
553
|
};
|
|
369
554
|
}
|
|
370
555
|
|
|
556
|
+
// src/handlers/fulfillment-result.ts
|
|
557
|
+
function serializeFulfillmentResult(result) {
|
|
558
|
+
if (result.kind === "inline") {
|
|
559
|
+
return {
|
|
560
|
+
kind: "inline",
|
|
561
|
+
body: Buffer.from(result.body).toString("base64"),
|
|
562
|
+
contentType: result.contentType
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
return {
|
|
566
|
+
kind: "async",
|
|
567
|
+
...result.pointer !== void 0 ? { pointer: result.pointer } : {}
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
371
571
|
// src/handlers/types.ts
|
|
372
572
|
function handlerOk(body) {
|
|
373
573
|
return { ok: true, status: 200, body };
|
|
@@ -889,6 +1089,7 @@ async function handleCommitAndRedeem(input, ctx) {
|
|
|
889
1089
|
});
|
|
890
1090
|
}
|
|
891
1091
|
async function handleCommitImpl(input, ctx, expected) {
|
|
1092
|
+
const logger = ctx.logger ?? noopLogger;
|
|
892
1093
|
const decoded = decodeXPaymentHeader(input.paymentHeader);
|
|
893
1094
|
if (!decoded.ok) {
|
|
894
1095
|
const status = decoded.code === "MISSING_HEADER" ? 402 : 400;
|
|
@@ -977,28 +1178,38 @@ async function handleCommitImpl(input, ctx, expected) {
|
|
|
977
1178
|
);
|
|
978
1179
|
}
|
|
979
1180
|
if (expected.expectedState === x402Actions.ExchangeState.COMMITTED) {
|
|
980
|
-
ctx.exchangeFulfillmentOptionStore.set(
|
|
1181
|
+
await ctx.exchangeFulfillmentOptionStore.set(
|
|
981
1182
|
settleResult.exchangeId,
|
|
982
1183
|
input.requirements.fulfillment?.options.map((option) => option.id) ?? []
|
|
983
1184
|
);
|
|
984
1185
|
}
|
|
985
1186
|
const warnings = [];
|
|
1187
|
+
let delivery;
|
|
986
1188
|
if (expected.expectedState === x402Actions.ExchangeState.REDEEMED && decoded.payload.fulfillment !== void 0 && decoded.payload.fulfillment.data !== void 0) {
|
|
987
1189
|
const pending = {
|
|
988
1190
|
exchangeId: settleResult.exchangeId,
|
|
989
1191
|
option: decoded.payload.fulfillment.option,
|
|
990
1192
|
data: decoded.payload.fulfillment.data,
|
|
991
1193
|
redeemer: decoded.payload.payload.buyer,
|
|
992
|
-
recordedAt: Date.now()
|
|
1194
|
+
recordedAt: Date.now(),
|
|
1195
|
+
phase: "commit"
|
|
993
1196
|
};
|
|
994
|
-
ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, pending);
|
|
1197
|
+
await ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, pending);
|
|
1198
|
+
logger.debug("x402-server: fulfillment recovery entry recorded (Flow B)", {
|
|
1199
|
+
exchangeId: settleResult.exchangeId,
|
|
1200
|
+
option: decoded.payload.fulfillment.option
|
|
1201
|
+
});
|
|
995
1202
|
const channel = channelById.get(decoded.payload.fulfillment.option);
|
|
996
1203
|
if (channel === void 0) {
|
|
997
1204
|
const reason = "no channel adapter is registered";
|
|
998
|
-
ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
|
|
1205
|
+
await ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
|
|
999
1206
|
...pending,
|
|
1000
1207
|
error: reason
|
|
1001
1208
|
});
|
|
1209
|
+
logger.error("x402-server: Flow B channel adapter missing post-settle", {
|
|
1210
|
+
exchangeId: settleResult.exchangeId,
|
|
1211
|
+
option: decoded.payload.fulfillment.option
|
|
1212
|
+
});
|
|
1002
1213
|
warnings.push({
|
|
1003
1214
|
code: "FULFILLMENT_COMMIT_DEFERRED",
|
|
1004
1215
|
reason: "atomic redeem succeeded on-chain, but no channel adapter is registered",
|
|
@@ -1011,13 +1222,54 @@ async function handleCommitImpl(input, ctx, expected) {
|
|
|
1011
1222
|
} else {
|
|
1012
1223
|
try {
|
|
1013
1224
|
await channel.onCommit(settleResult.exchangeId, decoded.payload.fulfillment.data);
|
|
1014
|
-
|
|
1225
|
+
if (channel.onFulfill !== void 0) {
|
|
1226
|
+
const deliveryPending = {
|
|
1227
|
+
...pending,
|
|
1228
|
+
phase: "delivery",
|
|
1229
|
+
recordedAt: Date.now()
|
|
1230
|
+
};
|
|
1231
|
+
await ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, deliveryPending);
|
|
1232
|
+
try {
|
|
1233
|
+
delivery = serializeFulfillmentResult(await channel.onFulfill(settleResult.exchangeId));
|
|
1234
|
+
await ctx.fulfillmentRecoveryStore.delete(settleResult.exchangeId);
|
|
1235
|
+
logger.debug("x402-server: Flow B channel onCommit succeeded", {
|
|
1236
|
+
exchangeId: settleResult.exchangeId,
|
|
1237
|
+
option: decoded.payload.fulfillment.option
|
|
1238
|
+
});
|
|
1239
|
+
} catch (e) {
|
|
1240
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
1241
|
+
await ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
|
|
1242
|
+
...deliveryPending,
|
|
1243
|
+
error: reason
|
|
1244
|
+
});
|
|
1245
|
+
warnings.push({
|
|
1246
|
+
code: "FULFILLMENT_DELIVERY_DEFERRED",
|
|
1247
|
+
reason: "atomic redeem succeeded on-chain, but the channel's delivery dispatch failed",
|
|
1248
|
+
details: {
|
|
1249
|
+
exchangeId: settleResult.exchangeId,
|
|
1250
|
+
option: decoded.payload.fulfillment.option,
|
|
1251
|
+
error: reason
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
} else {
|
|
1256
|
+
await ctx.fulfillmentRecoveryStore.delete(settleResult.exchangeId);
|
|
1257
|
+
logger.debug("x402-server: Flow B channel onCommit succeeded", {
|
|
1258
|
+
exchangeId: settleResult.exchangeId,
|
|
1259
|
+
option: decoded.payload.fulfillment.option
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1015
1262
|
} catch (e) {
|
|
1016
1263
|
const reason = e instanceof Error ? e.message : String(e);
|
|
1017
|
-
ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
|
|
1264
|
+
await ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
|
|
1018
1265
|
...pending,
|
|
1019
1266
|
error: reason
|
|
1020
1267
|
});
|
|
1268
|
+
logger.warn("x402-server: Flow B channel onCommit failed; recovery entry retained", {
|
|
1269
|
+
exchangeId: settleResult.exchangeId,
|
|
1270
|
+
option: decoded.payload.fulfillment.option,
|
|
1271
|
+
error: reason
|
|
1272
|
+
});
|
|
1021
1273
|
warnings.push({
|
|
1022
1274
|
code: "FULFILLMENT_COMMIT_DEFERRED",
|
|
1023
1275
|
reason: "atomic redeem succeeded on-chain, but the channel adapter rejected the data",
|
|
@@ -1042,7 +1294,8 @@ async function handleCommitImpl(input, ctx, expected) {
|
|
|
1042
1294
|
exchangeId: settleResult.exchangeId,
|
|
1043
1295
|
txHash: settleResult.txHash,
|
|
1044
1296
|
nextActions,
|
|
1045
|
-
...warnings.length > 0 ? { warnings } : {}
|
|
1297
|
+
...warnings.length > 0 ? { warnings } : {},
|
|
1298
|
+
...delivery !== void 0 ? { fulfillment: delivery } : {}
|
|
1046
1299
|
});
|
|
1047
1300
|
}
|
|
1048
1301
|
function buildExpectedFromRequirements(requirements, state) {
|
|
@@ -1142,7 +1395,7 @@ async function handleRedeem(input, ctx) {
|
|
|
1142
1395
|
}
|
|
1143
1396
|
let resolvedChannel;
|
|
1144
1397
|
if (input.fulfillment !== void 0) {
|
|
1145
|
-
const advertisedOptions = ctx.exchangeFulfillmentOptionStore.get(input.exchangeId);
|
|
1398
|
+
const advertisedOptions = await ctx.exchangeFulfillmentOptionStore.get(input.exchangeId);
|
|
1146
1399
|
if (advertisedOptions !== void 0 && !advertisedOptions.includes(input.fulfillment.option)) {
|
|
1147
1400
|
return handlerErr(
|
|
1148
1401
|
400,
|
|
@@ -1185,23 +1438,40 @@ async function handleRedeem(input, ctx) {
|
|
|
1185
1438
|
}
|
|
1186
1439
|
const result = await handlePerformAction("boson-redeem", input, ctx);
|
|
1187
1440
|
if (!result.ok) return result;
|
|
1188
|
-
|
|
1441
|
+
const warnings = [];
|
|
1442
|
+
let delivery;
|
|
1189
1443
|
if (resolvedChannel !== void 0 && input.fulfillment !== void 0) {
|
|
1190
1444
|
const pending = {
|
|
1191
1445
|
exchangeId: input.exchangeId,
|
|
1192
1446
|
option: input.fulfillment.option,
|
|
1193
1447
|
data: input.fulfillment.data,
|
|
1194
1448
|
redeemer,
|
|
1195
|
-
recordedAt: Date.now()
|
|
1449
|
+
recordedAt: Date.now(),
|
|
1450
|
+
phase: "commit"
|
|
1196
1451
|
};
|
|
1197
|
-
ctx.
|
|
1452
|
+
const logger = ctx.logger ?? noopLogger;
|
|
1453
|
+
await ctx.fulfillmentRecoveryStore.set(input.exchangeId, pending);
|
|
1454
|
+
logger.debug("x402-server: fulfillment recovery entry recorded (Flow A redeem)", {
|
|
1455
|
+
exchangeId: input.exchangeId,
|
|
1456
|
+
option: input.fulfillment.option
|
|
1457
|
+
});
|
|
1458
|
+
let onCommitOk = false;
|
|
1198
1459
|
try {
|
|
1199
1460
|
await resolvedChannel.onCommit(input.exchangeId, input.fulfillment.data);
|
|
1200
|
-
|
|
1461
|
+
onCommitOk = true;
|
|
1462
|
+
logger.debug("x402-server: Flow A channel onCommit succeeded", {
|
|
1463
|
+
exchangeId: input.exchangeId,
|
|
1464
|
+
option: input.fulfillment.option
|
|
1465
|
+
});
|
|
1201
1466
|
} catch (e) {
|
|
1202
1467
|
const reason = errorMessage(e);
|
|
1203
|
-
ctx.fulfillmentRecoveryStore.set(input.exchangeId, { ...pending, error: reason });
|
|
1204
|
-
|
|
1468
|
+
await ctx.fulfillmentRecoveryStore.set(input.exchangeId, { ...pending, error: reason });
|
|
1469
|
+
logger.warn("x402-server: Flow A channel onCommit failed; recovery entry retained", {
|
|
1470
|
+
exchangeId: input.exchangeId,
|
|
1471
|
+
option: input.fulfillment.option,
|
|
1472
|
+
error: reason
|
|
1473
|
+
});
|
|
1474
|
+
warnings.push({
|
|
1205
1475
|
code: "FULFILLMENT_UPDATE_DEFERRED",
|
|
1206
1476
|
reason: "redeem succeeded on-chain, but the server could not persist the fulfillment update",
|
|
1207
1477
|
details: {
|
|
@@ -1209,16 +1479,48 @@ async function handleRedeem(input, ctx) {
|
|
|
1209
1479
|
option: input.fulfillment.option,
|
|
1210
1480
|
error: reason
|
|
1211
1481
|
}
|
|
1212
|
-
};
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
if (onCommitOk) {
|
|
1485
|
+
if (resolvedChannel.onFulfill !== void 0) {
|
|
1486
|
+
const deliveryPending = {
|
|
1487
|
+
...pending,
|
|
1488
|
+
phase: "delivery",
|
|
1489
|
+
recordedAt: Date.now()
|
|
1490
|
+
};
|
|
1491
|
+
await ctx.fulfillmentRecoveryStore.set(input.exchangeId, deliveryPending);
|
|
1492
|
+
try {
|
|
1493
|
+
delivery = serializeFulfillmentResult(await resolvedChannel.onFulfill(input.exchangeId));
|
|
1494
|
+
await ctx.fulfillmentRecoveryStore.delete(input.exchangeId);
|
|
1495
|
+
} catch (e) {
|
|
1496
|
+
const reason = errorMessage(e);
|
|
1497
|
+
await ctx.fulfillmentRecoveryStore.set(input.exchangeId, {
|
|
1498
|
+
...deliveryPending,
|
|
1499
|
+
error: reason
|
|
1500
|
+
});
|
|
1501
|
+
warnings.push({
|
|
1502
|
+
code: "FULFILLMENT_DELIVERY_DEFERRED",
|
|
1503
|
+
reason: "redeem succeeded on-chain, but the fulfillment channel's delivery dispatch failed",
|
|
1504
|
+
details: {
|
|
1505
|
+
exchangeId: input.exchangeId,
|
|
1506
|
+
option: input.fulfillment.option,
|
|
1507
|
+
error: reason
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
} else {
|
|
1512
|
+
await ctx.fulfillmentRecoveryStore.delete(input.exchangeId);
|
|
1513
|
+
}
|
|
1213
1514
|
}
|
|
1214
1515
|
}
|
|
1215
|
-
ctx.exchangeFulfillmentOptionStore.delete(input.exchangeId);
|
|
1216
|
-
if (
|
|
1516
|
+
await ctx.exchangeFulfillmentOptionStore.delete(input.exchangeId);
|
|
1517
|
+
if (warnings.length > 0 || delivery !== void 0) {
|
|
1217
1518
|
return {
|
|
1218
1519
|
...result,
|
|
1219
1520
|
body: {
|
|
1220
1521
|
...result.body,
|
|
1221
|
-
|
|
1522
|
+
...delivery !== void 0 ? { fulfillment: delivery } : {},
|
|
1523
|
+
...warnings.length > 0 ? { warnings: [...result.body.warnings ?? [], ...warnings] } : {}
|
|
1222
1524
|
}
|
|
1223
1525
|
};
|
|
1224
1526
|
}
|
|
@@ -1461,6 +1763,52 @@ function asCoreSdkReadAdapter(coreSdk) {
|
|
|
1461
1763
|
return coreSdk;
|
|
1462
1764
|
}
|
|
1463
1765
|
|
|
1766
|
+
// src/concurrency.ts
|
|
1767
|
+
function createKeyedMutex() {
|
|
1768
|
+
const chains = /* @__PURE__ */ new Map();
|
|
1769
|
+
return {
|
|
1770
|
+
runExclusive(key, fn) {
|
|
1771
|
+
const prev = chains.get(key) ?? Promise.resolve();
|
|
1772
|
+
const run = prev.then(fn, fn);
|
|
1773
|
+
const tracked = run.catch(() => void 0);
|
|
1774
|
+
chains.set(key, tracked);
|
|
1775
|
+
tracked.then(() => {
|
|
1776
|
+
if (chains.get(key) === tracked) chains.delete(key);
|
|
1777
|
+
});
|
|
1778
|
+
return run;
|
|
1779
|
+
}
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// src/health.ts
|
|
1784
|
+
function createHealthCheck(deps) {
|
|
1785
|
+
return async () => {
|
|
1786
|
+
const facilitator = await probe(() => deps.facilitator.healthCheck()) ? "ok" : "down";
|
|
1787
|
+
let readClient;
|
|
1788
|
+
let readClientFailed = false;
|
|
1789
|
+
if (typeof deps.coreSdkRead === "function") {
|
|
1790
|
+
try {
|
|
1791
|
+
readClient = deps.coreSdkRead();
|
|
1792
|
+
} catch {
|
|
1793
|
+
readClientFailed = true;
|
|
1794
|
+
}
|
|
1795
|
+
} else {
|
|
1796
|
+
readClient = deps.coreSdkRead;
|
|
1797
|
+
}
|
|
1798
|
+
const subgraph = readClientFailed ? "down" : readClient === void 0 ? "n/a" : await probe(() => readClient.getSellersByAddress(ZERO_ADDRESS_PROBE)) ? "ok" : "down";
|
|
1799
|
+
return { facilitator, subgraph };
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
async function probe(fn) {
|
|
1803
|
+
try {
|
|
1804
|
+
await fn();
|
|
1805
|
+
return true;
|
|
1806
|
+
} catch {
|
|
1807
|
+
return false;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
var ZERO_ADDRESS_PROBE = "0x0000000000000000000000000000000000000000";
|
|
1811
|
+
|
|
1464
1812
|
// src/server.ts
|
|
1465
1813
|
function withFacilitatorEndpoints(requirements, facilitatorUrl) {
|
|
1466
1814
|
return {
|
|
@@ -1474,9 +1822,23 @@ function withFacilitatorEndpoints(requirements, facilitatorUrl) {
|
|
|
1474
1822
|
function createX402bServer(config) {
|
|
1475
1823
|
const validated = x402bServerConfigSchema.parse(config);
|
|
1476
1824
|
assertChannelRegistryEscrowMatch(validated);
|
|
1477
|
-
const
|
|
1478
|
-
|
|
1479
|
-
|
|
1825
|
+
const logger = validated.logger ?? noopLogger;
|
|
1826
|
+
logger.info("x402-server: createX402bServer", {
|
|
1827
|
+
network: validated.network,
|
|
1828
|
+
chainId: validated.chainId,
|
|
1829
|
+
escrow: validated.escrow,
|
|
1830
|
+
facilitatorUrl: validated.facilitator.url
|
|
1831
|
+
});
|
|
1832
|
+
const facilitator = createFacilitatorClient({
|
|
1833
|
+
url: validated.facilitator.url,
|
|
1834
|
+
logger,
|
|
1835
|
+
...validated.facilitator.timeoutMs !== void 0 ? { timeoutMs: validated.facilitator.timeoutMs } : {},
|
|
1836
|
+
...validated.facilitator.retry !== void 0 ? { retry: validated.facilitator.retry } : {},
|
|
1837
|
+
...validated.facilitator.idempotencyKey !== void 0 ? { idempotencyKey: validated.facilitator.idempotencyKey } : {}
|
|
1838
|
+
});
|
|
1839
|
+
const exchangeMutex = createKeyedMutex();
|
|
1840
|
+
const exchangeFulfillmentOptionStore = validated.exchangeFulfillmentOptionStore ?? mapAsStore(/* @__PURE__ */ new Map());
|
|
1841
|
+
const fulfillmentRecoveryStore = validated.fulfillmentRecoveryStore ?? mapAsStore(/* @__PURE__ */ new Map());
|
|
1480
1842
|
validated.exchangeFulfillmentOptionStore = exchangeFulfillmentOptionStore;
|
|
1481
1843
|
validated.fulfillmentRecoveryStore = fulfillmentRecoveryStore;
|
|
1482
1844
|
const signOffer = (unsigned) => signFullOffer({
|
|
@@ -1494,14 +1856,10 @@ function createX402bServer(config) {
|
|
|
1494
1856
|
return validated.exchangeReader;
|
|
1495
1857
|
};
|
|
1496
1858
|
let cachedCoreSdkRead;
|
|
1497
|
-
const
|
|
1859
|
+
const getOrCreateCoreSdkRead = () => {
|
|
1498
1860
|
if (validated.coreSdkRead !== void 0) return validated.coreSdkRead;
|
|
1499
1861
|
if (cachedCoreSdkRead !== void 0) return cachedCoreSdkRead;
|
|
1500
|
-
if (validated.subgraphUrl === void 0)
|
|
1501
|
-
throw new Error(
|
|
1502
|
-
`x402-server: handlers.${action}() requires either \`coreSdkRead\` or \`subgraphUrl\` in config (subgraph read step).`
|
|
1503
|
-
);
|
|
1504
|
-
}
|
|
1862
|
+
if (validated.subgraphUrl === void 0) return void 0;
|
|
1505
1863
|
cachedCoreSdkRead = asCoreSdkReadAdapter(
|
|
1506
1864
|
new coreSdk.CoreSDK({
|
|
1507
1865
|
web3Lib: createReadOnlyWeb3LibStub(),
|
|
@@ -1512,10 +1870,106 @@ function createX402bServer(config) {
|
|
|
1512
1870
|
);
|
|
1513
1871
|
return cachedCoreSdkRead;
|
|
1514
1872
|
};
|
|
1873
|
+
const requireCoreSdkRead = (action) => {
|
|
1874
|
+
const client = getOrCreateCoreSdkRead();
|
|
1875
|
+
if (client === void 0) {
|
|
1876
|
+
throw new Error(
|
|
1877
|
+
`x402-server: handlers.${action}() requires either \`coreSdkRead\` or \`subgraphUrl\` in config (subgraph read step).`
|
|
1878
|
+
);
|
|
1879
|
+
}
|
|
1880
|
+
return client;
|
|
1881
|
+
};
|
|
1882
|
+
const recovery = {
|
|
1883
|
+
async list() {
|
|
1884
|
+
const entries = [];
|
|
1885
|
+
for await (const [, value] of fulfillmentRecoveryStore.entries()) {
|
|
1886
|
+
entries.push(value);
|
|
1887
|
+
}
|
|
1888
|
+
return entries;
|
|
1889
|
+
},
|
|
1890
|
+
async replay(exchangeId) {
|
|
1891
|
+
const entry = await fulfillmentRecoveryStore.get(exchangeId);
|
|
1892
|
+
if (entry === void 0) {
|
|
1893
|
+
return { ok: false, reason: `no pending recovery entry for exchangeId '${exchangeId}'` };
|
|
1894
|
+
}
|
|
1895
|
+
const channels = validated.fulfillmentChannels ?? [];
|
|
1896
|
+
const channel = channels.find((c) => c.id === entry.option);
|
|
1897
|
+
if (channel === void 0) {
|
|
1898
|
+
const reason = `no channel adapter is registered for option '${entry.option}'`;
|
|
1899
|
+
await fulfillmentRecoveryStore.set(exchangeId, { ...entry, error: reason });
|
|
1900
|
+
logger.warn("x402-server: recovery replay failed (no adapter)", {
|
|
1901
|
+
exchangeId,
|
|
1902
|
+
option: entry.option
|
|
1903
|
+
});
|
|
1904
|
+
return { ok: false, reason };
|
|
1905
|
+
}
|
|
1906
|
+
if (entry.phase === "delivery") {
|
|
1907
|
+
if (channel.onFulfill === void 0) {
|
|
1908
|
+
const reason = `channel '${entry.option}' has no onFulfill; cannot replay delivery phase`;
|
|
1909
|
+
await fulfillmentRecoveryStore.set(exchangeId, { ...entry, error: reason });
|
|
1910
|
+
logger.warn("x402-server: recovery replay failed (no onFulfill)", {
|
|
1911
|
+
exchangeId,
|
|
1912
|
+
option: entry.option
|
|
1913
|
+
});
|
|
1914
|
+
return { ok: false, reason };
|
|
1915
|
+
}
|
|
1916
|
+
try {
|
|
1917
|
+
await channel.onFulfill(exchangeId);
|
|
1918
|
+
await fulfillmentRecoveryStore.delete(exchangeId);
|
|
1919
|
+
logger.info("x402-server: recovery replay succeeded", {
|
|
1920
|
+
exchangeId,
|
|
1921
|
+
option: entry.option,
|
|
1922
|
+
phase: entry.phase
|
|
1923
|
+
});
|
|
1924
|
+
return { ok: true };
|
|
1925
|
+
} catch (e) {
|
|
1926
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
1927
|
+
await fulfillmentRecoveryStore.set(exchangeId, { ...entry, error: reason });
|
|
1928
|
+
logger.warn("x402-server: recovery replay failed (channel error)", {
|
|
1929
|
+
exchangeId,
|
|
1930
|
+
option: entry.option,
|
|
1931
|
+
phase: entry.phase,
|
|
1932
|
+
error: reason
|
|
1933
|
+
});
|
|
1934
|
+
return { ok: false, reason };
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
try {
|
|
1938
|
+
await channel.onCommit(exchangeId, entry.data);
|
|
1939
|
+
await fulfillmentRecoveryStore.delete(exchangeId);
|
|
1940
|
+
logger.info("x402-server: recovery replay succeeded", {
|
|
1941
|
+
exchangeId,
|
|
1942
|
+
option: entry.option,
|
|
1943
|
+
phase: entry.phase
|
|
1944
|
+
});
|
|
1945
|
+
return { ok: true };
|
|
1946
|
+
} catch (e) {
|
|
1947
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
1948
|
+
await fulfillmentRecoveryStore.set(exchangeId, { ...entry, error: reason });
|
|
1949
|
+
logger.warn("x402-server: recovery replay failed (channel error)", {
|
|
1950
|
+
exchangeId,
|
|
1951
|
+
option: entry.option,
|
|
1952
|
+
phase: entry.phase,
|
|
1953
|
+
error: reason
|
|
1954
|
+
});
|
|
1955
|
+
return { ok: false, reason };
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
};
|
|
1959
|
+
const healthCheck = createHealthCheck({
|
|
1960
|
+
facilitator,
|
|
1961
|
+
// Materialise the lazy `subgraphUrl`-backed read client on first
|
|
1962
|
+
// probe so a configured subgraph is actually checked (reporting
|
|
1963
|
+
// `"ok"` / `"down"` rather than `"n/a"`). Only the
|
|
1964
|
+
// "no `coreSdkRead` AND no `subgraphUrl`" case maps to `"n/a"`.
|
|
1965
|
+
coreSdkRead: () => getOrCreateCoreSdkRead()
|
|
1966
|
+
});
|
|
1515
1967
|
return {
|
|
1516
1968
|
config: validated,
|
|
1517
1969
|
facilitator,
|
|
1518
1970
|
signOffer,
|
|
1971
|
+
recovery,
|
|
1972
|
+
healthCheck,
|
|
1519
1973
|
async buildPaymentRequirements(input) {
|
|
1520
1974
|
const offer = "unsigned" in input.offer ? await signOffer(input.offer.unsigned) : input.offer;
|
|
1521
1975
|
const requirements = buildPaymentRequirements({
|
|
@@ -1538,47 +1992,73 @@ function createX402bServer(config) {
|
|
|
1538
1992
|
facilitator,
|
|
1539
1993
|
exchangeReader: await requireReader("commit"),
|
|
1540
1994
|
fulfillmentRecoveryStore,
|
|
1541
|
-
exchangeFulfillmentOptionStore
|
|
1995
|
+
exchangeFulfillmentOptionStore,
|
|
1996
|
+
logger
|
|
1542
1997
|
}),
|
|
1543
1998
|
commitAndRedeem: async (input) => handleCommitAndRedeem(input, {
|
|
1544
1999
|
config: validated,
|
|
1545
2000
|
facilitator,
|
|
1546
2001
|
exchangeReader: await requireReader("commitAndRedeem"),
|
|
1547
2002
|
fulfillmentRecoveryStore,
|
|
1548
|
-
exchangeFulfillmentOptionStore
|
|
1549
|
-
}),
|
|
1550
|
-
redeem: async (input) => handleRedeem(input, {
|
|
1551
|
-
config: validated,
|
|
1552
|
-
facilitator,
|
|
1553
|
-
exchangeReader: await requireReader("redeem"),
|
|
1554
2003
|
exchangeFulfillmentOptionStore,
|
|
1555
|
-
|
|
1556
|
-
}),
|
|
1557
|
-
complete: async (input) => handleComplete(input, {
|
|
1558
|
-
config: validated,
|
|
1559
|
-
facilitator,
|
|
1560
|
-
exchangeReader: await requireReader("complete")
|
|
1561
|
-
}),
|
|
1562
|
-
disputeRaise: async (input) => handleDisputeRaise(input, {
|
|
1563
|
-
config: validated,
|
|
1564
|
-
facilitator,
|
|
1565
|
-
exchangeReader: await requireReader("disputeRaise")
|
|
1566
|
-
}),
|
|
1567
|
-
disputeResolve: async (input) => handleDisputeResolve(input, {
|
|
1568
|
-
config: validated,
|
|
1569
|
-
facilitator,
|
|
1570
|
-
exchangeReader: await requireReader("disputeResolve")
|
|
1571
|
-
}),
|
|
1572
|
-
disputeRetract: async (input) => handleDisputeRetract(input, {
|
|
1573
|
-
config: validated,
|
|
1574
|
-
facilitator,
|
|
1575
|
-
exchangeReader: await requireReader("disputeRetract")
|
|
1576
|
-
}),
|
|
1577
|
-
disputeEscalate: async (input) => handleDisputeEscalate(input, {
|
|
1578
|
-
config: validated,
|
|
1579
|
-
facilitator,
|
|
1580
|
-
exchangeReader: await requireReader("disputeEscalate")
|
|
2004
|
+
logger
|
|
1581
2005
|
}),
|
|
2006
|
+
redeem: async (input) => exchangeMutex.runExclusive(
|
|
2007
|
+
input.exchangeId,
|
|
2008
|
+
async () => handleRedeem(input, {
|
|
2009
|
+
config: validated,
|
|
2010
|
+
facilitator,
|
|
2011
|
+
exchangeReader: await requireReader("redeem"),
|
|
2012
|
+
exchangeFulfillmentOptionStore,
|
|
2013
|
+
fulfillmentRecoveryStore,
|
|
2014
|
+
logger
|
|
2015
|
+
})
|
|
2016
|
+
),
|
|
2017
|
+
complete: async (input) => exchangeMutex.runExclusive(
|
|
2018
|
+
input.exchangeId,
|
|
2019
|
+
async () => handleComplete(input, {
|
|
2020
|
+
config: validated,
|
|
2021
|
+
facilitator,
|
|
2022
|
+
exchangeReader: await requireReader("complete"),
|
|
2023
|
+
logger
|
|
2024
|
+
})
|
|
2025
|
+
),
|
|
2026
|
+
disputeRaise: async (input) => exchangeMutex.runExclusive(
|
|
2027
|
+
input.exchangeId,
|
|
2028
|
+
async () => handleDisputeRaise(input, {
|
|
2029
|
+
config: validated,
|
|
2030
|
+
facilitator,
|
|
2031
|
+
exchangeReader: await requireReader("disputeRaise"),
|
|
2032
|
+
logger
|
|
2033
|
+
})
|
|
2034
|
+
),
|
|
2035
|
+
disputeResolve: async (input) => exchangeMutex.runExclusive(
|
|
2036
|
+
input.exchangeId,
|
|
2037
|
+
async () => handleDisputeResolve(input, {
|
|
2038
|
+
config: validated,
|
|
2039
|
+
facilitator,
|
|
2040
|
+
exchangeReader: await requireReader("disputeResolve"),
|
|
2041
|
+
logger
|
|
2042
|
+
})
|
|
2043
|
+
),
|
|
2044
|
+
disputeRetract: async (input) => exchangeMutex.runExclusive(
|
|
2045
|
+
input.exchangeId,
|
|
2046
|
+
async () => handleDisputeRetract(input, {
|
|
2047
|
+
config: validated,
|
|
2048
|
+
facilitator,
|
|
2049
|
+
exchangeReader: await requireReader("disputeRetract"),
|
|
2050
|
+
logger
|
|
2051
|
+
})
|
|
2052
|
+
),
|
|
2053
|
+
disputeEscalate: async (input) => exchangeMutex.runExclusive(
|
|
2054
|
+
input.exchangeId,
|
|
2055
|
+
async () => handleDisputeEscalate(input, {
|
|
2056
|
+
config: validated,
|
|
2057
|
+
facilitator,
|
|
2058
|
+
exchangeReader: await requireReader("disputeEscalate"),
|
|
2059
|
+
logger
|
|
2060
|
+
})
|
|
2061
|
+
),
|
|
1582
2062
|
withdrawFunds: async (input) => handleWithdrawFunds(input, {
|
|
1583
2063
|
config: validated,
|
|
1584
2064
|
facilitator,
|
|
@@ -1610,11 +2090,14 @@ exports.ADDRESS_RE = ADDRESS_RE;
|
|
|
1610
2090
|
exports.DECIMAL_UINT_RE = DECIMAL_UINT_RE;
|
|
1611
2091
|
exports.FacilitatorHttpError = FacilitatorHttpError;
|
|
1612
2092
|
exports.HEX_BYTES_RE = HEX_BYTES_RE;
|
|
2093
|
+
exports.IDEMPOTENCY_KEY_HEADER = IDEMPOTENCY_KEY_HEADER;
|
|
1613
2094
|
exports.X_PAYMENT_RESPONSE_HEADER = X_PAYMENT_RESPONSE_HEADER;
|
|
1614
2095
|
exports.asCoreSdkReadAdapter = asCoreSdkReadAdapter;
|
|
1615
2096
|
exports.assertChannelRegistryEscrowMatch = assertChannelRegistryEscrowMatch;
|
|
1616
2097
|
exports.buildPaymentRequirements = buildPaymentRequirements;
|
|
1617
2098
|
exports.createFacilitatorClient = createFacilitatorClient;
|
|
2099
|
+
exports.createHealthCheck = createHealthCheck;
|
|
2100
|
+
exports.createKeyedMutex = createKeyedMutex;
|
|
1618
2101
|
exports.createX402bServer = createX402bServer;
|
|
1619
2102
|
exports.decodeXPaymentHeader = decodeXPaymentHeader;
|
|
1620
2103
|
exports.emitNextActions = emitNextActions;
|
|
@@ -1632,6 +2115,9 @@ exports.handleRedeem = handleRedeem;
|
|
|
1632
2115
|
exports.handleWithdrawFunds = handleWithdrawFunds;
|
|
1633
2116
|
exports.handlerErr = handlerErr;
|
|
1634
2117
|
exports.handlerOk = handlerOk;
|
|
2118
|
+
exports.isStore = isStore;
|
|
2119
|
+
exports.mapAsStore = mapAsStore;
|
|
2120
|
+
exports.noopLogger = noopLogger;
|
|
1635
2121
|
exports.plainHandlerOk = plainHandlerOk;
|
|
1636
2122
|
exports.resolveEntityId = resolveEntityId;
|
|
1637
2123
|
exports.signFullOffer = signFullOffer;
|