run402 4.10.1 → 4.11.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.
@@ -18,6 +18,7 @@
18
18
  import type { CredentialsProvider } from "../credentials.js";
19
19
  import { Run402Error } from "../errors.js";
20
20
  import type { MppStack, X402Stack } from "./_paid-stack.js";
21
+ import { PaymentBuyerError, type PayExecutor } from "../namespaces/pay.js";
21
22
  import { type PaymentAttemptStore } from "./payment-attempts.js";
22
23
  type FetchFn = typeof globalThis.fetch;
23
24
  interface RpcClient {
@@ -34,6 +35,36 @@ interface PaymentRequirementLike {
34
35
  amount?: string;
35
36
  [key: string]: unknown;
36
37
  }
38
+ export interface X402PaymentRequirements {
39
+ scheme: string;
40
+ network: string;
41
+ asset: string;
42
+ amount: string;
43
+ payTo: string;
44
+ maxTimeoutSeconds: number;
45
+ extra: Record<string, unknown>;
46
+ }
47
+ export interface X402PaymentRequired {
48
+ x402Version: number;
49
+ error?: string;
50
+ resource: {
51
+ url: string;
52
+ description?: string;
53
+ mimeType?: string;
54
+ };
55
+ accepts: X402PaymentRequirements[];
56
+ extensions?: Record<string, unknown>;
57
+ }
58
+ export interface X402PaymentPayload {
59
+ x402Version: number;
60
+ resource?: X402PaymentRequired["resource"];
61
+ accepted: X402PaymentRequirements;
62
+ payload: Record<string, unknown>;
63
+ extensions?: Record<string, unknown>;
64
+ }
65
+ export interface X402BuyerClient {
66
+ createPaymentPayload(required: X402PaymentRequired): Promise<X402PaymentPayload>;
67
+ }
37
68
  interface KnownBalance {
38
69
  status: "known";
39
70
  balance: bigint;
@@ -106,12 +137,15 @@ export interface PaymentPayerProvenance {
106
137
  }
107
138
  export type ConfiguredPaidFetch = FetchFn & {
108
139
  readonly payer: PaymentPayerProvenance;
140
+ readonly pay?: PayExecutor;
109
141
  /** Refreshes mutable balance state without re-resolving the selected payer. */
110
142
  refreshBalances(): Promise<void>;
111
143
  };
112
144
  export type LazyPaidFetch = FetchFn & {
113
145
  /** Initializes the selected source if needed and returns public payer provenance only. */
114
146
  getPayer(): Promise<PaymentPayerProvenance | null>;
147
+ /** Execute the receipt-bearing arbitrary-URL buyer flow. */
148
+ pay: PayExecutor;
115
149
  };
116
150
  export interface PaidFetchOptions {
117
151
  /** Explicit local allowance file. When set, no other allowance is consulted. */
@@ -131,6 +165,7 @@ interface TrackedPaidFetchOptions {
131
165
  createAttemptId?: () => string;
132
166
  now?: () => string;
133
167
  fetch?: FetchFn;
168
+ classifyPaymentResponse?: (response: Response) => Promise<"completed" | "failed" | "already_settled" | "intent_pending" | "ambiguous">;
134
169
  }
135
170
  /** @internal Exported only for deterministic source-level tests; not re-exported by the package. */
136
171
  export declare function checkBalanceAcrossProviders(clients: readonly RpcClient[], tokenAddress: string, walletAddress: string, network: string, options?: BalanceRetryOptions): Promise<bigint>;
@@ -148,6 +183,16 @@ export declare function createLazyPaidFetch(options?: PaidFetchOptions): LazyPai
148
183
  export declare const __paidFetchInternals: {
149
184
  createLazyPaidFetchFrom: typeof createLazyPaidFetchFrom;
150
185
  };
186
+ interface X402BuyerFetchOptions extends TrackedPaidFetchOptions {
187
+ supportedNetworks: readonly string[];
188
+ }
189
+ /**
190
+ * Receipt-bearing buyer orchestration over an already-configured x402 client.
191
+ * Signed proofs live only in this SDK instance's memory. An ambiguous retry of
192
+ * the identical request re-presents that proof; it never mints a replacement.
193
+ */
194
+ export declare function createX402BuyerFetch(client: X402BuyerClient, options: X402BuyerFetchOptions): PayExecutor;
195
+ export declare function paymentNetworkUnsupportedError(challengeNetworkValues: string[], walletNetworkValues: string[], extra?: Record<string, unknown>): PaymentBuyerError;
151
196
  /**
152
197
  * Request-scoped payment tracker around `@x402/fetch`.
153
198
  *
@@ -1 +1 @@
1
- {"version":3,"file":"paid-fetch.d.ts","sourceRoot":"","sources":["../../src/node/paid-fetch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"paid-fetch.d.ts","sourceRoot":"","sources":["../../src/node/paid-fetch.ts"],"names":[],"mappings":"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"}
@@ -17,8 +17,10 @@
17
17
  */
18
18
  import { readAllowance } from "../../core-dist/allowance.js";
19
19
  import { AsyncLocalStorage } from "node:async_hooks";
20
+ import { createHash } from "node:crypto";
20
21
  import { LocalError, PaymentAttemptError, Run402Error, } from "../errors.js";
21
22
  import { PaidStackUnavailable, loadMppStack, loadX402Stack } from "./_paid-stack.js";
23
+ import { DEFAULT_PAYMENT_MAX_USD_MICROS, PaymentBuyerError, gatewayPaymentBuyerError, isTrustedRun402PaymentUrl, isTrustedRun402PendingResponse, payResponseMetadata, paymentExceedsMaxError, readPaymentErrorEnvelope, responseSignalsReplay, walletUnavailableError, } from "../namespaces/pay.js";
22
24
  import { attemptIdFromRequest, createFilePaymentAttemptStore, createPaymentAttemptId, hasPaymentAuthorization, requestSummary, withPaymentAttemptHeader, } from "./payment-attempts.js";
23
25
  /**
24
26
  * A machine-readable x402 balance-preflight failure.
@@ -361,6 +363,13 @@ export async function setupPaidFetch(options = {}) {
361
363
  }
362
364
  };
363
365
  }, client);
366
+ const supportedNetworks = [
367
+ ...(mainnetSigner ? ["eip155:8453"] : []),
368
+ ...(sepoliaSigner ? ["eip155:84532"] : []),
369
+ ];
370
+ const buyer = createX402BuyerFetch(client, {
371
+ supportedNetworks,
372
+ });
364
373
  return withPayer(trackedFetch, {
365
374
  source: options.paymentSigner ? "payment_signer" : resolvedAllowance.source,
366
375
  rail: "x402",
@@ -368,7 +377,7 @@ export async function setupPaidFetch(options = {}) {
368
377
  ...(mainnetSigner ? [{ address: mainnetSigner.address, network: "eip155:8453" }] : []),
369
378
  ...(sepoliaSigner ? [{ address: sepoliaSigner.address, network: "eip155:84532" }] : []),
370
379
  ],
371
- }, refreshBalances);
380
+ }, refreshBalances, buyer);
372
381
  }
373
382
  catch (err) {
374
383
  // Missing optional peers are a stable local capability state. Other setup
@@ -479,6 +488,25 @@ export function createLazyPaidFetch(options = {}) {
479
488
  async getPayer() {
480
489
  return (await initialize())?.payer ?? null;
481
490
  },
491
+ async pay(url, init, payOptions) {
492
+ const configured = await initialize();
493
+ if (configured?.pay)
494
+ return configured.pay(url, init, payOptions);
495
+ const response = await globalThis.fetch(url, init);
496
+ if (response.status !== 402) {
497
+ return {
498
+ response,
499
+ payment: null,
500
+ outcome: "not_required",
501
+ replay: await responseSignalsReplay(response, payOptions.idempotencyKey !== undefined),
502
+ ...payResponseMetadata(response),
503
+ };
504
+ }
505
+ if (configured?.payer.rail === "mpp") {
506
+ throw paymentNetworkUnsupportedError(challengeNetworks(response), configured.payer.payers.flatMap((payer) => payer.network ? [payer.network] : []), { configured_rail: "mpp" });
507
+ }
508
+ throw walletUnavailableError({ challenge_networks: challengeNetworks(response) });
509
+ },
482
510
  });
483
511
  }
484
512
  function validatePaymentSource(options) {
@@ -515,8 +543,8 @@ async function resolveAllowance(options) {
515
543
  return null;
516
544
  }
517
545
  }
518
- function withPayer(fetchFn, payer, refreshBalances = async () => { }) {
519
- return Object.assign(fetchFn, { payer, refreshBalances });
546
+ function withPayer(fetchFn, payer, refreshBalances = async () => { }, pay) {
547
+ return Object.assign(fetchFn, { payer, refreshBalances, ...(pay ? { pay } : {}) });
520
548
  }
521
549
  function localAllowanceSigners(stack, allowance, mainnetClient, sepoliaClient) {
522
550
  const account = stack.privateKeyToAccount(allowance.privateKey);
@@ -529,6 +557,431 @@ function localAllowanceSigners(stack, allowance, mainnetClient, sepoliaClient) {
529
557
  export const __paidFetchInternals = {
530
558
  createLazyPaidFetchFrom,
531
559
  };
560
+ /**
561
+ * Receipt-bearing buyer orchestration over an already-configured x402 client.
562
+ * Signed proofs live only in this SDK instance's memory. An ambiguous retry of
563
+ * the identical request re-presents that proof; it never mints a replacement.
564
+ */
565
+ export function createX402BuyerFetch(client, options) {
566
+ const calls = new AsyncLocalStorage();
567
+ const proofs = new Map();
568
+ const creatingProofs = new Map();
569
+ const tracked = createTrackedX402Fetch((baseFetch) => async (input, init) => {
570
+ const call = calls.getStore();
571
+ if (!call) {
572
+ throw settlementError("The x402 buyer lost its request context before dispatch.", false);
573
+ }
574
+ const request = new Request(input, init);
575
+ const cached = proofs.get(call.fingerprint);
576
+ if (cached) {
577
+ call.proof = cached;
578
+ call.replayedProof = true;
579
+ return baseFetch(withProof(request, cached.headers));
580
+ }
581
+ const retryRequest = request.clone();
582
+ const firstResponse = await baseFetch(request);
583
+ if (firstResponse.status !== 402)
584
+ return firstResponse;
585
+ const required = await decodePaymentRequired(firstResponse);
586
+ const guarded = guardPaymentRequired(required, options.supportedNetworks, call.maxUsdMicros, request.url);
587
+ let pending = creatingProofs.get(call.fingerprint);
588
+ if (!pending) {
589
+ pending = createProof(client, guarded)
590
+ .finally(() => creatingProofs.delete(call.fingerprint));
591
+ creatingProofs.set(call.fingerprint, pending);
592
+ }
593
+ const proof = await pending;
594
+ proofs.set(call.fingerprint, proof);
595
+ call.proof = proof;
596
+ return baseFetch(withProof(retryRequest, proof.headers));
597
+ }, client, {
598
+ store: options.store,
599
+ createAttemptId: options.createAttemptId,
600
+ now: options.now,
601
+ fetch: options.fetch,
602
+ async classifyPaymentResponse(response) {
603
+ const call = calls.getStore();
604
+ const envelope = await readPaymentErrorEnvelope(response);
605
+ if (call && isTrustedRun402PendingResponse({
606
+ requestUrl: call.requestUrl,
607
+ response,
608
+ envelope,
609
+ paymentBearing: true,
610
+ redirectsDisabled: true,
611
+ })) {
612
+ return "intent_pending";
613
+ }
614
+ if (call && envelope && trustedRun402ResponseOrigin(call.requestUrl, response) &&
615
+ gatewayPaymentBuyerError(response, envelope)) {
616
+ return "failed";
617
+ }
618
+ const failure = await upstreamFailure(response);
619
+ if (call?.replayedProof && isAlreadyUsedFailure(failure)) {
620
+ call.alreadySettled = true;
621
+ return "already_settled";
622
+ }
623
+ return isProvenNoSettlementFailure(failure) ? "failed" : "ambiguous";
624
+ },
625
+ });
626
+ return async (url, init, payOptions) => {
627
+ const maxUsdMicros = payOptions.maxUsdMicros ?? DEFAULT_PAYMENT_MAX_USD_MICROS;
628
+ const fingerprint = await paymentRequestFingerprint(url, init, maxUsdMicros);
629
+ const call = {
630
+ fingerprint,
631
+ requestUrl: url,
632
+ maxUsdMicros,
633
+ replayedProof: false,
634
+ alreadySettled: false,
635
+ };
636
+ try {
637
+ const response = await calls.run(call, () => tracked(url, init));
638
+ if (!call.proof) {
639
+ return {
640
+ response,
641
+ payment: null,
642
+ outcome: "not_required",
643
+ replay: await responseSignalsReplay(response, payOptions.idempotencyKey !== undefined),
644
+ ...payResponseMetadata(response),
645
+ };
646
+ }
647
+ if (call.alreadySettled) {
648
+ proofs.delete(fingerprint);
649
+ return {
650
+ response,
651
+ payment: null,
652
+ outcome: "already_settled",
653
+ replay: true,
654
+ ...payResponseMetadata(response),
655
+ };
656
+ }
657
+ if (!response.ok) {
658
+ const envelope = await readPaymentErrorEnvelope(response);
659
+ if (envelope && trustedRun402ResponseOrigin(url, response)) {
660
+ const gatewayError = gatewayPaymentBuyerError(response, envelope);
661
+ if (gatewayError)
662
+ throw gatewayError;
663
+ }
664
+ const failure = await upstreamFailure(response);
665
+ proofs.delete(fingerprint);
666
+ if (failure.code === "payment_insufficient_funds") {
667
+ throw walletUnavailableError({
668
+ upstream_code: failure.code,
669
+ ...(failure.details ?? {}),
670
+ });
671
+ }
672
+ throw settlementError("The x402 payment proof was rejected before settlement.", false, {
673
+ upstream_code: failure.code,
674
+ ...(failure.x402Error ? { x402_error: failure.x402Error } : {}),
675
+ });
676
+ }
677
+ const payment = receiptFromResponse(response, call.proof.accepted, url);
678
+ proofs.delete(fingerprint);
679
+ return {
680
+ response,
681
+ payment,
682
+ outcome: "settled",
683
+ replay: call.replayedProof,
684
+ ...payResponseMetadata(response),
685
+ };
686
+ }
687
+ catch (cause) {
688
+ if (cause instanceof PaymentBuyerError)
689
+ throw cause;
690
+ if (cause instanceof X402BalanceError) {
691
+ if (cause.code === "X402_INSUFFICIENT_FUNDS") {
692
+ throw walletUnavailableError(asRecord(cause.details));
693
+ }
694
+ throw settlementError("The buyer could not confirm wallet funds before signing; no funds moved.", false, { upstream_code: cause.code, ...(asRecord(cause.details)) }, cause, true);
695
+ }
696
+ if (cause instanceof PaymentAttemptError) {
697
+ const fundsMoved = cause.mutationState === "ambiguous" ? "unknown" : false;
698
+ throw settlementError(fundsMoved === "unknown"
699
+ ? "The payment-bearing request lost its response; settlement may have occurred. The same SDK instance retains the original proof for an identical retry."
700
+ : "The payment attempt failed before funds could move.", fundsMoved, {
701
+ payment_attempt_id: cause.paymentAttemptId,
702
+ phase: cause.phase,
703
+ provider_started: cause.providerStarted,
704
+ upstream_code: cause.code,
705
+ }, cause, cause.safeToRetry, cause.nextActions);
706
+ }
707
+ throw settlementError("The x402 payment authorization could not be created; no funds moved.", false, {}, cause);
708
+ }
709
+ };
710
+ }
711
+ async function createProof(client, required) {
712
+ try {
713
+ const payload = await client.createPaymentPayload(required);
714
+ return {
715
+ accepted: payload.accepted,
716
+ headers: {
717
+ [payload.x402Version === 1 ? "X-PAYMENT" : "PAYMENT-SIGNATURE"]: encodeBase64Json(payload),
718
+ },
719
+ };
720
+ }
721
+ catch (cause) {
722
+ throw unwrapPolicyError(cause) ?? cause;
723
+ }
724
+ }
725
+ function guardPaymentRequired(required, supportedNetworks, maxUsdMicros, requestUrl) {
726
+ let challengeUrl;
727
+ try {
728
+ challengeUrl = new URL(required.resource.url).toString();
729
+ }
730
+ catch (cause) {
731
+ throw settlementError("The x402 challenge resource URL is invalid.", false, { challenge_url: required.resource.url, request_url: requestUrl }, cause);
732
+ }
733
+ if (challengeUrl !== new URL(requestUrl).toString()) {
734
+ throw settlementError("The x402 challenge is bound to a different resource URL.", false, { challenge_url: challengeUrl, request_url: requestUrl });
735
+ }
736
+ const supportedByNetwork = required.accepts.filter((accept) => accept.scheme === "exact" && supportedNetworks.includes(accept.network));
737
+ if (supportedByNetwork.length === 0) {
738
+ throw paymentNetworkUnsupportedError([...new Set(required.accepts.map((accept) => accept.network))], [...supportedNetworks]);
739
+ }
740
+ const supported = supportedByNetwork.filter((accept) => accept.asset.toLowerCase() === supportedPaymentAsset(accept.network)?.toLowerCase());
741
+ if (supported.length === 0) {
742
+ throw paymentNetworkUnsupportedError([...new Set(required.accepts.map((accept) => accept.network))], [...supportedNetworks], {
743
+ challenge_assets: [...new Set(supportedByNetwork.map((accept) => accept.asset))],
744
+ wallet_assets: [...new Set(supportedNetworks.flatMap((network) => {
745
+ const asset = supportedPaymentAsset(network);
746
+ return asset ? [asset] : [];
747
+ }))],
748
+ });
749
+ }
750
+ const valid = supported.map((accept) => ({ accept, amount: atomicAmount(accept.amount) }));
751
+ if (valid.some(({ amount }) => amount === null)) {
752
+ throw settlementError("The x402 challenge contains an invalid atomic amount.", false, { challenge_amounts: supported.map((accept) => accept.amount) });
753
+ }
754
+ const withinLimit = valid.filter((entry) => entry.amount !== null && entry.amount <= maxUsdMicros);
755
+ if (withinLimit.length === 0) {
756
+ const challenged = Math.min(...valid.map(({ amount }) => amount));
757
+ throw paymentExceedsMaxError(challenged, maxUsdMicros);
758
+ }
759
+ return { ...required, accepts: withinLimit.map(({ accept }) => accept) };
760
+ }
761
+ function supportedPaymentAsset(network) {
762
+ if (network === "eip155:8453")
763
+ return USDC_MAINNET;
764
+ if (network === "eip155:84532")
765
+ return USDC_SEPOLIA;
766
+ return null;
767
+ }
768
+ function atomicAmount(value) {
769
+ if (!/^\d+$/.test(value))
770
+ return null;
771
+ const amount = Number(value);
772
+ return Number.isSafeInteger(amount) ? amount : null;
773
+ }
774
+ function withProof(request, proofHeaders) {
775
+ const headers = new Headers(request.headers);
776
+ for (const [name, value] of Object.entries(proofHeaders))
777
+ headers.set(name, value);
778
+ return new Request(request, { headers });
779
+ }
780
+ async function decodePaymentRequired(response) {
781
+ const header = response.headers.get("PAYMENT-REQUIRED");
782
+ if (!header) {
783
+ throw settlementError("The 402 response did not include a PAYMENT-REQUIRED challenge header.", false);
784
+ }
785
+ let decoded;
786
+ try {
787
+ decoded = decodeBase64Json(header);
788
+ }
789
+ catch (cause) {
790
+ throw settlementError("The PAYMENT-REQUIRED challenge header is invalid.", false, {}, cause);
791
+ }
792
+ if (!isPaymentRequired(decoded)) {
793
+ throw settlementError("The PAYMENT-REQUIRED challenge has an invalid shape.", false);
794
+ }
795
+ return decoded;
796
+ }
797
+ function isPaymentRequired(value) {
798
+ if (!value || typeof value !== "object" || Array.isArray(value))
799
+ return false;
800
+ const candidate = value;
801
+ return Number.isInteger(candidate.x402Version) &&
802
+ Boolean(candidate.resource && typeof candidate.resource.url === "string") &&
803
+ Array.isArray(candidate.accepts) &&
804
+ candidate.accepts.every((accept) => Boolean(accept &&
805
+ typeof accept.scheme === "string" &&
806
+ typeof accept.network === "string" &&
807
+ typeof accept.asset === "string" &&
808
+ typeof accept.amount === "string" &&
809
+ typeof accept.payTo === "string"));
810
+ }
811
+ function receiptFromResponse(response, accepted, url) {
812
+ const header = response.headers.get("PAYMENT-RESPONSE") ?? response.headers.get("X-PAYMENT-RESPONSE");
813
+ if (!header) {
814
+ throw settlementError("The paid response omitted the standard PAYMENT-RESPONSE settlement receipt, so settlement cannot be reported faithfully.", "unknown", { response_status: response.status });
815
+ }
816
+ let settlement;
817
+ try {
818
+ settlement = decodeBase64Json(header);
819
+ }
820
+ catch (cause) {
821
+ throw settlementError("The PAYMENT-RESPONSE settlement receipt is invalid.", "unknown", { response_status: response.status }, cause);
822
+ }
823
+ const record = asRecord(settlement);
824
+ if (record.success !== true || typeof record.transaction !== "string" || record.transaction === "") {
825
+ throw settlementError("The payment response does not prove a successful on-chain settlement.", record.success === false ? false : "unknown", { response_status: response.status });
826
+ }
827
+ if (record.network !== accepted.network) {
828
+ throw settlementError("The payment response network does not match the accepted x402 challenge.", "unknown", {
829
+ accepted_network: accepted.network,
830
+ receipt_network: typeof record.network === "string" ? record.network : null,
831
+ response_status: response.status,
832
+ });
833
+ }
834
+ const amount = atomicAmount(accepted.amount);
835
+ if (amount === null) {
836
+ throw settlementError("The selected payment amount cannot be represented as usd_micros.", "unknown");
837
+ }
838
+ return {
839
+ amount_usd_micros: amount,
840
+ pay_to: accepted.payTo,
841
+ network: record.network,
842
+ tx_ref: record.transaction,
843
+ url,
844
+ };
845
+ }
846
+ async function upstreamFailure(response) {
847
+ const challengeError = paymentRequiredError(response);
848
+ if (!response.headers.get("content-type")?.includes("application/json")) {
849
+ return { code: null, x402Error: challengeError, details: null };
850
+ }
851
+ try {
852
+ const body = asRecord(await response.clone().json());
853
+ const details = asRecord(body.details);
854
+ return {
855
+ code: typeof body.code === "string" ? body.code : null,
856
+ x402Error: typeof details.x402_error === "string" ? details.x402_error : challengeError,
857
+ details: Object.keys(details).length > 0 ? details : null,
858
+ };
859
+ }
860
+ catch {
861
+ return { code: null, x402Error: challengeError, details: null };
862
+ }
863
+ }
864
+ function paymentRequiredError(response) {
865
+ const header = response.headers.get("PAYMENT-REQUIRED");
866
+ if (!header)
867
+ return null;
868
+ try {
869
+ const decoded = asRecord(decodeBase64Json(header));
870
+ return typeof decoded.error === "string" ? decoded.error : null;
871
+ }
872
+ catch {
873
+ return null;
874
+ }
875
+ }
876
+ function isAlreadyUsedFailure(failure) {
877
+ if (failure.code !== "TENANT_X402_PAYMENT_INVALID")
878
+ return false;
879
+ return /already.{0,20}used|used.{0,20}(authorization|nonce)|nonce.{0,20}used/i.test(failure.x402Error ?? "");
880
+ }
881
+ function isProvenNoSettlementFailure(failure) {
882
+ if (failure.code === "payment_insufficient_funds")
883
+ return true;
884
+ if (failure.code === "TENANT_X402_SETTLEMENT_FAILED")
885
+ return true;
886
+ return failure.code === "TENANT_X402_PAYMENT_INVALID" && !isAlreadyUsedFailure(failure);
887
+ }
888
+ export function paymentNetworkUnsupportedError(challengeNetworkValues, walletNetworkValues, extra = {}) {
889
+ return new PaymentBuyerError({
890
+ code: "PAYMENT_NETWORK_UNSUPPORTED",
891
+ message: "The x402 challenge does not offer an exact-scheme network and asset supported by this wallet.",
892
+ fundsMoved: false,
893
+ details: {
894
+ challenge_networks: challengeNetworkValues,
895
+ wallet_networks: walletNetworkValues,
896
+ ...extra,
897
+ },
898
+ nextActions: [
899
+ {
900
+ type: "edit_request",
901
+ why: "Use a priced endpoint on one of the wallet networks, or configure a signer for a challenge network.",
902
+ },
903
+ ],
904
+ });
905
+ }
906
+ function settlementError(message, fundsMoved, details = {}, cause, retryable = false, nextActions) {
907
+ return new PaymentBuyerError({
908
+ code: "PAYMENT_SETTLEMENT_FAILED",
909
+ message,
910
+ fundsMoved,
911
+ details,
912
+ retryable,
913
+ safeToRetry: fundsMoved === false,
914
+ cause,
915
+ nextActions: nextActions && nextActions.length > 0
916
+ ? nextActions
917
+ : fundsMoved === "unknown"
918
+ ? [
919
+ {
920
+ type: "reconcile_payment",
921
+ safe_to_auto_execute: false,
922
+ why: "Confirm the original settlement before authorizing a different payment proof.",
923
+ },
924
+ ]
925
+ : [
926
+ {
927
+ type: "retry",
928
+ why: "No funds moved; retry after correcting the reported pre-settlement failure.",
929
+ },
930
+ ],
931
+ });
932
+ }
933
+ function challengeNetworks(response) {
934
+ const header = response.headers.get("PAYMENT-REQUIRED");
935
+ if (!header)
936
+ return [];
937
+ try {
938
+ const decoded = decodeBase64Json(header);
939
+ return [...new Set((decoded.accepts ?? []).flatMap((accept) => typeof accept.network === "string" ? [accept.network] : []))];
940
+ }
941
+ catch {
942
+ return [];
943
+ }
944
+ }
945
+ async function paymentRequestFingerprint(url, init, maxUsdMicros) {
946
+ let request;
947
+ try {
948
+ request = new Request(url, init);
949
+ }
950
+ catch (cause) {
951
+ throw settlementError("The paid request could not be constructed.", false, {}, cause);
952
+ }
953
+ const headerEntries = [];
954
+ request.headers.forEach((value, name) => headerEntries.push([name, value]));
955
+ const headers = headerEntries
956
+ .filter(([name]) => ![
957
+ "payment-signature",
958
+ "x-payment",
959
+ "x-run402-payment-attempt-id",
960
+ ].includes(name.toLowerCase()))
961
+ .sort(([a], [b]) => a.localeCompare(b));
962
+ let bodyHash = "";
963
+ try {
964
+ const body = new Uint8Array(await request.clone().arrayBuffer());
965
+ bodyHash = createHash("sha256").update(body).digest("hex");
966
+ }
967
+ catch (cause) {
968
+ throw settlementError("The paid request body cannot be replayed safely.", false, {}, cause);
969
+ }
970
+ return createHash("sha256")
971
+ .update(JSON.stringify({ method: request.method, url: request.url, headers, bodyHash, maxUsdMicros }))
972
+ .digest("hex");
973
+ }
974
+ function encodeBase64Json(value) {
975
+ return Buffer.from(JSON.stringify(value)).toString("base64url");
976
+ }
977
+ function decodeBase64Json(value) {
978
+ return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
979
+ }
980
+ function asRecord(value) {
981
+ return value && typeof value === "object" && !Array.isArray(value)
982
+ ? value
983
+ : {};
984
+ }
532
985
  /**
533
986
  * Request-scoped payment tracker around `@x402/fetch`.
534
987
  *
@@ -678,13 +1131,29 @@ export function createTrackedX402Fetch(wrapFetchWithPayment, client, opts = {})
678
1131
  try {
679
1132
  const response = await paidFetch(input, init);
680
1133
  if (context.providerStarted) {
681
- const completed = response.ok;
1134
+ const classification = response.ok
1135
+ ? "completed"
1136
+ : await opts.classifyPaymentResponse?.(response) ?? "ambiguous";
1137
+ const completed = classification === "completed" || classification === "already_settled";
1138
+ const failed = classification === "failed";
1139
+ const intentPending = classification === "intent_pending";
1140
+ const retryAfter = retryAfterSeconds(response);
682
1141
  writeRecordBestEffort(context, store, {
683
- state: completed ? "completed" : "ambiguous",
684
- mutation_state: completed ? "completed" : "ambiguous",
1142
+ state: completed ? "completed" : failed ? "failed" : intentPending ? "intent_pending" : "ambiguous",
1143
+ mutation_state: completed ? "completed" : failed ? "not_started" : "ambiguous",
685
1144
  response_status: response.status,
1145
+ ...(intentPending ? {
1146
+ last_error_code: "PAYMENT_INTENT_PENDING",
1147
+ ...(nonEmptyHeader(response, "x-run402-payment-id")
1148
+ ? { payment_id: nonEmptyHeader(response, "x-run402-payment-id") }
1149
+ : {}),
1150
+ intent_state: "pending",
1151
+ ...(retryAfter !== null ? { retry_after_seconds: retryAfter } : {}),
1152
+ } : {}),
686
1153
  });
687
- if (!completed) {
1154
+ if (intentPending)
1155
+ return response;
1156
+ if (!completed && !failed) {
688
1157
  throw new PaymentAttemptError({
689
1158
  code: "X402_PAYMENT_OUTCOME_AMBIGUOUS",
690
1159
  message: "The x402 payment target returned a non-success response after provider dispatch; reconcile the attempt before paying again.",
@@ -793,6 +1262,9 @@ function claimIntent(context, store, now) {
793
1262
  method: context.request.method,
794
1263
  origin: context.request.origin,
795
1264
  path_sha256: context.request.path_sha256,
1265
+ ...(context.request.caller_key_sha256
1266
+ ? { caller_key_sha256: context.request.caller_key_sha256 }
1267
+ : {}),
796
1268
  created_at: context.createdAt,
797
1269
  updated_at: updatedAt,
798
1270
  };
@@ -807,6 +1279,29 @@ function claimIntent(context, store, now) {
807
1279
  throw new AttemptJournalWriteError(cause);
808
1280
  }
809
1281
  }
1282
+ function trustedRun402ResponseOrigin(requestUrl, response) {
1283
+ if (!response.url || response.redirected)
1284
+ return false;
1285
+ try {
1286
+ const request = new URL(requestUrl);
1287
+ const returned = new URL(response.url);
1288
+ return request.origin === returned.origin && isTrustedRun402PaymentUrl(request);
1289
+ }
1290
+ catch {
1291
+ return false;
1292
+ }
1293
+ }
1294
+ function retryAfterSeconds(response) {
1295
+ const raw = response.headers.get("retry-after");
1296
+ if (!raw || !/^\d+$/.test(raw))
1297
+ return null;
1298
+ const seconds = Number(raw);
1299
+ return Number.isSafeInteger(seconds) ? seconds : null;
1300
+ }
1301
+ function nonEmptyHeader(response, name) {
1302
+ const value = response.headers.get(name)?.trim();
1303
+ return value ? value : null;
1304
+ }
810
1305
  function writeRecord(context, store, patch) {
811
1306
  if (!context.record)
812
1307
  throw new AttemptJournalWriteError("payment intent missing");