@parallel-protocol/x402 0.7.1 → 0.8.1
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/README.md +158 -58
- package/dist/{chunk-AENXD4OC.js → chunk-GXPUE3ES.js} +233 -77
- package/dist/{chunk-JPMK5YBU.cjs → chunk-KZZY5LS6.cjs} +13 -2
- package/dist/{chunk-AFRDHGDT.js → chunk-QAECRSWF.js} +13 -2
- package/dist/{chunk-POU7EH3E.cjs → chunk-SNW5Z5HB.cjs} +255 -97
- package/dist/express.cjs +6 -6
- package/dist/express.d.cts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/express.js +2 -2
- package/dist/fastify.cjs +20 -29
- package/dist/fastify.d.cts +1 -1
- package/dist/fastify.d.ts +1 -1
- package/dist/fastify.js +13 -22
- package/dist/hono.cjs +7 -7
- package/dist/hono.d.cts +1 -1
- package/dist/hono.d.ts +1 -1
- package/dist/hono.js +2 -2
- package/dist/index.cjs +27 -19
- package/dist/index.d.cts +56 -7
- package/dist/index.d.ts +56 -7
- package/dist/index.js +2 -2
- package/dist/llm.cjs +13 -13
- package/dist/llm.d.cts +1 -1
- package/dist/llm.d.ts +1 -1
- package/dist/llm.js +1 -1
- package/dist/next.cjs +28 -44
- package/dist/next.d.cts +1 -1
- package/dist/next.d.ts +1 -1
- package/dist/next.js +20 -36
- package/dist/{types-q_gu5wjz.d.cts → types-BkGQ2lPA.d.cts} +30 -1
- package/dist/{types-q_gu5wjz.d.ts → types-BkGQ2lPA.d.ts} +30 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkKZZY5LS6_cjs = require('./chunk-KZZY5LS6.cjs');
|
|
4
4
|
var chains = require('@parallel-protocol/chains');
|
|
5
5
|
var zod = require('zod');
|
|
6
6
|
|
|
@@ -129,8 +129,18 @@ var priceSchema = zod.z.union([
|
|
|
129
129
|
"Price must be a string, a bigint or a function of the request"
|
|
130
130
|
)
|
|
131
131
|
]);
|
|
132
|
+
var LOCAL_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
133
|
+
function isFacilitatorUrl(value) {
|
|
134
|
+
if (!URL.canParse(value)) return false;
|
|
135
|
+
const { protocol, hostname } = new URL(value);
|
|
136
|
+
return protocol === "https:" || protocol === "http:" && LOCAL_HOSTNAMES.has(hostname);
|
|
137
|
+
}
|
|
138
|
+
var facilitatorUrlSchema = zod.z.string().refine(
|
|
139
|
+
isFacilitatorUrl,
|
|
140
|
+
"The facilitator URL must use https (plain http only on localhost, 127.0.0.1 or [::1])"
|
|
141
|
+
);
|
|
132
142
|
var facilitatorConfigSchema = zod.z.object({
|
|
133
|
-
url:
|
|
143
|
+
url: facilitatorUrlSchema,
|
|
134
144
|
apiKey: zod.z.string().optional()
|
|
135
145
|
});
|
|
136
146
|
var UPTO_MAX_TIMEOUT_SECONDS = 3600;
|
|
@@ -172,26 +182,32 @@ var facilitatorResponseSchema = zod.z.discriminatedUnion("success", [
|
|
|
172
182
|
chain: zod.z.string(),
|
|
173
183
|
gasSponsored: zod.z.boolean(),
|
|
174
184
|
networkId: zod.z.string(),
|
|
175
|
-
amountSettled: zod.z.string().optional()
|
|
185
|
+
amountSettled: zod.z.string().optional(),
|
|
186
|
+
alreadySettled: zod.z.boolean().optional()
|
|
176
187
|
}),
|
|
177
188
|
zod.z.object({
|
|
178
189
|
success: zod.z.literal(false),
|
|
179
190
|
error: zod.z.object({
|
|
180
191
|
code: zod.z.string(),
|
|
181
192
|
message: zod.z.string(),
|
|
182
|
-
hint: zod.z.string().optional()
|
|
193
|
+
hint: zod.z.string().optional(),
|
|
194
|
+
status: zod.z.number().optional(),
|
|
195
|
+
retryAfter: zod.z.string().optional()
|
|
183
196
|
})
|
|
184
197
|
})
|
|
185
198
|
]);
|
|
186
199
|
var verifyResponseSchema = zod.z.object({
|
|
187
|
-
isValid: zod.z.literal(true)
|
|
200
|
+
isValid: zod.z.literal(true),
|
|
201
|
+
settleToken: zod.z.string().min(1)
|
|
188
202
|
});
|
|
189
203
|
var settleSuccessSchema = zod.z.object({
|
|
190
204
|
txHash: txHashSchema,
|
|
191
205
|
route: zod.z.string(),
|
|
192
206
|
gasSponsored: zod.z.boolean(),
|
|
193
207
|
// Metered routes: the amount actually charged (smallest unit).
|
|
194
|
-
amountSettled: zod.z.string().optional()
|
|
208
|
+
amountSettled: zod.z.string().optional(),
|
|
209
|
+
// The facilitator answered from a recorded outcome, nothing was broadcast.
|
|
210
|
+
alreadySettled: zod.z.boolean().optional()
|
|
195
211
|
});
|
|
196
212
|
var settleErrorSchema = zod.z.object({
|
|
197
213
|
error: zod.z.string(),
|
|
@@ -205,71 +221,133 @@ var settleResponseSchema = zod.z.union([
|
|
|
205
221
|
// src/client.ts
|
|
206
222
|
var FACILITATOR_TIMEOUT_MS = 1e4;
|
|
207
223
|
var SETTLE_TIMEOUT_MS = 6e4;
|
|
224
|
+
var RETRY_AFTER_SHAPE = /^(?:\d{1,10}|[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT)$/;
|
|
225
|
+
async function readErrorBody(response) {
|
|
226
|
+
const json = await response.json().catch(() => null);
|
|
227
|
+
if (typeof json !== "object" || json === null) return {};
|
|
228
|
+
const { error, message } = json;
|
|
229
|
+
return {
|
|
230
|
+
...typeof error === "string" && { error },
|
|
231
|
+
...typeof message === "string" && { message }
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function retryAfterOf(response) {
|
|
235
|
+
const value = response.headers.get("retry-after");
|
|
236
|
+
return value !== null && RETRY_AFTER_SHAPE.test(value) ? value : void 0;
|
|
237
|
+
}
|
|
238
|
+
function throwIfRedirect(response) {
|
|
239
|
+
const redirected = response.type === "opaqueredirect" || response.status >= 300 && response.status < 400;
|
|
240
|
+
if (!redirected) return;
|
|
241
|
+
response.body?.cancel().catch(() => {
|
|
242
|
+
});
|
|
243
|
+
throw new chunkKZZY5LS6_cjs.X402RuntimeError(chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE, {
|
|
244
|
+
status: response.status
|
|
245
|
+
});
|
|
246
|
+
}
|
|
208
247
|
function decodePaymentHeader(paymentHeader) {
|
|
209
248
|
try {
|
|
210
249
|
return decodeBase64(paymentHeader);
|
|
211
250
|
} catch {
|
|
212
|
-
throw new
|
|
251
|
+
throw new chunkKZZY5LS6_cjs.X402RuntimeError(chunkKZZY5LS6_cjs.X402_ERROR_CODES.INVALID_PAYMENT);
|
|
213
252
|
}
|
|
214
253
|
}
|
|
215
254
|
var FacilitatorClient = class {
|
|
255
|
+
// Checked and kept when the client is built: a config changed afterwards
|
|
256
|
+
// does not move the calls elsewhere.
|
|
257
|
+
url;
|
|
258
|
+
apiKey;
|
|
216
259
|
constructor(config) {
|
|
217
|
-
|
|
260
|
+
const url = facilitatorUrlSchema.safeParse(config.url);
|
|
261
|
+
if (!url.success) {
|
|
262
|
+
throw new chunkKZZY5LS6_cjs.X402ConfigError(
|
|
263
|
+
url.error.issues[0]?.message ?? "Invalid facilitator URL",
|
|
264
|
+
url.error
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
this.url = new URL(url.data).href.replace(/\/+$/, "");
|
|
268
|
+
this.apiKey = config.apiKey;
|
|
218
269
|
}
|
|
219
|
-
config;
|
|
220
270
|
async verify(paymentHeader, paymentRequirements) {
|
|
221
271
|
const paymentPayload = decodePaymentHeader(paymentHeader);
|
|
222
272
|
let json;
|
|
223
273
|
try {
|
|
224
|
-
const response = await fetch(`${this.
|
|
274
|
+
const response = await fetch(`${this.url}/x402/verify`, {
|
|
225
275
|
method: "POST",
|
|
226
276
|
headers: this.buildHeaders(),
|
|
227
277
|
body: JSON.stringify({ paymentPayload, paymentRequirements }),
|
|
278
|
+
redirect: "manual",
|
|
228
279
|
signal: AbortSignal.timeout(FACILITATOR_TIMEOUT_MS)
|
|
229
280
|
});
|
|
281
|
+
throwIfRedirect(response);
|
|
230
282
|
if (!response.ok) {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
283
|
+
const body = await readErrorBody(response);
|
|
284
|
+
throw new chunkKZZY5LS6_cjs.X402RuntimeError(
|
|
285
|
+
body.error ?? chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE,
|
|
286
|
+
{
|
|
287
|
+
detail: body.message,
|
|
288
|
+
status: response.status,
|
|
289
|
+
retryAfter: retryAfterOf(response)
|
|
290
|
+
}
|
|
291
|
+
);
|
|
234
292
|
}
|
|
235
293
|
json = await response.json();
|
|
236
294
|
} catch (e) {
|
|
237
|
-
if (e instanceof
|
|
238
|
-
throw new
|
|
295
|
+
if (e instanceof chunkKZZY5LS6_cjs.X402RuntimeError) throw e;
|
|
296
|
+
throw new chunkKZZY5LS6_cjs.X402RuntimeError(chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
|
|
239
297
|
}
|
|
240
298
|
try {
|
|
241
|
-
verifyResponseSchema.parse(json);
|
|
299
|
+
const { settleToken } = verifyResponseSchema.parse(json);
|
|
300
|
+
return { settleToken };
|
|
242
301
|
} catch {
|
|
243
|
-
throw new
|
|
302
|
+
throw new chunkKZZY5LS6_cjs.X402RuntimeError(chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
|
|
244
303
|
}
|
|
245
304
|
}
|
|
246
305
|
async settle(paymentHeader, paymentRequirements, options) {
|
|
247
306
|
const paymentPayload = decodePaymentHeader(paymentHeader);
|
|
248
307
|
let json;
|
|
249
308
|
try {
|
|
250
|
-
const response = await fetch(`${this.
|
|
309
|
+
const response = await fetch(`${this.url}/x402/settle`, {
|
|
251
310
|
method: "POST",
|
|
252
311
|
headers: this.buildHeaders(),
|
|
253
|
-
// settleAmount only travels for metered routes
|
|
312
|
+
// settleAmount only travels for metered routes; the settle token
|
|
313
|
+
// travels whenever verify issued one.
|
|
254
314
|
body: JSON.stringify({
|
|
255
315
|
paymentPayload,
|
|
256
316
|
paymentRequirements,
|
|
257
317
|
...options?.amount !== void 0 && {
|
|
258
318
|
settleAmount: options.amount
|
|
319
|
+
},
|
|
320
|
+
...options?.settleToken !== void 0 && {
|
|
321
|
+
settleToken: options.settleToken
|
|
259
322
|
}
|
|
260
323
|
}),
|
|
324
|
+
redirect: "manual",
|
|
261
325
|
signal: AbortSignal.timeout(SETTLE_TIMEOUT_MS)
|
|
262
326
|
});
|
|
327
|
+
throwIfRedirect(response);
|
|
263
328
|
if (!response.ok) {
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
329
|
+
const body = await readErrorBody(response);
|
|
330
|
+
if (body.error === void 0 && response.status >= 500) {
|
|
331
|
+
throw new chunkKZZY5LS6_cjs.X402RuntimeError(chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
|
|
332
|
+
}
|
|
333
|
+
const retryAfter = retryAfterOf(response);
|
|
334
|
+
return {
|
|
335
|
+
success: false,
|
|
336
|
+
error: {
|
|
337
|
+
code: body.error ?? "SUBMISSION_FAILED",
|
|
338
|
+
message: body.message ?? "Settlement failed",
|
|
339
|
+
status: response.status,
|
|
340
|
+
...retryAfter !== void 0 && { retryAfter }
|
|
341
|
+
}
|
|
342
|
+
};
|
|
268
343
|
}
|
|
269
344
|
json = await response.json();
|
|
270
345
|
} catch (e) {
|
|
271
|
-
if (e instanceof
|
|
272
|
-
|
|
346
|
+
if (e instanceof chunkKZZY5LS6_cjs.X402RuntimeError) throw e;
|
|
347
|
+
if (e instanceof Error && e.name === "TimeoutError") {
|
|
348
|
+
throw new chunkKZZY5LS6_cjs.X402RuntimeError(chunkKZZY5LS6_cjs.X402_ERROR_CODES.SETTLE_TIMEOUT);
|
|
349
|
+
}
|
|
350
|
+
throw new chunkKZZY5LS6_cjs.X402RuntimeError(chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
|
|
273
351
|
}
|
|
274
352
|
try {
|
|
275
353
|
const parsed = settleResponseSchema.parse(json);
|
|
@@ -283,7 +361,8 @@ var FacilitatorClient = class {
|
|
|
283
361
|
networkId: toEip155Network(paymentRequirements.network),
|
|
284
362
|
...parsed.amountSettled !== void 0 && {
|
|
285
363
|
amountSettled: parsed.amountSettled
|
|
286
|
-
}
|
|
364
|
+
},
|
|
365
|
+
...parsed.alreadySettled && { alreadySettled: true }
|
|
287
366
|
};
|
|
288
367
|
}
|
|
289
368
|
return {
|
|
@@ -291,39 +370,45 @@ var FacilitatorClient = class {
|
|
|
291
370
|
error: { code: parsed.error, message: parsed.message }
|
|
292
371
|
};
|
|
293
372
|
} catch {
|
|
294
|
-
throw new
|
|
373
|
+
throw new chunkKZZY5LS6_cjs.X402RuntimeError(chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
|
|
295
374
|
}
|
|
296
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* @deprecated The Parallel facilitator no longer serves `/x402/pay`, so this
|
|
378
|
+
* call fails with `FACILITATOR_UNAVAILABLE`. Use `verify`, then `settle`.
|
|
379
|
+
*/
|
|
297
380
|
async pay(paymentHeader) {
|
|
298
381
|
const headers = this.buildHeaders();
|
|
299
382
|
headers["X-PAYMENT"] = paymentHeader;
|
|
300
383
|
let json;
|
|
301
384
|
try {
|
|
302
|
-
const response = await fetch(`${this.
|
|
385
|
+
const response = await fetch(`${this.url}/x402/pay`, {
|
|
303
386
|
method: "POST",
|
|
304
387
|
headers,
|
|
388
|
+
redirect: "manual",
|
|
305
389
|
signal: AbortSignal.timeout(FACILITATOR_TIMEOUT_MS)
|
|
306
390
|
});
|
|
391
|
+
throwIfRedirect(response);
|
|
307
392
|
if (!response.ok) {
|
|
308
|
-
throw new
|
|
393
|
+
throw new chunkKZZY5LS6_cjs.X402RuntimeError(chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
|
|
309
394
|
}
|
|
310
395
|
json = await response.json();
|
|
311
396
|
} catch (e) {
|
|
312
|
-
if (e instanceof
|
|
313
|
-
throw new
|
|
397
|
+
if (e instanceof chunkKZZY5LS6_cjs.X402RuntimeError) throw e;
|
|
398
|
+
throw new chunkKZZY5LS6_cjs.X402RuntimeError(chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
|
|
314
399
|
}
|
|
315
400
|
try {
|
|
316
401
|
return facilitatorResponseSchema.parse(json);
|
|
317
402
|
} catch {
|
|
318
|
-
throw new
|
|
403
|
+
throw new chunkKZZY5LS6_cjs.X402RuntimeError(chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
|
|
319
404
|
}
|
|
320
405
|
}
|
|
321
406
|
buildHeaders() {
|
|
322
407
|
const headers = {
|
|
323
408
|
"Content-Type": "application/json"
|
|
324
409
|
};
|
|
325
|
-
if (this.
|
|
326
|
-
headers["X-API-Key"] = this.
|
|
410
|
+
if (this.apiKey) {
|
|
411
|
+
headers["X-API-Key"] = this.apiKey;
|
|
327
412
|
}
|
|
328
413
|
return headers;
|
|
329
414
|
}
|
|
@@ -358,20 +443,20 @@ async function requestContext(adapter) {
|
|
|
358
443
|
}
|
|
359
444
|
async function resolvePrice(config, ctx) {
|
|
360
445
|
if (typeof config.price !== "function") return config.price;
|
|
361
|
-
if (!ctx) throw new
|
|
446
|
+
if (!ctx) throw new chunkKZZY5LS6_cjs.X402ConfigError("A price function needs the request");
|
|
362
447
|
let price;
|
|
363
448
|
try {
|
|
364
449
|
price = await config.price(ctx);
|
|
365
450
|
} catch (cause) {
|
|
366
|
-
if (
|
|
367
|
-
throw new
|
|
451
|
+
if (chunkKZZY5LS6_cjs.isPriceRejection(cause)) throw cause;
|
|
452
|
+
throw new chunkKZZY5LS6_cjs.X402ConfigError(
|
|
368
453
|
`The price function of route "${config.network}${ctx.path}" threw`,
|
|
369
454
|
cause
|
|
370
455
|
);
|
|
371
456
|
}
|
|
372
457
|
const valid = typeof price === "bigint" ? price > 0n : typeof price === "string" && PRICE_FORMAT.test(price) && Number(price) > 0;
|
|
373
458
|
if (!valid) {
|
|
374
|
-
throw new
|
|
459
|
+
throw new chunkKZZY5LS6_cjs.X402ConfigError(
|
|
375
460
|
`The price function of route "${config.network}${ctx.path}" returned ${JSON.stringify(
|
|
376
461
|
typeof price === "bigint" ? price.toString() : price
|
|
377
462
|
)} \u2014 expected a positive amount like "0.01" or a bigint`
|
|
@@ -381,10 +466,21 @@ async function resolvePrice(config, ctx) {
|
|
|
381
466
|
}
|
|
382
467
|
var SETTLE_AMOUNT_HEADER = "payment-settle-amount";
|
|
383
468
|
var DEFAULT_MAX_TIMEOUT_SECONDS = 300;
|
|
469
|
+
var SETTLEMENT_RESERVE_SECONDS = 120;
|
|
384
470
|
function handlerTimeoutFor(config) {
|
|
385
471
|
if ((config.scheme ?? "exact") !== "upto") return HANDLER_TIMEOUT_MS;
|
|
386
472
|
const seconds = config.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;
|
|
387
|
-
return Math.max(
|
|
473
|
+
return Math.max(
|
|
474
|
+
HANDLER_TIMEOUT_MS,
|
|
475
|
+
(seconds - SETTLEMENT_RESERVE_SECONDS) * 1e3
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
function jsonResult(status, body, headers = {}) {
|
|
479
|
+
return {
|
|
480
|
+
status,
|
|
481
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
482
|
+
body
|
|
483
|
+
};
|
|
388
484
|
}
|
|
389
485
|
function resolveSettleAmount(raw, maxAmount, decimals) {
|
|
390
486
|
if (raw === void 0)
|
|
@@ -412,13 +508,16 @@ var INVALID_METER_RESPONSE = {
|
|
|
412
508
|
message: "The route's meter function failed or returned something that is not an amount; the response was discarded and nothing was charged."
|
|
413
509
|
};
|
|
414
510
|
function invalidSettleAmountResponse(reason) {
|
|
415
|
-
return
|
|
511
|
+
return jsonResult(
|
|
512
|
+
402,
|
|
513
|
+
reason === "meter" ? INVALID_METER_RESPONSE : INVALID_SETTLE_AMOUNT_RESPONSE
|
|
514
|
+
);
|
|
416
515
|
}
|
|
417
516
|
var AMOUNT_FORMAT = /^\d+(?:\.\d+)?$/;
|
|
418
517
|
async function resolveCharge(verified, settleHeader, response) {
|
|
419
518
|
if (verified.meter) {
|
|
420
519
|
if (!verified.request) {
|
|
421
|
-
throw new
|
|
520
|
+
throw new chunkKZZY5LS6_cjs.X402ConfigError(
|
|
422
521
|
"A metered payment carries a meter but no request context"
|
|
423
522
|
);
|
|
424
523
|
}
|
|
@@ -469,14 +568,14 @@ function resolveAcceptedTokens(config) {
|
|
|
469
568
|
}
|
|
470
569
|
const defaults = config.scheme === "upto" ? getMeteredAcceptedTokens(config.network) : getDefaultAcceptedTokens(config.network);
|
|
471
570
|
if (defaults) return defaults;
|
|
472
|
-
throw new
|
|
571
|
+
throw new chunkKZZY5LS6_cjs.X402ConfigError(
|
|
473
572
|
`acceptedTokens is required for network "${config.network}" \u2014 no defaults available for this chain`
|
|
474
573
|
);
|
|
475
574
|
}
|
|
476
575
|
function resolveDecimals(config, asset) {
|
|
477
576
|
const decimals = config.decimals ?? getTokenDecimals(config.network, asset);
|
|
478
577
|
if (decimals === void 0) {
|
|
479
|
-
throw new
|
|
578
|
+
throw new chunkKZZY5LS6_cjs.X402ConfigError(
|
|
480
579
|
`Unknown decimals for token ${asset} on "${config.network}" \u2014 add it to the @parallel-protocol/chains catalog or set the route's \`decimals\` explicitly`
|
|
481
580
|
);
|
|
482
581
|
}
|
|
@@ -485,7 +584,7 @@ function resolveDecimals(config, asset) {
|
|
|
485
584
|
function priceInUnits(price, decimals, asset) {
|
|
486
585
|
const units = parsePrice(price, decimals);
|
|
487
586
|
if (units === 0n) {
|
|
488
|
-
throw new
|
|
587
|
+
throw new chunkKZZY5LS6_cjs.X402ConfigError(
|
|
489
588
|
`Price ${typeof price === "bigint" ? price.toString() : `"${price}"`} is below the precision of token ${asset} (${decimals} decimals)`
|
|
490
589
|
);
|
|
491
590
|
}
|
|
@@ -560,31 +659,46 @@ function buildFacilitatorRequirements(config, asset, price) {
|
|
|
560
659
|
}
|
|
561
660
|
};
|
|
562
661
|
}
|
|
563
|
-
|
|
662
|
+
var CODE_SHAPE = /^[A-Z][A-Z0-9_]{2,63}$/;
|
|
663
|
+
var MESSAGE_MAX_LENGTH = 300;
|
|
664
|
+
function shapedCode(code) {
|
|
665
|
+
return typeof code === "string" && CODE_SHAPE.test(code) ? code : void 0;
|
|
666
|
+
}
|
|
667
|
+
function boundedMessage(message) {
|
|
668
|
+
if (typeof message !== "string") return void 0;
|
|
669
|
+
const oneLine = message.replace(/[\p{Cc}\p{Cf}]+/gu, " ").replace(/\s+/g, " ").trim();
|
|
670
|
+
if (!oneLine) return void 0;
|
|
671
|
+
const chars = Array.from(oneLine);
|
|
672
|
+
return chars.length > MESSAGE_MAX_LENGTH ? `${chars.slice(0, MESSAGE_MAX_LENGTH).join("")}\u2026` : oneLine;
|
|
673
|
+
}
|
|
674
|
+
function paymentRequiredResponse(url, config, resolvedTokens, price, refusal) {
|
|
564
675
|
const paymentRequired = buildPaymentRequired(
|
|
565
676
|
url,
|
|
566
677
|
config,
|
|
567
678
|
resolvedTokens,
|
|
568
679
|
price
|
|
569
680
|
);
|
|
570
|
-
if (
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
681
|
+
if (refusal) {
|
|
682
|
+
paymentRequired.error = shapedCode(refusal.code) ?? chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE;
|
|
683
|
+
const message = boundedMessage(refusal.detail);
|
|
684
|
+
if (message) paymentRequired.message = message;
|
|
685
|
+
}
|
|
686
|
+
return jsonResult(402, paymentRequired, {
|
|
687
|
+
"payment-required": encodeBase64(paymentRequired),
|
|
688
|
+
"access-control-expose-headers": "payment-required",
|
|
689
|
+
...refusal?.retryAfter !== void 0 && {
|
|
690
|
+
"Retry-After": refusal.retryAfter
|
|
691
|
+
}
|
|
692
|
+
});
|
|
580
693
|
}
|
|
581
694
|
function createPaymentGate(config) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
695
|
+
const checked = paymentMiddlewareConfigSchema.safeParse(config);
|
|
696
|
+
if (!checked.success) {
|
|
697
|
+
const issue = checked.error.issues[0];
|
|
698
|
+
const where = issue?.path.length ? ` at ${issue.path.join(".")}` : "";
|
|
699
|
+
throw new chunkKZZY5LS6_cjs.X402ConfigError(
|
|
700
|
+
`Invalid paymentMiddleware config${where}: ${issue?.message ?? "check your routes, facilitator URL and payTo addresses"}`,
|
|
701
|
+
checked.error
|
|
588
702
|
);
|
|
589
703
|
}
|
|
590
704
|
const client = new FacilitatorClient(config.facilitator);
|
|
@@ -599,7 +713,7 @@ function createPaymentGate(config) {
|
|
|
599
713
|
try {
|
|
600
714
|
price = await resolvePrice(routeConfig, request);
|
|
601
715
|
} catch (err) {
|
|
602
|
-
if (!
|
|
716
|
+
if (!chunkKZZY5LS6_cjs.isPriceRejection(err)) throw err;
|
|
603
717
|
const status = Number.isInteger(err.status) && err.status >= 400 && err.status <= 599 ? err.status : 400;
|
|
604
718
|
return {
|
|
605
719
|
type: "error",
|
|
@@ -632,10 +746,11 @@ function createPaymentGate(config) {
|
|
|
632
746
|
asset,
|
|
633
747
|
price
|
|
634
748
|
);
|
|
749
|
+
let settleToken;
|
|
635
750
|
try {
|
|
636
|
-
await client.verify(paymentHeader, requirements);
|
|
751
|
+
({ settleToken } = await client.verify(paymentHeader, requirements));
|
|
637
752
|
} catch (e) {
|
|
638
|
-
const
|
|
753
|
+
const refusal = e instanceof chunkKZZY5LS6_cjs.X402RuntimeError ? { code: e.code, detail: e.detail, retryAfter: e.retryAfter } : { code: chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE };
|
|
639
754
|
return {
|
|
640
755
|
type: "error",
|
|
641
756
|
result: paymentRequiredResponse(
|
|
@@ -643,7 +758,7 @@ function createPaymentGate(config) {
|
|
|
643
758
|
routeConfig,
|
|
644
759
|
resolvedTokens,
|
|
645
760
|
price,
|
|
646
|
-
|
|
761
|
+
refusal
|
|
647
762
|
)
|
|
648
763
|
};
|
|
649
764
|
}
|
|
@@ -654,12 +769,71 @@ function createPaymentGate(config) {
|
|
|
654
769
|
maxAmount: BigInt(requirements.maxAmountRequired),
|
|
655
770
|
decimals: resolveDecimals(routeConfig, asset),
|
|
656
771
|
handlerTimeoutMs: handlerTimeoutFor(routeConfig),
|
|
657
|
-
|
|
658
|
-
settle: (options) => options ? client.settle(paymentHeader, requirements, options) : client.settle(paymentHeader, requirements),
|
|
772
|
+
settle: (options) => client.settle(paymentHeader, requirements, { ...options, settleToken }),
|
|
659
773
|
...routeConfig.meter && { meter: routeConfig.meter, request }
|
|
660
774
|
};
|
|
661
775
|
};
|
|
662
776
|
}
|
|
777
|
+
var UNKNOWN_SETTLEMENT_MESSAGE = "The payment could not be settled.";
|
|
778
|
+
var MERCHANT_SETTLEMENT_MESSAGE = "The merchant could not complete the settlement of this payment. The payment was not taken: do not sign another payment for this request before checking with the merchant.";
|
|
779
|
+
var AGENT_MESSAGES = {
|
|
780
|
+
[chunkKZZY5LS6_cjs.X402_ERROR_CODES.SETTLE_TIMEOUT]: "The facilitator did not confirm the settlement in time. The payment may still settle: do not sign another payment for this request before checking with the merchant.",
|
|
781
|
+
[chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE]: "The facilitator could not be reached to settle the payment. It may still have settled: do not sign another payment for this request before checking with the merchant.",
|
|
782
|
+
[chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE]: "The facilitator answered the settlement with an unexpected response. The payment may have settled: do not sign another payment for this request before checking with the merchant.",
|
|
783
|
+
SETTLE_IN_PROGRESS: "The facilitator is still settling this payment. Do not sign another payment for this request before checking with the merchant.",
|
|
784
|
+
RECEIPT_UNAVAILABLE: "The facilitator sent the transaction but could not read its confirmation. The payment may have settled: do not sign another payment for this request before checking with the merchant.",
|
|
785
|
+
SETTLE_TOKEN_INVALID: MERCHANT_SETTLEMENT_MESSAGE,
|
|
786
|
+
SETTLE_TOKEN_REQUIRED: MERCHANT_SETTLEMENT_MESSAGE,
|
|
787
|
+
AMOUNT_MISMATCH: "The facilitator settled a different amount than this request asked for. The response was not served: do not sign another payment for this request before checking with the merchant.",
|
|
788
|
+
UNKNOWN: UNKNOWN_SETTLEMENT_MESSAGE
|
|
789
|
+
};
|
|
790
|
+
function settlementFailure(code, failure) {
|
|
791
|
+
const reason = shapedCode(code) ?? "UNKNOWN";
|
|
792
|
+
return {
|
|
793
|
+
settled: false,
|
|
794
|
+
reason,
|
|
795
|
+
message: AGENT_MESSAGES[reason] ?? boundedMessage(failure?.message) ?? UNKNOWN_SETTLEMENT_MESSAGE,
|
|
796
|
+
...failure?.retryAfter !== void 0 && {
|
|
797
|
+
retryAfter: failure.retryAfter
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
var RETRY_ONCE_CODES = /* @__PURE__ */ new Set([
|
|
802
|
+
chunkKZZY5LS6_cjs.X402_ERROR_CODES.SETTLE_TIMEOUT,
|
|
803
|
+
chunkKZZY5LS6_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE
|
|
804
|
+
]);
|
|
805
|
+
async function settleVerified(verified, options) {
|
|
806
|
+
let response;
|
|
807
|
+
for (let attempt = 0; response === void 0; attempt++) {
|
|
808
|
+
try {
|
|
809
|
+
response = await verified.settle(options);
|
|
810
|
+
} catch (e) {
|
|
811
|
+
const code = e instanceof chunkKZZY5LS6_cjs.X402RuntimeError ? e.code : "UNKNOWN";
|
|
812
|
+
if (attempt > 0 || !RETRY_ONCE_CODES.has(code)) {
|
|
813
|
+
return settlementFailure(code);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
if (!response.success) {
|
|
818
|
+
return settlementFailure(response.error.code, response.error);
|
|
819
|
+
}
|
|
820
|
+
if (options?.amount !== void 0 && response.amountSettled !== options.amount) {
|
|
821
|
+
return settlementFailure("AMOUNT_MISMATCH");
|
|
822
|
+
}
|
|
823
|
+
return { settled: true, confirmation: response };
|
|
824
|
+
}
|
|
825
|
+
function settlementFailureResponse(failure) {
|
|
826
|
+
const body = {
|
|
827
|
+
error: "PAYMENT_SETTLEMENT_FAILED",
|
|
828
|
+
reason: failure.reason,
|
|
829
|
+
message: failure.message
|
|
830
|
+
};
|
|
831
|
+
return jsonResult(402, body, {
|
|
832
|
+
...failure.retryAfter !== void 0 && {
|
|
833
|
+
"Retry-After": failure.retryAfter
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
}
|
|
663
837
|
function createPaymentMiddleware(config) {
|
|
664
838
|
const gate = createPaymentGate(config);
|
|
665
839
|
return async (adapter) => {
|
|
@@ -689,11 +863,7 @@ function createPaymentMiddleware(config) {
|
|
|
689
863
|
});
|
|
690
864
|
if (resolved.kind === "invalid") {
|
|
691
865
|
handlerResult.discardResponse();
|
|
692
|
-
return
|
|
693
|
-
status: 402,
|
|
694
|
-
headers: { "Content-Type": "application/json" },
|
|
695
|
-
body: invalidSettleAmountResponse(resolved.reason)
|
|
696
|
-
};
|
|
866
|
+
return invalidSettleAmountResponse(resolved.reason);
|
|
697
867
|
}
|
|
698
868
|
if (resolved.kind === "skip") {
|
|
699
869
|
handlerResult.sendResponse();
|
|
@@ -701,33 +871,19 @@ function createPaymentMiddleware(config) {
|
|
|
701
871
|
}
|
|
702
872
|
settleOptions = { amount: resolved.amount };
|
|
703
873
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const settleResponse = settleOptions ? await gateResult.settle(settleOptions) : await gateResult.settle();
|
|
707
|
-
if (settleResponse.success) {
|
|
708
|
-
handlerResult.setResponseHeader(
|
|
709
|
-
"payment-response",
|
|
710
|
-
encodeBase64(settleResponse)
|
|
711
|
-
);
|
|
712
|
-
handlerResult.setResponseHeader(
|
|
713
|
-
"access-control-expose-headers",
|
|
714
|
-
"payment-response"
|
|
715
|
-
);
|
|
716
|
-
settled = true;
|
|
717
|
-
}
|
|
718
|
-
} catch {
|
|
719
|
-
}
|
|
720
|
-
if (!settled) {
|
|
874
|
+
const outcome = await settleVerified(gateResult, settleOptions);
|
|
875
|
+
if (!outcome.settled) {
|
|
721
876
|
handlerResult.discardResponse();
|
|
722
|
-
return
|
|
723
|
-
status: 402,
|
|
724
|
-
headers: { "Content-Type": "application/json" },
|
|
725
|
-
body: {
|
|
726
|
-
error: "PAYMENT_SETTLEMENT_FAILED",
|
|
727
|
-
message: "Payment could not be confirmed after multiple attempts. Please retry."
|
|
728
|
-
}
|
|
729
|
-
};
|
|
877
|
+
return settlementFailureResponse(outcome);
|
|
730
878
|
}
|
|
879
|
+
handlerResult.setResponseHeader(
|
|
880
|
+
"payment-response",
|
|
881
|
+
encodeBase64(outcome.confirmation)
|
|
882
|
+
);
|
|
883
|
+
handlerResult.setResponseHeader(
|
|
884
|
+
"access-control-expose-headers",
|
|
885
|
+
"payment-response"
|
|
886
|
+
);
|
|
731
887
|
}
|
|
732
888
|
handlerResult.sendResponse();
|
|
733
889
|
return { pass: true, handlerRan: true };
|
|
@@ -751,5 +907,7 @@ exports.parsePrice = parsePrice;
|
|
|
751
907
|
exports.raceTimeout = raceTimeout;
|
|
752
908
|
exports.resolveCharge = resolveCharge;
|
|
753
909
|
exports.resolveSettleAmount = resolveSettleAmount;
|
|
910
|
+
exports.settleVerified = settleVerified;
|
|
911
|
+
exports.settlementFailureResponse = settlementFailureResponse;
|
|
754
912
|
exports.toEip155Network = toEip155Network;
|
|
755
913
|
exports.validateAddress = validateAddress;
|
package/dist/express.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
require('./chunk-
|
|
3
|
+
var chunkSNW5Z5HB_cjs = require('./chunk-SNW5Z5HB.cjs');
|
|
4
|
+
require('./chunk-KZZY5LS6.cjs');
|
|
5
5
|
|
|
6
6
|
// src/express/adapter.ts
|
|
7
7
|
function chunkBytes(args) {
|
|
@@ -108,8 +108,8 @@ var ExpressAdapter = class {
|
|
|
108
108
|
return originalEnd(...args);
|
|
109
109
|
});
|
|
110
110
|
next();
|
|
111
|
-
const outcome = await
|
|
112
|
-
if (outcome ===
|
|
111
|
+
const outcome = await chunkSNW5Z5HB_cjs.raceTimeout(endPromise, options?.timeoutMs);
|
|
112
|
+
if (outcome === chunkSNW5Z5HB_cjs.HANDLER_TIMEOUT) {
|
|
113
113
|
discarded = true;
|
|
114
114
|
return {
|
|
115
115
|
status: 504,
|
|
@@ -146,7 +146,7 @@ var ExpressAdapter = class {
|
|
|
146
146
|
const chunk = chunkBytes(args);
|
|
147
147
|
if (chunk) bytes.push(chunk);
|
|
148
148
|
}
|
|
149
|
-
return
|
|
149
|
+
return chunkSNW5Z5HB_cjs.parseJsonBody(
|
|
150
150
|
contentType,
|
|
151
151
|
Buffer.concat(bytes).toString("utf8")
|
|
152
152
|
);
|
|
@@ -170,7 +170,7 @@ var ExpressAdapter = class {
|
|
|
170
170
|
|
|
171
171
|
// src/express/index.ts
|
|
172
172
|
function paymentMiddleware(config) {
|
|
173
|
-
const middleware =
|
|
173
|
+
const middleware = chunkSNW5Z5HB_cjs.createPaymentMiddleware(config);
|
|
174
174
|
return async (req, res, next) => {
|
|
175
175
|
const result = await middleware(new ExpressAdapter(req, res, next));
|
|
176
176
|
if ("pass" in result) {
|
package/dist/express.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Request, Response, NextFunction } from 'express';
|
|
2
|
-
import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-
|
|
2
|
+
import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-BkGQ2lPA.cjs';
|
|
3
3
|
import 'viem';
|
|
4
4
|
|
|
5
5
|
declare class ExpressAdapter implements HTTPAdapter {
|
package/dist/express.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Request, Response, NextFunction } from 'express';
|
|
2
|
-
import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-
|
|
2
|
+
import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-BkGQ2lPA.js';
|
|
3
3
|
import 'viem';
|
|
4
4
|
|
|
5
5
|
declare class ExpressAdapter implements HTTPAdapter {
|
package/dist/express.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { raceTimeout, HANDLER_TIMEOUT, parseJsonBody, createPaymentMiddleware } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
import { raceTimeout, HANDLER_TIMEOUT, parseJsonBody, createPaymentMiddleware } from './chunk-GXPUE3ES.js';
|
|
2
|
+
import './chunk-QAECRSWF.js';
|
|
3
3
|
|
|
4
4
|
// src/express/adapter.ts
|
|
5
5
|
function chunkBytes(args) {
|