uvd-x402-sdk 2.74.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.
Files changed (86) hide show
  1. package/README.md +145 -1
  2. package/dist/adapters/index.d.mts +3 -3
  3. package/dist/adapters/index.d.ts +3 -3
  4. package/dist/adapters/index.js.map +1 -1
  5. package/dist/adapters/index.mjs.map +1 -1
  6. package/dist/backend/index.d.mts +350 -54
  7. package/dist/backend/index.d.ts +350 -54
  8. package/dist/backend/index.js +488 -253
  9. package/dist/backend/index.js.map +1 -1
  10. package/dist/backend/index.mjs +477 -254
  11. package/dist/backend/index.mjs.map +1 -1
  12. package/dist/erc8128/index.d.mts +1 -1
  13. package/dist/erc8128/index.d.ts +1 -1
  14. package/dist/{index-BkeMrSHP.d.mts → index-CiRbsqXe.d.mts} +2 -2
  15. package/dist/{index-BQ45e-Xa.d.ts → index-DTXTOEby.d.ts} +2 -2
  16. package/dist/{index-DeJMYEKC.d.mts → index-ZH10otHE.d.mts} +1 -1
  17. package/dist/{index-DeJMYEKC.d.ts → index-ZH10otHE.d.ts} +1 -1
  18. package/dist/index.d.mts +50 -10
  19. package/dist/index.d.ts +50 -10
  20. package/dist/index.js +238 -39
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +228 -40
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/{ows-Z9v4GxOZ.d.mts → ows-CYIVd4xO.d.mts} +1 -1
  25. package/dist/{ows-C-KmORG9.d.ts → ows-DTDixPzO.d.ts} +1 -1
  26. package/dist/providers/algorand/index.d.mts +1 -1
  27. package/dist/providers/algorand/index.d.ts +1 -1
  28. package/dist/providers/algorand/index.js +2 -0
  29. package/dist/providers/algorand/index.js.map +1 -1
  30. package/dist/providers/algorand/index.mjs +2 -0
  31. package/dist/providers/algorand/index.mjs.map +1 -1
  32. package/dist/providers/evm/index.d.mts +1 -1
  33. package/dist/providers/evm/index.d.ts +1 -1
  34. package/dist/providers/evm/index.js.map +1 -1
  35. package/dist/providers/evm/index.mjs.map +1 -1
  36. package/dist/providers/near/index.d.mts +1 -1
  37. package/dist/providers/near/index.d.ts +1 -1
  38. package/dist/providers/near/index.js +2 -0
  39. package/dist/providers/near/index.js.map +1 -1
  40. package/dist/providers/near/index.mjs +2 -0
  41. package/dist/providers/near/index.mjs.map +1 -1
  42. package/dist/providers/solana/index.d.mts +1 -1
  43. package/dist/providers/solana/index.d.ts +1 -1
  44. package/dist/providers/solana/index.js +2 -0
  45. package/dist/providers/solana/index.js.map +1 -1
  46. package/dist/providers/solana/index.mjs +2 -0
  47. package/dist/providers/solana/index.mjs.map +1 -1
  48. package/dist/providers/stellar/index.d.mts +1 -1
  49. package/dist/providers/stellar/index.d.ts +1 -1
  50. package/dist/providers/stellar/index.js +2 -0
  51. package/dist/providers/stellar/index.js.map +1 -1
  52. package/dist/providers/stellar/index.mjs +2 -0
  53. package/dist/providers/stellar/index.mjs.map +1 -1
  54. package/dist/providers/sui/index.d.mts +1 -1
  55. package/dist/providers/sui/index.d.ts +1 -1
  56. package/dist/providers/sui/index.js +2 -0
  57. package/dist/providers/sui/index.js.map +1 -1
  58. package/dist/providers/sui/index.mjs +2 -0
  59. package/dist/providers/sui/index.mjs.map +1 -1
  60. package/dist/providers/xrpl/index.d.mts +1 -1
  61. package/dist/providers/xrpl/index.d.ts +1 -1
  62. package/dist/providers/xrpl/index.js +2 -0
  63. package/dist/providers/xrpl/index.js.map +1 -1
  64. package/dist/providers/xrpl/index.mjs +2 -0
  65. package/dist/providers/xrpl/index.mjs.map +1 -1
  66. package/dist/react/index.d.mts +3 -3
  67. package/dist/react/index.d.ts +3 -3
  68. package/dist/react/index.js.map +1 -1
  69. package/dist/react/index.mjs.map +1 -1
  70. package/dist/utils/index.d.mts +31 -260
  71. package/dist/utils/index.d.ts +31 -260
  72. package/dist/utils/index.js +17 -0
  73. package/dist/utils/index.js.map +1 -1
  74. package/dist/utils/index.mjs +16 -1
  75. package/dist/utils/index.mjs.map +1 -1
  76. package/dist/validation-De3bessL.d.mts +273 -0
  77. package/dist/validation-DsbfDAtV.d.ts +273 -0
  78. package/dist/{wallet-w7BnImDG.d.mts → wallet-0cX9Pw2F.d.mts} +1 -1
  79. package/dist/{wallet-w7BnImDG.d.ts → wallet-0cX9Pw2F.d.ts} +1 -1
  80. package/package.json +1 -1
  81. package/src/backend/facilitator-error.ts +355 -0
  82. package/src/backend/index.ts +536 -263
  83. package/src/dx402.ts +123 -8
  84. package/src/index.ts +17 -0
  85. package/src/utils/index.ts +5 -0
  86. package/src/utils/personal-sign.ts +54 -0
