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