uvd-x402-sdk 2.96.0 → 2.97.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 (71) hide show
  1. package/README.md +26 -6
  2. package/dist/adapters/index.js +2 -1
  3. package/dist/adapters/index.js.map +1 -1
  4. package/dist/adapters/index.mjs +2 -1
  5. package/dist/adapters/index.mjs.map +1 -1
  6. package/dist/backend/index.d.mts +2 -2
  7. package/dist/backend/index.d.ts +2 -2
  8. package/dist/backend/index.js +262 -32
  9. package/dist/backend/index.js.map +1 -1
  10. package/dist/backend/index.mjs +252 -33
  11. package/dist/backend/index.mjs.map +1 -1
  12. package/dist/{index-DRDWlAcU.d.ts → index--bevvYU9.d.ts} +1 -1
  13. package/dist/{index-CtxLKroV.d.mts → index-B2i1mGpW.d.mts} +1 -1
  14. package/dist/{index-DMSZJyZ9.d.mts → index-ClOAmprt.d.mts} +221 -21
  15. package/dist/{index-AGj6ViCS.d.ts → index-DNTFp6WT.d.ts} +221 -21
  16. package/dist/index.d.mts +3 -3
  17. package/dist/index.d.ts +3 -3
  18. package/dist/index.js +262 -32
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs +252 -33
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/providers/algorand/index.js +2 -1
  23. package/dist/providers/algorand/index.js.map +1 -1
  24. package/dist/providers/algorand/index.mjs +2 -1
  25. package/dist/providers/algorand/index.mjs.map +1 -1
  26. package/dist/providers/evm/index.js +2 -1
  27. package/dist/providers/evm/index.js.map +1 -1
  28. package/dist/providers/evm/index.mjs +2 -1
  29. package/dist/providers/evm/index.mjs.map +1 -1
  30. package/dist/providers/near/index.js +2 -1
  31. package/dist/providers/near/index.js.map +1 -1
  32. package/dist/providers/near/index.mjs +2 -1
  33. package/dist/providers/near/index.mjs.map +1 -1
  34. package/dist/providers/solana/index.js +2 -1
  35. package/dist/providers/solana/index.js.map +1 -1
  36. package/dist/providers/solana/index.mjs +2 -1
  37. package/dist/providers/solana/index.mjs.map +1 -1
  38. package/dist/providers/stellar/index.js +2 -1
  39. package/dist/providers/stellar/index.js.map +1 -1
  40. package/dist/providers/stellar/index.mjs +2 -1
  41. package/dist/providers/stellar/index.mjs.map +1 -1
  42. package/dist/providers/sui/index.js +2 -1
  43. package/dist/providers/sui/index.js.map +1 -1
  44. package/dist/providers/sui/index.mjs +2 -1
  45. package/dist/providers/sui/index.mjs.map +1 -1
  46. package/dist/providers/xrpl/index.js +2 -1
  47. package/dist/providers/xrpl/index.js.map +1 -1
  48. package/dist/providers/xrpl/index.mjs +2 -1
  49. package/dist/providers/xrpl/index.mjs.map +1 -1
  50. package/dist/react/index.d.mts +2 -2
  51. package/dist/react/index.d.ts +2 -2
  52. package/dist/react/index.js +2 -1
  53. package/dist/react/index.js.map +1 -1
  54. package/dist/react/index.mjs +2 -1
  55. package/dist/react/index.mjs.map +1 -1
  56. package/dist/react/picker/index.js +2 -1
  57. package/dist/react/picker/index.js.map +1 -1
  58. package/dist/react/picker/index.mjs +2 -1
  59. package/dist/react/picker/index.mjs.map +1 -1
  60. package/dist/{receipts-CwaG8BpU.d.ts → receipts-Co6C9FPO.d.ts} +13 -1
  61. package/dist/{receipts-sEYlpK10.d.mts → receipts-D0zg-8uK.d.mts} +13 -1
  62. package/dist/utils/index.js +2 -1
  63. package/dist/utils/index.js.map +1 -1
  64. package/dist/utils/index.mjs +2 -1
  65. package/dist/utils/index.mjs.map +1 -1
  66. package/package.json +1 -1
  67. package/src/backend/facilitator-error.ts +87 -2
  68. package/src/backend/index.ts +408 -45
  69. package/src/chains/index.ts +2 -1
  70. package/src/index.ts +16 -0
  71. package/src/receipts.ts +28 -0
@@ -104,6 +104,24 @@ function paymentResponseHeaders(result) {
104
104
  "Cache-Control": "no-store"
105
105
  };
106
106
  }