@@ -1,5 +1,210 @@
1
- import { S as SigningWalletAdapter } from '../wallet-w7BnImDG.mjs';
2
- import { e as X402Header, X as X402Version, f as X402PayloadData } from '../index-DeJMYEKC.mjs';
1
+ import { S as SigningWalletAdapter } from '../wallet-0cX9Pw2F.mjs';
2
+ import { x as X402Header, Q as X402Version, G as X402PayloadData } from '../index-ZH10otHE.mjs';
3
+
4
+ /**
5
+ * Reading a facilitator refusal as DATA instead of prose.
6
+ *
7
+ * # Why this file exists
8
+ *
9
+ * `402` and `503` say opposite things and the SDK used to collapse both into
10
+ * `success: false` plus an English sentence.
11
+ *
12
+ * - **402** means the payment was REJECTED. The credential is spent; sign a new
13
+ * authorization.
14
+ * - **503** means NO VERDICT WAS REACHED. Nothing was rejected and nothing was
15
+ * executed; retry the SAME credential.
16
+ *
17
+ * Turning a 503 into a 402 makes the buyer sign and send a second payment for a
18
+ * money movement that was never refused — they pay twice. That is the most
19
+ * expensive mistake this SDK can make, so every facilitator edge now reports the
20
+ * status, the facilitator's own `reason`, and whether the request may be
21
+ * replayed.
22
+ *
23
+ * # The writer lease
24
+ *
25
+ * The facilitator serialises every EVM write through the one process that holds
26
+ * the EVM writer lease, because they all spend gas from the same EOA and the
27
+ * nonce for it is allocated in memory. A task that does not hold the lease
28
+ * forwards the request to the one that does; when it cannot, it answers
29
+ * `503` + `Retry-After: 5` + `{"error": "...", "reason": "<why>"}`.
30
+ *
31
+ * The five reasons do NOT share retry semantics, which is the whole point of
32
+ * surfacing them:
33
+ *
34
+ * | reason | did the write run? | replay? |
35
+ * |----------------------------|--------------------|---------|
36
+ * | `holder_unknown` | no | yes |
37
+ * | `forwarding_disabled` | no | yes |
38
+ * | `forwarded_but_not_writer` | no | yes |
39
+ * | `body_unreadable` | no | yes |
40
+ * | `forward_failed` | **maybe** | **no** |
41
+ *
42
+ * `forward_failed` is emitted after the forward was attempted: the holder may
43
+ * have executed the write and the response been lost on the way back. It is a
44
+ * timeout wearing a status code. Replaying a `/register` on it is exactly the
45
+ * sequence that once minted five duplicate agents — reconcile with
46
+ * `GET /identity/{network}/owner/{recipient}` or `getRegisterStatus` instead.
47
+ *
48
+ * Source of the shape: x402-rs `src/handlers.rs` `writer_lease_unavailable()`
49
+ * and `require_writer_lease()`.
50
+ */
51
+ /**
52
+ * A `reason` the facilitator attaches to a writer-lease 503.
53
+ *
54
+ * Typed as a union for discrimination but never used to VALIDATE: the fields
55
+ * that carry it are plain `string`, so a reason added on the server does not
56
+ * break compilation here.
57
+ */
58
+ type WriterLeaseReason = 'holder_unknown' | 'forwarding_disabled' | 'forwarded_but_not_writer' | 'body_unreadable' | 'forward_failed';
59
+ /** Every writer-lease reason the facilitator emits today. */
60
+ declare const WRITER_LEASE_REASONS: readonly WriterLeaseReason[];
61
+ /**
62
+ * The reasons returned BEFORE the write is handed to anyone.
63
+ *
64
+ * The facilitator refuses these in its router, so the request provably did not
65
+ * execute and re-sending the identical body is safe — including a mint.
66
+ */
67
+ declare const REPLAYABLE_LEASE_REASONS: readonly WriterLeaseReason[];
68
+ /**
69
+ * The reason that is ambiguous: the write may already have happened.
70
+ *
71
+ * Still `retryable` — the credential was not rejected — but never replayed
72
+ * automatically. Resolve it by reading state, not by re-POSTing.
73
+ */
74
+ declare const AMBIGUOUS_LEASE_REASONS: readonly WriterLeaseReason[];
75
+ /**
76
+ * Ceiling, in seconds, on how long an automatic retry will wait.
77
+ *
78
+ * `Retry-After` is a hint from a server that may be misconfigured. Honouring a
79
+ * literal `Retry-After: 3600` would hang the caller's request for an hour
80
+ * inside a function documented as returning promptly, so the header is honoured
81
+ * only up to this bound.
82
+ */
83
+ declare const MAX_RETRY_AFTER_SECONDS = 15;
84
+ /** Wait used when a 503 carries no usable `Retry-After`. */
85
+ declare const DEFAULT_RETRY_AFTER_SECONDS = 5;
86
+ /** How many EXTRA attempts a retryable facilitator refusal gets by default. */
87
+ declare const DEFAULT_FACILITATOR_RETRIES = 2;
88
+ /**
89
+ * A non-2xx answer from the facilitator, kept structured.
90
+ *
91
+ * `error` is byte-identical to the string this SDK has always produced, so
92
+ * callers matching on it keep working; everything else is new and optional.
93
+ */
94
+ interface FacilitatorErrorInfo {
95
+ /** Legacy flattened message: `Facilitator error: <status> - <body>`. */
96
+ error: string;
97
+ /** HTTP status the facilitator answered with. */
98
+ status: number;
99
+ /** The facilitator's own `reason`, when the body carried one. */
100
+ reason?: string;
101
+ /**
102
+ * Seconds to wait before retrying, already clamped to
103
+ * {@link MAX_RETRY_AFTER_SECONDS}. Absent when the answer is not retryable.
104
+ */
105
+ retryAfterSeconds?: number;
106
+ /**
107
+ * The request reached no verdict; the credential is untouched and the SAME
108
+ * request may be sent again. Never surface this as a payment rejection.
109
+ */
110
+ retryable: boolean;
111
+ /**
112
+ * The facilitator NAMED a reason that proves it executed nothing, so an
113
+ * automatic replay cannot double-write.
114
+ *
115
+ * False for `forward_failed`, whose write may already have landed, and false
116
+ * for an unattributed 5xx from a proxy — "something in front answered" is not
117
+ * evidence that nothing ran.
118
+ */
119
+ safeToReplay: boolean;
120
+ /** Raw response body, for logs. */
121
+ body: string;
122
+ }
123
+ /** Fields every facilitator response type gained so a 503 stops looking terminal. */
124
+ interface FacilitatorFailureFields {
125
+ /** HTTP status, when the failure was transport-level rather than a verdict. */
126
+ status?: number;
127
+ /** The facilitator's `reason` for the refusal (see {@link WriterLeaseReason}). */
128
+ reason?: string;
129
+ /** True when the same request may be sent again without re-signing anything. */
130
+ retryable?: boolean;
131
+ /** Seconds to wait before retrying, clamped to {@link MAX_RETRY_AFTER_SECONDS}. */
132
+ retryAfterSeconds?: number;
133
+ /** True when the facilitator provably executed nothing. */
134
+ safeToReplay?: boolean;
135
+ }
136
+ /** A 503/429 that carries a reason known to be pre-execution. */
137
+ declare function isReplayableLeaseReason(reason?: string): boolean;
138
+ /** A 503 whose write may already have run. Reconcile, do not re-POST. */
139
+ declare function isAmbiguousLeaseReason(reason?: string): boolean;
140
+ /**
141
+ * Read `Retry-After` and clamp it.
142
+ *
143
+ * Tolerates a `Response`-shaped object with no `headers` at all: test doubles
144
+ * and non-standard fetch polyfills routinely omit it, and throwing there would
145
+ * turn a readable refusal into an unreadable crash.
146
+ */
147
+ declare function parseRetryAfterSeconds(response: {
148
+ headers?: {
149
+ get?: (name: string) => string | null;
150
+ };
151
+ }): number | undefined;
152
+ /**
153
+ * Turn a non-2xx facilitator response into {@link FacilitatorErrorInfo}.
154
+ *
155
+ * Reads the body exactly once. The `error` string keeps the historical format
156
+ * verbatim; reformatting it would break callers that match on it.
157
+ */
158
+ declare function readFacilitatorError(response: {
159
+ status: number;
160
+ text: () => Promise<string>;
161
+ headers?: {
162
+ get?: (name: string) => string | null;
163
+ };
164
+ }): Promise<FacilitatorErrorInfo>;
165
+ /**
166
+ * Copy the failure fields off one response onto another.
167
+ *
168
+ * Used where a wrapper returns its own shape: without this the wrapper flattens
169
+ * a `503` back into a bare failure and reintroduces, one level up, exactly the
170
+ * ambiguity the fields exist to remove.
171
+ */
172
+ declare function carryFailureFields(source: FacilitatorFailureFields): FacilitatorFailureFields;
173
+ /** Options for {@link facilitatorFetch}. */
174
+ interface FacilitatorFetchOptions {
175
+ /** Abort the request after this many milliseconds. */
176
+ timeoutMs: number;
177
+ /** Extra attempts after the first. Default {@link DEFAULT_FACILITATOR_RETRIES}. */
178
+ retries?: number;
179
+ /**
180
+ * Whether this particular refusal may be replayed.
181
+ *
182
+ * Default: only when the facilitator proved it executed nothing
183
+ * (`info.safeToReplay`). Pass a stricter predicate on paths where even a
184
+ * proven-safe replay is unwanted.
185
+ */
186
+ canReplay?: (info: FacilitatorErrorInfo) => boolean;
187
+ /** Injected for tests. */
188
+ fetchImpl?: typeof fetch;
189
+ /** Injected for tests, so a retry does not really sleep. */
190
+ sleepImpl?: (ms: number) => Promise<void>;
191
+ }
192
+ /**
193
+ * POST/GET the facilitator, replaying a refusal that provably executed nothing.
194
+ *
195
+ * Returns the response plus, when it was not ok, the structured refusal — the
196
+ * body has already been consumed to build it, so callers must not read it
197
+ * again.
198
+ *
199
+ * Network errors and timeouts still THROW, exactly as before: callers already
200
+ * have `catch` blocks that turn them into `{ success: false }`, and an
201
+ * `AbortError` on the escrow paths triggers an on-chain reconciliation that
202
+ * must keep firing.
203
+ */
204
+ declare function facilitatorFetch(url: string, init: RequestInit, options: FacilitatorFetchOptions): Promise<{
205
+ response: Response;
206
+ error?: FacilitatorErrorInfo;
207
+ }>;
3
208
 
