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/README.md +145 -1
- package/dist/backend/index.d.mts +348 -52
- package/dist/backend/index.d.ts +348 -52
- package/dist/backend/index.js +470 -235
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/index.mjs +459 -236
- package/dist/backend/index.mjs.map +1 -1
- package/dist/index.d.mts +44 -4
- package/dist/index.d.ts +44 -4
- package/dist/index.js +238 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +228 -40
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/backend/facilitator-error.ts +355 -0
- package/src/backend/index.ts +536 -263
- package/src/dx402.ts +123 -8
- package/src/index.ts +17 -0
package/dist/backend/index.js
CHANGED
|
@@ -1093,6 +1093,118 @@ function decodeX402Header(encoded) {
|
|
|
1093
1093
|
var REVIEW_WINDOW_SEC = 7 * 24 * 3600;
|
|
1094
1094
|
var REFUND_WINDOW_SEC = 7 * 24 * 3600;
|
|
1095
1095
|
|
|
1096
|
+
// src/backend/facilitator-error.ts
|
|
1097
|
+
var WRITER_LEASE_REASONS = [
|
|
1098
|
+
"holder_unknown",
|
|
1099
|
+
"forwarding_disabled",
|
|
1100
|
+
"forwarded_but_not_writer",
|
|
1101
|
+
"body_unreadable",
|
|
1102
|
+
"forward_failed"
|
|
1103
|
+
];
|
|
1104
|
+
var REPLAYABLE_LEASE_REASONS = [
|
|
1105
|
+
"holder_unknown",
|
|
1106
|
+
"forwarding_disabled",
|
|
1107
|
+
"forwarded_but_not_writer",
|
|
1108
|
+
"body_unreadable"
|
|
1109
|
+
];
|
|
1110
|
+
var AMBIGUOUS_LEASE_REASONS = ["forward_failed"];
|
|
1111
|
+
var MAX_RETRY_AFTER_SECONDS = 15;
|
|
1112
|
+
var DEFAULT_RETRY_AFTER_SECONDS = 5;
|
|
1113
|
+
var DEFAULT_FACILITATOR_RETRIES = 2;
|
|
1114
|
+
function isReplayableLeaseReason(reason) {
|
|
1115
|
+
return REPLAYABLE_LEASE_REASONS.includes(reason);
|
|
1116
|
+
}
|
|
1117
|
+
function isAmbiguousLeaseReason(reason) {
|
|
1118
|
+
return AMBIGUOUS_LEASE_REASONS.includes(reason);
|
|
1119
|
+
}
|
|
1120
|
+
function parseRetryAfterSeconds(response) {
|
|
1121
|
+
let raw;
|
|
1122
|
+
try {
|
|
1123
|
+
raw = response?.headers?.get?.("retry-after");
|
|
1124
|
+
} catch {
|
|
1125
|
+
return void 0;
|
|
1126
|
+
}
|
|
1127
|
+
if (raw === null || raw === void 0 || raw === "") return void 0;
|
|
1128
|
+
const seconds = Number(raw);
|
|
1129
|
+
if (!Number.isFinite(seconds) || seconds < 0) return void 0;
|
|
1130
|
+
return Math.min(seconds, MAX_RETRY_AFTER_SECONDS);
|
|
1131
|
+
}
|
|
1132
|
+
function reasonFrom(body) {
|
|
1133
|
+
try {
|
|
1134
|
+
const parsed = JSON.parse(body);
|
|
1135
|
+
if (parsed && typeof parsed === "object" && typeof parsed.reason === "string") {
|
|
1136
|
+
return parsed.reason;
|
|
1137
|
+
}
|
|
1138
|
+
} catch {
|
|
1139
|
+
}
|
|
1140
|
+
return void 0;
|
|
1141
|
+
}
|
|
1142
|
+
async function readFacilitatorError(response) {
|
|
1143
|
+
let body = "";
|
|
1144
|
+
try {
|
|
1145
|
+
body = await response.text();
|
|
1146
|
+
} catch {
|
|
1147
|
+
body = "";
|
|
1148
|
+
}
|
|
1149
|
+
const status = response.status;
|
|
1150
|
+
const reason = reasonFrom(body);
|
|
1151
|
+
const retryable = status === 429 || status === 502 || status === 503 || status === 504;
|
|
1152
|
+
const retryAfterSeconds = retryable ? parseRetryAfterSeconds(response) ?? DEFAULT_RETRY_AFTER_SECONDS : void 0;
|
|
1153
|
+
const safeToReplay = status === 429 || status === 503 && isReplayableLeaseReason(reason);
|
|
1154
|
+
return {
|
|
1155
|
+
error: `Facilitator error: ${status} - ${body}`,
|
|
1156
|
+
status,
|
|
1157
|
+
reason,
|
|
1158
|
+
retryAfterSeconds,
|
|
1159
|
+
retryable,
|
|
1160
|
+
safeToReplay,
|
|
1161
|
+
body
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
function failureFields(info) {
|
|
1165
|
+
return {
|
|
1166
|
+
status: info.status,
|
|
1167
|
+
retryable: info.retryable,
|
|
1168
|
+
safeToReplay: info.safeToReplay,
|
|
1169
|
+
...info.reason !== void 0 ? { reason: info.reason } : {},
|
|
1170
|
+
...info.retryAfterSeconds !== void 0 ? { retryAfterSeconds: info.retryAfterSeconds } : {}
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
function carryFailureFields(source) {
|
|
1174
|
+
return {
|
|
1175
|
+
...source.status !== void 0 ? { status: source.status } : {},
|
|
1176
|
+
...source.reason !== void 0 ? { reason: source.reason } : {},
|
|
1177
|
+
...source.retryable !== void 0 ? { retryable: source.retryable } : {},
|
|
1178
|
+
...source.retryAfterSeconds !== void 0 ? { retryAfterSeconds: source.retryAfterSeconds } : {},
|
|
1179
|
+
...source.safeToReplay !== void 0 ? { safeToReplay: source.safeToReplay } : {}
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1183
|
+
async function facilitatorFetch(url, init, options) {
|
|
1184
|
+
const retries = options.retries ?? DEFAULT_FACILITATOR_RETRIES;
|
|
1185
|
+
const canReplay = options.canReplay ?? ((info) => info.safeToReplay);
|
|
1186
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
1187
|
+
const sleep = options.sleepImpl ?? defaultSleep;
|
|
1188
|
+
let attempt = 0;
|
|
1189
|
+
for (; ; ) {
|
|
1190
|
+
const controller = new AbortController();
|
|
1191
|
+
const timeoutId = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
1192
|
+
let response;
|
|
1193
|
+
try {
|
|
1194
|
+
response = await doFetch(url, { ...init, signal: controller.signal });
|
|
1195
|
+
} finally {
|
|
1196
|
+
clearTimeout(timeoutId);
|
|
1197
|
+
}
|
|
1198
|
+
if (response.ok) return { response };
|
|
1199
|
+
const error = await readFacilitatorError(response);
|
|
1200
|
+
if (attempt >= retries || !error.retryable || !canReplay(error)) {
|
|
1201
|
+
return { response, error };
|
|
1202
|
+
}
|
|
1203
|
+
attempt += 1;
|
|
1204
|
+
await sleep((error.retryAfterSeconds ?? DEFAULT_RETRY_AFTER_SECONDS) * 1e3);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1096
1208
|
// src/backend/index.ts
|
|
1097
1209
|
function parsePaymentHeader(headerValue) {
|
|
1098
1210
|
if (!headerValue) {
|
|
@@ -1199,10 +1311,12 @@ var FacilitatorClient = class {
|
|
|
1199
1311
|
baseUrl;
|
|
1200
1312
|
timeout;
|
|
1201
1313
|
explicitTimeout;
|
|
1314
|
+
retries;
|
|
1202
1315
|
constructor(options = {}) {
|
|
1203
1316
|
this.baseUrl = options.baseUrl || "https://facilitator.ultravioletadao.xyz";
|
|
1204
1317
|
this.explicitTimeout = options.timeout !== void 0;
|
|
1205
1318
|
this.timeout = options.timeout || 3e4;
|
|
1319
|
+
this.retries = options.retries;
|
|
1206
1320
|
}
|
|
1207
1321
|
/**
|
|
1208
1322
|
* Get timeout for a specific network, using per-chain defaults when no explicit timeout was set.
|
|
@@ -1229,29 +1343,31 @@ var FacilitatorClient = class {
|
|
|
1229
1343
|
*/
|
|
1230
1344
|
async verify(paymentHeader, requirements) {
|
|
1231
1345
|
const body = buildVerifyRequest(paymentHeader, requirements);
|
|
1232
|
-
const controller = new AbortController();
|
|
1233
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
1234
1346
|
try {
|
|
1235
|
-
const response = await
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1347
|
+
const { response, error } = await facilitatorFetch(
|
|
1348
|
+
`${this.baseUrl}/verify`,
|
|
1349
|
+
{
|
|
1350
|
+
method: "POST",
|
|
1351
|
+
headers: { "Content-Type": "application/json" },
|
|
1352
|
+
body: JSON.stringify(body)
|
|
1353
|
+
},
|
|
1354
|
+
{ timeoutMs: this.timeout, retries: this.retries }
|
|
1355
|
+
);
|
|
1356
|
+
if (error) {
|
|
1244
1357
|
return {
|
|
1245
1358
|
isValid: false,
|
|
1246
|
-
invalidReason:
|
|
1359
|
+
invalidReason: error.error,
|
|
1360
|
+
...failureFields(error)
|
|
1247
1361
|
};
|
|
1248
1362
|
}
|
|
1249
1363
|
return await response.json();
|
|
1250
1364
|
} catch (error) {
|
|
1251
|
-
clearTimeout(timeoutId);
|
|
1252
1365
|
return {
|
|
1253
1366
|
isValid: false,
|
|
1254
|
-
invalidReason: error instanceof Error ? error.message : "Unknown error"
|
|
1367
|
+
invalidReason: error instanceof Error ? error.message : "Unknown error",
|
|
1368
|
+
retryable: true,
|
|
1369
|
+
safeToReplay: false,
|
|
1370
|
+
retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
|
|
1255
1371
|
};
|
|
1256
1372
|
}
|
|
1257
1373
|
}
|
|
@@ -1267,21 +1383,21 @@ var FacilitatorClient = class {
|
|
|
1267
1383
|
async settle(paymentHeader, requirements) {
|
|
1268
1384
|
const body = buildSettleRequest(paymentHeader, requirements);
|
|
1269
1385
|
const settleTimeout = this.getTimeout(requirements.network);
|
|
1270
|
-
const controller = new AbortController();
|
|
1271
|
-
const timeoutId = setTimeout(() => controller.abort(), settleTimeout);
|
|
1272
1386
|
try {
|
|
1273
|
-
const response = await
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1387
|
+
const { response, error } = await facilitatorFetch(
|
|
1388
|
+
`${this.baseUrl}/settle`,
|
|
1389
|
+
{
|
|
1390
|
+
method: "POST",
|
|
1391
|
+
headers: { "Content-Type": "application/json" },
|
|
1392
|
+
body: JSON.stringify(body)
|
|
1393
|
+
},
|
|
1394
|
+
{ timeoutMs: settleTimeout, retries: this.retries }
|
|
1395
|
+
);
|
|
1396
|
+
if (error) {
|
|
1282
1397
|
return {
|
|
1283
1398
|
success: false,
|
|
1284
|
-
error:
|
|
1399
|
+
error: error.error,
|
|
1400
|
+
...failureFields(error)
|
|
1285
1401
|
};
|
|
1286
1402
|
}
|
|
1287
1403
|
const result = await response.json();
|
|
@@ -1322,10 +1438,12 @@ var FacilitatorClient = class {
|
|
|
1322
1438
|
payer: result.payer
|
|
1323
1439
|
};
|
|
1324
1440
|
} catch (error) {
|
|
1325
|
-
clearTimeout(timeoutId);
|
|
1326
1441
|
return {
|
|
1327
1442
|
success: false,
|
|
1328
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1443
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1444
|
+
retryable: true,
|
|
1445
|
+
safeToReplay: false,
|
|
1446
|
+
retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
|
|
1329
1447
|
};
|
|
1330
1448
|
}
|
|
1331
1449
|
}
|
|
@@ -1345,7 +1463,8 @@ var FacilitatorClient = class {
|
|
|
1345
1463
|
return {
|
|
1346
1464
|
verified: false,
|
|
1347
1465
|
settled: false,
|
|
1348
|
-
error: verifyResult.invalidReason
|
|
1466
|
+
error: verifyResult.invalidReason,
|
|
1467
|
+
...carryFailureFields(verifyResult)
|
|
1349
1468
|
};
|
|
1350
1469
|
}
|
|
1351
1470
|
const settleResult = await this.settle(paymentHeader, requirements);
|
|
@@ -1353,7 +1472,8 @@ var FacilitatorClient = class {
|
|
|
1353
1472
|
verified: true,
|
|
1354
1473
|
settled: settleResult.success,
|
|
1355
1474
|
transactionHash: settleResult.transactionHash,
|
|
1356
|
-
error: settleResult.error
|
|
1475
|
+
error: settleResult.error,
|
|
1476
|
+
...carryFailureFields(settleResult)
|
|
1357
1477
|
};
|
|
1358
1478
|
}
|
|
1359
1479
|
/**
|
|
@@ -1755,10 +1875,29 @@ function createVerifiedPaymentState(client, payment, requirements, verifyResult)
|
|
|
1755
1875
|
}
|
|
1756
1876
|
};
|
|
1757
1877
|
}
|
|
1878
|
+
function respondUnavailable(res, message, failure) {
|
|
1879
|
+
const seconds = Math.max(1, Math.ceil(failure.retryAfterSeconds ?? DEFAULT_RETRY_AFTER_SECONDS));
|
|
1880
|
+
const body = {
|
|
1881
|
+
error: message,
|
|
1882
|
+
reason: failure.reason ?? failure.invalidReason ?? failure.error,
|
|
1883
|
+
retryable: true,
|
|
1884
|
+
retryAfterSeconds: seconds,
|
|
1885
|
+
// False for `forward_failed` and for a bare timeout: the write may already
|
|
1886
|
+
// have landed, so the caller must reconcile before resending.
|
|
1887
|
+
safeToReplay: failure.safeToReplay === true
|
|
1888
|
+
};
|
|
1889
|
+
const staged = res.status(503);
|
|
1890
|
+
if (typeof staged.set === "function") {
|
|
1891
|
+
staged.set({ "Retry-After": String(seconds) }).json(body);
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
staged.json(body);
|
|
1895
|
+
}
|
|
1758
1896
|
function createPaymentMiddleware(getRequirements, options = {}) {
|
|
1759
1897
|
const client = new FacilitatorClient({
|
|
1760
1898
|
baseUrl: options.facilitatorUrl || options.baseUrl,
|
|
1761
|
-
timeout: options.timeout
|
|
1899
|
+
timeout: options.timeout,
|
|
1900
|
+
retries: options.retries
|
|
1762
1901
|
});
|
|
1763
1902
|
const settlementStrategy = options.settlementStrategy || "before-handler";
|
|
1764
1903
|
return async (req, res, next) => {
|
|
@@ -1773,6 +1912,10 @@ function createPaymentMiddleware(getRequirements, options = {}) {
|
|
|
1773
1912
|
const requirements = buildPaymentRequirements(reqOptions);
|
|
1774
1913
|
const verifyResult = await client.verify(payment, requirements);
|
|
1775
1914
|
if (!verifyResult.isValid) {
|
|
1915
|
+
if (verifyResult.retryable) {
|
|
1916
|
+
respondUnavailable(res, "Payment verification unavailable", verifyResult);
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1776
1919
|
res.status(402).json({
|
|
1777
1920
|
error: "Payment verification failed",
|
|
1778
1921
|
reason: verifyResult.invalidReason
|
|
@@ -1783,6 +1926,10 @@ function createPaymentMiddleware(getRequirements, options = {}) {
|
|
|
1783
1926
|
if (settlementStrategy === "before-handler") {
|
|
1784
1927
|
const settleResult = await req.x402.settle();
|
|
1785
1928
|
if (!settleResult.success) {
|
|
1929
|
+
if (settleResult.retryable) {
|
|
1930
|
+
respondUnavailable(res, "Payment settlement unavailable", settleResult);
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1786
1933
|
res.status(500).json({
|
|
1787
1934
|
error: "Payment settlement failed",
|
|
1788
1935
|
reason: settleResult.error || "Unknown settlement error"
|
|
@@ -1793,10 +1940,25 @@ function createPaymentMiddleware(getRequirements, options = {}) {
|
|
|
1793
1940
|
next();
|
|
1794
1941
|
};
|
|
1795
1942
|
}
|
|
1943
|
+
function honoUnavailable(c, message, failure) {
|
|
1944
|
+
const seconds = Math.max(1, Math.ceil(failure.retryAfterSeconds ?? DEFAULT_RETRY_AFTER_SECONDS));
|
|
1945
|
+
c.header?.("Retry-After", String(seconds));
|
|
1946
|
+
return c.json(
|
|
1947
|
+
{
|
|
1948
|
+
error: message,
|
|
1949
|
+
reason: failure.reason ?? failure.invalidReason ?? failure.error,
|
|
1950
|
+
retryable: true,
|
|
1951
|
+
retryAfterSeconds: seconds,
|
|
1952
|
+
safeToReplay: failure.safeToReplay === true
|
|
1953
|
+
},
|
|
1954
|
+
503
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1796
1957
|
function createHonoMiddleware(options) {
|
|
1797
1958
|
const client = new FacilitatorClient({
|
|
1798
1959
|
baseUrl: options.facilitatorUrl || options.baseUrl,
|
|
1799
|
-
timeout: options.timeout
|
|
1960
|
+
timeout: options.timeout,
|
|
1961
|
+
retries: options.retries
|
|
1800
1962
|
});
|
|
1801
1963
|
const settlementStrategy = options.settlementStrategy || "before-handler";
|
|
1802
1964
|
if (!options.accepts[0]) {
|
|
@@ -1836,6 +1998,9 @@ function createHonoMiddleware(options) {
|
|
|
1836
1998
|
}
|
|
1837
1999
|
const verifyResult = await client.verify(parsed, requirement);
|
|
1838
2000
|
if (!verifyResult.isValid) {
|
|
2001
|
+
if (verifyResult.retryable) {
|
|
2002
|
+
return honoUnavailable(c, "Payment verification unavailable", verifyResult);
|
|
2003
|
+
}
|
|
1839
2004
|
return c.json({
|
|
1840
2005
|
error: "Payment verification failed",
|
|
1841
2006
|
reason: verifyResult.invalidReason
|
|
@@ -1846,6 +2011,9 @@ function createHonoMiddleware(options) {
|
|
|
1846
2011
|
if (settlementStrategy === "before-handler") {
|
|
1847
2012
|
const settleResult = await verifiedPayment.settle();
|
|
1848
2013
|
if (!settleResult.success) {
|
|
2014
|
+
if (settleResult.retryable) {
|
|
2015
|
+
return honoUnavailable(c, "Payment settlement unavailable", settleResult);
|
|
2016
|
+
}
|
|
1849
2017
|
return c.json({
|
|
1850
2018
|
error: "Payment settlement failed",
|
|
1851
2019
|
reason: settleResult.error || "Unknown error"
|
|
@@ -2627,19 +2795,65 @@ var Erc8004LookupError = class extends Error {
|
|
|
2627
2795
|
status;
|
|
2628
2796
|
/** Raw response body, for debugging */
|
|
2629
2797
|
body;
|
|
2630
|
-
|
|
2798
|
+
/**
|
|
2799
|
+
* `Retry-After`, already clamped, when the facilitator sent one.
|
|
2800
|
+
*
|
|
2801
|
+
* Optional so every existing three-argument construction keeps compiling; it
|
|
2802
|
+
* falls back to the default wait rather than to zero, because a caller that
|
|
2803
|
+
* retries instantly on a 503 is the load that caused it.
|
|
2804
|
+
*/
|
|
2805
|
+
retryAfterHint;
|
|
2806
|
+
constructor(message, status, body, retryAfterSeconds) {
|
|
2631
2807
|
super(message);
|
|
2632
2808
|
this.name = "Erc8004LookupError";
|
|
2633
2809
|
this.status = status;
|
|
2634
2810
|
this.body = body;
|
|
2811
|
+
this.retryAfterHint = retryAfterSeconds;
|
|
2635
2812
|
}
|
|
2636
2813
|
/** The address genuinely owns no agent on this network. */
|
|
2637
2814
|
get notFound() {
|
|
2638
2815
|
return this.status === 404;
|
|
2639
2816
|
}
|
|
2640
|
-
/**
|
|
2817
|
+
/**
|
|
2818
|
+
* The lookup reached no verdict. Retry; never read as "owns nothing".
|
|
2819
|
+
*
|
|
2820
|
+
* `502` and `504` join `503` and `429` here: a gateway that answered on the
|
|
2821
|
+
* facilitator's behalf is exactly as silent about the agent's existence, and
|
|
2822
|
+
* reading either as absence has the same consequence -- a duplicate mint.
|
|
2823
|
+
*/
|
|
2641
2824
|
get retryable() {
|
|
2642
|
-
return this.status === 503;
|
|
2825
|
+
return this.status === 429 || this.status === 502 || this.status === 503 || this.status === 504;
|
|
2826
|
+
}
|
|
2827
|
+
/**
|
|
2828
|
+
* The facilitator's own `reason`, when the body carried one.
|
|
2829
|
+
*
|
|
2830
|
+
* On a WRITE route this is the writer-lease reason and it decides whether the
|
|
2831
|
+
* request may be re-sent; see {@link isReplayableLeaseReason}.
|
|
2832
|
+
*/
|
|
2833
|
+
get reason() {
|
|
2834
|
+
try {
|
|
2835
|
+
const parsed = JSON.parse(this.body);
|
|
2836
|
+
if (parsed && typeof parsed === "object" && typeof parsed.reason === "string") {
|
|
2837
|
+
return parsed.reason;
|
|
2838
|
+
}
|
|
2839
|
+
} catch {
|
|
2840
|
+
}
|
|
2841
|
+
return void 0;
|
|
2842
|
+
}
|
|
2843
|
+
/**
|
|
2844
|
+
* The facilitator NAMED a reason proving it executed nothing.
|
|
2845
|
+
*
|
|
2846
|
+
* False for `forward_failed` and for every unattributed 5xx: "something
|
|
2847
|
+
* answered" is not evidence that nothing ran. On `/register`, replaying when
|
|
2848
|
+
* this is false is the sequence that minted five duplicate agents.
|
|
2849
|
+
*/
|
|
2850
|
+
get safeToReplay() {
|
|
2851
|
+
return this.status === 429 || this.status === 503 && isReplayableLeaseReason(this.reason);
|
|
2852
|
+
}
|
|
2853
|
+
/** Seconds to wait before retrying, clamped. Absent when not retryable. */
|
|
2854
|
+
get retryAfterSeconds() {
|
|
2855
|
+
if (!this.retryable) return void 0;
|
|
2856
|
+
return this.retryAfterHint ?? DEFAULT_RETRY_AFTER_SECONDS;
|
|
2643
2857
|
}
|
|
2644
2858
|
};
|
|
2645
2859
|
function isRegisterJobTerminal(job) {
|
|
@@ -2676,9 +2890,38 @@ function jobToRegisterResponse(job, network) {
|
|
|
2676
2890
|
var Erc8004Client = class {
|
|
2677
2891
|
baseUrl;
|
|
2678
2892
|
timeout;
|
|
2893
|
+
retries;
|
|
2679
2894
|
constructor(options = {}) {
|
|
2680
2895
|
this.baseUrl = options.baseUrl || "https://facilitator.ultravioletadao.xyz";
|
|
2681
2896
|
this.timeout = options.timeout || 3e4;
|
|
2897
|
+
this.retries = options.retries;
|
|
2898
|
+
}
|
|
2899
|
+
/**
|
|
2900
|
+
* POST a write route, keeping a refusal readable.
|
|
2901
|
+
*
|
|
2902
|
+
* Every ERC-8004 write goes through the facilitator's EVM writer lease, so
|
|
2903
|
+
* every one of them can answer `503` + `reason`. Flattened to a string, those
|
|
2904
|
+
* are indistinguishable from "the registry rejected your feedback" — and on
|
|
2905
|
+
* `/register` the wrong reading re-POSTs a mint that may already have landed,
|
|
2906
|
+
* which is precisely how five duplicate agents were once created.
|
|
2907
|
+
*
|
|
2908
|
+
* A refusal the facilitator proved it did not execute is replayed
|
|
2909
|
+
* automatically (`safeToReplay`); `forward_failed` never is.
|
|
2910
|
+
*/
|
|
2911
|
+
async writeJson(url, body, extraHeaders = {}) {
|
|
2912
|
+
return facilitatorFetch(
|
|
2913
|
+
url,
|
|
2914
|
+
{
|
|
2915
|
+
method: "POST",
|
|
2916
|
+
headers: {
|
|
2917
|
+
"Content-Type": "application/json",
|
|
2918
|
+
Accept: "application/json",
|
|
2919
|
+
...extraHeaders
|
|
2920
|
+
},
|
|
2921
|
+
body: JSON.stringify(body)
|
|
2922
|
+
},
|
|
2923
|
+
{ timeoutMs: this.timeout, retries: this.retries }
|
|
2924
|
+
);
|
|
2682
2925
|
}
|
|
2683
2926
|
/**
|
|
2684
2927
|
* Get agent identity from the Identity Registry
|
|
@@ -2727,26 +2970,21 @@ var Erc8004Client = class {
|
|
|
2727
2970
|
const url = `${this.baseUrl}/identity/${wireNetwork(network)}/owner/${address}`;
|
|
2728
2971
|
const controller = new AbortController();
|
|
2729
2972
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
);
|
|
2744
|
-
}
|
|
2745
|
-
return await response.json();
|
|
2746
|
-
} catch (error) {
|
|
2747
|
-
clearTimeout(timeoutId);
|
|
2748
|
-
throw error;
|
|
2973
|
+
const response = await fetch(url, {
|
|
2974
|
+
method: "GET",
|
|
2975
|
+
headers: { Accept: "application/json" },
|
|
2976
|
+
signal: controller.signal
|
|
2977
|
+
}).finally(() => clearTimeout(timeoutId));
|
|
2978
|
+
if (!response.ok) {
|
|
2979
|
+
const info = await readFacilitatorError(response);
|
|
2980
|
+
throw new Erc8004LookupError(
|
|
2981
|
+
`ERC-8004 API error: ${info.status} - ${info.body}`,
|
|
2982
|
+
info.status,
|
|
2983
|
+
info.body,
|
|
2984
|
+
info.retryAfterSeconds
|
|
2985
|
+
);
|
|
2749
2986
|
}
|
|
2987
|
+
return await response.json();
|
|
2750
2988
|
}
|
|
2751
2989
|
/**
|
|
2752
2990
|
* Resolve agent registration file from agentURI
|
|
@@ -2855,34 +3093,28 @@ var Erc8004Client = class {
|
|
|
2855
3093
|
*/
|
|
2856
3094
|
async submitFeedback(request) {
|
|
2857
3095
|
const url = `${this.baseUrl}/feedback`;
|
|
2858
|
-
const controller = new AbortController();
|
|
2859
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2860
3096
|
try {
|
|
2861
|
-
const response = await
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
"Content-Type": "application/json",
|
|
2865
|
-
"Accept": "application/json"
|
|
2866
|
-
},
|
|
2867
|
-
body: JSON.stringify({ ...request, network: wireNetwork(request.network) }),
|
|
2868
|
-
signal: controller.signal
|
|
3097
|
+
const { response, error } = await this.writeJson(url, {
|
|
3098
|
+
...request,
|
|
3099
|
+
network: wireNetwork(request.network)
|
|
2869
3100
|
});
|
|
2870
|
-
|
|
2871
|
-
if (!response.ok) {
|
|
2872
|
-
const errorText = await response.text();
|
|
3101
|
+
if (error) {
|
|
2873
3102
|
return {
|
|
2874
3103
|
success: false,
|
|
2875
|
-
error:
|
|
2876
|
-
network: request.network
|
|
3104
|
+
error: error.error,
|
|
3105
|
+
network: request.network,
|
|
3106
|
+
...failureFields(error)
|
|
2877
3107
|
};
|
|
2878
3108
|
}
|
|
2879
3109
|
return await response.json();
|
|
2880
3110
|
} catch (error) {
|
|
2881
|
-
clearTimeout(timeoutId);
|
|
2882
3111
|
return {
|
|
2883
3112
|
success: false,
|
|
2884
3113
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
2885
|
-
network: request.network
|
|
3114
|
+
network: request.network,
|
|
3115
|
+
retryable: true,
|
|
3116
|
+
safeToReplay: false,
|
|
3117
|
+
retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
|
|
2886
3118
|
};
|
|
2887
3119
|
}
|
|
2888
3120
|
}
|
|
@@ -2991,34 +3223,28 @@ var Erc8004Client = class {
|
|
|
2991
3223
|
*/
|
|
2992
3224
|
async submitRelayedFeedback(request) {
|
|
2993
3225
|
const url = `${this.baseUrl}/feedback/evm/submit`;
|
|
2994
|
-
const controller = new AbortController();
|
|
2995
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2996
3226
|
try {
|
|
2997
|
-
const response = await
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
"Content-Type": "application/json",
|
|
3001
|
-
"Accept": "application/json"
|
|
3002
|
-
},
|
|
3003
|
-
body: JSON.stringify({ ...request, network: wireNetwork(request.network) }),
|
|
3004
|
-
signal: controller.signal
|
|
3227
|
+
const { response, error } = await this.writeJson(url, {
|
|
3228
|
+
...request,
|
|
3229
|
+
network: wireNetwork(request.network)
|
|
3005
3230
|
});
|
|
3006
|
-
|
|
3007
|
-
if (!response.ok) {
|
|
3008
|
-
const errorText = await response.text();
|
|
3231
|
+
if (error) {
|
|
3009
3232
|
return {
|
|
3010
3233
|
success: false,
|
|
3011
|
-
error:
|
|
3012
|
-
network: request.network
|
|
3234
|
+
error: error.error,
|
|
3235
|
+
network: request.network,
|
|
3236
|
+
...failureFields(error)
|
|
3013
3237
|
};
|
|
3014
3238
|
}
|
|
3015
3239
|
return await response.json();
|
|
3016
3240
|
} catch (error) {
|
|
3017
|
-
clearTimeout(timeoutId);
|
|
3018
3241
|
return {
|
|
3019
3242
|
success: false,
|
|
3020
3243
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
3021
|
-
network: request.network
|
|
3244
|
+
network: request.network,
|
|
3245
|
+
retryable: true,
|
|
3246
|
+
safeToReplay: false,
|
|
3247
|
+
retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
|
|
3022
3248
|
};
|
|
3023
3249
|
}
|
|
3024
3250
|
}
|
|
@@ -3034,8 +3260,6 @@ var Erc8004Client = class {
|
|
|
3034
3260
|
*/
|
|
3035
3261
|
async revokeFeedback(network, agentId, feedbackIndex, options) {
|
|
3036
3262
|
const url = `${this.baseUrl}/feedback/revoke`;
|
|
3037
|
-
const controller = new AbortController();
|
|
3038
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
3039
3263
|
const payload = {
|
|
3040
3264
|
x402Version: 1,
|
|
3041
3265
|
network: wireNetwork(network),
|
|
@@ -3048,31 +3272,24 @@ var Erc8004Client = class {
|
|
|
3048
3272
|
payload.originalFeedback = options.originalFeedback;
|
|
3049
3273
|
}
|
|
3050
3274
|
try {
|
|
3051
|
-
const response = await
|
|
3052
|
-
|
|
3053
|
-
headers: {
|
|
3054
|
-
"Content-Type": "application/json",
|
|
3055
|
-
"Accept": "application/json"
|
|
3056
|
-
},
|
|
3057
|
-
body: JSON.stringify(payload),
|
|
3058
|
-
signal: controller.signal
|
|
3059
|
-
});
|
|
3060
|
-
clearTimeout(timeoutId);
|
|
3061
|
-
if (!response.ok) {
|
|
3062
|
-
const errorText = await response.text();
|
|
3275
|
+
const { response, error } = await this.writeJson(url, payload);
|
|
3276
|
+
if (error) {
|
|
3063
3277
|
return {
|
|
3064
3278
|
success: false,
|
|
3065
|
-
error:
|
|
3066
|
-
network
|
|
3279
|
+
error: error.error,
|
|
3280
|
+
network,
|
|
3281
|
+
...failureFields(error)
|
|
3067
3282
|
};
|
|
3068
3283
|
}
|
|
3069
3284
|
return await response.json();
|
|
3070
3285
|
} catch (error) {
|
|
3071
|
-
clearTimeout(timeoutId);
|
|
3072
3286
|
return {
|
|
3073
3287
|
success: false,
|
|
3074
3288
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
3075
|
-
network
|
|
3289
|
+
network,
|
|
3290
|
+
retryable: true,
|
|
3291
|
+
safeToReplay: false,
|
|
3292
|
+
retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
|
|
3076
3293
|
};
|
|
3077
3294
|
}
|
|
3078
3295
|
}
|
|
@@ -3162,24 +3379,23 @@ var Erc8004Client = class {
|
|
|
3162
3379
|
}
|
|
3163
3380
|
/** Shared POST for the relay routes: a refusal is data, never a throw. */
|
|
3164
3381
|
async postRelay(path, request, onError) {
|
|
3165
|
-
const controller = new AbortController();
|
|
3166
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
3167
3382
|
try {
|
|
3168
|
-
const response = await
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
body: JSON.stringify({ ...request, network: wireNetwork(request.network) }),
|
|
3172
|
-
signal: controller.signal
|
|
3383
|
+
const { response, error } = await this.writeJson(`${this.baseUrl}${path}`, {
|
|
3384
|
+
...request,
|
|
3385
|
+
network: wireNetwork(request.network)
|
|
3173
3386
|
});
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
const errorText = await response.text();
|
|
3177
|
-
return { ...onError, error: `Facilitator error: ${response.status} - ${errorText}` };
|
|
3387
|
+
if (error) {
|
|
3388
|
+
return { ...onError, error: error.error, ...failureFields(error) };
|
|
3178
3389
|
}
|
|
3179
3390
|
return await response.json();
|
|
3180
3391
|
} catch (error) {
|
|
3181
|
-
|
|
3182
|
-
|
|
3392
|
+
return {
|
|
3393
|
+
...onError,
|
|
3394
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
3395
|
+
retryable: true,
|
|
3396
|
+
safeToReplay: false,
|
|
3397
|
+
retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
|
|
3398
|
+
};
|
|
3183
3399
|
}
|
|
3184
3400
|
}
|
|
3185
3401
|
/**
|
|
@@ -3216,8 +3432,6 @@ var Erc8004Client = class {
|
|
|
3216
3432
|
*/
|
|
3217
3433
|
async appendResponse(network, agentId, feedbackIndex, response, options) {
|
|
3218
3434
|
const url = `${this.baseUrl}/feedback/response`;
|
|
3219
|
-
const controller = new AbortController();
|
|
3220
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
3221
3435
|
const payload = {
|
|
3222
3436
|
x402Version: 1,
|
|
3223
3437
|
network: wireNetwork(network),
|
|
@@ -3232,31 +3446,24 @@ var Erc8004Client = class {
|
|
|
3232
3446
|
payload.sealHash = options.sealHash;
|
|
3233
3447
|
}
|
|
3234
3448
|
try {
|
|
3235
|
-
const fetchResponse = await
|
|
3236
|
-
|
|
3237
|
-
headers: {
|
|
3238
|
-
"Content-Type": "application/json",
|
|
3239
|
-
"Accept": "application/json"
|
|
3240
|
-
},
|
|
3241
|
-
body: JSON.stringify(payload),
|
|
3242
|
-
signal: controller.signal
|
|
3243
|
-
});
|
|
3244
|
-
clearTimeout(timeoutId);
|
|
3245
|
-
if (!fetchResponse.ok) {
|
|
3246
|
-
const errorText = await fetchResponse.text();
|
|
3449
|
+
const { response: fetchResponse, error } = await this.writeJson(url, payload);
|
|
3450
|
+
if (error) {
|
|
3247
3451
|
return {
|
|
3248
3452
|
success: false,
|
|
3249
|
-
error:
|
|
3250
|
-
network
|
|
3453
|
+
error: error.error,
|
|
3454
|
+
network,
|
|
3455
|
+
...failureFields(error)
|
|
3251
3456
|
};
|
|
3252
3457
|
}
|
|
3253
3458
|
return await fetchResponse.json();
|
|
3254
3459
|
} catch (error) {
|
|
3255
|
-
clearTimeout(timeoutId);
|
|
3256
3460
|
return {
|
|
3257
3461
|
success: false,
|
|
3258
3462
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
3259
|
-
network
|
|
3463
|
+
network,
|
|
3464
|
+
retryable: true,
|
|
3465
|
+
safeToReplay: false,
|
|
3466
|
+
retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
|
|
3260
3467
|
};
|
|
3261
3468
|
}
|
|
3262
3469
|
}
|
|
@@ -3300,47 +3507,42 @@ var Erc8004Client = class {
|
|
|
3300
3507
|
return jobToRegisterResponse(terminal, request.network);
|
|
3301
3508
|
}
|
|
3302
3509
|
const url = `${this.baseUrl}/register`;
|
|
3303
|
-
const controller = new AbortController();
|
|
3304
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
3305
3510
|
try {
|
|
3306
|
-
const response = await
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
"Content-Type": "application/json",
|
|
3310
|
-
"Accept": "application/json"
|
|
3311
|
-
},
|
|
3312
|
-
body: JSON.stringify({ ...request, network: wireNetwork(request.network) }),
|
|
3313
|
-
signal: controller.signal
|
|
3511
|
+
const { response, error } = await this.writeJson(url, {
|
|
3512
|
+
...request,
|
|
3513
|
+
network: wireNetwork(request.network)
|
|
3314
3514
|
});
|
|
3315
|
-
|
|
3316
|
-
if (!response.ok) {
|
|
3317
|
-
const errorText = await response.text();
|
|
3515
|
+
if (error) {
|
|
3318
3516
|
try {
|
|
3319
|
-
const parsed = JSON.parse(
|
|
3517
|
+
const parsed = JSON.parse(error.body);
|
|
3320
3518
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
3321
3519
|
return {
|
|
3322
3520
|
...parsed,
|
|
3323
|
-
// Never let a
|
|
3521
|
+
// Never let a non-2xx body claim success, whatever it says.
|
|
3324
3522
|
success: false,
|
|
3325
|
-
error: parsed.error ?? `Facilitator error: ${
|
|
3326
|
-
network: parsed.network ?? request.network
|
|
3523
|
+
error: parsed.error ?? `Facilitator error: ${error.status}`,
|
|
3524
|
+
network: parsed.network ?? request.network,
|
|
3525
|
+
...failureFields(error)
|
|
3327
3526
|
};
|
|
3328
3527
|
}
|
|
3329
3528
|
} catch {
|
|
3330
3529
|
}
|
|
3331
3530
|
return {
|
|
3332
3531
|
success: false,
|
|
3333
|
-
error:
|
|
3334
|
-
network: request.network
|
|
3532
|
+
error: error.error,
|
|
3533
|
+
network: request.network,
|
|
3534
|
+
...failureFields(error)
|
|
3335
3535
|
};
|
|
3336
3536
|
}
|
|
3337
3537
|
return await response.json();
|
|
3338
3538
|
} catch (error) {
|
|
3339
|
-
clearTimeout(timeoutId);
|
|
3340
3539
|
return {
|
|
3341
3540
|
success: false,
|
|
3342
3541
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
3343
|
-
network: request.network
|
|
3542
|
+
network: request.network,
|
|
3543
|
+
retryable: true,
|
|
3544
|
+
safeToReplay: false,
|
|
3545
|
+
retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS
|
|
3344
3546
|
};
|
|
3345
3547
|
}
|
|
3346
3548
|
}
|
|
@@ -3358,33 +3560,20 @@ var Erc8004Client = class {
|
|
|
3358
3560
|
*/
|
|
3359
3561
|
async registerAgentAsync(request) {
|
|
3360
3562
|
const url = `${this.baseUrl}/register`;
|
|
3361
|
-
const
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
});
|
|
3374
|
-
clearTimeout(timeoutId);
|
|
3375
|
-
if (!response.ok) {
|
|
3376
|
-
const errorText = await response.text();
|
|
3377
|
-
throw new Erc8004LookupError(
|
|
3378
|
-
`ERC-8004 API error: ${response.status} - ${errorText}`,
|
|
3379
|
-
response.status,
|
|
3380
|
-
errorText
|
|
3381
|
-
);
|
|
3382
|
-
}
|
|
3383
|
-
return await response.json();
|
|
3384
|
-
} catch (error) {
|
|
3385
|
-
clearTimeout(timeoutId);
|
|
3386
|
-
throw error;
|
|
3563
|
+
const { response, error } = await this.writeJson(
|
|
3564
|
+
url,
|
|
3565
|
+
{ ...request, network: wireNetwork(request.network) },
|
|
3566
|
+
{ Prefer: "respond-async" }
|
|
3567
|
+
);
|
|
3568
|
+
if (error) {
|
|
3569
|
+
throw new Erc8004LookupError(
|
|
3570
|
+
`ERC-8004 API error: ${error.status} - ${error.body}`,
|
|
3571
|
+
error.status,
|
|
3572
|
+
error.body,
|
|
3573
|
+
error.retryAfterSeconds
|
|
3574
|
+
);
|
|
3387
3575
|
}
|
|
3576
|
+
return await response.json();
|
|
3388
3577
|
}
|
|
3389
3578
|
/**
|
|
3390
3579
|
* Read the current state of an asynchronous registration.
|
|
@@ -3396,26 +3585,21 @@ var Erc8004Client = class {
|
|
|
3396
3585
|
const url = `${this.baseUrl}/register/status/${encodeURIComponent(jobId)}`;
|
|
3397
3586
|
const controller = new AbortController();
|
|
3398
3587
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
);
|
|
3413
|
-
}
|
|
3414
|
-
return await response.json();
|
|
3415
|
-
} catch (error) {
|
|
3416
|
-
clearTimeout(timeoutId);
|
|
3417
|
-
throw error;
|
|
3588
|
+
const response = await fetch(url, {
|
|
3589
|
+
method: "GET",
|
|
3590
|
+
headers: { Accept: "application/json" },
|
|
3591
|
+
signal: controller.signal
|
|
3592
|
+
}).finally(() => clearTimeout(timeoutId));
|
|
3593
|
+
if (!response.ok) {
|
|
3594
|
+
const info = await readFacilitatorError(response);
|
|
3595
|
+
throw new Erc8004LookupError(
|
|
3596
|
+
`ERC-8004 API error: ${info.status} - ${info.body}`,
|
|
3597
|
+
info.status,
|
|
3598
|
+
info.body,
|
|
3599
|
+
info.retryAfterSeconds
|
|
3600
|
+
);
|
|
3418
3601
|
}
|
|
3602
|
+
return await response.json();
|
|
3419
3603
|
}
|
|
3420
3604
|
/**
|
|
3421
3605
|
* Poll an asynchronous registration until it finishes.
|
|
@@ -3716,6 +3900,7 @@ var AdvancedEscrowClient = class {
|
|
|
3716
3900
|
chainId;
|
|
3717
3901
|
gasLimit;
|
|
3718
3902
|
timeout;
|
|
3903
|
+
retries;
|
|
3719
3904
|
contracts;
|
|
3720
3905
|
signer;
|
|
3721
3906
|
// ethers.Signer (legacy mode)
|
|
@@ -3752,6 +3937,7 @@ var AdvancedEscrowClient = class {
|
|
|
3752
3937
|
this.chainId = options.chainId || 8453;
|
|
3753
3938
|
this.gasLimit = options.gasLimit || 3e5;
|
|
3754
3939
|
this.timeout = options.timeout || ESCROW_TIMEOUT_MS[this.chainId] || DEFAULT_ESCROW_TIMEOUT_MS;
|
|
3940
|
+
this.retries = options.retries;
|
|
3755
3941
|
if (this.walletAdapter && !this.rpcUrl) {
|
|
3756
3942
|
throw new Error(
|
|
3757
3943
|
"AdvancedEscrowClient: rpcUrl is required when using a SigningWalletAdapter. The adapter signs transactions offline; an RPC provider is needed to build and broadcast them."
|
|
@@ -4124,16 +4310,19 @@ var AdvancedEscrowClient = class {
|
|
|
4124
4310
|
}
|
|
4125
4311
|
}
|
|
4126
4312
|
};
|
|
4127
|
-
const controller = new AbortController();
|
|
4128
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
4129
4313
|
try {
|
|
4130
|
-
const response = await
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4314
|
+
const { response, error } = await facilitatorFetch(
|
|
4315
|
+
`${this.facilitatorUrl}/settle`,
|
|
4316
|
+
{
|
|
4317
|
+
method: "POST",
|
|
4318
|
+
headers: { "Content-Type": "application/json" },
|
|
4319
|
+
body: JSON.stringify(payload)
|
|
4320
|
+
},
|
|
4321
|
+
{ timeoutMs: this.timeout, retries: this.retries }
|
|
4322
|
+
);
|
|
4323
|
+
if (error) {
|
|
4324
|
+
return { success: false, error: error.error, ...failureFields(error) };
|
|
4325
|
+
}
|
|
4137
4326
|
const result = await response.json();
|
|
4138
4327
|
if (result.success) {
|
|
4139
4328
|
return {
|
|
@@ -4146,7 +4335,6 @@ var AdvancedEscrowClient = class {
|
|
|
4146
4335
|
error: result.errorReason || result.error || `Release refused with no reason (HTTP ${response.status}, body keys: ${Object.keys(result ?? {}).sort().join(", ") || "none"})`
|
|
4147
4336
|
};
|
|
4148
4337
|
} catch (fetchErr) {
|
|
4149
|
-
clearTimeout(timeoutId);
|
|
4150
4338
|
if (fetchErr.name === "AbortError") {
|
|
4151
4339
|
try {
|
|
4152
4340
|
const state = await this.queryEscrowState(paymentInfo);
|
|
@@ -4164,23 +4352,53 @@ var AdvancedEscrowClient = class {
|
|
|
4164
4352
|
}
|
|
4165
4353
|
}
|
|
4166
4354
|
/**
|
|
4167
|
-
* GASLESS REFUND:
|
|
4355
|
+
* GASLESS REFUND: return escrowed funds to the payer via the facilitator.
|
|
4168
4356
|
*
|
|
4169
|
-
*
|
|
4170
|
-
*
|
|
4171
|
-
*
|
|
4357
|
+
* Sends `action: "refundInEscrow"` to `POST /settle`; the facilitator's
|
|
4358
|
+
* PaymentOperator calls `AuthCaptureEscrow.partialVoid`.
|
|
4359
|
+
*
|
|
4360
|
+
* # This is how an EXPIRED escrow is recovered
|
|
4361
|
+
*
|
|
4362
|
+
* A widely repeated claim — including in this SDK's own comments until now —
|
|
4363
|
+
* says that once `authorizationExpiry` passes, only the payer's `reclaim()`
|
|
4364
|
+
* can move the funds. **That is false, and believing it has left real money
|
|
4365
|
+
* stranded.**
|
|
4366
|
+
*
|
|
4367
|
+
* Read the contract (`AuthCaptureEscrow.sol`):
|
|
4368
|
+
*
|
|
4369
|
+
* - `partialVoid` is `onlySender(paymentInfo.operator)` — the operator is the
|
|
4370
|
+
* FACILITATOR, not the payer — it sends the tokens **to the payer**, and it
|
|
4371
|
+
* **does not check `authorizationExpiry` at all**. It works before expiry
|
|
4372
|
+
* and after it, and the payer never has to appear.
|
|
4373
|
+
* - `reclaim` is `onlySender(paymentInfo.payer)` and only after expiry. It is
|
|
4374
|
+
* a payer's self-service escape hatch, which is why this facilitator does
|
|
4375
|
+
* not expose it — **not** the only way out.
|
|
4376
|
+
*
|
|
4377
|
+
* So a release that reverted with `AfterAuthorizationExpiry` is recoverable
|
|
4378
|
+
* from here, with no gas and no cooperation from the payer. Get the amount
|
|
4379
|
+
* from {@link queryEscrowState}'s `capturableAmount`.
|
|
4380
|
+
*
|
|
4381
|
+
* # A refusal that is not a refusal
|
|
4382
|
+
*
|
|
4383
|
+
* Check `retryable` before writing an escrow off. A `503` means the
|
|
4384
|
+
* facilitator never reached the chain — the escrow is untouched and the same
|
|
4385
|
+
* request should be sent again.
|
|
4172
4386
|
*
|
|
4173
4387
|
* @param paymentInfo - PaymentInfo from the authorize step
|
|
4174
|
-
* @param amount - Amount to refund in atomic units (defaults to maxAmount)
|
|
4388
|
+
* @param amount - Amount to refund in atomic units (defaults to maxAmount).
|
|
4389
|
+
* For a stuck escrow pass `capturableAmount` from {@link queryEscrowState}.
|
|
4175
4390
|
* @returns Transaction result from the facilitator
|
|
4176
4391
|
*
|
|
4177
|
-
* @example
|
|
4392
|
+
* @example Recovering an escrow whose release window already closed
|
|
4178
4393
|
* ```typescript
|
|
4179
|
-
* const
|
|
4180
|
-
*
|
|
4181
|
-
*
|
|
4182
|
-
*
|
|
4183
|
-
*
|
|
4394
|
+
* const state = await client.queryEscrowState(pi);
|
|
4395
|
+
* if (state.capturableAmount !== '0') {
|
|
4396
|
+
* // No payer needed, no gas, and expiry is irrelevant to partialVoid.
|
|
4397
|
+
* const result = await client.refundViaFacilitator(pi, state.capturableAmount);
|
|
4398
|
+
* if (!result.success && result.retryable) {
|
|
4399
|
+
* // No verdict was reached. The funds are still there; send it again.
|
|
4400
|
+
* }
|
|
4401
|
+
* }
|
|
4184
4402
|
* ```
|
|
4185
4403
|
*/
|
|
4186
4404
|
async refundViaFacilitator(paymentInfo, amount) {
|
|
@@ -4217,16 +4435,19 @@ var AdvancedEscrowClient = class {
|
|
|
4217
4435
|
}
|
|
4218
4436
|
}
|
|
4219
4437
|
};
|
|
4220
|
-
const controller = new AbortController();
|
|
4221
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
4222
4438
|
try {
|
|
4223
|
-
const response = await
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4439
|
+
const { response, error } = await facilitatorFetch(
|
|
4440
|
+
`${this.facilitatorUrl}/settle`,
|
|
4441
|
+
{
|
|
4442
|
+
method: "POST",
|
|
4443
|
+
headers: { "Content-Type": "application/json" },
|
|
4444
|
+
body: JSON.stringify(payload)
|
|
4445
|
+
},
|
|
4446
|
+
{ timeoutMs: this.timeout, retries: this.retries }
|
|
4447
|
+
);
|
|
4448
|
+
if (error) {
|
|
4449
|
+
return { success: false, error: error.error, ...failureFields(error) };
|
|
4450
|
+
}
|
|
4230
4451
|
const result = await response.json();
|
|
4231
4452
|
if (result.success) {
|
|
4232
4453
|
return {
|
|
@@ -4234,9 +4455,11 @@ var AdvancedEscrowClient = class {
|
|
|
4234
4455
|
transactionHash: result.transaction || result.transactionHash || result.transaction_hash
|
|
4235
4456
|
};
|
|
4236
4457
|
}
|
|
4237
|
-
return {
|
|
4458
|
+
return {
|
|
4459
|
+
success: false,
|
|
4460
|
+
error: result.errorReason || result.error || `Refund refused with no reason (HTTP ${response.status}, body keys: ${Object.keys(result ?? {}).sort().join(", ") || "none"})`
|
|
4461
|
+
};
|
|
4238
4462
|
} catch (fetchErr) {
|
|
4239
|
-
clearTimeout(timeoutId);
|
|
4240
4463
|
if (fetchErr.name === "AbortError") {
|
|
4241
4464
|
try {
|
|
4242
4465
|
const state = await this.queryEscrowState(paymentInfo);
|
|
@@ -4462,9 +4685,12 @@ var AdvancedEscrowClient = class {
|
|
|
4462
4685
|
}
|
|
4463
4686
|
};
|
|
4464
4687
|
|
|
4688
|
+
exports.AMBIGUOUS_LEASE_REASONS = AMBIGUOUS_LEASE_REASONS;
|
|
4465
4689
|
exports.AdvancedEscrowClient = AdvancedEscrowClient;
|
|
4466
4690
|
exports.BASE_MAINNET_CONTRACTS = BASE_MAINNET_CONTRACTS;
|
|
4467
4691
|
exports.BazaarClient = BazaarClient;
|
|
4692
|
+
exports.DEFAULT_FACILITATOR_RETRIES = DEFAULT_FACILITATOR_RETRIES;
|
|
4693
|
+
exports.DEFAULT_RETRY_AFTER_SECONDS = DEFAULT_RETRY_AFTER_SECONDS;
|
|
4468
4694
|
exports.DEPOSIT_LIMIT_USDC = DEPOSIT_LIMIT_USDC;
|
|
4469
4695
|
exports.ERC8004_CONTRACTS = ERC8004_CONTRACTS;
|
|
4470
4696
|
exports.ERC8004_EXTENSION_ID = ERC8004_EXTENSION_ID;
|
|
@@ -4475,15 +4701,18 @@ exports.Erc8004LookupError = Erc8004LookupError;
|
|
|
4475
4701
|
exports.EscrowClient = EscrowClient;
|
|
4476
4702
|
exports.FacilitatorClient = FacilitatorClient;
|
|
4477
4703
|
exports.HEALTH_FILTERS = HEALTH_FILTERS;
|
|
4704
|
+
exports.MAX_RETRY_AFTER_SECONDS = MAX_RETRY_AFTER_SECONDS;
|
|
4478
4705
|
exports.MAX_SEARCH_LEN = MAX_SEARCH_LEN;
|
|
4479
4706
|
exports.OPERATOR_ABI = OPERATOR_ABI;
|
|
4480
4707
|
exports.OPERATOR_ABI_CREATE3 = OPERATOR_ABI_CREATE3;
|
|
4481
4708
|
exports.PAYMENT_INFO_TYPEHASH = PAYMENT_INFO_TYPEHASH;
|
|
4482
4709
|
exports.RELAYED_FEEDBACK_NETWORKS = RELAYED_FEEDBACK_NETWORKS;
|
|
4710
|
+
exports.REPLAYABLE_LEASE_REASONS = REPLAYABLE_LEASE_REASONS;
|
|
4483
4711
|
exports.RegistrationPendingError = RegistrationPendingError;
|
|
4484
4712
|
exports.TIER_FILTERS = TIER_FILTERS;
|
|
4485
4713
|
exports.TIER_TIMINGS = TIER_TIMINGS;
|
|
4486
4714
|
exports.USDC_DOMAIN_NAME = USDC_DOMAIN_NAME;
|
|
4715
|
+
exports.WRITER_LEASE_REASONS = WRITER_LEASE_REASONS;
|
|
4487
4716
|
exports.X402_CORS_HEADERS = X402_CORS_HEADERS;
|
|
4488
4717
|
exports.X402_HEADER_NAMES = X402_HEADER_NAMES;
|
|
4489
4718
|
exports.ZERO_ADDRESS = ZERO_ADDRESS;
|
|
@@ -4495,20 +4724,26 @@ exports.buildVerifyRequest = buildVerifyRequest;
|
|
|
4495
4724
|
exports.buildVerifyRequestV2 = buildVerifyRequestV2;
|
|
4496
4725
|
exports.canRefundEscrow = canRefundEscrow;
|
|
4497
4726
|
exports.canReleaseEscrow = canReleaseEscrow;
|
|
4727
|
+
exports.carryFailureFields = carryFailureFields;
|
|
4498
4728
|
exports.create402Response = create402Response;
|
|
4499
4729
|
exports.createHonoMiddleware = createHonoMiddleware;
|
|
4500
4730
|
exports.createPaymentMiddleware = createPaymentMiddleware;
|
|
4501
4731
|
exports.epochToDate = epochToDate;
|
|
4502
4732
|
exports.escrowTimeRemaining = escrowTimeRemaining;
|
|
4503
4733
|
exports.extractPaymentFromHeaders = extractPaymentFromHeaders;
|
|
4734
|
+
exports.facilitatorFetch = facilitatorFetch;
|
|
4504
4735
|
exports.getCorsHeaders = getCorsHeaders;
|
|
4505
4736
|
exports.getEscrowContractsByChainId = getEscrowContractsByChainId;
|
|
4506
4737
|
exports.getEscrowSupportedChainIds = getEscrowSupportedChainIds;
|
|
4507
4738
|
exports.isAlive = isAlive;
|
|
4739
|
+
exports.isAmbiguousLeaseReason = isAmbiguousLeaseReason;
|
|
4508
4740
|
exports.isEscrowExpired = isEscrowExpired;
|
|
4509
4741
|
exports.isEscrowSupportedOnChain = isEscrowSupportedOnChain;
|
|
4510
4742
|
exports.isRegisterJobTerminal = isRegisterJobTerminal;
|
|
4743
|
+
exports.isReplayableLeaseReason = isReplayableLeaseReason;
|
|
4511
4744
|
exports.parsePaymentHeader = parsePaymentHeader;
|
|
4745
|
+
exports.parseRetryAfterSeconds = parseRetryAfterSeconds;
|
|
4746
|
+
exports.readFacilitatorError = readFacilitatorError;
|
|
4512
4747
|
exports.supportsRelayedFeedback = supportsRelayedFeedback;
|
|
4513
4748
|
exports.wireNetwork = wireNetwork;
|
|
4514
4749
|
//# sourceMappingURL=index.js.map
|