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