4
209
  /**
5
210
  * Payment requirements sent to the facilitator
@@ -47,8 +252,16 @@ interface SettleRequest {
47
252
  /**
48
253
  * Verify response from the facilitator
49
254
  */
50
- interface VerifyResponse {
255
+ interface VerifyResponse extends FacilitatorFailureFields {
51
256
  isValid: boolean;
257
+ /**
258
+ * Why the payment is not valid.
259
+ *
260
+ * Read `retryable` before showing this to anyone. When `retryable` is true the
261
+ * facilitator reached NO VERDICT -- it did not reject the payment, so this
262
+ * string is a transport diagnosis, not a rejection, and re-signing on it makes
263
+ * the buyer pay twice.
264
+ */
52
265
  invalidReason?: string;
53
266
  payer?: string;
54
267
  network?: string;
@@ -56,11 +269,17 @@ interface VerifyResponse {
56
269
  /**
57
270
  * Settle response from the facilitator
58
271
  */
59
- interface SettleResponse {
272
+ interface SettleResponse extends FacilitatorFailureFields {
60
273
  success: boolean;
61
274
  transactionHash?: string;
62
275
  network?: string;
63
- /** Transport-level failure (unreachable facilitator, non-2xx, timeout). */
276
+ /**
277
+ * Transport-level failure (unreachable facilitator, non-2xx, timeout).
278
+ *
279
+ * `success: false` with `retryable: true` is NOT a rejected payment. The
280
+ * authorization is untouched and the same one must be resent; treating it as
281
+ * a refusal and asking for a new signature charges the buyer twice.
282
+ */
64
283
  error?: string;
65
284
  /**
66
285
  * The facilitator's own reason when it settled nothing — e.g. a transfer that
@@ -357,6 +576,15 @@ interface FacilitatorClientOptions {
357
576
  * Set explicitly to override per-network auto-detection.
358
577
  */
359
578
  timeout?: number;
579
+ /**
580
+ * Extra attempts after the first when the facilitator answers a refusal it
581
+ * proved it did not execute (`safeToReplay`). Default 2; `0` disables.
582
+ *
583
+ * Only ever spent on `429` and on a `503` naming a pre-execution writer-lease
584
+ * reason. An ambiguous `forward_failed` -- whose write may already have landed
585
+ * -- is never replayed here, at any setting.
586
+ */
587
+ retries?: number;
360
588
  }
361
589
  /**
362
590
  * Client for interacting with the x402 facilitator API
@@ -382,6 +610,7 @@ declare class FacilitatorClient {
382
610
  private readonly baseUrl;
383
611
  private readonly timeout;
384
612
  private readonly explicitTimeout;
613
+ private readonly retries;
385
614
  constructor(options?: FacilitatorClientOptions);
386
615
  /**
387
616
  * Get timeout for a specific network, using per-chain defaults when no explicit timeout was set.
@@ -422,7 +651,7 @@ declare class FacilitatorClient {
422
651
  settled: boolean;
423
652
  transactionHash?: string;
424
653
  error?: string;
425
- }>;
654
+ } & FacilitatorFailureFields>;
426
655
  /**
427
656
  * Check if the facilitator is healthy
428
657
  *
@@ -645,37 +874,6 @@ interface HonoMiddlewareOptions extends PaymentMiddlewareOptions {
645
874
  /** Custom requirement resolver for ambiguous multi-accept flows */
646
875
  resolveRequirement?: PaymentRequirementResolver;
647
876
  }
648
- /**
649
- * Create a Hono-compatible middleware for x402 payments.
650
- *
651
- * Handles the x402 payment flow:
652
- * 1. Returns 402 with payment requirements if no X-PAYMENT header
653
- * 2. Verifies the payment with the facilitator
654
- * 3. Optionally settles before the handler when settlementStrategy is set
655
- * 4. Passes control to the next handler on success
656
- *
657
- * @param options - Middleware options with facilitator URL and payment accepts
658
- * @returns Hono middleware function
659
- *
660
- * @example
661
- * ```ts
662
- * import { createHonoMiddleware } from 'uvd-x402-sdk';
663
- *
664
- * const paywall = createHonoMiddleware({
665
- * accepts: [{
666
- * network: 'skale-base',
667
- * asset: '0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20',
668
- * amount: '1000000',
669
- * payTo: '0xYourWallet',
670
- * extra: { name: 'Bridged USDC (SKALE Bridge)', version: '2' },
671
- * }],
672
- * });
673
- *
674
- * app.get('/api/premium', paywall, (c) => {
675
- * return c.json({ message: 'Premium content!' });
676
- * });
677
- * ```
678
- */
679
877
  declare function createHonoMiddleware(options: HonoMiddlewareOptions): (c: {
680
878
  req: {
681
879
  header: (name: string) => string | undefined;
@@ -683,6 +881,8 @@ declare function createHonoMiddleware(options: HonoMiddlewareOptions): (c: {
683
881
  };
684
882
  json: (body: unknown, status?: number) => unknown;
685
883
  set?: (key: string, value: unknown) => void;
884
+ /** Hono's response-header setter. Optional so older context doubles still fit. */
885
+ header?: (name: string, value: string) => void;
686
886
  }, next: () => Promise<void>) => Promise<unknown>;
687
887
  /**
688
888
  * Maximum length of the free-text `q` filter.
@@ -1744,7 +1944,7 @@ interface FeedbackRequest {
1744
1944
  /**
1745
1945
  * Feedback response from POST /feedback
1746
1946
  */
1747
- interface FeedbackResponse {
1947
+ interface FeedbackResponse extends FacilitatorFailureFields {
1748
1948
  /** Whether the feedback was successfully submitted */
1749
1949
  success: boolean;
1750
1950
  /** Transaction hash of the feedback submission */
@@ -1774,11 +1974,42 @@ declare class Erc8004LookupError extends Error {
1774
1974
  readonly status: number;
1775
1975
  /** Raw response body, for debugging */
1776
1976
  readonly body: string;
1777
- constructor(message: string, status: number, body: string);
1977
+ /**
1978
+ * `Retry-After`, already clamped, when the facilitator sent one.
1979
+ *
1980
+ * Optional so every existing three-argument construction keeps compiling; it
1981
+ * falls back to the default wait rather than to zero, because a caller that
1982
+ * retries instantly on a 503 is the load that caused it.
1983
+ */
1984
+ private readonly retryAfterHint;
1985
+ constructor(message: string, status: number, body: string, retryAfterSeconds?: number);
1778
1986
  /** The address genuinely owns no agent on this network. */
1779
1987
  get notFound(): boolean;
1780
- /** The lookup reached no verdict. Retry; never read as "owns nothing". */
1988
+ /**
1989
+ * The lookup reached no verdict. Retry; never read as "owns nothing".
1990
+ *
1991
+ * `502` and `504` join `503` and `429` here: a gateway that answered on the
1992
+ * facilitator's behalf is exactly as silent about the agent's existence, and
1993
+ * reading either as absence has the same consequence -- a duplicate mint.
1994
+ */
1781
1995
  get retryable(): boolean;
1996
+ /**
1997
+ * The facilitator's own `reason`, when the body carried one.
1998
+ *
1999
+ * On a WRITE route this is the writer-lease reason and it decides whether the
2000
+ * request may be re-sent; see {@link isReplayableLeaseReason}.
2001
+ */
2002
+ get reason(): string | undefined;
2003
+ /**
2004
+ * The facilitator NAMED a reason proving it executed nothing.
2005
+ *
2006
+ * False for `forward_failed` and for every unattributed 5xx: "something
2007
+ * answered" is not evidence that nothing ran. On `/register`, replaying when
2008
+ * this is false is the sequence that minted five duplicate agents.
2009
+ */
2010
+ get safeToReplay(): boolean;
2011
+ /** Seconds to wait before retrying, clamped. Absent when not retryable. */
2012
+ get retryAfterSeconds(): number | undefined;
1782
2013
  }
1783
2014
  /**
1784
2015
  * ATOM Engine reputation analytics (Solana only).
@@ -1851,7 +2082,7 @@ interface RegisterAgentRequest {
1851
2082
  /**
1852
2083
  * Response from POST /register
1853
2084
  */
1854
- interface RegisterAgentResponse {
2085
+ interface RegisterAgentResponse extends FacilitatorFailureFields {
1855
2086
  /** Whether registration succeeded */
1856
2087
  success: boolean;
1857
2088
  /** The newly assigned agent ID (EVM: tokenId number, Solana: base58 pubkey string) */
@@ -1972,6 +2203,12 @@ interface Erc8004ClientOptions {
1972
2203
  baseUrl?: string;
1973
2204
  /** Request timeout in milliseconds (default: 30000) */
1974
2205
  timeout?: number;
2206
+ /**
2207
+ * Extra attempts after the first, spent only on a refusal the facilitator
2208
+ * proved it did not execute. Default 2; `0` disables. An ambiguous
2209
+ * `forward_failed` is never replayed at any setting.
2210
+ */
2211
+ retries?: number;
1975
2212
  }
1976
2213
  /**
1977
2214
  * Client for ERC-8004 Trustless Agents API
@@ -2013,7 +2250,21 @@ interface Erc8004ClientOptions {
2013
2250
  declare class Erc8004Client {
2014
2251
  private readonly baseUrl;
2015
2252
  private readonly timeout;
2253
+ private readonly retries;
2016
2254
  constructor(options?: Erc8004ClientOptions);
2255
+ /**
2256
+ * POST a write route, keeping a refusal readable.
2257
+ *
2258
+ * Every ERC-8004 write goes through the facilitator's EVM writer lease, so
2259
+ * every one of them can answer `503` + `reason`. Flattened to a string, those
2260
+ * are indistinguishable from "the registry rejected your feedback" — and on
2261
+ * `/register` the wrong reading re-POSTs a mint that may already have landed,
2262
+ * which is precisely how five duplicate agents were once created.
2263
+ *
2264
+ * A refusal the facilitator proved it did not execute is replayed
2265
+ * automatically (`safeToReplay`); `forward_failed` never is.
2266
+ */
2267
+ private writeJson;
2017
2268
  /**
2018
2269
  * Get agent identity from the Identity Registry
2019
2270
  *
@@ -2489,10 +2740,19 @@ interface AdvancedAuthorizationResult {
2489
2740
  /**
2490
2741
  * Result of an on-chain transaction (release, refund, charge).
2491
2742
  */
2492
- interface AdvancedTransactionResult {
2743
+ interface AdvancedTransactionResult extends FacilitatorFailureFields {
2493
2744
  success: boolean;
2494
2745
  transactionHash?: string;
2495
2746
  gasUsed?: number;
2747
+ /**
2748
+ * Why it failed.
2749
+ *
2750
+ * On the gasless (`*ViaFacilitator`) paths, check `retryable` first: a
2751
+ * facilitator that could not hand the write to its lease holder rejected
2752
+ * nothing on-chain, and the escrow is exactly as it was. Reporting that as a
2753
+ * failed refund is how funds get written off while they are still sitting in
2754
+ * escrow, recoverable.
2755
+ */
2496
2756
  error?: string;
2497
2757
  }
2498
2758
  /**
@@ -2583,6 +2843,11 @@ interface AdvancedEscrowClientOptions {
2583
2843
  * ```
2584
2844
  */
2585
2845
  wallet?: SigningWalletAdapter;
2846
+ /**
2847
+ * Extra attempts on the gasless facilitator paths when the facilitator proved
2848
+ * it executed nothing. Default 2; `0` disables.
2849
+ */
2850
+ retries?: number;
2586
2851
  }
2587
2852
  /**
2588
2853
  * Minimal PaymentOperator ABI for the 4 on-chain functions.
@@ -2644,6 +2909,7 @@ declare class AdvancedEscrowClient {
2644
2909
  private chainId;
2645
2910
  private gasLimit;
2646
2911
  private readonly timeout;
2912
+ private readonly retries;
2647
2913
  private contracts;
2648
2914
  private signer;
2649
2915
  private walletAdapter;
@@ -2751,23 +3017,53 @@ declare class AdvancedEscrowClient {
2751
3017
  */
2752
3018
  releaseViaFacilitator(paymentInfo: AdvancedPaymentInfo, amount?: string): Promise<AdvancedTransactionResult>;
2753
3019
  /**
2754
- * GASLESS REFUND: Refund escrowed funds via the facilitator.
3020
+ * GASLESS REFUND: return escrowed funds to the payer via the facilitator.
2755
3021
  *
2756
- * Instead of calling the PaymentOperator contract directly (which requires
2757
- * gas), this sends a refundInEscrow request to the facilitator, which
2758
- * submits the transaction on your behalf.
3022
+ * Sends `action: "refundInEscrow"` to `POST /settle`; the facilitator's
3023
+ * PaymentOperator calls `AuthCaptureEscrow.partialVoid`.
3024
+ *
3025
+ * # This is how an EXPIRED escrow is recovered
3026
+ *
3027
+ * A widely repeated claim — including in this SDK's own comments until now —
3028
+ * says that once `authorizationExpiry` passes, only the payer's `reclaim()`
3029
+ * can move the funds. **That is false, and believing it has left real money
3030
+ * stranded.**
3031
+ *
3032
+ * Read the contract (`AuthCaptureEscrow.sol`):
3033
+ *
3034
+ * - `partialVoid` is `onlySender(paymentInfo.operator)` — the operator is the
3035
+ * FACILITATOR, not the payer — it sends the tokens **to the payer**, and it
3036
+ * **does not check `authorizationExpiry` at all**. It works before expiry
3037
+ * and after it, and the payer never has to appear.
3038
+ * - `reclaim` is `onlySender(paymentInfo.payer)` and only after expiry. It is
3039
+ * a payer's self-service escape hatch, which is why this facilitator does
3040
+ * not expose it — **not** the only way out.
3041
+ *
3042
+ * So a release that reverted with `AfterAuthorizationExpiry` is recoverable
3043
+ * from here, with no gas and no cooperation from the payer. Get the amount
3044
+ * from {@link queryEscrowState}'s `capturableAmount`.
3045
+ *
3046
+ * # A refusal that is not a refusal
3047
+ *
3048
+ * Check `retryable` before writing an escrow off. A `503` means the
3049
+ * facilitator never reached the chain — the escrow is untouched and the same
3050
+ * request should be sent again.
2759
3051
  *
2760
3052
  * @param paymentInfo - PaymentInfo from the authorize step
2761
- * @param amount - Amount to refund in atomic units (defaults to maxAmount)
3053
+ * @param amount - Amount to refund in atomic units (defaults to maxAmount).
3054
+ * For a stuck escrow pass `capturableAmount` from {@link queryEscrowState}.
2762
3055
  * @returns Transaction result from the facilitator
2763
3056
  *
2764
- * @example
3057
+ * @example Recovering an escrow whose release window already closed
2765
3058
  * ```typescript
2766
- * const pi = client.buildPaymentInfo('0xWorker...', '5000000', 'standard');
2767
- * await client.authorize(pi);
2768
- * // Task cancelled...
2769
- * const result = await client.refundViaFacilitator(pi);
2770
- * console.log(result.transactionHash);
3059
+ * const state = await client.queryEscrowState(pi);
3060
+ * if (state.capturableAmount !== '0') {
3061
+ * // No payer needed, no gas, and expiry is irrelevant to partialVoid.
3062
+ * const result = await client.refundViaFacilitator(pi, state.capturableAmount);
3063
+ * if (!result.success && result.retryable) {
3064
+ * // No verdict was reached. The funds are still there; send it again.
3065
+ * }
3066
+ * }
2771
3067
  * ```
2772
3068
  */
2773
3069
  refundViaFacilitator(paymentInfo: AdvancedPaymentInfo, amount?: string): Promise<AdvancedTransactionResult>;
@@ -2838,4 +3134,4 @@ declare class AdvancedEscrowClient {
2838
3134
  private sendViaAdapter;
2839
3135
  }
2840
3136
 
2841
- export { type AdvancedAuthorizationResult, AdvancedEscrowClient, type AdvancedEscrowClientOptions, type AdvancedEscrowContracts, type AdvancedEscrowTaskTier, type AdvancedPaymentInfo, type AdvancedTransactionResult, type AgentId, type AgentIdentity, type AgentRegistration, type AgentRegistrationFile, type AgentService, type AtomStats, BASE_MAINNET_CONTRACTS, BazaarClient, type BazaarClientOptions, type BazaarDiscoverOptions, type BazaarDiscoverResponse, type BazaarRegisterOptions, type BazaarResource, type CreateEscrowOptions, DEPOSIT_LIMIT_USDC, type DiscoveryAccepts, type DiscoveryCuration, type DiscoveryHealth, type DiscoveryHealthStatus, type DiscoveryListOptions, type DiscoveryPagination, type DiscoveryRegisterOptions, type DiscoveryResource, type DiscoveryResponse, type DiscoverySource, type DiscoveryStats, type DiscoveryTier, type Dispute, type DisputeOutcome, ERC8004_CONTRACTS, ERC8004_EXTENSION_ID, ESCROW_CONTRACTS, ESCROW_TIMEOUT_MS, Erc8004Client, type Erc8004ClientOptions, Erc8004LookupError, type Erc8004Network, EscrowClient, type EscrowClientOptions, type EscrowPayment, type EscrowStateResponse, type EscrowStatus, FacilitatorClient, type FacilitatorClientOptions, type FeedbackEntry, type FeedbackParams, type FeedbackRequest, type FeedbackResponse, HEALTH_FILTERS, type HonoMiddlewareOptions, type IdentityByOwnerResponse, type IdentityMetadataResponse, type IdentityTotalSupplyResponse, MAX_SEARCH_LEN, type MetadataEntryParam, OPERATOR_ABI, OPERATOR_ABI_CREATE3, PAYMENT_INFO_TYPEHASH, type PaymentAcceptance, type PaymentMiddlewareOptions, type PaymentPayloadV2, type PaymentRequirementResolver, type PaymentRequirements, type PaymentRequirementsOptions, type PaymentRequirementsV2, type PrepareRelayFeedbackRequest, type PrepareRelayFeedbackResponse, type PrepareRelayResponseRequest, type ProofOfPayment, RELAYED_FEEDBACK_NETWORKS, type RefundRequest, type RefundStatus, type RegisterAgentRequest, type RegisterAgentResponse, type RegisterJobResponse, type RegisterJobStatus, RegistrationPendingError, type RelayAuthorizationParams, type ReputationResponse, type ReputationSummary, type RequestRefundOptions, type ResourceInfoV2, type SettleRequest, type SettleRequestV2, type SettleResponse, type SettleResponseWithProof, type SubmitRelayFeedbackRequest, type SubmitRelayResponseRequest, TIER_FILTERS, TIER_TIMINGS, USDC_DOMAIN_NAME, type VerifiedPaymentState, type VerifyRequest, type VerifyRequestV2, type VerifyResponse, X402_CORS_HEADERS, X402_HEADER_NAMES, ZERO_ADDRESS, buildErc8004PaymentRequirements, buildPaymentRequirements, buildSettleRequest, buildSettleRequestV2, buildVerifyRequest, buildVerifyRequestV2, canRefundEscrow, canReleaseEscrow, create402Response, createHonoMiddleware, createPaymentMiddleware, epochToDate, escrowTimeRemaining, extractPaymentFromHeaders, getCorsHeaders, getEscrowContractsByChainId, getEscrowSupportedChainIds, isAlive, isEscrowExpired, isEscrowSupportedOnChain, isRegisterJobTerminal, parsePaymentHeader, supportsRelayedFeedback, wireNetwork };
3137
+ export { AMBIGUOUS_LEASE_REASONS, type AdvancedAuthorizationResult, AdvancedEscrowClient, type AdvancedEscrowClientOptions, type AdvancedEscrowContracts, type AdvancedEscrowTaskTier, type AdvancedPaymentInfo, type AdvancedTransactionResult, type AgentId, type AgentIdentity, type AgentRegistration, type AgentRegistrationFile, type AgentService, type AtomStats, BASE_MAINNET_CONTRACTS, BazaarClient, type BazaarClientOptions, type BazaarDiscoverOptions, type BazaarDiscoverResponse, type BazaarRegisterOptions, type BazaarResource, type CreateEscrowOptions, DEFAULT_FACILITATOR_RETRIES, DEFAULT_RETRY_AFTER_SECONDS, DEPOSIT_LIMIT_USDC, type DiscoveryAccepts, type DiscoveryCuration, type DiscoveryHealth, type DiscoveryHealthStatus, type DiscoveryListOptions, type DiscoveryPagination, type DiscoveryRegisterOptions, type DiscoveryResource, type DiscoveryResponse, type DiscoverySource, type DiscoveryStats, type DiscoveryTier, type Dispute, type DisputeOutcome, ERC8004_CONTRACTS, ERC8004_EXTENSION_ID, ESCROW_CONTRACTS, ESCROW_TIMEOUT_MS, Erc8004Client, type Erc8004ClientOptions, Erc8004LookupError, type Erc8004Network, EscrowClient, type EscrowClientOptions, type EscrowPayment, type EscrowStateResponse, type EscrowStatus, FacilitatorClient, type FacilitatorClientOptions, type FacilitatorErrorInfo, type FacilitatorFailureFields, type FacilitatorFetchOptions, type FeedbackEntry, type FeedbackParams, type FeedbackRequest, type FeedbackResponse, HEALTH_FILTERS, type HonoMiddlewareOptions, type IdentityByOwnerResponse, type IdentityMetadataResponse, type IdentityTotalSupplyResponse, MAX_RETRY_AFTER_SECONDS, MAX_SEARCH_LEN, type MetadataEntryParam, OPERATOR_ABI, OPERATOR_ABI_CREATE3, PAYMENT_INFO_TYPEHASH, type PaymentAcceptance, type PaymentMiddlewareOptions, type PaymentPayloadV2, type PaymentRequirementResolver, type PaymentRequirements, type PaymentRequirementsOptions, type PaymentRequirementsV2, type PrepareRelayFeedbackRequest, type PrepareRelayFeedbackResponse, type PrepareRelayResponseRequest, type ProofOfPayment, RELAYED_FEEDBACK_NETWORKS, REPLAYABLE_LEASE_REASONS, type RefundRequest, type RefundStatus, type RegisterAgentRequest, type RegisterAgentResponse, type RegisterJobResponse, type RegisterJobStatus, RegistrationPendingError, type RelayAuthorizationParams, type ReputationResponse, type ReputationSummary, type RequestRefundOptions, type ResourceInfoV2, type SettleRequest, type SettleRequestV2, type SettleResponse, type SettleResponseWithProof, type SubmitRelayFeedbackRequest, type SubmitRelayResponseRequest, TIER_FILTERS, TIER_TIMINGS, USDC_DOMAIN_NAME, type VerifiedPaymentState, type VerifyRequest, type VerifyRequestV2, type VerifyResponse, WRITER_LEASE_REASONS, type WriterLeaseReason, X402_CORS_HEADERS, X402_HEADER_NAMES, ZERO_ADDRESS, buildErc8004PaymentRequirements, buildPaymentRequirements, buildSettleRequest, buildSettleRequestV2, buildVerifyRequest, buildVerifyRequestV2, canRefundEscrow, canReleaseEscrow, carryFailureFields, create402Response, createHonoMiddleware, createPaymentMiddleware, epochToDate, escrowTimeRemaining, extractPaymentFromHeaders, facilitatorFetch, getCorsHeaders, getEscrowContractsByChainId, getEscrowSupportedChainIds, isAlive, isAmbiguousLeaseReason, isEscrowExpired, isEscrowSupportedOnChain, isRegisterJobTerminal, isReplayableLeaseReason, parsePaymentHeader, parseRetryAfterSeconds, readFacilitatorError, supportsRelayedFeedback, wireNetwork };