107
+ var LIST_HEADERS = ["Access-Control-Expose-Headers", "Cache-Control"];
108
+ function mergeHeaderList(existing, additions) {
109
+ const name = (entry) => entry.split("=")[0].trim().toLowerCase();
110
+ const kept = (existing || "").split(",").map((entry) => entry.trim()).filter(Boolean);
111
+ const seen = new Set(kept.map(name));
112
+ for (const entry of additions.split(",").map((value) => value.trim()).filter(Boolean)) {
113
+ if (!seen.has(name(entry))) {
114
+ kept.push(entry);
115
+ seen.add(name(entry));
116
+ }
117
+ }
118
+ return kept.join(", ");
119
+ }
120
+ function mergePaymentResponseHeaders(result, current) {
121
+ const headers = paymentResponseHeaders(result);
122
+ for (const name of LIST_HEADERS) headers[name] = mergeHeaderList(current(name), headers[name]);
123
+ return headers;
124
+ }
107
125
  function receiptFromResponse(response) {
108
126
  const values = [response.headers.get("PAYMENT-RESPONSE"), response.headers.get("X-PAYMENT-RESPONSE")].filter((v) => v !== null);
109
127
  if (!values.length) return null;
@@ -991,7 +1009,8 @@ var SUPPORTED_CHAINS = {
991
1009
  // Pinned by src/arc-testnet.test.ts.
992
1010
  //
993
1011
  // Mainnet (5042) and testnet (5042002) were verified independently against
994
- // their official RPCs on 2026-09-16. EURC domains checked on 2026-09-17; live payments pending.
1012
+ // their official RPCs on 2026-09-16. EURC domains checked on 2026-09-17; funded EURC payments settled on Arc mainnet on
1013
+ // 2026-09-22 (x402 v1/v2, docs/networks/arc.md); testnet pending.
995
1014
  arc: {
996
1015
  chainId: 5042,
997
1016
  chainIdHex: "0x13b2",
@@ -1706,6 +1725,9 @@ var REPLAYABLE_LEASE_REASONS = [
1706
1725
  ];
1707
1726
  var AMBIGUOUS_LEASE_REASONS = ["forward_failed"];
1708
1727
  var SETTLEMENT_UNCONFIRMED = "settlement_unconfirmed";
1728
+ var AUTHORIZATION_ALREADY_SETTLED = "authorization_already_settled";
1729
+ var AUTHORIZATION_IN_FLIGHT = "authorization_in_flight";
1730
+ var RECEIPT_REQUEST_CONFLICT = "receipt_request_conflict";
1709
1731
  var MAX_RETRY_AFTER_SECONDS = 15;
1710
1732
  var DEFAULT_RETRY_AFTER_SECONDS = 5;
1711
1733
  var DEFAULT_FACILITATOR_RETRIES = 2;
@@ -1766,6 +1788,12 @@ function isDeclaredUnretryable(parsed) {
1766
1788
  function isSettlementUnconfirmed(failure) {
1767
1789
  return failure.errorCode === SETTLEMENT_UNCONFIRMED;
1768
1790
  }
1791
+ function isAuthorizationAlreadyUsed(failure) {
1792
+ return failure.errorCode === AUTHORIZATION_ALREADY_SETTLED || failure.errorCode === RECEIPT_REQUEST_CONFLICT;
1793
+ }
1794
+ function isAuthorizationInFlight(failure) {
1795
+ return failure.errorCode === AUTHORIZATION_IN_FLIGHT;
1796
+ }
1769
1797
  async function readFacilitatorError(response) {
1770
1798
  let body = "";
1771
1799
  try {
@@ -1777,7 +1805,8 @@ async function readFacilitatorError(response) {
1777
1805
  const parsed = parseFacilitatorErrorBody(body);
1778
1806
  const reason = parsed.reason;
1779
1807
  const transportRetryable = status === 429 || status === 502 || status === 503 || status === 504;
1780
- const retryable = transportRetryable && !isDeclaredUnretryable(parsed);
1808
+ const inFlight = status === 409 && parsed.errorCode === AUTHORIZATION_IN_FLIGHT;
1809
+ const retryable = inFlight || transportRetryable && !isDeclaredUnretryable(parsed);
1781
1810
  const retryAfterSeconds = retryable ? parseRetryAfterSeconds(response) ?? DEFAULT_RETRY_AFTER_SECONDS : void 0;
1782
1811
  const safeToReplay = retryable && (status === 429 || status === 503 && isReplayableLeaseReason(reason));
1783
1812
  return {
@@ -2053,6 +2082,78 @@ function getCorsHeaders(origin = "*") {
2053
2082
  ...X402_CORS_HEADERS
2054
2083
  };
2055
2084
  }
2085
+ var IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
2086
+ var IDEMPOTENT_REPLAYED_HEADER = "Idempotent-Replayed";
2087
+ function createIdempotencyKey() {
2088
+ return `x402-${bytesToHex(randomBytes(32))}`;
2089
+ }
2090
+ function checkIdempotencyKey(key) {
2091
+ if (typeof key !== "string" || !/^[\x21-\x7e]{1,255}$/.test(key) || key.startsWith("receipt:")) {
2092
+ throw new Error('idempotencyKey must be 1-255 visible ASCII characters and must not start with "receipt:"');
2093
+ }
2094
+ return key;
2095
+ }
2096
+ function isReplayed(response) {
2097
+ try {
2098
+ return response?.headers?.get?.(IDEMPOTENT_REPLAYED_HEADER)?.trim().toLowerCase() === "true";
2099
+ } catch {
2100
+ return false;
2101
+ }
2102
+ }
2103
+ function refuseUnboundReplay(result) {
2104
+ if (!result.replayed) return result;
2105
+ const pending = result.receipt?.status === "pending" || result.receipt?.status === "unknown" || result.errorCode === "settlement_in_progress";
2106
+ if (!result.success && !pending) return result;
2107
+ const {
2108
+ transactionHash: _tx,
2109
+ proofOfPayment: _proof,
2110
+ paymentId: _id,
2111
+ transaction: _t,
2112
+ retryAfterSeconds,
2113
+ ...rest
2114
+ } = result;
2115
+ if (result.success) {
2116
+ return {
2117
+ ...rest,
2118
+ success: false,
2119
+ error: AUTHORIZATION_ALREADY_SETTLED,
2120
+ errorCode: AUTHORIZATION_ALREADY_SETTLED,
2121
+ retryable: false,
2122
+ safeToReplay: false
2123
+ };
2124
+ }
2125
+ return {
2126
+ ...rest,
2127
+ success: false,
2128
+ error: AUTHORIZATION_IN_FLIGHT,
2129
+ errorCode: AUTHORIZATION_IN_FLIGHT,
2130
+ retryable: true,
2131
+ safeToReplay: false,
2132
+ retryAfterSeconds: retryAfterSeconds ?? DEFAULT_RETRY_AFTER_SECONDS
2133
+ };
2134
+ }
2135
+ function facilitatorHeaders(idempotencyKey, receiptContext) {
2136
+ return {
2137
+ "Content-Type": "application/json",
2138
+ [IDEMPOTENCY_KEY_HEADER]: idempotencyKey,
2139
+ ...receiptContext ? { "X-UVD-Purchase": receiptContext } : {}
2140
+ };
2141
+ }
2142
+ function admittedVerdict(result) {
2143
+ if (result.isValid !== false) return {};
2144
+ if (result.invalidReason === AUTHORIZATION_IN_FLIGHT) {
2145
+ return {
2146
+ errorCode: AUTHORIZATION_IN_FLIGHT,
2147
+ retryable: true,
2148
+ safeToReplay: false,
2149
+ retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
2150
+ };
2151
+ }
2152
+ if (result.invalidReason === AUTHORIZATION_ALREADY_SETTLED) {
2153
+ return { errorCode: AUTHORIZATION_ALREADY_SETTLED, retryable: false, safeToReplay: false };
2154
+ }
2155
+ return {};
2156
+ }
2056
2157
  var FacilitatorClient = class {
2057
2158
  baseUrl;
2058
2159
  timeout;
@@ -2085,11 +2186,19 @@ var FacilitatorClient = class {
2085
2186
  *
2086
2187
  * Call this before providing the paid resource to validate the payment.
2087
2188
  *
2189
+ * An authorization the facilitator already admitted for another request
2190
+ * comes back invalid with `errorCode` {@link AUTHORIZATION_ALREADY_SETTLED}
2191
+ * (do not serve, do not ask for a new signature) or
2192
+ * {@link AUTHORIZATION_IN_FLIGHT} (`retryable`: the same request learns the
2193
+ * verdict later). Neither is a rejected payment: never answer them with 402.
2194
+ *
2088
2195
  * @param paymentHeader - Parsed x402 payment header
2089
2196
  * @param requirements - Payment requirements
2197
+ * @param options - Purchase context and the payment's `idempotencyKey`
2090
2198
  * @returns Verification result
2091
2199
  */
2092
2200
  async verify(paymentHeader, requirements, options = {}) {
2201
+ const idempotencyKey = checkIdempotencyKey(options.idempotencyKey ?? createIdempotencyKey());
2093
2202
  const body = buildVerifyRequestForVersion(
2094
2203
  paymentHeader,
2095
2204
  requirements,
@@ -2100,7 +2209,7 @@ var FacilitatorClient = class {
2100
2209
  `${this.baseUrl}/verify`,
2101
2210
  {
2102
2211
  method: "POST",
2103
- headers: { "Content-Type": "application/json", ...options.receiptContext ? { "X-UVD-Purchase": options.receiptContext } : {} },
2212
+ headers: facilitatorHeaders(idempotencyKey, options.receiptContext),
2104
2213
  body: JSON.stringify(body)
2105
2214
  },
2106
2215
  { timeoutMs: this.timeout, retries: this.retries }
@@ -2110,18 +2219,20 @@ var FacilitatorClient = class {
2110
2219
  isValid: false,
2111
2220
  invalidReason: error.error,
2112
2221
  ...failureFields(error),
2113
- receipt: receiptFromErrorBody(error.body)
2222
+ receipt: receiptFromErrorBody(error.body),
2223
+ idempotencyKey
2114
2224
  };
2115
2225
  }
2116
2226
  const result = await response.json();
2117
- return { ...result, receipt: parseFacilitatorReceipt(result.receipt) };
2227
+ return { ...result, ...admittedVerdict(result), receipt: parseFacilitatorReceipt(result.receipt), idempotencyKey };
2118
2228
  } catch (error) {
2119
2229
  return {
2120
2230
  isValid: false,
2121
2231
  invalidReason: error instanceof Error ? error.message : "Unknown error",
2122
2232
  retryable: true,
2123
2233
  safeToReplay: false,
2124
- retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
2234
+ retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS,
2235
+ idempotencyKey
2125
2236
  };
2126
2237
  }
2127
2238
  }
@@ -2130,11 +2241,23 @@ var FacilitatorClient = class {
2130
2241
  *
2131
2242
  * Call this after providing the paid resource to execute the on-chain transfer.
2132
2243
  *
2244
+ * Send the `idempotencyKey` the payment was verified with, and the same one
2245
+ * on any retry: it is the binding that earns the original answer back after
2246
+ * a lost response (`replayed: true`). Without it, or with another one, an
2247
+ * authorization already admitted comes back `409`
2248
+ * {@link AUTHORIZATION_ALREADY_SETTLED}, {@link AUTHORIZATION_IN_FLIGHT} or
2249
+ * {@link RECEIPT_REQUEST_CONFLICT} -- the X-PAYMENT was used; do not serve it
2250
+ * again. With no key and no context the call uses a fresh key, so a replay
2251
+ * it receives is refused the same way.
2252
+ *
2133
2253
  * @param paymentHeader - Parsed x402 payment header
2134
2254
  * @param requirements - Payment requirements
2255
+ * @param options - Purchase context and the payment's `idempotencyKey`
2135
2256
  * @returns Settlement result with transaction hash
2136
2257
  */
2137
2258
  async settle(paymentHeader, requirements, options = {}) {
2259
+ const unbound = options.idempotencyKey === void 0 && !options.receiptContext;
2260
+ const idempotencyKey = checkIdempotencyKey(options.idempotencyKey ?? createIdempotencyKey());
2138
2261
  const body = buildSettleRequestForVersion(
2139
2262
  paymentHeader,
2140
2263
  requirements,
@@ -2146,7 +2269,8 @@ var FacilitatorClient = class {
2146
2269
  `${this.baseUrl}/settle`,
2147
2270
  {
2148
2271
  method: "POST",
2149
- headers: { "Content-Type": "application/json", ...options.receiptContext ? { "X-UVD-Purchase": options.receiptContext } : {} },
2272
+ // The same init, key included, goes out on every automatic retry.
2273
+ headers: facilitatorHeaders(idempotencyKey, options.receiptContext),
2150
2274
  body: JSON.stringify(body)
2151
2275
  },
2152
2276
  { timeoutMs: settleTimeout, retries: this.retries }
@@ -2156,7 +2280,8 @@ var FacilitatorClient = class {
2156
2280
  success: false,
2157
2281
  error: error.error,
2158
2282
  ...failureFields(error),
2159
- receipt: receiptFromErrorBody(error.body)
2283
+ receipt: receiptFromErrorBody(error.body),
2284
+ idempotencyKey
2160
2285
  };
2161
2286
  }
2162
2287
  const result = await response.json();
@@ -2166,7 +2291,7 @@ var FacilitatorClient = class {
2166
2291
  "[x402] settle reported success but carried no transaction hash under transaction/transactionHash/transaction_hash \u2014 treat delivery as unconfirmed"
2167
2292
  );
2168
2293
  }
2169
- return {
2294
+ const settled = {
2170
2295
  // Read the facilitator's verdict instead of asserting one.
2171
2296
  //
2172
2297
  // This was the literal `true`, so a settle was reported successful
@@ -2197,15 +2322,21 @@ var FacilitatorClient = class {
2197
2322
  payer: result.payer,
2198
2323
  receipt: parseFacilitatorReceipt(result.receipt),
2199
2324
  paymentId: result.paymentId,
2200
- ...result.receipt?.status === "unknown" || result.receipt?.status === "pending" ? { retryable: true, safeToReplay: false } : {}
2325
+ ...result.receipt?.status === "unknown" || result.receipt?.status === "pending" ? { retryable: true, safeToReplay: false } : {},
2326
+ // `202 settlement_in_progress` and friends: a 2xx that names its state.
2327
+ ...!result.success && typeof result.error === "string" && result.error ? { errorCode: result.error } : {},
2328
+ ...isReplayed(response) ? { replayed: true } : {},
2329
+ idempotencyKey
2201
2330
  };
2331
+ return unbound ? refuseUnboundReplay(settled) : settled;
2202
2332
  } catch (error) {
2203
2333
  return {
2204
2334
  success: false,
2205
2335
  error: error instanceof Error ? error.message : "Unknown error",
2206
2336
  retryable: true,
2207
2337
  safeToReplay: false,
2208
- retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
2338
+ retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS,
2339
+ idempotencyKey
2209
2340
  };
2210
2341
  }
2211
2342
  }
@@ -2215,27 +2346,41 @@ var FacilitatorClient = class {
2215
2346
  * Convenience method that verifies first, then settles if valid.
2216
2347
  * Use this for simple payment flows where you don't need custom logic between verify and settle.
2217
2348
  *
2349
+ * Both steps carry ONE `Idempotency-Key` (`options.idempotencyKey`, or a
2350
+ * fresh one reported back as `idempotencyKey`). With a fresh key and no
2351
+ * purchase context, a replay of an earlier settle is not this purchase and
2352
+ * comes back `settled: false` with `errorCode`
2353
+ * {@link AUTHORIZATION_ALREADY_SETTLED}.
2354
+ *
2218
2355
  * @param paymentHeader - Parsed x402 payment header
2219
2356
  * @param requirements - Payment requirements
2357
+ * @param options - Purchase context and the payment's `idempotencyKey`
2220
2358
  * @returns Combined result with verify and settle status
2221
2359
  */
2222
- async verifyAndSettle(paymentHeader, requirements) {
2223
- const verifyResult = await this.verify(paymentHeader, requirements);
2360
+ async verifyAndSettle(paymentHeader, requirements, options = {}) {
2361
+ const unbound = options.idempotencyKey === void 0 && !options.receiptContext;
2362
+ const binding = { ...options, idempotencyKey: checkIdempotencyKey(options.idempotencyKey ?? createIdempotencyKey()) };
2363
+ const { idempotencyKey } = binding;
2364
+ const verifyResult = await this.verify(paymentHeader, requirements, binding);
2224
2365
  if (!verifyResult.isValid) {
2225
2366
  return {
2226
2367
  verified: false,
2227
2368
  settled: false,
2228
2369
  error: verifyResult.invalidReason,
2229
- ...carryFailureFields(verifyResult)
2370
+ ...carryFailureFields(verifyResult),
2371
+ idempotencyKey
2230
2372
  };
2231
2373
  }
2232
- const settleResult = await this.settle(paymentHeader, requirements);
2374
+ const answered = await this.settle(paymentHeader, requirements, binding);
2375
+ const settleResult = unbound ? refuseUnboundReplay(answered) : answered;
2233
2376
  return {
2234
2377
  verified: true,
2235
2378
  settled: settleResult.success,
2236
2379
  transactionHash: settleResult.transactionHash,
2237
2380
  error: settleResult.error,
2238
- ...carryFailureFields(settleResult)
2381
+ ...carryFailureFields(settleResult),
2382
+ ...settleResult.replayed ? { replayed: true } : {},
2383
+ idempotencyKey
2239
2384
  };
2240
2385
  }
2241
2386
  /**
@@ -2623,16 +2768,22 @@ async function resolvePaymentRequirement(payment, requirements, resolver) {
2623
2768
  }
2624
2769
  return { requirement: matches[0] };
2625
2770
  }
2626
- function createVerifiedPaymentState(client, payment, requirements, verifyResult, receiptContext, onSettled) {
2771
+ function createVerifiedPaymentState(client, payment, requirements, verifyResult, binding, onSettled) {
2627
2772
  let settlePromise = null;
2628
2773
  return {
2629
2774
  payment,
2630
2775
  requirements,
2631
2776
  verifyResult,
2777
+ idempotencyKey: binding.idempotencyKey,
2632
2778
  settle: () => {
2633
2779
  if (!settlePromise) {
2634
- settlePromise = client.settle(payment, requirements, { receiptContext }).then((result) => {
2635
- onSettled?.(result);
2780
+ settlePromise = client.settle(payment, requirements, binding).then((answer) => {
2781
+ const result = binding.receiptContext ? answer : refuseUnboundReplay(answer);
2782
+ try {
2783
+ onSettled?.(result);
2784
+ } catch (error) {
2785
+ console.warn("[x402] settled, but the payment response headers could not be attached:", error);
2786
+ }
2636
2787
  return result;
2637
2788
  });
2638
2789
  }
@@ -2640,6 +2791,28 @@ function createVerifiedPaymentState(client, payment, requirements, verifyResult,
2640
2791
  }
2641
2792
  };
2642
2793
  }
2794
+ function propagatedResult(result) {
2795
+ const { idempotencyKey: _key, ...propagated } = result;
2796
+ return propagated;
2797
+ }
2798
+ function buildPaymentConflictResponse(failure) {
2799
+ if (isAuthorizationInFlight(failure)) {
2800
+ const { headers, body } = buildUnavailableResponse("Payment authorization in flight", failure);
2801
+ return { status: 503, headers, body: { ...body, reason: AUTHORIZATION_IN_FLIGHT } };
2802
+ }
2803
+ const used = isAuthorizationAlreadyUsed(failure);
2804
+ if (!used && failure.status !== 409) return null;
2805
+ return {
2806
+ status: 409,
2807
+ headers: {},
2808
+ body: {
2809
+ error: used ? "Payment authorization already used" : "Payment request conflict",
2810
+ reason: failure.errorCode ?? failure.invalidReason ?? failure.error,
2811
+ retryable: false,
2812
+ safeToReplay: false
2813
+ }
2814
+ };
2815
+ }
2643
2816
  function buildUnavailableResponse(message, failure) {
2644
2817
  const seconds = Math.max(1, Math.ceil(failure.retryAfterSeconds ?? DEFAULT_RETRY_AFTER_SECONDS));
2645
2818
  return {
@@ -2656,14 +2829,25 @@ function buildUnavailableResponse(message, failure) {
2656
2829
  };
2657
2830
  }
2658
2831
  function respondUnavailable(res, message, failure) {
2659
- const { status, headers, body } = buildUnavailableResponse(message, failure);
2832
+ respondExpress(res, buildUnavailableResponse(message, failure));
2833
+ }
2834
+ function respondExpress(res, { status, headers, body }) {
2660
2835
  const staged = res.status(status);
2661
- if (typeof staged.set === "function") {
2836
+ if (Object.keys(headers).length > 0 && typeof staged.set === "function") {
2662
2837
  staged.set(headers).json(body);
2663
2838
  return;
2664
2839
  }
2665
2840
  staged.json(body);
2666
2841
  }
2842
+ function setExpressPaymentHeaders(res, result) {
2843
+ if (!result.receipt || !res.set || res.headersSent) return;
2844
+ const current = (name) => {
2845
+ const value = res.getHeader ? res.getHeader(name) : res.get?.(name);
2846
+ if (value === void 0 || value === null) return void 0;
2847
+ return Array.isArray(value) ? value.join(", ") : String(value);
2848
+ };
2849
+ res.set(mergePaymentResponseHeaders(propagatedResult(result), current));
2850
+ }
2667
2851
  function createPaymentMiddleware(getRequirements, options = {}) {
2668
2852
  const client = new FacilitatorClient({
2669
2853
  baseUrl: options.facilitatorUrl || options.baseUrl,
@@ -2694,9 +2878,15 @@ function createPaymentMiddleware(getRequirements, options = {}) {
2694
2878
  res.status(400).json({ error: "receipt_context_mismatch" });
2695
2879
  return;
2696
2880
  }
2697
- const verifyResult = await client.verify(payment, requirements, { receiptContext });
2698
- if (verifyResult.receipt) res.set?.(paymentResponseHeaders({ ...verifyResult }));
2881
+ const binding = { idempotencyKey: createIdempotencyKey(), receiptContext };
2882
+ const verifyResult = await client.verify(payment, requirements, binding);
2883
+ setExpressPaymentHeaders(res, verifyResult);
2699
2884
  if (!verifyResult.isValid) {
2885
+ const conflict = buildPaymentConflictResponse(verifyResult);
2886
+ if (conflict) {
2887
+ respondExpress(res, conflict);
2888
+ return;
2889
+ }
2700
2890
  if (verifyResult.retryable) {
2701
2891
  respondUnavailable(res, "Payment verification unavailable", verifyResult);
2702
2892
  return;
@@ -2707,12 +2897,22 @@ function createPaymentMiddleware(getRequirements, options = {}) {
2707
2897
  });
2708
2898
  return;
2709
2899
  }
2710
- req.x402 = createVerifiedPaymentState(client, payment, requirements, verifyResult, receiptContext, (result) => {
2711
- if (result.receipt) res.set?.(paymentResponseHeaders({ ...result }));
2712
- });
2900
+ req.x402 = createVerifiedPaymentState(
2901
+ client,
2902
+ payment,
2903
+ requirements,
2904
+ verifyResult,
2905
+ binding,
2906
+ (result) => setExpressPaymentHeaders(res, result)
2907
+ );
2713
2908
  if (settlementStrategy === "before-handler") {
2714
2909
  const settleResult = await req.x402.settle();
2715
2910
  if (!settleResult.success) {
2911
+ const conflict = buildPaymentConflictResponse(settleResult);
2912
+ if (conflict) {
2913
+ respondExpress(res, conflict);
2914
+ return;
2915
+ }
2716
2916
  if (settleResult.retryable) {
2717
2917
  respondUnavailable(res, "Payment settlement unavailable", settleResult);
2718
2918
  return;
@@ -2735,12 +2935,21 @@ function settlementFailureBody(failure, fallbackReason) {
2735
2935
  };
2736
2936
  }
2737
2937
  function honoUnavailable(c, message, failure) {
2738
- const { status, headers, body } = buildUnavailableResponse(message, failure);
2938
+ return respondHono(c, buildUnavailableResponse(message, failure));
2939
+ }
2940
+ function respondHono(c, { status, headers, body }) {
2739
2941
  for (const [name, value] of Object.entries(headers)) {
2740
2942
  c.header?.(name, value);
2741
2943
  }
2742
2944
  return c.json(body, status);
2743
2945
  }
2946
+ function setHonoPaymentHeaders(c, result) {
2947
+ if (!result.receipt || !c.header || c.finalized) return;
2948
+ const current = (name) => c.res?.headers.get(name) ?? void 0;
2949
+ for (const [name, value] of Object.entries(mergePaymentResponseHeaders(propagatedResult(result), current))) {
2950
+ c.header(name, value);
2951
+ }
2952
+ }
2744
2953
  function createHonoMiddleware(options) {
2745
2954
  const client = new FacilitatorClient({
2746
2955
  baseUrl: options.facilitatorUrl || options.baseUrl,
@@ -2795,9 +3004,12 @@ function createHonoMiddleware(options) {
2795
3004
  } catch {
2796
3005
  return c.json({ error: "receipt_context_mismatch" }, 400);
2797
3006
  }
2798
- const verifyResult = await client.verify(parsed, requirement, { receiptContext });
2799
- if (verifyResult.receipt) for (const [name, value] of Object.entries(paymentResponseHeaders({ ...verifyResult }))) c.header?.(name, value);
3007
+ const binding = { idempotencyKey: createIdempotencyKey(), receiptContext };
3008
+ const verifyResult = await client.verify(parsed, requirement, binding);
3009
+ setHonoPaymentHeaders(c, verifyResult);
2800
3010
  if (!verifyResult.isValid) {
3011
+ const conflict = buildPaymentConflictResponse(verifyResult);
3012
+ if (conflict) return respondHono(c, conflict);
2801
3013
  if (verifyResult.retryable) {
2802
3014
  return honoUnavailable(c, "Payment verification unavailable", verifyResult);
2803
3015
  }
@@ -2806,13 +3018,20 @@ function createHonoMiddleware(options) {
2806
3018
  reason: verifyResult.invalidReason
2807
3019
  }, 402);
2808
3020
  }
2809
- const verifiedPayment = createVerifiedPaymentState(client, parsed, requirement, verifyResult, receiptContext, (result) => {
2810
- if (result.receipt) for (const [name, value] of Object.entries(paymentResponseHeaders({ ...result }))) c.header?.(name, value);
2811
- });
3021
+ const verifiedPayment = createVerifiedPaymentState(
3022
+ client,
3023
+ parsed,
3024
+ requirement,
3025
+ verifyResult,
3026
+ binding,
3027
+ (result) => setHonoPaymentHeaders(c, result)
3028
+ );
2812
3029
  c.set?.("x402", verifiedPayment);
2813
3030
  if (settlementStrategy === "before-handler") {
2814
3031
  const settleResult = await verifiedPayment.settle();
2815
3032
  if (!settleResult.success) {
3033
+ const conflict = buildPaymentConflictResponse(settleResult);
3034
+ if (conflict) return respondHono(c, conflict);
2816
3035
  if (settleResult.retryable) {
2817
3036
  return honoUnavailable(c, "Payment settlement unavailable", settleResult);
2818
3037
  }
@@ -5703,6 +5922,6 @@ var AdvancedEscrowClient = class {
5703
5922
  }
5704
5923
  };
5705
5924
 
5706
- export { AMBIGUOUS_LEASE_REASONS, AdvancedEscrowClient, BASE_MAINNET_CONTRACTS, BazaarClient, DEFAULT_FACILITATOR_RETRIES, DEFAULT_RETRY_AFTER_SECONDS, DEPOSIT_LIMIT_USDC, ERC8004_CONTRACTS, ERC8004_EXTENSION_ID, ESCROW_CONTRACTS, ESCROW_TIMEOUT_MS, Erc8004Client, Erc8004LookupError, EscrowClient, FacilitatorClient, HEALTH_FILTERS, MAX_RETRY_AFTER_SECONDS, MAX_SEARCH_LEN, OPERATOR_ABI, OPERATOR_ABI_CREATE3, PAYMENT_INFO_TYPEHASH, RECEIPT_ISSUER, RELAYED_FEEDBACK_NETWORKS, REPLAYABLE_LEASE_REASONS, RegistrationPendingError, SETTLEMENT_UNCONFIRMED, SOLANA_FEEDBACK_NETWORKS, TIER_FILTERS, TIER_TIMINGS, USDC_DOMAIN_NAME, WRITER_LEASE_REASONS, X402_CORS_HEADERS, X402_HEADER_NAMES, ZERO_ADDRESS, buildErc8004PaymentRequirements, buildPaymentRequirements, buildSettleRequest, buildSettleRequestForVersion, buildSettleRequestV2, buildUnavailableResponse, buildVerifyRequest, buildVerifyRequestForVersion, buildVerifyRequestV2, canRefundEscrow, canReleaseEscrow, canonicalReceiptJson, carryFailureFields, create402Response, createHonoMiddleware, createPaymentMiddleware, createPurchaseContext, epochToDate, escrowTimeRemaining, extractPaymentFromHeaders, facilitatorFetch, fetchWithReceipt, getCorsHeaders, getEscrowContractsByChainId, getEscrowSupportedChainIds, getFacilitatorReceipt, isAlive, isAmbiguousLeaseReason, isEscrowExpired, isEscrowSupportedOnChain, isRegisterJobTerminal, isReplayableLeaseReason, isSettlementUnconfirmed, parseFacilitatorErrorBody, parseFacilitatorReceipt, parsePaymentHeader, parseRetryAfterSeconds, paymentResponseHeaders, purchaseContextHeader, readFacilitatorError, receiptCommitment, receiptFromErrorBody, receiptFromResponse, resolveEnvelopeVersion, supportsRelayedFeedback, supportsSolanaFeedback, toPaymentRequirementsV2, toResourceInfoV2, validatePurchaseContext, verifyFacilitatorReceipt, wireNetwork };
5925
+ export { AMBIGUOUS_LEASE_REASONS, AUTHORIZATION_ALREADY_SETTLED, AUTHORIZATION_IN_FLIGHT, AdvancedEscrowClient, BASE_MAINNET_CONTRACTS, BazaarClient, DEFAULT_FACILITATOR_RETRIES, DEFAULT_RETRY_AFTER_SECONDS, DEPOSIT_LIMIT_USDC, ERC8004_CONTRACTS, ERC8004_EXTENSION_ID, ESCROW_CONTRACTS, ESCROW_TIMEOUT_MS, Erc8004Client, Erc8004LookupError, EscrowClient, FacilitatorClient, HEALTH_FILTERS, IDEMPOTENCY_KEY_HEADER, IDEMPOTENT_REPLAYED_HEADER, MAX_RETRY_AFTER_SECONDS, MAX_SEARCH_LEN, OPERATOR_ABI, OPERATOR_ABI_CREATE3, PAYMENT_INFO_TYPEHASH, RECEIPT_ISSUER, RECEIPT_REQUEST_CONFLICT, RELAYED_FEEDBACK_NETWORKS, REPLAYABLE_LEASE_REASONS, RegistrationPendingError, SETTLEMENT_UNCONFIRMED, SOLANA_FEEDBACK_NETWORKS, TIER_FILTERS, TIER_TIMINGS, USDC_DOMAIN_NAME, WRITER_LEASE_REASONS, X402_CORS_HEADERS, X402_HEADER_NAMES, ZERO_ADDRESS, buildErc8004PaymentRequirements, buildPaymentConflictResponse, buildPaymentRequirements, buildSettleRequest, buildSettleRequestForVersion, buildSettleRequestV2, buildUnavailableResponse, buildVerifyRequest, buildVerifyRequestForVersion, buildVerifyRequestV2, canRefundEscrow, canReleaseEscrow, canonicalReceiptJson, carryFailureFields, create402Response, createHonoMiddleware, createIdempotencyKey, createPaymentMiddleware, createPurchaseContext, epochToDate, escrowTimeRemaining, extractPaymentFromHeaders, facilitatorFetch, fetchWithReceipt, getCorsHeaders, getEscrowContractsByChainId, getEscrowSupportedChainIds, getFacilitatorReceipt, isAlive, isAmbiguousLeaseReason, isAuthorizationAlreadyUsed, isAuthorizationInFlight, isEscrowExpired, isEscrowSupportedOnChain, isRegisterJobTerminal, isReplayableLeaseReason, isSettlementUnconfirmed, mergeHeaderList, mergePaymentResponseHeaders, parseFacilitatorErrorBody, parseFacilitatorReceipt, parsePaymentHeader, parseRetryAfterSeconds, paymentResponseHeaders, purchaseContextHeader, readFacilitatorError, receiptCommitment, receiptFromErrorBody, receiptFromResponse, resolveEnvelopeVersion, supportsRelayedFeedback, supportsSolanaFeedback, toPaymentRequirementsV2, toResourceInfoV2, validatePurchaseContext, verifyFacilitatorReceipt, wireNetwork };
5707
5926
  //# sourceMappingURL=index.mjs.map
5708
5927
  //# sourceMappingURL=index.mjs.map