uvd-x402-sdk 2.75.0 → 2.76.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/index.d.mts CHANGED
@@ -4,7 +4,7 @@ export { A as AlgorandPaymentPayload, C as CAIP2_IDENTIFIERS, a as CAIP2_TO_CHAI
4
4
  import { S as SigningWalletAdapter } from './wallet-0cX9Pw2F.mjs';
5
5
  export { E as EIP3009Authorization, a as EIP3009Params } from './wallet-0cX9Pw2F.mjs';
6
6
  export { E as EnvKeyAdapter, O as OWSWallet, a as OWSWalletAdapter } from './ows-CYIVd4xO.mjs';
7
- export { FacilitatorClient, FacilitatorClientOptions, HonoMiddlewareOptions, PaymentAcceptance, PaymentMiddlewareOptions, PaymentPayloadV2, PaymentRequirementsV2, ResourceInfoV2, SettleRequestV2, VerifiedPaymentState, VerifyRequestV2, X402_CORS_HEADERS, X402_HEADER_NAMES, buildPaymentRequirements, buildSettleRequest, buildSettleRequestV2, buildVerifyRequest, buildVerifyRequestV2, create402Response, createHonoMiddleware, createPaymentMiddleware, extractPaymentFromHeaders, getCorsHeaders } from './backend/index.mjs';
7
+ export { AMBIGUOUS_LEASE_REASONS, DEFAULT_FACILITATOR_RETRIES, DEFAULT_RETRY_AFTER_SECONDS, FacilitatorClient, FacilitatorClientOptions, FacilitatorErrorInfo, FacilitatorFailureFields, FacilitatorFetchOptions, HonoMiddlewareOptions, MAX_RETRY_AFTER_SECONDS, PaymentAcceptance, PaymentMiddlewareOptions, PaymentPayloadV2, PaymentRequirementsV2, REPLAYABLE_LEASE_REASONS, ResourceInfoV2, SettleRequestV2, VerifiedPaymentState, VerifyRequestV2, WRITER_LEASE_REASONS, WriterLeaseReason, X402_CORS_HEADERS, X402_HEADER_NAMES, buildPaymentRequirements, buildSettleRequest, buildSettleRequestV2, buildVerifyRequest, buildVerifyRequestV2, create402Response, createHonoMiddleware, createPaymentMiddleware, extractPaymentFromHeaders, facilitatorFetch, getCorsHeaders, isAmbiguousLeaseReason, isReplayableLeaseReason, parseRetryAfterSeconds, readFacilitatorError } from './backend/index.mjs';
8
8
 
9
9
  /**
10
10
  * DX402 `durable-evidence`: recover a paid response after the fact.
@@ -303,6 +303,41 @@ interface AnchorOptions {
303
303
  * anchor for the same payment supersedes it.
304
304
  */
305
305
  sign?: (digest: Uint8Array) => string | Promise<string>;
306
+ /**
307
+ * `(sealedBytes) => pointer`. Bring your own storage.
308
+ *
309
+ * Without this the sealed envelope travels **inside** the anchor request and
310
+ * the facilitator hosts it, which is the easy path and stays the default. With
311
+ * it, the SDK still seals exactly as before -- the buyer has to be able to
312
+ * decrypt, so the sealing is not optional -- then hands you the ciphertext,
313
+ * you write it wherever you keep durable bytes, and you return the pointer
314
+ * that addresses it. The request then carries only that pointer, so the
315
+ * ciphertext never travels through the anchor call and
316
+ * {@link ANCHOR_MAX_REQUEST_BYTES} does not apply to your body.
317
+ *
318
+ * A callable rather than a precomputed pointer, for the same reason
319
+ * {@link AnchorOptions.sign} is one: the SDK must seal first, and only then
320
+ * can anything be uploaded. Handing in a pointer computed in advance would
321
+ * mean addressing bytes that do not exist yet.
322
+ *
323
+ * The returned pointer is what the buyer will dereference, so it must be
324
+ * readable by them -- see {@link dereferencePointer} for the schemes the SDK
325
+ * resolves out of the box. It is also bound into the seller signature.
326
+ *
327
+ * **It is never allowed to break the sale.** If it throws or returns
328
+ * something unusable, the anchor degrades to a skip and the payment is
329
+ * untouched.
330
+ */
331
+ upload?: (sealed: Uint8Array) => string | Promise<string>;
332
+ /**
333
+ * Which storage family the pointer belongs to: `s3`, `ipfs` or `arweave`.
334
+ *
335
+ * Defaults to `s3`, and in {@link AnchorOptions.upload} mode it is inferred
336
+ * from the pointer's scheme when you do not say. Distinct from `storage`,
337
+ * which names one of the FACILITATOR's own offers and is meaningless when you
338
+ * supply the bytes yourself.
339
+ */
340
+ backend?: 's3' | 'ipfs' | 'arweave';
306
341
  retention?: string;
307
342
  facilitator?: string;
308
343
  fetch?: typeof fetch;
@@ -322,10 +357,15 @@ interface AnchorOptions {
322
357
  * identical, the ed25519 form was refused (`409 dx402_already_anchored`) and the
323
358
  * EVM form superseded the provisional.
324
359
  *
325
- * `pointer` stays empty on both branches: this call sends `sealed`, so the
326
- * facilitator issues the pointer and you cannot sign what you have not seen.
360
+ * `pointer` defaults to the empty string, which is right when you send `sealed`:
361
+ * the facilitator issues the pointer and you cannot sign what you have not seen.
362
+ * When you supply your OWN pointer it must be passed here **verbatim** -- the
363
+ * facilitator verifies against `req.pointer` when one is present and against
364
+ * `""` when it is absent (x402-rs `dx402/service.rs`, `signed_pointer`), so
365
+ * signing the empty string alongside a pointer produces a signature that never
366
+ * verifies and an anchor that silently stays provisional.
327
367
  */
328
- declare function sellerDigestFor(paymentId: string, contentHash: string, payee: string, network: string): Uint8Array | undefined;
368
+ declare function sellerDigestFor(paymentId: string, contentHash: string, payee: string, network: string, pointer?: string): Uint8Array | undefined;
329
369
  /**
330
370
  * Largest `POST /dx402/anchor` request the facilitator accepts, mirroring its
331
371
  * `MAX_REQUEST_BODY_BYTES` (default 64 KiB, an anti-OOM bound on every route).
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export { A as AlgorandPaymentPayload, C as CAIP2_IDENTIFIERS, a as CAIP2_TO_CHAI
4
4
  import { S as SigningWalletAdapter } from './wallet-0cX9Pw2F.js';
5
5
  export { E as EIP3009Authorization, a as EIP3009Params } from './wallet-0cX9Pw2F.js';
6
6
  export { E as EnvKeyAdapter, O as OWSWallet, a as OWSWalletAdapter } from './ows-DTDixPzO.js';
7
- export { FacilitatorClient, FacilitatorClientOptions, HonoMiddlewareOptions, PaymentAcceptance, PaymentMiddlewareOptions, PaymentPayloadV2, PaymentRequirementsV2, ResourceInfoV2, SettleRequestV2, VerifiedPaymentState, VerifyRequestV2, X402_CORS_HEADERS, X402_HEADER_NAMES, buildPaymentRequirements, buildSettleRequest, buildSettleRequestV2, buildVerifyRequest, buildVerifyRequestV2, create402Response, createHonoMiddleware, createPaymentMiddleware, extractPaymentFromHeaders, getCorsHeaders } from './backend/index.js';
7
+ export { AMBIGUOUS_LEASE_REASONS, DEFAULT_FACILITATOR_RETRIES, DEFAULT_RETRY_AFTER_SECONDS, FacilitatorClient, FacilitatorClientOptions, FacilitatorErrorInfo, FacilitatorFailureFields, FacilitatorFetchOptions, HonoMiddlewareOptions, MAX_RETRY_AFTER_SECONDS, PaymentAcceptance, PaymentMiddlewareOptions, PaymentPayloadV2, PaymentRequirementsV2, REPLAYABLE_LEASE_REASONS, ResourceInfoV2, SettleRequestV2, VerifiedPaymentState, VerifyRequestV2, WRITER_LEASE_REASONS, WriterLeaseReason, X402_CORS_HEADERS, X402_HEADER_NAMES, buildPaymentRequirements, buildSettleRequest, buildSettleRequestV2, buildVerifyRequest, buildVerifyRequestV2, create402Response, createHonoMiddleware, createPaymentMiddleware, extractPaymentFromHeaders, facilitatorFetch, getCorsHeaders, isAmbiguousLeaseReason, isReplayableLeaseReason, parseRetryAfterSeconds, readFacilitatorError } from './backend/index.js';
8
8
 
9
9
  /**
10
10
  * DX402 `durable-evidence`: recover a paid response after the fact.
@@ -303,6 +303,41 @@ interface AnchorOptions {
303
303
  * anchor for the same payment supersedes it.
304
304
  */
305
305
  sign?: (digest: Uint8Array) => string | Promise<string>;
306
+ /**
307
+ * `(sealedBytes) => pointer`. Bring your own storage.
308
+ *
309
+ * Without this the sealed envelope travels **inside** the anchor request and
310
+ * the facilitator hosts it, which is the easy path and stays the default. With
311
+ * it, the SDK still seals exactly as before -- the buyer has to be able to
312
+ * decrypt, so the sealing is not optional -- then hands you the ciphertext,
313
+ * you write it wherever you keep durable bytes, and you return the pointer
314
+ * that addresses it. The request then carries only that pointer, so the
315
+ * ciphertext never travels through the anchor call and
316
+ * {@link ANCHOR_MAX_REQUEST_BYTES} does not apply to your body.
317
+ *
318
+ * A callable rather than a precomputed pointer, for the same reason
319
+ * {@link AnchorOptions.sign} is one: the SDK must seal first, and only then
320
+ * can anything be uploaded. Handing in a pointer computed in advance would
321
+ * mean addressing bytes that do not exist yet.
322
+ *
323
+ * The returned pointer is what the buyer will dereference, so it must be
324
+ * readable by them -- see {@link dereferencePointer} for the schemes the SDK
325
+ * resolves out of the box. It is also bound into the seller signature.
326
+ *
327
+ * **It is never allowed to break the sale.** If it throws or returns
328
+ * something unusable, the anchor degrades to a skip and the payment is
329
+ * untouched.
330
+ */
331
+ upload?: (sealed: Uint8Array) => string | Promise<string>;
332
+ /**
333
+ * Which storage family the pointer belongs to: `s3`, `ipfs` or `arweave`.
334
+ *
335
+ * Defaults to `s3`, and in {@link AnchorOptions.upload} mode it is inferred
336
+ * from the pointer's scheme when you do not say. Distinct from `storage`,
337
+ * which names one of the FACILITATOR's own offers and is meaningless when you
338
+ * supply the bytes yourself.
339
+ */
340
+ backend?: 's3' | 'ipfs' | 'arweave';
306
341
  retention?: string;
307
342
  facilitator?: string;
308
343
  fetch?: typeof fetch;
@@ -322,10 +357,15 @@ interface AnchorOptions {
322
357
  * identical, the ed25519 form was refused (`409 dx402_already_anchored`) and the
323
358
  * EVM form superseded the provisional.
324
359
  *
325
- * `pointer` stays empty on both branches: this call sends `sealed`, so the
326
- * facilitator issues the pointer and you cannot sign what you have not seen.
360
+ * `pointer` defaults to the empty string, which is right when you send `sealed`:
361
+ * the facilitator issues the pointer and you cannot sign what you have not seen.
362
+ * When you supply your OWN pointer it must be passed here **verbatim** -- the
363
+ * facilitator verifies against `req.pointer` when one is present and against
364
+ * `""` when it is absent (x402-rs `dx402/service.rs`, `signed_pointer`), so
365
+ * signing the empty string alongside a pointer produces a signature that never
366
+ * verifies and an anchor that silently stays provisional.
327
367
  */
328
- declare function sellerDigestFor(paymentId: string, contentHash: string, payee: string, network: string): Uint8Array | undefined;
368
+ declare function sellerDigestFor(paymentId: string, contentHash: string, payee: string, network: string, pointer?: string): Uint8Array | undefined;
329
369
  /**
330
370
  * Largest `POST /dx402/anchor` request the facilitator accepts, mirroring its
331
371
  * `MAX_REQUEST_BODY_BYTES` (default 64 KiB, an anti-OOM bound on every route).
package/dist/index.js CHANGED
@@ -1584,13 +1584,13 @@ function chainIdFor(network) {
1584
1584
  if (Number.isFinite(byId) && byId > 0) return getChainById(byId)?.chainId;
1585
1585
  return getChainByName(name)?.chainId;
1586
1586
  }
1587
- function sellerDigestFor(paymentId2, contentHash2, payee, network) {
1587
+ function sellerDigestFor(paymentId2, contentHash2, payee, network, pointer = "") {
1588
1588
  if (isEvmAddress(payee)) {
1589
1589
  const chainId = chainIdFor(network);
1590
1590
  if (!chainId) return void 0;
1591
- return anchorDigest(paymentId2, contentHash2, "", payee, chainId);
1591
+ return anchorDigest(paymentId2, contentHash2, pointer, payee, chainId);
1592
1592
  }
1593
- return anchorDigest(paymentId2, contentHash2, "", ZERO_ADDRESS, 0);
1593
+ return anchorDigest(paymentId2, contentHash2, pointer, ZERO_ADDRESS, 0);
1594
1594
  }
1595
1595
  var ANCHOR_MAX_REQUEST_BYTES = 64 * 1024;
1596
1596
  function toBase64(bytes) {
@@ -1601,6 +1601,11 @@ function toBase64(bytes) {
1601
1601
  }
1602
1602
  return btoa(out);
1603
1603
  }
1604
+ function backendForPointer(pointer) {
1605
+ if (pointer.startsWith("ipfs://")) return "ipfs";
1606
+ if (pointer.startsWith("ar://")) return "arweave";
1607
+ return "s3";
1608
+ }
1604
1609
  async function anchorEvidence(body, opts) {
1605
1610
  try {
1606
1611
  const recipients = [
@@ -1611,14 +1616,29 @@ async function anchorEvidence(body, opts) {
1611
1616
  }
1612
1617
  const blob = sealEvidenceTo(body, recipients, opts.paymentId);
1613
1618
  const hash = contentHash(body);
1619
+ let pointer;
1620
+ if (opts.upload) {
1621
+ try {
1622
+ pointer = await opts.upload(blob);
1623
+ } catch {
1624
+ return { v: 1, skipped: "anchor_failed", stage: "upload" };
1625
+ }
1626
+ if (typeof pointer !== "string" || pointer.trim() === "") {
1627
+ return { v: 1, skipped: "anchor_failed", stage: "upload" };
1628
+ }
1629
+ pointer = pointer.trim();
1630
+ }
1614
1631
  const payload = {
1615
1632
  paymentId: opts.paymentId,
1616
1633
  network: opts.network,
1617
1634
  txHash: opts.txHash,
1618
1635
  payer: opts.payer,
1619
1636
  payee: opts.payee,
1620
- sealed: toBase64(blob),
1621
- backend: "s3",
1637
+ // Exactly one of these. The facilitator dispatches on their presence:
1638
+ // `sealed` -> it hosts and derives the pointer; `pointer` alone -> it uses
1639
+ // yours; neither -> error (x402-rs `dx402/service.rs`).
1640
+ ...pointer !== void 0 ? { pointer } : { sealed: toBase64(blob) },
1641
+ backend: opts.backend ?? (pointer !== void 0 ? backendForPointer(pointer) : "s3"),
1622
1642
  contentHash: hash,
1623
1643
  keyAlg: opts.payerKey.length === 32 ? "ECIES-X25519" : "ECIES-secp256k1",
1624
1644
  mode: "direct",
@@ -1632,7 +1652,7 @@ async function anchorEvidence(body, opts) {
1632
1652
  payload.storage = opts.storage;
1633
1653
  }
1634
1654
  if (opts.sign) {
1635
- const digest = sellerDigestFor(opts.paymentId, hash, opts.payee, opts.network);
1655
+ const digest = sellerDigestFor(opts.paymentId, hash, opts.payee, opts.network, pointer ?? "");
1636
1656
  if (digest === void 0) {
1637
1657
  unsigned = "unknown_chain_id";
1638
1658
  } else {
@@ -1640,7 +1660,7 @@ async function anchorEvidence(body, opts) {
1640
1660
  }
1641
1661
  }
1642
1662
  const wire = JSON.stringify(payload);
1643
- if (new TextEncoder().encode(wire).length > ANCHOR_MAX_REQUEST_BYTES) {
1663
+ if (pointer === void 0 && new TextEncoder().encode(wire).length > ANCHOR_MAX_REQUEST_BYTES) {
1644
1664
  return { v: 1, skipped: "too_large" };
1645
1665
  }
1646
1666
  const base = (opts.facilitator ?? "https://facilitator.ultravioletadao.xyz").replace(
@@ -3723,6 +3743,118 @@ async function buildEscrowPreAuth(wallet, params) {
3723
3743
  });
3724
3744
  }
3725
3745
 
3746
+ // src/backend/facilitator-error.ts
3747
+ var WRITER_LEASE_REASONS = [
3748
+ "holder_unknown",
3749
+ "forwarding_disabled",
3750
+ "forwarded_but_not_writer",
3751
+ "body_unreadable",
3752
+ "forward_failed"
3753
+ ];
3754
+ var REPLAYABLE_LEASE_REASONS = [
3755
+ "holder_unknown",
3756
+ "forwarding_disabled",
3757
+ "forwarded_but_not_writer",
3758
+ "body_unreadable"
3759
+ ];
3760
+ var AMBIGUOUS_LEASE_REASONS = ["forward_failed"];
3761
+ var MAX_RETRY_AFTER_SECONDS = 15;
3762
+ var DEFAULT_RETRY_AFTER_SECONDS = 5;
3763
+ var DEFAULT_FACILITATOR_RETRIES = 2;
3764
+ function isReplayableLeaseReason(reason) {
3765
+ return REPLAYABLE_LEASE_REASONS.includes(reason);
3766
+ }
3767
+ function isAmbiguousLeaseReason(reason) {
3768
+ return AMBIGUOUS_LEASE_REASONS.includes(reason);
3769
+ }
3770
+ function parseRetryAfterSeconds(response) {
3771
+ let raw;
3772
+ try {
3773
+ raw = response?.headers?.get?.("retry-after");
3774
+ } catch {
3775
+ return void 0;
3776
+ }
3777
+ if (raw === null || raw === void 0 || raw === "") return void 0;
3778
+ const seconds = Number(raw);
3779
+ if (!Number.isFinite(seconds) || seconds < 0) return void 0;
3780
+ return Math.min(seconds, MAX_RETRY_AFTER_SECONDS);
3781
+ }
3782
+ function reasonFrom(body) {
3783
+ try {
3784
+ const parsed = JSON.parse(body);
3785
+ if (parsed && typeof parsed === "object" && typeof parsed.reason === "string") {
3786
+ return parsed.reason;
3787
+ }
3788
+ } catch {
3789
+ }
3790
+ return void 0;
3791
+ }
3792
+ async function readFacilitatorError(response) {
3793
+ let body = "";
3794
+ try {
3795
+ body = await response.text();
3796
+ } catch {
3797
+ body = "";
3798
+ }
3799
+ const status = response.status;
3800
+ const reason = reasonFrom(body);
3801
+ const retryable = status === 429 || status === 502 || status === 503 || status === 504;
3802
+ const retryAfterSeconds = retryable ? parseRetryAfterSeconds(response) ?? DEFAULT_RETRY_AFTER_SECONDS : void 0;
3803
+ const safeToReplay = status === 429 || status === 503 && isReplayableLeaseReason(reason);
3804
+ return {
3805
+ error: `Facilitator error: ${status} - ${body}`,
3806
+ status,
3807
+ reason,
3808
+ retryAfterSeconds,
3809
+ retryable,
3810
+ safeToReplay,
3811
+ body
3812
+ };
3813
+ }
3814
+ function failureFields(info) {
3815
+ return {
3816
+ status: info.status,
3817
+ retryable: info.retryable,
3818
+ safeToReplay: info.safeToReplay,
3819
+ ...info.reason !== void 0 ? { reason: info.reason } : {},
3820
+ ...info.retryAfterSeconds !== void 0 ? { retryAfterSeconds: info.retryAfterSeconds } : {}
3821
+ };
3822
+ }
3823
+ function carryFailureFields(source) {
3824
+ return {
3825
+ ...source.status !== void 0 ? { status: source.status } : {},
3826
+ ...source.reason !== void 0 ? { reason: source.reason } : {},
3827
+ ...source.retryable !== void 0 ? { retryable: source.retryable } : {},
3828
+ ...source.retryAfterSeconds !== void 0 ? { retryAfterSeconds: source.retryAfterSeconds } : {},
3829
+ ...source.safeToReplay !== void 0 ? { safeToReplay: source.safeToReplay } : {}
3830
+ };
3831
+ }
3832
+ var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3833
+ async function facilitatorFetch(url, init, options) {
3834
+ const retries = options.retries ?? DEFAULT_FACILITATOR_RETRIES;
3835
+ const canReplay = options.canReplay ?? ((info) => info.safeToReplay);
3836
+ const doFetch = options.fetchImpl ?? fetch;
3837
+ const sleep = options.sleepImpl ?? defaultSleep;
3838
+ let attempt = 0;
3839
+ for (; ; ) {
3840
+ const controller = new AbortController();
3841
+ const timeoutId = setTimeout(() => controller.abort(), options.timeoutMs);
3842
+ let response;
3843
+ try {
3844
+ response = await doFetch(url, { ...init, signal: controller.signal });
3845
+ } finally {
3846
+ clearTimeout(timeoutId);
3847
+ }
3848
+ if (response.ok) return { response };
3849
+ const error = await readFacilitatorError(response);
3850
+ if (attempt >= retries || !error.retryable || !canReplay(error)) {
3851
+ return { response, error };
3852
+ }
3853
+ attempt += 1;
3854
+ await sleep((error.retryAfterSeconds ?? DEFAULT_RETRY_AFTER_SECONDS) * 1e3);
3855
+ }
3856
+ }
3857
+
3726
3858
  // src/backend/index.ts
3727
3859
  function parsePaymentHeader(headerValue) {
3728
3860
  if (!headerValue) {
@@ -3829,10 +3961,12 @@ var FacilitatorClient = class {
3829
3961
  baseUrl;
3830
3962
  timeout;
3831
3963
  explicitTimeout;
3964
+ retries;
3832
3965
  constructor(options = {}) {
3833
3966
  this.baseUrl = options.baseUrl || "https://facilitator.ultravioletadao.xyz";
3834
3967
  this.explicitTimeout = options.timeout !== void 0;
3835
3968
  this.timeout = options.timeout || 3e4;
3969
+ this.retries = options.retries;
3836
3970
  }
3837
3971
  /**
3838
3972
  * Get timeout for a specific network, using per-chain defaults when no explicit timeout was set.
@@ -3859,29 +3993,31 @@ var FacilitatorClient = class {
3859
3993
  */
3860
3994
  async verify(paymentHeader, requirements) {
3861
3995
  const body = buildVerifyRequest(paymentHeader, requirements);
3862
- const controller = new AbortController();
3863
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
3864
3996
  try {
3865
- const response = await fetch(`${this.baseUrl}/verify`, {
3866
- method: "POST",
3867
- headers: { "Content-Type": "application/json" },
3868
- body: JSON.stringify(body),
3869
- signal: controller.signal
3870
- });
3871
- clearTimeout(timeoutId);
3872
- if (!response.ok) {
3873
- const errorText = await response.text();
3997
+ const { response, error } = await facilitatorFetch(
3998
+ `${this.baseUrl}/verify`,
3999
+ {
4000
+ method: "POST",
4001
+ headers: { "Content-Type": "application/json" },
4002
+ body: JSON.stringify(body)
4003
+ },
4004
+ { timeoutMs: this.timeout, retries: this.retries }
4005
+ );
4006
+ if (error) {
3874
4007
  return {
3875
4008
  isValid: false,
3876
- invalidReason: `Facilitator error: ${response.status} - ${errorText}`
4009
+ invalidReason: error.error,
4010
+ ...failureFields(error)
3877
4011
  };
3878
4012
  }
3879
4013
  return await response.json();
3880
4014
  } catch (error) {
3881
- clearTimeout(timeoutId);
3882
4015
  return {
3883
4016
  isValid: false,
3884
- invalidReason: error instanceof Error ? error.message : "Unknown error"
4017
+ invalidReason: error instanceof Error ? error.message : "Unknown error",
4018
+ retryable: true,
4019
+ safeToReplay: false,
4020
+ retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
3885
4021
  };
3886
4022
  }
3887
4023
  }
@@ -3897,21 +4033,21 @@ var FacilitatorClient = class {
3897
4033
  async settle(paymentHeader, requirements) {
3898
4034
  const body = buildSettleRequest(paymentHeader, requirements);
3899
4035
  const settleTimeout = this.getTimeout(requirements.network);
3900
- const controller = new AbortController();
3901
- const timeoutId = setTimeout(() => controller.abort(), settleTimeout);
3902
4036
  try {
3903
- const response = await fetch(`${this.baseUrl}/settle`, {
3904
- method: "POST",
3905
- headers: { "Content-Type": "application/json" },
3906
- body: JSON.stringify(body),
3907
- signal: controller.signal
3908
- });
3909
- clearTimeout(timeoutId);
3910
- if (!response.ok) {
3911
- const errorText = await response.text();
4037
+ const { response, error } = await facilitatorFetch(
4038
+ `${this.baseUrl}/settle`,
4039
+ {
4040
+ method: "POST",
4041
+ headers: { "Content-Type": "application/json" },
4042
+ body: JSON.stringify(body)
4043
+ },
4044
+ { timeoutMs: settleTimeout, retries: this.retries }
4045
+ );
4046
+ if (error) {
3912
4047
  return {
3913
4048
  success: false,
3914
- error: `Facilitator error: ${response.status} - ${errorText}`
4049
+ error: error.error,
4050
+ ...failureFields(error)
3915
4051
  };
3916
4052
  }
3917
4053
  const result = await response.json();
@@ -3952,10 +4088,12 @@ var FacilitatorClient = class {
3952
4088
  payer: result.payer
3953
4089
  };
3954
4090
  } catch (error) {
3955
- clearTimeout(timeoutId);
3956
4091
  return {
3957
4092
  success: false,
3958
- error: error instanceof Error ? error.message : "Unknown error"
4093
+ error: error instanceof Error ? error.message : "Unknown error",
4094
+ retryable: true,
4095
+ safeToReplay: false,
4096
+ retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
3959
4097
  };
3960
4098
  }
3961
4099
  }
@@ -3975,7 +4113,8 @@ var FacilitatorClient = class {
3975
4113
  return {
3976
4114
  verified: false,
3977
4115
  settled: false,
3978
- error: verifyResult.invalidReason
4116
+ error: verifyResult.invalidReason,
4117
+ ...carryFailureFields(verifyResult)
3979
4118
  };
3980
4119
  }
3981
4120
  const settleResult = await this.settle(paymentHeader, requirements);
@@ -3983,7 +4122,8 @@ var FacilitatorClient = class {
3983
4122
  verified: true,
3984
4123
  settled: settleResult.success,
3985
4124
  transactionHash: settleResult.transactionHash,
3986
- error: settleResult.error
4125
+ error: settleResult.error,
4126
+ ...carryFailureFields(settleResult)
3987
4127
  };
3988
4128
  }
3989
4129
  /**
@@ -4385,10 +4525,29 @@ function createVerifiedPaymentState(client, payment, requirements, verifyResult)
4385
4525
  }
4386
4526
  };
4387
4527
  }
4528
+ function respondUnavailable(res, message, failure) {
4529
+ const seconds = Math.max(1, Math.ceil(failure.retryAfterSeconds ?? DEFAULT_RETRY_AFTER_SECONDS));
4530
+ const body = {
4531
+ error: message,
4532
+ reason: failure.reason ?? failure.invalidReason ?? failure.error,
4533
+ retryable: true,
4534
+ retryAfterSeconds: seconds,
4535
+ // False for `forward_failed` and for a bare timeout: the write may already
4536
+ // have landed, so the caller must reconcile before resending.
4537
+ safeToReplay: failure.safeToReplay === true
4538
+ };
4539
+ const staged = res.status(503);
4540
+ if (typeof staged.set === "function") {
4541
+ staged.set({ "Retry-After": String(seconds) }).json(body);
4542
+ return;
4543
+ }
4544
+ staged.json(body);
4545
+ }
4388
4546
  function createPaymentMiddleware(getRequirements, options = {}) {
4389
4547
  const client = new FacilitatorClient({
4390
4548
  baseUrl: options.facilitatorUrl || options.baseUrl,
4391
- timeout: options.timeout
4549
+ timeout: options.timeout,
4550
+ retries: options.retries
4392
4551
  });
4393
4552
  const settlementStrategy = options.settlementStrategy || "before-handler";
4394
4553
  return async (req, res, next) => {
@@ -4403,6 +4562,10 @@ function createPaymentMiddleware(getRequirements, options = {}) {
4403
4562
  const requirements = buildPaymentRequirements(reqOptions);
4404
4563
  const verifyResult = await client.verify(payment, requirements);
4405
4564
  if (!verifyResult.isValid) {
4565
+ if (verifyResult.retryable) {
4566
+ respondUnavailable(res, "Payment verification unavailable", verifyResult);
4567
+ return;
4568
+ }
4406
4569
  res.status(402).json({
4407
4570
  error: "Payment verification failed",
4408
4571
  reason: verifyResult.invalidReason
@@ -4413,6 +4576,10 @@ function createPaymentMiddleware(getRequirements, options = {}) {
4413
4576
  if (settlementStrategy === "before-handler") {
4414
4577
  const settleResult = await req.x402.settle();
4415
4578
  if (!settleResult.success) {
4579
+ if (settleResult.retryable) {
4580
+ respondUnavailable(res, "Payment settlement unavailable", settleResult);
4581
+ return;
4582
+ }
4416
4583
  res.status(500).json({
4417
4584
  error: "Payment settlement failed",
4418
4585
  reason: settleResult.error || "Unknown settlement error"
@@ -4423,10 +4590,25 @@ function createPaymentMiddleware(getRequirements, options = {}) {
4423
4590
  next();
4424
4591
  };
4425
4592
  }
4593
+ function honoUnavailable(c, message, failure) {
4594
+ const seconds = Math.max(1, Math.ceil(failure.retryAfterSeconds ?? DEFAULT_RETRY_AFTER_SECONDS));
4595
+ c.header?.("Retry-After", String(seconds));
4596
+ return c.json(
4597
+ {
4598
+ error: message,
4599
+ reason: failure.reason ?? failure.invalidReason ?? failure.error,
4600
+ retryable: true,
4601
+ retryAfterSeconds: seconds,
4602
+ safeToReplay: failure.safeToReplay === true
4603
+ },
4604
+ 503
4605
+ );
4606
+ }
4426
4607
  function createHonoMiddleware(options) {
4427
4608
  const client = new FacilitatorClient({
4428
4609
  baseUrl: options.facilitatorUrl || options.baseUrl,
4429
- timeout: options.timeout
4610
+ timeout: options.timeout,
4611
+ retries: options.retries
4430
4612
  });
4431
4613
  const settlementStrategy = options.settlementStrategy || "before-handler";
4432
4614
  if (!options.accepts[0]) {
@@ -4466,6 +4648,9 @@ function createHonoMiddleware(options) {
4466
4648
  }
4467
4649
  const verifyResult = await client.verify(parsed, requirement);
4468
4650
  if (!verifyResult.isValid) {
4651
+ if (verifyResult.retryable) {
4652
+ return honoUnavailable(c, "Payment verification unavailable", verifyResult);
4653
+ }
4469
4654
  return c.json({
4470
4655
  error: "Payment verification failed",
4471
4656
  reason: verifyResult.invalidReason
@@ -4476,6 +4661,9 @@ function createHonoMiddleware(options) {
4476
4661
  if (settlementStrategy === "before-handler") {
4477
4662
  const settleResult = await verifiedPayment.settle();
4478
4663
  if (!settleResult.success) {
4664
+ if (settleResult.retryable) {
4665
+ return honoUnavailable(c, "Payment settlement unavailable", settleResult);
4666
+ }
4479
4667
  return c.json({
4480
4668
  error: "Payment settlement failed",
4481
4669
  reason: settleResult.error || "Unknown error"
@@ -4508,14 +4696,17 @@ var ESCROW_TIMEOUT_MS = {
4508
4696
  // Monad: 90s
4509
4697
  };
4510
4698
 
4699
+ exports.AMBIGUOUS_LEASE_REASONS = AMBIGUOUS_LEASE_REASONS;
4511
4700
  exports.ANCHOR_MAX_REQUEST_BYTES = ANCHOR_MAX_REQUEST_BYTES;
4512
4701
  exports.CAIP2_IDENTIFIERS = CAIP2_IDENTIFIERS;
4513
4702
  exports.CAIP2_TO_CHAIN = CAIP2_TO_CHAIN;
4514
4703
  exports.ContentHashMismatch = ContentHashMismatch;
4515
4704
  exports.DEFAULT_CHAIN = DEFAULT_CHAIN;
4516
4705
  exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
4706
+ exports.DEFAULT_FACILITATOR_RETRIES = DEFAULT_FACILITATOR_RETRIES;
4517
4707
  exports.DEFAULT_FACILITATOR_URL = DEFAULT_FACILITATOR_URL;
4518
4708
  exports.DEFAULT_PAYMENT_HEADER = DEFAULT_PAYMENT_HEADER;
4709
+ exports.DEFAULT_RETRY_AFTER_SECONDS = DEFAULT_RETRY_AFTER_SECONDS;
4519
4710
  exports.DELEGATE_PREFIX = DELEGATE_PREFIX;
4520
4711
  exports.DX402Error = DX402Error;
4521
4712
  exports.ESCROW_DEPOSIT_LIMIT_USD = ESCROW_DEPOSIT_LIMIT_USD;
@@ -4527,13 +4718,16 @@ exports.EvidenceSkipped = EvidenceSkipped;
4527
4718
  exports.FACILITATOR_ADDRESSES = FACILITATOR_ADDRESSES;
4528
4719
  exports.FacilitatorClient = FacilitatorClient;
4529
4720
  exports.KEEPALIVE_INTERVAL_MS = KEEPALIVE_INTERVAL_MS;
4721
+ exports.MAX_RETRY_AFTER_SECONDS = MAX_RETRY_AFTER_SECONDS;
4530
4722
  exports.OPERATOR_FEE_BPS = OPERATOR_FEE_BPS;
4531
4723
  exports.OWSWalletAdapter = OWSWalletAdapter;
4532
4724
  exports.PAYMENT_HEADER_NAMES = PAYMENT_HEADER_NAMES;
4725
+ exports.REPLAYABLE_LEASE_REASONS = REPLAYABLE_LEASE_REASONS;
4533
4726
  exports.SMA_WRAP_TARGETS = SMA_WRAP_TARGETS;
4534
4727
  exports.SSEParser = SSEParser;
4535
4728
  exports.SUPPORTED_CHAINS = SUPPORTED_CHAINS;
4536
4729
  exports.TrafficStreamError = TrafficStreamError;
4730
+ exports.WRITER_LEASE_REASONS = WRITER_LEASE_REASONS;
4537
4731
  exports.X402Client = X402Client;
4538
4732
  exports.X402Error = X402Error;
4539
4733
  exports.X402_CORS_HEADERS = X402_CORS_HEADERS;
@@ -4577,6 +4771,7 @@ exports.encodeX402Header = encodeX402Header;
4577
4771
  exports.evidenceFromHeaders = evidenceFromHeaders;
4578
4772
  exports.evidenceHeader = evidenceHeader;
4579
4773
  exports.extractPaymentFromHeaders = extractPaymentFromHeaders;
4774
+ exports.facilitatorFetch = facilitatorFetch;
4580
4775
  exports.fetchNonce = fetchNonce;
4581
4776
  exports.generatePaymentOptions = generatePaymentOptions;
4582
4777
  exports.getAlgorandChains = getAlgorandChains;
@@ -4599,10 +4794,12 @@ exports.getTokenByAddress = getTokenByAddress;
4599
4794
  exports.getTokenConfig = getTokenConfig;
4600
4795
  exports.getXRPLChains = getXRPLChains;
4601
4796
  exports.isAlgorandChain = isAlgorandChain;
4797
+ exports.isAmbiguousLeaseReason = isAmbiguousLeaseReason;
4602
4798
  exports.isCAIP2Format = isCAIP2Format;
4603
4799
  exports.isChainSupported = isChainSupported;
4604
4800
  exports.isDelegated = isDelegated;
4605
4801
  exports.isEndToEnd = isEndToEnd;
4802
+ exports.isReplayableLeaseReason = isReplayableLeaseReason;
4606
4803
  exports.isSVMChain = isSVMChain;
4607
4804
  exports.isSuiChain = isSuiChain;
4608
4805
  exports.isTokenSupported = isTokenSupported;
@@ -4611,11 +4808,13 @@ exports.matchesFilters = matchesFilters;
4611
4808
  exports.needsAccountWrap = needsAccountWrap;
4612
4809
  exports.parseEvidenceHeader = parseEvidenceHeader;
4613
4810
  exports.parseNetworkIdentifier = parseNetworkIdentifier;
4811
+ exports.parseRetryAfterSeconds = parseRetryAfterSeconds;
4614
4812
  exports.parseSealed = parseSealed;
4615
4813
  exports.parseTrafficEvent = parseTrafficEvent;
4616
4814
  exports.payerKeyFromEvmSignature = payerKeyFromEvmSignature;
4617
4815
  exports.payerKeyFromSolanaAddress = payerKeyFromSolanaAddress;
4618
4816
  exports.paymentChallengeFrom = paymentChallengeFrom;
4817
+ exports.readFacilitatorError = readFacilitatorError;
4619
4818
  exports.recoverEvidence = recoverEvidence;
4620
4819
  exports.replaySafeTypedData = replaySafeTypedData;
4621
4820
  exports.resolveDelegation = resolveDelegation;