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
@@ -1091,6 +1091,118 @@ function decodeX402Header(encoded) {
1091
1091
  var REVIEW_WINDOW_SEC = 7 * 24 * 3600;
1092
1092
  var REFUND_WINDOW_SEC = 7 * 24 * 3600;
1093
1093
 
1094
+ // src/backend/facilitator-error.ts
1095
+ var WRITER_LEASE_REASONS = [
1096
+ "holder_unknown",
1097
+ "forwarding_disabled",
1098
+ "forwarded_but_not_writer",
1099
+ "body_unreadable",
1100
+ "forward_failed"
1101
+ ];
1102
+ var REPLAYABLE_LEASE_REASONS = [
1103
+ "holder_unknown",
1104
+ "forwarding_disabled",
1105
+ "forwarded_but_not_writer",
1106
+ "body_unreadable"
1107
+ ];
1108
+ var AMBIGUOUS_LEASE_REASONS = ["forward_failed"];
1109
+ var MAX_RETRY_AFTER_SECONDS = 15;
1110
+ var DEFAULT_RETRY_AFTER_SECONDS = 5;
1111
+ var DEFAULT_FACILITATOR_RETRIES = 2;
1112
+ function isReplayableLeaseReason(reason) {
1113
+ return REPLAYABLE_LEASE_REASONS.includes(reason);
1114
+ }
1115
+ function isAmbiguousLeaseReason(reason) {
1116
+ return AMBIGUOUS_LEASE_REASONS.includes(reason);
1117
+ }
1118
+ function parseRetryAfterSeconds(response) {
1119
+ let raw;
1120
+ try {
1121
+ raw = response?.headers?.get?.("retry-after");
1122
+ } catch {
1123
+ return void 0;
1124
+ }
1125
+ if (raw === null || raw === void 0 || raw === "") return void 0;
1126
+ const seconds = Number(raw);
1127
+ if (!Number.isFinite(seconds) || seconds < 0) return void 0;
1128
+ return Math.min(seconds, MAX_RETRY_AFTER_SECONDS);
1129
+ }
1130
+ function reasonFrom(body) {
1131
+ try {
1132
+ const parsed = JSON.parse(body);
1133
+ if (parsed && typeof parsed === "object" && typeof parsed.reason === "string") {
1134
+ return parsed.reason;
1135
+ }
1136
+ } catch {
1137
+ }
1138
+ return void 0;
1139
+ }
1140
+ async function readFacilitatorError(response) {
1141
+ let body = "";
1142
+ try {
1143
+ body = await response.text();
1144
+ } catch {
1145
+ body = "";
1146
+ }
1147
+ const status = response.status;
1148
+ const reason = reasonFrom(body);
1149
+ const retryable = status === 429 || status === 502 || status === 503 || status === 504;
1150
+ const retryAfterSeconds = retryable ? parseRetryAfterSeconds(response) ?? DEFAULT_RETRY_AFTER_SECONDS : void 0;
1151
+ const safeToReplay = status === 429 || status === 503 && isReplayableLeaseReason(reason);
1152
+ return {
1153
+ error: `Facilitator error: ${status} - ${body}`,
1154
+ status,
1155
+ reason,
1156
+ retryAfterSeconds,
1157
+ retryable,
1158
+ safeToReplay,
1159
+ body
1160
+ };
1161
+ }
1162
+ function failureFields(info) {
1163
+ return {
1164
+ status: info.status,
1165
+ retryable: info.retryable,
1166
+ safeToReplay: info.safeToReplay,
1167
+ ...info.reason !== void 0 ? { reason: info.reason } : {},
1168
+ ...info.retryAfterSeconds !== void 0 ? { retryAfterSeconds: info.retryAfterSeconds } : {}
1169
+ };
1170
+ }
1171
+ function carryFailureFields(source) {
1172
+ return {
1173
+ ...source.status !== void 0 ? { status: source.status } : {},
1174
+ ...source.reason !== void 0 ? { reason: source.reason } : {},
1175
+ ...source.retryable !== void 0 ? { retryable: source.retryable } : {},
1176
+ ...source.retryAfterSeconds !== void 0 ? { retryAfterSeconds: source.retryAfterSeconds } : {},
1177
+ ...source.safeToReplay !== void 0 ? { safeToReplay: source.safeToReplay } : {}
1178
+ };
1179
+ }
1180
+ var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1181
+ async function facilitatorFetch(url, init, options) {
1182
+ const retries = options.retries ?? DEFAULT_FACILITATOR_RETRIES;
1183
+ const canReplay = options.canReplay ?? ((info) => info.safeToReplay);
1184
+ const doFetch = options.fetchImpl ?? fetch;
1185
+ const sleep = options.sleepImpl ?? defaultSleep;
1186
+ let attempt = 0;
1187
+ for (; ; ) {
1188
+ const controller = new AbortController();
1189
+ const timeoutId = setTimeout(() => controller.abort(), options.timeoutMs);
1190
+ let response;
1191
+ try {
1192
+ response = await doFetch(url, { ...init, signal: controller.signal });
1193
+ } finally {
1194
+ clearTimeout(timeoutId);
1195
+ }
1196
+ if (response.ok) return { response };
1197
+ const error = await readFacilitatorError(response);
1198
+ if (attempt >= retries || !error.retryable || !canReplay(error)) {
1199
+ return { response, error };
1200
+ }
1201
+ attempt += 1;
1202
+ await sleep((error.retryAfterSeconds ?? DEFAULT_RETRY_AFTER_SECONDS) * 1e3);
1203
+ }
1204
+ }
1205
+
1094
1206
  // src/backend/index.ts
1095
1207
  function parsePaymentHeader(headerValue) {
1096
1208
  if (!headerValue) {
@@ -1197,10 +1309,12 @@ var FacilitatorClient = class {
1197
1309
  baseUrl;
1198
1310
  timeout;
1199
1311
  explicitTimeout;
1312
+ retries;
1200
1313
  constructor(options = {}) {
1201
1314
  this.baseUrl = options.baseUrl || "https://facilitator.ultravioletadao.xyz";
1202
1315
  this.explicitTimeout = options.timeout !== void 0;
1203
1316
  this.timeout = options.timeout || 3e4;
1317
+ this.retries = options.retries;
1204
1318
  }
1205
1319
  /**
1206
1320
  * Get timeout for a specific network, using per-chain defaults when no explicit timeout was set.
@@ -1227,29 +1341,31 @@ var FacilitatorClient = class {
1227
1341
  */
1228
1342
  async verify(paymentHeader, requirements) {
1229
1343
  const body = buildVerifyRequest(paymentHeader, requirements);
1230
- const controller = new AbortController();
1231
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1232
1344
  try {
1233
- const response = await fetch(`${this.baseUrl}/verify`, {
1234
- method: "POST",
1235
- headers: { "Content-Type": "application/json" },
1236
- body: JSON.stringify(body),
1237
- signal: controller.signal
1238
- });
1239
- clearTimeout(timeoutId);
1240
- if (!response.ok) {
1241
- const errorText = await response.text();
1345
+ const { response, error } = await facilitatorFetch(
1346
+ `${this.baseUrl}/verify`,
1347
+ {
1348
+ method: "POST",
1349
+ headers: { "Content-Type": "application/json" },
1350
+ body: JSON.stringify(body)
1351
+ },
1352
+ { timeoutMs: this.timeout, retries: this.retries }
1353
+ );
1354
+ if (error) {
1242
1355
  return {
1243
1356
  isValid: false,
1244
- invalidReason: `Facilitator error: ${response.status} - ${errorText}`
1357
+ invalidReason: error.error,
1358
+ ...failureFields(error)
1245
1359
  };
1246
1360
  }
1247
1361
  return await response.json();
1248
1362
  } catch (error) {
1249
- clearTimeout(timeoutId);
1250
1363
  return {
1251
1364
  isValid: false,
1252
- invalidReason: error instanceof Error ? error.message : "Unknown error"
1365
+ invalidReason: error instanceof Error ? error.message : "Unknown error",
1366
+ retryable: true,
1367
+ safeToReplay: false,
1368
+ retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
1253
1369
  };
1254
1370
  }
1255
1371
  }
@@ -1265,21 +1381,21 @@ var FacilitatorClient = class {
1265
1381
  async settle(paymentHeader, requirements) {
1266
1382
  const body = buildSettleRequest(paymentHeader, requirements);
1267
1383
  const settleTimeout = this.getTimeout(requirements.network);
1268
- const controller = new AbortController();
1269
- const timeoutId = setTimeout(() => controller.abort(), settleTimeout);
1270
1384
  try {
1271
- const response = await fetch(`${this.baseUrl}/settle`, {
1272
- method: "POST",
1273
- headers: { "Content-Type": "application/json" },
1274
- body: JSON.stringify(body),
1275
- signal: controller.signal
1276
- });
1277
- clearTimeout(timeoutId);
1278
- if (!response.ok) {
1279
- const errorText = await response.text();
1385
+ const { response, error } = await facilitatorFetch(
1386
+ `${this.baseUrl}/settle`,
1387
+ {
1388
+ method: "POST",
1389
+ headers: { "Content-Type": "application/json" },
1390
+ body: JSON.stringify(body)
1391
+ },
1392
+ { timeoutMs: settleTimeout, retries: this.retries }
1393
+ );
1394
+ if (error) {
1280
1395
  return {
1281
1396
  success: false,
1282
- error: `Facilitator error: ${response.status} - ${errorText}`
1397
+ error: error.error,
1398
+ ...failureFields(error)
1283
1399
  };
1284
1400
  }
1285
1401
  const result = await response.json();
@@ -1320,10 +1436,12 @@ var FacilitatorClient = class {
1320
1436
  payer: result.payer
1321
1437
  };
1322
1438
  } catch (error) {
1323
- clearTimeout(timeoutId);
1324
1439
  return {
1325
1440
  success: false,
1326
- error: error instanceof Error ? error.message : "Unknown error"
1441
+ error: error instanceof Error ? error.message : "Unknown error",
1442
+ retryable: true,
1443
+ safeToReplay: false,
1444
+ retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
1327
1445
  };
1328
1446
  }
1329
1447
  }
@@ -1343,7 +1461,8 @@ var FacilitatorClient = class {
1343
1461
  return {
1344
1462
  verified: false,
1345
1463
  settled: false,
1346
- error: verifyResult.invalidReason
1464
+ error: verifyResult.invalidReason,
1465
+ ...carryFailureFields(verifyResult)
1347
1466
  };
1348
1467
  }
1349
1468
  const settleResult = await this.settle(paymentHeader, requirements);
@@ -1351,7 +1470,8 @@ var FacilitatorClient = class {
1351
1470
  verified: true,
1352
1471
  settled: settleResult.success,
1353
1472
  transactionHash: settleResult.transactionHash,
1354
- error: settleResult.error
1473
+ error: settleResult.error,
1474
+ ...carryFailureFields(settleResult)
1355
1475
  };
1356
1476
  }
1357
1477
  /**
@@ -1753,10 +1873,29 @@ function createVerifiedPaymentState(client, payment, requirements, verifyResult)
1753
1873
  }
1754
1874
  };
1755
1875
  }
1876
+ function respondUnavailable(res, message, failure) {
1877
+ const seconds = Math.max(1, Math.ceil(failure.retryAfterSeconds ?? DEFAULT_RETRY_AFTER_SECONDS));
1878
+ const body = {
1879
+ error: message,
1880
+ reason: failure.reason ?? failure.invalidReason ?? failure.error,
1881
+ retryable: true,
1882
+ retryAfterSeconds: seconds,
1883
+ // False for `forward_failed` and for a bare timeout: the write may already
1884
+ // have landed, so the caller must reconcile before resending.
1885
+ safeToReplay: failure.safeToReplay === true
1886
+ };
1887
+ const staged = res.status(503);
1888
+ if (typeof staged.set === "function") {
1889
+ staged.set({ "Retry-After": String(seconds) }).json(body);
1890
+ return;
1891
+ }
1892
+ staged.json(body);
1893
+ }
1756
1894
  function createPaymentMiddleware(getRequirements, options = {}) {
1757
1895
  const client = new FacilitatorClient({
1758
1896
  baseUrl: options.facilitatorUrl || options.baseUrl,
1759
- timeout: options.timeout
1897
+ timeout: options.timeout,
1898
+ retries: options.retries
1760
1899
  });
1761
1900
  const settlementStrategy = options.settlementStrategy || "before-handler";
1762
1901
  return async (req, res, next) => {
@@ -1771,6 +1910,10 @@ function createPaymentMiddleware(getRequirements, options = {}) {
1771
1910
  const requirements = buildPaymentRequirements(reqOptions);
1772
1911
  const verifyResult = await client.verify(payment, requirements);
1773
1912
  if (!verifyResult.isValid) {
1913
+ if (verifyResult.retryable) {
1914
+ respondUnavailable(res, "Payment verification unavailable", verifyResult);
1915
+ return;
1916
+ }
1774
1917
  res.status(402).json({
1775
1918
  error: "Payment verification failed",
1776
1919
  reason: verifyResult.invalidReason
@@ -1781,6 +1924,10 @@ function createPaymentMiddleware(getRequirements, options = {}) {
1781
1924
  if (settlementStrategy === "before-handler") {
1782
1925
  const settleResult = await req.x402.settle();
1783
1926
  if (!settleResult.success) {
1927
+ if (settleResult.retryable) {
1928
+ respondUnavailable(res, "Payment settlement unavailable", settleResult);
1929
+ return;
1930
+ }
1784
1931
  res.status(500).json({
1785
1932
  error: "Payment settlement failed",
1786
1933
  reason: settleResult.error || "Unknown settlement error"
@@ -1791,10 +1938,25 @@ function createPaymentMiddleware(getRequirements, options = {}) {
1791
1938
  next();
1792
1939
  };
1793
1940
  }
1941
+ function honoUnavailable(c, message, failure) {
1942
+ const seconds = Math.max(1, Math.ceil(failure.retryAfterSeconds ?? DEFAULT_RETRY_AFTER_SECONDS));
1943
+ c.header?.("Retry-After", String(seconds));
1944
+ return c.json(
1945
+ {
1946
+ error: message,
1947
+ reason: failure.reason ?? failure.invalidReason ?? failure.error,
1948
+ retryable: true,
1949
+ retryAfterSeconds: seconds,
1950
+ safeToReplay: failure.safeToReplay === true
1951
+ },
1952
+ 503
1953
+ );
1954
+ }
1794
1955
  function createHonoMiddleware(options) {
1795
1956
  const client = new FacilitatorClient({
1796
1957
  baseUrl: options.facilitatorUrl || options.baseUrl,
1797
- timeout: options.timeout
1958
+ timeout: options.timeout,
1959
+ retries: options.retries
1798
1960
  });
1799
1961
  const settlementStrategy = options.settlementStrategy || "before-handler";
1800
1962
  if (!options.accepts[0]) {
@@ -1834,6 +1996,9 @@ function createHonoMiddleware(options) {
1834
1996
  }
1835
1997
  const verifyResult = await client.verify(parsed, requirement);
1836
1998
  if (!verifyResult.isValid) {
1999
+ if (verifyResult.retryable) {
2000
+ return honoUnavailable(c, "Payment verification unavailable", verifyResult);
2001
+ }
1837
2002
  return c.json({
1838
2003
  error: "Payment verification failed",
1839
2004
  reason: verifyResult.invalidReason
@@ -1844,6 +2009,9 @@ function createHonoMiddleware(options) {
1844
2009
  if (settlementStrategy === "before-handler") {
1845
2010
  const settleResult = await verifiedPayment.settle();
1846
2011
  if (!settleResult.success) {
2012
+ if (settleResult.retryable) {
2013
+ return honoUnavailable(c, "Payment settlement unavailable", settleResult);
2014
+ }
1847
2015
  return c.json({
1848
2016
  error: "Payment settlement failed",
1849
2017
  reason: settleResult.error || "Unknown error"
@@ -2625,19 +2793,65 @@ var Erc8004LookupError = class extends Error {
2625
2793
  status;
2626
2794
  /** Raw response body, for debugging */
2627
2795
  body;
2628
- constructor(message, status, body) {
2796
+ /**
2797
+ * `Retry-After`, already clamped, when the facilitator sent one.
2798
+ *
2799
+ * Optional so every existing three-argument construction keeps compiling; it
2800
+ * falls back to the default wait rather than to zero, because a caller that
2801
+ * retries instantly on a 503 is the load that caused it.
2802
+ */
2803
+ retryAfterHint;
2804
+ constructor(message, status, body, retryAfterSeconds) {
2629
2805
  super(message);
2630
2806
  this.name = "Erc8004LookupError";
2631
2807
  this.status = status;
2632
2808
  this.body = body;
2809
+ this.retryAfterHint = retryAfterSeconds;
2633
2810
  }
2634
2811
  /** The address genuinely owns no agent on this network. */
2635
2812
  get notFound() {
2636
2813
  return this.status === 404;
2637
2814
  }
2638
- /** The lookup reached no verdict. Retry; never read as "owns nothing". */
2815
+ /**
2816
+ * The lookup reached no verdict. Retry; never read as "owns nothing".
2817
+ *
2818
+ * `502` and `504` join `503` and `429` here: a gateway that answered on the
2819
+ * facilitator's behalf is exactly as silent about the agent's existence, and
2820
+ * reading either as absence has the same consequence -- a duplicate mint.
2821
+ */
2639
2822
  get retryable() {
2640
- return this.status === 503;
2823
+ return this.status === 429 || this.status === 502 || this.status === 503 || this.status === 504;
2824
+ }
2825
+ /**
2826
+ * The facilitator's own `reason`, when the body carried one.
2827
+ *
2828
+ * On a WRITE route this is the writer-lease reason and it decides whether the
2829
+ * request may be re-sent; see {@link isReplayableLeaseReason}.
2830
+ */
2831
+ get reason() {
2832
+ try {
2833
+ const parsed = JSON.parse(this.body);
2834
+ if (parsed && typeof parsed === "object" && typeof parsed.reason === "string") {
2835
+ return parsed.reason;
2836
+ }
2837
+ } catch {
2838
+ }
2839
+ return void 0;
2840
+ }
2841
+ /**
2842
+ * The facilitator NAMED a reason proving it executed nothing.
2843
+ *
2844
+ * False for `forward_failed` and for every unattributed 5xx: "something
2845
+ * answered" is not evidence that nothing ran. On `/register`, replaying when
2846
+ * this is false is the sequence that minted five duplicate agents.
2847
+ */
2848
+ get safeToReplay() {
2849
+ return this.status === 429 || this.status === 503 && isReplayableLeaseReason(this.reason);
2850
+ }
2851
+ /** Seconds to wait before retrying, clamped. Absent when not retryable. */
2852
+ get retryAfterSeconds() {
2853
+ if (!this.retryable) return void 0;
2854
+ return this.retryAfterHint ?? DEFAULT_RETRY_AFTER_SECONDS;
2641
2855
  }
2642
2856
  };
2643
2857
  function isRegisterJobTerminal(job) {
@@ -2674,9 +2888,38 @@ function jobToRegisterResponse(job, network) {
2674
2888
  var Erc8004Client = class {
2675
2889
  baseUrl;
2676
2890
  timeout;
2891
+ retries;
2677
2892
  constructor(options = {}) {
2678
2893
  this.baseUrl = options.baseUrl || "https://facilitator.ultravioletadao.xyz";
2679
2894
  this.timeout = options.timeout || 3e4;
2895
+ this.retries = options.retries;
2896
+ }
2897
+ /**
2898
+ * POST a write route, keeping a refusal readable.
2899
+ *
2900
+ * Every ERC-8004 write goes through the facilitator's EVM writer lease, so
2901
+ * every one of them can answer `503` + `reason`. Flattened to a string, those
2902
+ * are indistinguishable from "the registry rejected your feedback" — and on
2903
+ * `/register` the wrong reading re-POSTs a mint that may already have landed,
2904
+ * which is precisely how five duplicate agents were once created.
2905
+ *
2906
+ * A refusal the facilitator proved it did not execute is replayed
2907
+ * automatically (`safeToReplay`); `forward_failed` never is.
2908
+ */
2909
+ async writeJson(url, body, extraHeaders = {}) {
2910
+ return facilitatorFetch(
2911
+ url,
2912
+ {
2913
+ method: "POST",
2914
+ headers: {
2915
+ "Content-Type": "application/json",
2916
+ Accept: "application/json",
2917
+ ...extraHeaders
2918
+ },
2919
+ body: JSON.stringify(body)
2920
+ },
2921
+ { timeoutMs: this.timeout, retries: this.retries }
2922
+ );
2680
2923
  }
2681
2924
  /**
2682
2925
  * Get agent identity from the Identity Registry
@@ -2725,26 +2968,21 @@ var Erc8004Client = class {
2725
2968
  const url = `${this.baseUrl}/identity/${wireNetwork(network)}/owner/${address}`;
2726
2969
  const controller = new AbortController();
2727
2970
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2728
- try {
2729
- const response = await fetch(url, {
2730
- method: "GET",
2731
- headers: { "Accept": "application/json" },
2732
- signal: controller.signal
2733
- });
2734
- clearTimeout(timeoutId);
2735
- if (!response.ok) {
2736
- const errorText = await response.text();
2737
- throw new Erc8004LookupError(
2738
- `ERC-8004 API error: ${response.status} - ${errorText}`,
2739
- response.status,
2740
- errorText
2741
- );
2742
- }
2743
- return await response.json();
2744
- } catch (error) {
2745
- clearTimeout(timeoutId);
2746
- throw error;
2971
+ const response = await fetch(url, {
2972
+ method: "GET",
2973
+ headers: { Accept: "application/json" },
2974
+ signal: controller.signal
2975
+ }).finally(() => clearTimeout(timeoutId));
2976
+ if (!response.ok) {
2977
+ const info = await readFacilitatorError(response);
2978
+ throw new Erc8004LookupError(
2979
+ `ERC-8004 API error: ${info.status} - ${info.body}`,
2980
+ info.status,
2981
+ info.body,
2982
+ info.retryAfterSeconds
2983
+ );
2747
2984
  }
2985
+ return await response.json();
2748
2986
  }
2749
2987
  /**
2750
2988
  * Resolve agent registration file from agentURI
@@ -2853,34 +3091,28 @@ var Erc8004Client = class {
2853
3091
  */
2854
3092
  async submitFeedback(request) {
2855
3093
  const url = `${this.baseUrl}/feedback`;
2856
- const controller = new AbortController();
2857
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2858
3094
  try {
2859
- const response = await fetch(url, {
2860
- method: "POST",
2861
- headers: {
2862
- "Content-Type": "application/json",
2863
- "Accept": "application/json"
2864
- },
2865
- body: JSON.stringify({ ...request, network: wireNetwork(request.network) }),
2866
- signal: controller.signal
3095
+ const { response, error } = await this.writeJson(url, {
3096
+ ...request,
3097
+ network: wireNetwork(request.network)
2867
3098
  });
2868
- clearTimeout(timeoutId);
2869
- if (!response.ok) {
2870
- const errorText = await response.text();
3099
+ if (error) {
2871
3100
  return {
2872
3101
  success: false,
2873
- error: `Facilitator error: ${response.status} - ${errorText}`,
2874
- network: request.network
3102
+ error: error.error,
3103
+ network: request.network,
3104
+ ...failureFields(error)
2875
3105
  };
2876
3106
  }
2877
3107
  return await response.json();
2878
3108
  } catch (error) {
2879
- clearTimeout(timeoutId);
2880
3109
  return {
2881
3110
  success: false,
2882
3111
  error: error instanceof Error ? error.message : "Unknown error",
2883
- network: request.network
3112
+ network: request.network,
3113
+ retryable: true,
3114
+ safeToReplay: false,
3115
+ retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
2884
3116
  };
2885
3117
  }
2886
3118
  }
@@ -2989,34 +3221,28 @@ var Erc8004Client = class {
2989
3221
  */
2990
3222
  async submitRelayedFeedback(request) {
2991
3223
  const url = `${this.baseUrl}/feedback/evm/submit`;
2992
- const controller = new AbortController();
2993
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2994
3224
  try {
2995
- const response = await fetch(url, {
2996
- method: "POST",
2997
- headers: {
2998
- "Content-Type": "application/json",
2999
- "Accept": "application/json"
3000
- },
3001
- body: JSON.stringify({ ...request, network: wireNetwork(request.network) }),
3002
- signal: controller.signal
3225
+ const { response, error } = await this.writeJson(url, {
3226
+ ...request,
3227
+ network: wireNetwork(request.network)
3003
3228
  });
3004
- clearTimeout(timeoutId);
3005
- if (!response.ok) {
3006
- const errorText = await response.text();
3229
+ if (error) {
3007
3230
  return {
3008
3231
  success: false,
3009
- error: `Facilitator error: ${response.status} - ${errorText}`,
3010
- network: request.network
3232
+ error: error.error,
3233
+ network: request.network,
3234
+ ...failureFields(error)
3011
3235
  };
3012
3236
  }
3013
3237
  return await response.json();
3014
3238
  } catch (error) {
3015
- clearTimeout(timeoutId);
3016
3239
  return {
3017
3240
  success: false,
3018
3241
  error: error instanceof Error ? error.message : "Unknown error",
3019
- network: request.network
3242
+ network: request.network,
3243
+ retryable: true,
3244
+ safeToReplay: false,
3245
+ retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
3020
3246
  };
3021
3247
  }
3022
3248
  }
@@ -3032,8 +3258,6 @@ var Erc8004Client = class {
3032
3258
  */
3033
3259
  async revokeFeedback(network, agentId, feedbackIndex, options) {
3034
3260
  const url = `${this.baseUrl}/feedback/revoke`;
3035
- const controller = new AbortController();
3036
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
3037
3261
  const payload = {
3038
3262
  x402Version: 1,
3039
3263
  network: wireNetwork(network),
@@ -3046,31 +3270,24 @@ var Erc8004Client = class {
3046
3270
  payload.originalFeedback = options.originalFeedback;
3047
3271
  }
3048
3272
  try {
3049
- const response = await fetch(url, {
3050
- method: "POST",
3051
- headers: {
3052
- "Content-Type": "application/json",
3053
- "Accept": "application/json"
3054
- },
3055
- body: JSON.stringify(payload),
3056
- signal: controller.signal
3057
- });
3058
- clearTimeout(timeoutId);
3059
- if (!response.ok) {
3060
- const errorText = await response.text();
3273
+ const { response, error } = await this.writeJson(url, payload);
3274
+ if (error) {
3061
3275
  return {
3062
3276
  success: false,
3063
- error: `Facilitator error: ${response.status} - ${errorText}`,
3064
- network
3277
+ error: error.error,
3278
+ network,
3279
+ ...failureFields(error)
3065
3280
  };
3066
3281
  }
3067
3282
  return await response.json();
3068
3283
  } catch (error) {
3069
- clearTimeout(timeoutId);
3070
3284
  return {
3071
3285
  success: false,
3072
3286
  error: error instanceof Error ? error.message : "Unknown error",
3073
- network
3287
+ network,
3288
+ retryable: true,
3289
+ safeToReplay: false,
3290
+ retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
3074
3291
  };
3075
3292
  }
3076
3293
  }
@@ -3160,24 +3377,23 @@ var Erc8004Client = class {
3160
3377
  }
3161
3378
  /** Shared POST for the relay routes: a refusal is data, never a throw. */
3162
3379
  async postRelay(path, request, onError) {
3163
- const controller = new AbortController();
3164
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
3165
3380
  try {
3166
- const response = await fetch(`${this.baseUrl}${path}`, {
3167
- method: "POST",
3168
- headers: { "Content-Type": "application/json", "Accept": "application/json" },
3169
- body: JSON.stringify({ ...request, network: wireNetwork(request.network) }),
3170
- signal: controller.signal
3381
+ const { response, error } = await this.writeJson(`${this.baseUrl}${path}`, {
3382
+ ...request,
3383
+ network: wireNetwork(request.network)
3171
3384
  });
3172
- clearTimeout(timeoutId);
3173
- if (!response.ok) {
3174
- const errorText = await response.text();
3175
- return { ...onError, error: `Facilitator error: ${response.status} - ${errorText}` };
3385
+ if (error) {
3386
+ return { ...onError, error: error.error, ...failureFields(error) };
3176
3387
  }
3177
3388
  return await response.json();
3178
3389
  } catch (error) {
3179
- clearTimeout(timeoutId);
3180
- return { ...onError, error: error instanceof Error ? error.message : "Unknown error" };
3390
+ return {
3391
+ ...onError,
3392
+ error: error instanceof Error ? error.message : "Unknown error",
3393
+ retryable: true,
3394
+ safeToReplay: false,
3395
+ retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
3396
+ };
3181
3397
  }
3182
3398
  }
3183
3399
  /**
@@ -3214,8 +3430,6 @@ var Erc8004Client = class {
3214
3430
  */
3215
3431
  async appendResponse(network, agentId, feedbackIndex, response, options) {
3216
3432
  const url = `${this.baseUrl}/feedback/response`;
3217
- const controller = new AbortController();
3218
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
3219
3433
  const payload = {
3220
3434
  x402Version: 1,
3221
3435
  network: wireNetwork(network),
@@ -3230,31 +3444,24 @@ var Erc8004Client = class {
3230
3444
  payload.sealHash = options.sealHash;
3231
3445
  }
3232
3446
  try {
3233
- const fetchResponse = await fetch(url, {
3234
- method: "POST",
3235
- headers: {
3236
- "Content-Type": "application/json",
3237
- "Accept": "application/json"
3238
- },
3239
- body: JSON.stringify(payload),
3240
- signal: controller.signal
3241
- });
3242
- clearTimeout(timeoutId);
3243
- if (!fetchResponse.ok) {
3244
- const errorText = await fetchResponse.text();
3447
+ const { response: fetchResponse, error } = await this.writeJson(url, payload);
3448
+ if (error) {
3245
3449
  return {
3246
3450
  success: false,
3247
- error: `Facilitator error: ${fetchResponse.status} - ${errorText}`,
3248
- network
3451
+ error: error.error,
3452
+ network,
3453
+ ...failureFields(error)
3249
3454
  };
3250
3455
  }
3251
3456
  return await fetchResponse.json();
3252
3457
  } catch (error) {
3253
- clearTimeout(timeoutId);
3254
3458
  return {
3255
3459
  success: false,
3256
3460
  error: error instanceof Error ? error.message : "Unknown error",
3257
- network
3461
+ network,
3462
+ retryable: true,
3463
+ safeToReplay: false,
3464
+ retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
3258
3465
  };
3259
3466
  }
3260
3467
  }
@@ -3298,47 +3505,42 @@ var Erc8004Client = class {
3298
3505
  return jobToRegisterResponse(terminal, request.network);
3299
3506
  }
3300
3507
  const url = `${this.baseUrl}/register`;
3301
- const controller = new AbortController();
3302
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
3303
3508
  try {
3304
- const response = await fetch(url, {
3305
- method: "POST",
3306
- headers: {
3307
- "Content-Type": "application/json",
3308
- "Accept": "application/json"
3309
- },
3310
- body: JSON.stringify({ ...request, network: wireNetwork(request.network) }),
3311
- signal: controller.signal
3509
+ const { response, error } = await this.writeJson(url, {
3510
+ ...request,
3511
+ network: wireNetwork(request.network)
3312
3512
  });
3313
- clearTimeout(timeoutId);
3314
- if (!response.ok) {
3315
- const errorText = await response.text();
3513
+ if (error) {
3316
3514
  try {
3317
- const parsed = JSON.parse(errorText);
3515
+ const parsed = JSON.parse(error.body);
3318
3516
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3319
3517
  return {
3320
3518
  ...parsed,
3321
- // Never let a 4xx body claim success, whatever it says.
3519
+ // Never let a non-2xx body claim success, whatever it says.
3322
3520
  success: false,
3323
- error: parsed.error ?? `Facilitator error: ${response.status}`,
3324
- network: parsed.network ?? request.network
3521
+ error: parsed.error ?? `Facilitator error: ${error.status}`,
3522
+ network: parsed.network ?? request.network,
3523
+ ...failureFields(error)
3325
3524
  };
3326
3525
  }
3327
3526
  } catch {
3328
3527
  }
3329
3528
  return {
3330
3529
  success: false,
3331
- error: `Facilitator error: ${response.status} - ${errorText}`,
3332
- network: request.network
3530
+ error: error.error,
3531
+ network: request.network,
3532
+ ...failureFields(error)
3333
3533
  };
3334
3534
  }
3335
3535
  return await response.json();
3336
3536
  } catch (error) {
3337
- clearTimeout(timeoutId);
3338
3537
  return {
3339
3538
  success: false,
3340
3539
  error: error instanceof Error ? error.message : "Unknown error",
3341
- network: request.network
3540
+ network: request.network,
3541
+ retryable: true,
3542
+ safeToReplay: false,
3543
+ retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
3342
3544
  };
3343
3545
  }
3344
3546
  }
@@ -3356,33 +3558,20 @@ var Erc8004Client = class {
3356
3558
  */
3357
3559
  async registerAgentAsync(request) {
3358
3560
  const url = `${this.baseUrl}/register`;
3359
- const controller = new AbortController();
3360
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
3361
- try {
3362
- const response = await fetch(url, {
3363
- method: "POST",
3364
- headers: {
3365
- "Content-Type": "application/json",
3366
- "Accept": "application/json",
3367
- "Prefer": "respond-async"
3368
- },
3369
- body: JSON.stringify({ ...request, network: wireNetwork(request.network) }),
3370
- signal: controller.signal
3371
- });
3372
- clearTimeout(timeoutId);
3373
- if (!response.ok) {
3374
- const errorText = await response.text();
3375
- throw new Erc8004LookupError(
3376
- `ERC-8004 API error: ${response.status} - ${errorText}`,
3377
- response.status,
3378
- errorText
3379
- );
3380
- }
3381
- return await response.json();
3382
- } catch (error) {
3383
- clearTimeout(timeoutId);
3384
- throw error;
3561
+ const { response, error } = await this.writeJson(
3562
+ url,
3563
+ { ...request, network: wireNetwork(request.network) },
3564
+ { Prefer: "respond-async" }
3565
+ );
3566
+ if (error) {
3567
+ throw new Erc8004LookupError(
3568
+ `ERC-8004 API error: ${error.status} - ${error.body}`,
3569
+ error.status,
3570
+ error.body,
3571
+ error.retryAfterSeconds
3572
+ );
3385
3573
  }
3574
+ return await response.json();
3386
3575
  }
3387
3576
  /**
3388
3577
  * Read the current state of an asynchronous registration.
@@ -3394,26 +3583,21 @@ var Erc8004Client = class {
3394
3583
  const url = `${this.baseUrl}/register/status/${encodeURIComponent(jobId)}`;
3395
3584
  const controller = new AbortController();
3396
3585
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
3397
- try {
3398
- const response = await fetch(url, {
3399
- method: "GET",
3400
- headers: { "Accept": "application/json" },
3401
- signal: controller.signal
3402
- });
3403
- clearTimeout(timeoutId);
3404
- if (!response.ok) {
3405
- const errorText = await response.text();
3406
- throw new Erc8004LookupError(
3407
- `ERC-8004 API error: ${response.status} - ${errorText}`,
3408
- response.status,
3409
- errorText
3410
- );
3411
- }
3412
- return await response.json();
3413
- } catch (error) {
3414
- clearTimeout(timeoutId);
3415
- throw error;
3586
+ const response = await fetch(url, {
3587
+ method: "GET",
3588
+ headers: { Accept: "application/json" },
3589
+ signal: controller.signal
3590
+ }).finally(() => clearTimeout(timeoutId));
3591
+ if (!response.ok) {
3592
+ const info = await readFacilitatorError(response);
3593
+ throw new Erc8004LookupError(
3594
+ `ERC-8004 API error: ${info.status} - ${info.body}`,
3595
+ info.status,
3596
+ info.body,
3597
+ info.retryAfterSeconds
3598
+ );
3416
3599
  }
3600
+ return await response.json();
3417
3601
  }
3418
3602
  /**
3419
3603
  * Poll an asynchronous registration until it finishes.
@@ -3714,6 +3898,7 @@ var AdvancedEscrowClient = class {
3714
3898
  chainId;
3715
3899
  gasLimit;
3716
3900
  timeout;
3901
+ retries;
3717
3902
  contracts;
3718
3903
  signer;
3719
3904
  // ethers.Signer (legacy mode)
@@ -3750,6 +3935,7 @@ var AdvancedEscrowClient = class {
3750
3935
  this.chainId = options.chainId || 8453;
3751
3936
  this.gasLimit = options.gasLimit || 3e5;
3752
3937
  this.timeout = options.timeout || ESCROW_TIMEOUT_MS[this.chainId] || DEFAULT_ESCROW_TIMEOUT_MS;
3938
+ this.retries = options.retries;
3753
3939
  if (this.walletAdapter && !this.rpcUrl) {
3754
3940
  throw new Error(
3755
3941
  "AdvancedEscrowClient: rpcUrl is required when using a SigningWalletAdapter. The adapter signs transactions offline; an RPC provider is needed to build and broadcast them."
@@ -3824,8 +4010,8 @@ var AdvancedEscrowClient = class {
3824
4010
  * Matches the on-chain AuthCaptureEscrow nonce derivation.
3825
4011
  */
3826
4012
  async computeNonce(paymentInfo) {
3827
- const { ethers: ethers2 } = await import('ethers');
3828
- const piTuple = ethers2.AbiCoder.defaultAbiCoder().encode(
4013
+ const { ethers: ethers3 } = await import('ethers');
4014
+ const piTuple = ethers3.AbiCoder.defaultAbiCoder().encode(
3829
4015
  ["bytes32", "tuple(address,address,address,address,uint120,uint48,uint48,uint48,uint16,uint16,address,uint256)"],
3830
4016
  [
3831
4017
  PAYMENT_INFO_TYPEHASH,
@@ -3846,12 +4032,12 @@ var AdvancedEscrowClient = class {
3846
4032
  ]
3847
4033
  ]
3848
4034
  );
3849
- const piHash = ethers2.keccak256(piTuple);
3850
- const finalEncoded = ethers2.AbiCoder.defaultAbiCoder().encode(
4035
+ const piHash = ethers3.keccak256(piTuple);
4036
+ const finalEncoded = ethers3.AbiCoder.defaultAbiCoder().encode(
3851
4037
  ["uint256", "address", "bytes32"],
3852
4038
  [this.chainId, this.contracts.escrow, piHash]
3853
4039
  );
3854
- return ethers2.keccak256(finalEncoded);
4040
+ return ethers3.keccak256(finalEncoded);
3855
4041
  }
3856
4042
  /**
3857
4043
  * Sign ReceiveWithAuthorization for ERC-3009.
@@ -4008,17 +4194,17 @@ var AdvancedEscrowClient = class {
4008
4194
  async release(paymentInfo, amount) {
4009
4195
  if (!this.payerAddress) await this.init();
4010
4196
  try {
4011
- const { ethers: ethers2 } = await import('ethers');
4197
+ const { ethers: ethers3 } = await import('ethers');
4012
4198
  const isCreate3 = CREATE3_CHAIN_IDS.has(this.chainId);
4013
4199
  const abi = isCreate3 ? OPERATOR_ABI_CREATE3 : OPERATOR_ABI;
4014
4200
  const amt = amount || paymentInfo.maxAmount;
4015
4201
  const tuple = this.buildTuple(paymentInfo);
4016
4202
  if (this.walletAdapter) {
4017
- return this.sendViaAdapter(ethers2, abi, (iface) => {
4203
+ return this.sendViaAdapter(ethers3, abi, (iface) => {
4018
4204
  return isCreate3 ? iface.encodeFunctionData("release", [tuple, amt, "0x"]) : iface.encodeFunctionData("release", [tuple, amt]);
4019
4205
  });
4020
4206
  }
4021
- const contract = new ethers2.Contract(this.contracts.operator, abi, this.signer);
4207
+ const contract = new ethers3.Contract(this.contracts.operator, abi, this.signer);
4022
4208
  const tx = isCreate3 ? await contract.release(tuple, amt, "0x", { gasLimit: this.gasLimit }) : await contract.release(tuple, amt, { gasLimit: this.gasLimit });
4023
4209
  const receipt = await tx.wait();
4024
4210
  return {
@@ -4042,17 +4228,17 @@ var AdvancedEscrowClient = class {
4042
4228
  async refundInEscrow(paymentInfo, amount) {
4043
4229
  if (!this.payerAddress) await this.init();
4044
4230
  try {
4045
- const { ethers: ethers2 } = await import('ethers');
4231
+ const { ethers: ethers3 } = await import('ethers');
4046
4232
  const isCreate3 = CREATE3_CHAIN_IDS.has(this.chainId);
4047
4233
  const abi = isCreate3 ? OPERATOR_ABI_CREATE3 : OPERATOR_ABI;
4048
4234
  const amt = amount || paymentInfo.maxAmount;
4049
4235
  const tuple = this.buildTuple(paymentInfo);
4050
4236
  if (this.walletAdapter) {
4051
- return this.sendViaAdapter(ethers2, abi, (iface) => {
4237
+ return this.sendViaAdapter(ethers3, abi, (iface) => {
4052
4238
  return isCreate3 ? iface.encodeFunctionData("refundInEscrow", [tuple, amt, "0x"]) : iface.encodeFunctionData("refundInEscrow", [tuple, amt]);
4053
4239
  });
4054
4240
  }
4055
- const contract = new ethers2.Contract(this.contracts.operator, abi, this.signer);
4241
+ const contract = new ethers3.Contract(this.contracts.operator, abi, this.signer);
4056
4242
  const tx = isCreate3 ? await contract.refundInEscrow(tuple, amt, "0x", { gasLimit: this.gasLimit }) : await contract.refundInEscrow(tuple, amt, { gasLimit: this.gasLimit });
4057
4243
  const receipt = await tx.wait();
4058
4244
  return {
@@ -4122,16 +4308,19 @@ var AdvancedEscrowClient = class {
4122
4308
  }
4123
4309
  }
4124
4310
  };
4125
- const controller = new AbortController();
4126
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
4127
4311
  try {
4128
- const response = await fetch(`${this.facilitatorUrl}/settle`, {
4129
- method: "POST",
4130
- headers: { "Content-Type": "application/json" },
4131
- body: JSON.stringify(payload),
4132
- signal: controller.signal
4133
- });
4134
- clearTimeout(timeoutId);
4312
+ const { response, error } = await facilitatorFetch(
4313
+ `${this.facilitatorUrl}/settle`,
4314
+ {
4315
+ method: "POST",
4316
+ headers: { "Content-Type": "application/json" },
4317
+ body: JSON.stringify(payload)
4318
+ },
4319
+ { timeoutMs: this.timeout, retries: this.retries }
4320
+ );
4321
+ if (error) {
4322
+ return { success: false, error: error.error, ...failureFields(error) };
4323
+ }
4135
4324
  const result = await response.json();
4136
4325
  if (result.success) {
4137
4326
  return {
@@ -4144,7 +4333,6 @@ var AdvancedEscrowClient = class {
4144
4333
  error: result.errorReason || result.error || `Release refused with no reason (HTTP ${response.status}, body keys: ${Object.keys(result ?? {}).sort().join(", ") || "none"})`
4145
4334
  };
4146
4335
  } catch (fetchErr) {
4147
- clearTimeout(timeoutId);
4148
4336
  if (fetchErr.name === "AbortError") {
4149
4337
  try {
4150
4338
  const state = await this.queryEscrowState(paymentInfo);
@@ -4162,23 +4350,53 @@ var AdvancedEscrowClient = class {
4162
4350
  }
4163
4351
  }
4164
4352
  /**
4165
- * GASLESS REFUND: Refund escrowed funds via the facilitator.
4353
+ * GASLESS REFUND: return escrowed funds to the payer via the facilitator.
4166
4354
  *
4167
- * Instead of calling the PaymentOperator contract directly (which requires
4168
- * gas), this sends a refundInEscrow request to the facilitator, which
4169
- * submits the transaction on your behalf.
4355
+ * Sends `action: "refundInEscrow"` to `POST /settle`; the facilitator's
4356
+ * PaymentOperator calls `AuthCaptureEscrow.partialVoid`.
4357
+ *
4358
+ * # This is how an EXPIRED escrow is recovered
4359
+ *
4360
+ * A widely repeated claim — including in this SDK's own comments until now —
4361
+ * says that once `authorizationExpiry` passes, only the payer's `reclaim()`
4362
+ * can move the funds. **That is false, and believing it has left real money
4363
+ * stranded.**
4364
+ *
4365
+ * Read the contract (`AuthCaptureEscrow.sol`):
4366
+ *
4367
+ * - `partialVoid` is `onlySender(paymentInfo.operator)` — the operator is the
4368
+ * FACILITATOR, not the payer — it sends the tokens **to the payer**, and it
4369
+ * **does not check `authorizationExpiry` at all**. It works before expiry
4370
+ * and after it, and the payer never has to appear.
4371
+ * - `reclaim` is `onlySender(paymentInfo.payer)` and only after expiry. It is
4372
+ * a payer's self-service escape hatch, which is why this facilitator does
4373
+ * not expose it — **not** the only way out.
4374
+ *
4375
+ * So a release that reverted with `AfterAuthorizationExpiry` is recoverable
4376
+ * from here, with no gas and no cooperation from the payer. Get the amount
4377
+ * from {@link queryEscrowState}'s `capturableAmount`.
4378
+ *
4379
+ * # A refusal that is not a refusal
4380
+ *
4381
+ * Check `retryable` before writing an escrow off. A `503` means the
4382
+ * facilitator never reached the chain — the escrow is untouched and the same
4383
+ * request should be sent again.
4170
4384
  *
4171
4385
  * @param paymentInfo - PaymentInfo from the authorize step
4172
- * @param amount - Amount to refund in atomic units (defaults to maxAmount)
4386
+ * @param amount - Amount to refund in atomic units (defaults to maxAmount).
4387
+ * For a stuck escrow pass `capturableAmount` from {@link queryEscrowState}.
4173
4388
  * @returns Transaction result from the facilitator
4174
4389
  *
4175
- * @example
4390
+ * @example Recovering an escrow whose release window already closed
4176
4391
  * ```typescript
4177
- * const pi = client.buildPaymentInfo('0xWorker...', '5000000', 'standard');
4178
- * await client.authorize(pi);
4179
- * // Task cancelled...
4180
- * const result = await client.refundViaFacilitator(pi);
4181
- * console.log(result.transactionHash);
4392
+ * const state = await client.queryEscrowState(pi);
4393
+ * if (state.capturableAmount !== '0') {
4394
+ * // No payer needed, no gas, and expiry is irrelevant to partialVoid.
4395
+ * const result = await client.refundViaFacilitator(pi, state.capturableAmount);
4396
+ * if (!result.success && result.retryable) {
4397
+ * // No verdict was reached. The funds are still there; send it again.
4398
+ * }
4399
+ * }
4182
4400
  * ```
4183
4401
  */
4184
4402
  async refundViaFacilitator(paymentInfo, amount) {
@@ -4215,16 +4433,19 @@ var AdvancedEscrowClient = class {
4215
4433
  }
4216
4434
  }
4217
4435
  };
4218
- const controller = new AbortController();
4219
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
4220
4436
  try {
4221
- const response = await fetch(`${this.facilitatorUrl}/settle`, {
4222
- method: "POST",
4223
- headers: { "Content-Type": "application/json" },
4224
- body: JSON.stringify(payload),
4225
- signal: controller.signal
4226
- });
4227
- clearTimeout(timeoutId);
4437
+ const { response, error } = await facilitatorFetch(
4438
+ `${this.facilitatorUrl}/settle`,
4439
+ {
4440
+ method: "POST",
4441
+ headers: { "Content-Type": "application/json" },
4442
+ body: JSON.stringify(payload)
4443
+ },
4444
+ { timeoutMs: this.timeout, retries: this.retries }
4445
+ );
4446
+ if (error) {
4447
+ return { success: false, error: error.error, ...failureFields(error) };
4448
+ }
4228
4449
  const result = await response.json();
4229
4450
  if (result.success) {
4230
4451
  return {
@@ -4232,9 +4453,11 @@ var AdvancedEscrowClient = class {
4232
4453
  transactionHash: result.transaction || result.transactionHash || result.transaction_hash
4233
4454
  };
4234
4455
  }
4235
- return { success: false, error: result.errorReason || result.error || "Refund failed" };
4456
+ return {
4457
+ success: false,
4458
+ error: result.errorReason || result.error || `Refund refused with no reason (HTTP ${response.status}, body keys: ${Object.keys(result ?? {}).sort().join(", ") || "none"})`
4459
+ };
4236
4460
  } catch (fetchErr) {
4237
- clearTimeout(timeoutId);
4238
4461
  if (fetchErr.name === "AbortError") {
4239
4462
  try {
4240
4463
  const state = await this.queryEscrowState(paymentInfo);
@@ -4318,7 +4541,7 @@ var AdvancedEscrowClient = class {
4318
4541
  async charge(paymentInfo, amount) {
4319
4542
  if (!this.payerAddress) await this.init();
4320
4543
  try {
4321
- const { ethers: ethers2 } = await import('ethers');
4544
+ const { ethers: ethers3 } = await import('ethers');
4322
4545
  const nonce = await this.computeNonce(paymentInfo);
4323
4546
  const amt = amount || paymentInfo.maxAmount;
4324
4547
  const auth = {
@@ -4330,10 +4553,10 @@ var AdvancedEscrowClient = class {
4330
4553
  nonce
4331
4554
  };
4332
4555
  const signature = await this.signErc3009(auth);
4333
- const collectorData = ethers2.getBytes(signature);
4556
+ const collectorData = ethers3.getBytes(signature);
4334
4557
  const tuple = this.buildTuple(paymentInfo);
4335
4558
  if (this.walletAdapter) {
4336
- return this.sendViaAdapter(ethers2, OPERATOR_ABI, (iface) => {
4559
+ return this.sendViaAdapter(ethers3, OPERATOR_ABI, (iface) => {
4337
4560
  return iface.encodeFunctionData("charge", [
4338
4561
  tuple,
4339
4562
  amt,
@@ -4342,7 +4565,7 @@ var AdvancedEscrowClient = class {
4342
4565
  ]);
4343
4566
  });
4344
4567
  }
4345
- const contract = new ethers2.Contract(this.contracts.operator, OPERATOR_ABI, this.signer);
4568
+ const contract = new ethers3.Contract(this.contracts.operator, OPERATOR_ABI, this.signer);
4346
4569
  const tx = await contract.charge(
4347
4570
  tuple,
4348
4571
  amt,
@@ -4384,11 +4607,11 @@ var AdvancedEscrowClient = class {
4384
4607
  async refundPostEscrow(paymentInfo, amount, tokenCollector, collectorData) {
4385
4608
  if (!this.payerAddress) await this.init();
4386
4609
  try {
4387
- const { ethers: ethers2 } = await import('ethers');
4610
+ const { ethers: ethers3 } = await import('ethers');
4388
4611
  const amt = amount || paymentInfo.maxAmount;
4389
4612
  const tuple = this.buildTuple(paymentInfo);
4390
4613
  if (this.walletAdapter) {
4391
- return this.sendViaAdapter(ethers2, OPERATOR_ABI, (iface) => {
4614
+ return this.sendViaAdapter(ethers3, OPERATOR_ABI, (iface) => {
4392
4615
  return iface.encodeFunctionData("refundPostEscrow", [
4393
4616
  tuple,
4394
4617
  amt,
@@ -4397,7 +4620,7 @@ var AdvancedEscrowClient = class {
4397
4620
  ]);
4398
4621
  });
4399
4622
  }
4400
- const contract = new ethers2.Contract(this.contracts.operator, OPERATOR_ABI, this.signer);
4623
+ const contract = new ethers3.Contract(this.contracts.operator, OPERATOR_ABI, this.signer);
4401
4624
  const tx = await contract.refundPostEscrow(
4402
4625
  tuple,
4403
4626
  amt,
@@ -4460,6 +4683,6 @@ var AdvancedEscrowClient = class {
4460
4683
  }
4461
4684
  };
4462
4685
 
4463
- export { AdvancedEscrowClient, BASE_MAINNET_CONTRACTS, BazaarClient, DEPOSIT_LIMIT_USDC, ERC8004_CONTRACTS, ERC8004_EXTENSION_ID, ESCROW_CONTRACTS, ESCROW_TIMEOUT_MS, Erc8004Client, Erc8004LookupError, EscrowClient, FacilitatorClient, HEALTH_FILTERS, MAX_SEARCH_LEN, OPERATOR_ABI, OPERATOR_ABI_CREATE3, PAYMENT_INFO_TYPEHASH, RELAYED_FEEDBACK_NETWORKS, RegistrationPendingError, TIER_FILTERS, TIER_TIMINGS, USDC_DOMAIN_NAME, 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 };
4686
+ 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, RELAYED_FEEDBACK_NETWORKS, REPLAYABLE_LEASE_REASONS, RegistrationPendingError, TIER_FILTERS, TIER_TIMINGS, USDC_DOMAIN_NAME, WRITER_LEASE_REASONS, 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 };
4464
4687
  //# sourceMappingURL=index.mjs.map
4465
4688
  //# sourceMappingURL=index.mjs.map