thirdweb 5.112.4 → 5.113.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/dist/cjs/exports/react.js +4 -1
- package/dist/cjs/exports/react.js.map +1 -1
- package/dist/cjs/exports/react.native.js +4 -1
- package/dist/cjs/exports/react.native.js.map +1 -1
- package/dist/cjs/react/core/hooks/x402/useFetchWithPaymentCore.js +110 -0
- package/dist/cjs/react/core/hooks/x402/useFetchWithPaymentCore.js.map +1 -0
- package/dist/cjs/react/native/hooks/x402/useFetchWithPayment.js +89 -0
- package/dist/cjs/react/native/hooks/x402/useFetchWithPayment.js.map +1 -0
- package/dist/cjs/react/web/hooks/x402/useFetchWithPayment.js +168 -0
- package/dist/cjs/react/web/hooks/x402/useFetchWithPayment.js.map +1 -0
- package/dist/cjs/react/web/ui/components/basic.js +1 -1
- package/dist/cjs/react/web/ui/x402/PaymentErrorModal.js +107 -0
- package/dist/cjs/react/web/ui/x402/PaymentErrorModal.js.map +1 -0
- package/dist/cjs/react/web/ui/x402/SignInRequiredModal.js +28 -0
- package/dist/cjs/react/web/ui/x402/SignInRequiredModal.js.map +1 -0
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/wallets/coinbase/coinbase-web.js +9 -0
- package/dist/cjs/wallets/coinbase/coinbase-web.js.map +1 -1
- package/dist/cjs/x402/facilitator.js +1 -1
- package/dist/cjs/x402/fetchWithPayment.js +9 -7
- package/dist/cjs/x402/fetchWithPayment.js.map +1 -1
- package/dist/cjs/x402/settle-payment.js +1 -1
- package/dist/cjs/x402/verify-payment.js +1 -1
- package/dist/esm/exports/react.js +2 -0
- package/dist/esm/exports/react.js.map +1 -1
- package/dist/esm/exports/react.native.js +2 -0
- package/dist/esm/exports/react.native.js.map +1 -1
- package/dist/esm/react/core/hooks/x402/useFetchWithPaymentCore.js +107 -0
- package/dist/esm/react/core/hooks/x402/useFetchWithPaymentCore.js.map +1 -0
- package/dist/esm/react/native/hooks/x402/useFetchWithPayment.js +86 -0
- package/dist/esm/react/native/hooks/x402/useFetchWithPayment.js.map +1 -0
- package/dist/esm/react/web/hooks/x402/useFetchWithPayment.js +165 -0
- package/dist/esm/react/web/hooks/x402/useFetchWithPayment.js.map +1 -0
- package/dist/esm/react/web/ui/components/basic.js +1 -1
- package/dist/esm/react/web/ui/x402/PaymentErrorModal.js +104 -0
- package/dist/esm/react/web/ui/x402/PaymentErrorModal.js.map +1 -0
- package/dist/esm/react/web/ui/x402/SignInRequiredModal.js +25 -0
- package/dist/esm/react/web/ui/x402/SignInRequiredModal.js.map +1 -0
- package/dist/esm/version.js +1 -1
- package/dist/esm/wallets/coinbase/coinbase-web.js +9 -0
- package/dist/esm/wallets/coinbase/coinbase-web.js.map +1 -1
- package/dist/esm/x402/facilitator.js +1 -1
- package/dist/esm/x402/fetchWithPayment.js +9 -7
- package/dist/esm/x402/fetchWithPayment.js.map +1 -1
- package/dist/esm/x402/settle-payment.js +1 -1
- package/dist/esm/x402/verify-payment.js +1 -1
- package/dist/scripts/bridge-widget.js +50 -50
- package/dist/types/exports/react.d.ts +1 -0
- package/dist/types/exports/react.d.ts.map +1 -1
- package/dist/types/exports/react.native.d.ts +1 -0
- package/dist/types/exports/react.native.d.ts.map +1 -1
- package/dist/types/react/core/hooks/x402/useFetchWithPaymentCore.d.ts +131 -0
- package/dist/types/react/core/hooks/x402/useFetchWithPaymentCore.d.ts.map +1 -0
- package/dist/types/react/native/hooks/x402/useFetchWithPayment.d.ts +189 -0
- package/dist/types/react/native/hooks/x402/useFetchWithPayment.d.ts.map +1 -0
- package/dist/types/react/web/hooks/x402/useFetchWithPayment.d.ts +248 -0
- package/dist/types/react/web/hooks/x402/useFetchWithPayment.d.ts.map +1 -0
- package/dist/types/react/web/ui/x402/PaymentErrorModal.d.ts +20 -0
- package/dist/types/react/web/ui/x402/PaymentErrorModal.d.ts.map +1 -0
- package/dist/types/react/web/ui/x402/SignInRequiredModal.d.ts +12 -0
- package/dist/types/react/web/ui/x402/SignInRequiredModal.d.ts.map +1 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types/x402/facilitator.d.ts +1 -1
- package/dist/types/x402/fetchWithPayment.d.ts +7 -3
- package/dist/types/x402/fetchWithPayment.d.ts.map +1 -1
- package/dist/types/x402/schemas.d.ts +2 -2
- package/dist/types/x402/settle-payment.d.ts +1 -1
- package/dist/types/x402/verify-payment.d.ts +1 -1
- package/package.json +1 -1
- package/src/exports/react.native.ts +5 -0
- package/src/exports/react.ts +5 -0
- package/src/react/core/hooks/x402/useFetchWithPaymentCore.ts +160 -0
- package/src/react/native/hooks/x402/useFetchWithPayment.ts +96 -0
- package/src/react/web/hooks/x402/useFetchWithPayment.tsx +238 -0
- package/src/react/web/ui/components/basic.tsx +1 -1
- package/src/react/web/ui/x402/PaymentErrorModal.tsx +261 -0
- package/src/react/web/ui/x402/SignInRequiredModal.tsx +75 -0
- package/src/version.ts +1 -1
- package/src/wallets/coinbase/coinbase-web.ts +10 -0
- package/src/x402/facilitator.ts +1 -1
- package/src/x402/fetchWithPayment.ts +19 -12
- package/src/x402/settle-payment.ts +1 -1
- package/src/x402/verify-payment.ts +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PaymentErrorModal.d.ts","sourceRoot":"","sources":["../../../../../../src/react/web/ui/x402/PaymentErrorModal.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAGL,KAAK,4BAA4B,EAClC,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAEvE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sCAAsC,CAAC;AAGlE,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAUxE,KAAK,sBAAsB,GAAG;IAC5B,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAC;IACjD,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,KAAK,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC;IAChC,iBAAiB,CAAC,EAAE,OAAO,CACzB,IAAI,CACF,cAAc,EACZ,QAAQ,GACR,OAAO,GACP,cAAc,GACd,QAAQ,GACR,WAAW,GACX,UAAU,GACV,OAAO,CACV,CACF,CAAC;IACF,2BAA2B,CAAC,EAAE,CAC5B,mBAAmB,EAAE,4BAA4B,EAAE,KAChD,4BAA4B,GAAG,SAAS,CAAC;CAC/C,CAAC;AAIF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,2CAiL9D"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Theme } from "../../../core/design-system/index.js";
|
|
2
|
+
type SignInRequiredModalProps = {
|
|
3
|
+
theme: Theme | "light" | "dark";
|
|
4
|
+
onSignIn: () => void;
|
|
5
|
+
onCancel: () => void;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare function SignInRequiredModal(props: SignInRequiredModalProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=SignInRequiredModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SignInRequiredModal.d.ts","sourceRoot":"","sources":["../../../../../../src/react/web/ui/x402/SignInRequiredModal.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sCAAsC,CAAC;AAWlE,KAAK,wBAAwB,GAAG;IAC9B,KAAK,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,2CAoDlE"}
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "5.
|
|
1
|
+
export declare const version = "5.113.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ThirdwebClient } from "../client/client.js";
|
|
2
2
|
import type { Wallet } from "../wallets/interfaces/wallet.js";
|
|
3
|
+
import { type RequestedPaymentRequirements } from "./schemas.js";
|
|
3
4
|
/**
|
|
4
5
|
* Enables the payment of APIs using the x402 payment protocol.
|
|
5
6
|
*
|
|
@@ -14,7 +15,7 @@ import type { Wallet } from "../wallets/interfaces/wallet.js";
|
|
|
14
15
|
* @param fetch - The fetch function to wrap (typically globalThis.fetch)
|
|
15
16
|
* @param client - The thirdweb client used to access RPC infrastructure
|
|
16
17
|
* @param wallet - The wallet used to sign payment messages
|
|
17
|
-
* @param maxValue - The maximum allowed payment amount in base units
|
|
18
|
+
* @param maxValue - The maximum allowed payment amount in base units
|
|
18
19
|
* @returns A wrapped fetch function that handles 402 responses automatically
|
|
19
20
|
*
|
|
20
21
|
* @example
|
|
@@ -37,7 +38,10 @@ import type { Wallet } from "../wallets/interfaces/wallet.js";
|
|
|
37
38
|
* @throws {Error} If a payment has already been attempted for this request
|
|
38
39
|
* @throws {Error} If there's an error creating the payment header
|
|
39
40
|
*
|
|
40
|
-
* @
|
|
41
|
+
* @x402
|
|
41
42
|
*/
|
|
42
|
-
export declare function wrapFetchWithPayment(fetch: typeof globalThis.fetch, client: ThirdwebClient, wallet: Wallet,
|
|
43
|
+
export declare function wrapFetchWithPayment(fetch: typeof globalThis.fetch, client: ThirdwebClient, wallet: Wallet, options?: {
|
|
44
|
+
maxValue?: bigint;
|
|
45
|
+
paymentRequirementsSelector?: (paymentRequirements: RequestedPaymentRequirements[]) => RequestedPaymentRequirements | undefined;
|
|
46
|
+
}): (input: RequestInfo, init?: RequestInit) => Promise<Response>;
|
|
43
47
|
//# sourceMappingURL=fetchWithPayment.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetchWithPayment.d.ts","sourceRoot":"","sources":["../../../src/x402/fetchWithPayment.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"fetchWithPayment.d.ts","sourceRoot":"","sources":["../../../src/x402/fetchWithPayment.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAGL,KAAK,4BAA4B,EAElC,MAAM,cAAc,CAAC;AAGtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,EAC9B,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IACR,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2BAA2B,CAAC,EAAE,CAC5B,mBAAmB,EAAE,4BAA4B,EAAE,KAChD,4BAA4B,GAAG,SAAS,CAAC;CAC/C,IAEa,OAAO,WAAW,EAAE,OAAO,WAAW,uBA8FrD"}
|
|
@@ -118,11 +118,11 @@ export declare const RequestedPaymentRequirementsSchema: z.ZodObject<{
|
|
|
118
118
|
scheme: "exact";
|
|
119
119
|
resource: string;
|
|
120
120
|
mimeType: string;
|
|
121
|
-
asset: string;
|
|
122
121
|
network: string;
|
|
123
122
|
maxAmountRequired: string;
|
|
124
123
|
payTo: string;
|
|
125
124
|
maxTimeoutSeconds: number;
|
|
125
|
+
asset: string;
|
|
126
126
|
outputSchema?: Record<string, any> | undefined;
|
|
127
127
|
extra?: Record<string, any> | undefined;
|
|
128
128
|
}, {
|
|
@@ -130,11 +130,11 @@ export declare const RequestedPaymentRequirementsSchema: z.ZodObject<{
|
|
|
130
130
|
scheme: "exact";
|
|
131
131
|
resource: string;
|
|
132
132
|
mimeType: string;
|
|
133
|
-
asset: string;
|
|
134
133
|
network: string;
|
|
135
134
|
maxAmountRequired: string;
|
|
136
135
|
payTo: string;
|
|
137
136
|
maxTimeoutSeconds: number;
|
|
137
|
+
asset: string;
|
|
138
138
|
outputSchema?: Record<string, any> | undefined;
|
|
139
139
|
extra?: Record<string, any> | undefined;
|
|
140
140
|
}>;
|
|
@@ -114,7 +114,7 @@ import { type SettlePaymentArgs, type SettlePaymentResult } from "./types.js";
|
|
|
114
114
|
*
|
|
115
115
|
* @public
|
|
116
116
|
* @beta
|
|
117
|
-
* @
|
|
117
|
+
* @x402
|
|
118
118
|
*/
|
|
119
119
|
export declare function settlePayment(args: SettlePaymentArgs): Promise<SettlePaymentResult>;
|
|
120
120
|
//# sourceMappingURL=settle-payment.d.ts.map
|
|
@@ -64,7 +64,7 @@ import { type PaymentArgs, type VerifyPaymentResult } from "./types.js";
|
|
|
64
64
|
*
|
|
65
65
|
* @public
|
|
66
66
|
* @beta
|
|
67
|
-
* @
|
|
67
|
+
* @x402
|
|
68
68
|
*/
|
|
69
69
|
export declare function verifyPayment(args: PaymentArgs): Promise<VerifyPaymentResult>;
|
|
70
70
|
//# sourceMappingURL=verify-payment.d.ts.map
|
package/package.json
CHANGED
|
@@ -114,6 +114,11 @@ export { useAutoConnect } from "../react/native/hooks/wallets/useAutoConnect.js"
|
|
|
114
114
|
export { useLinkProfile } from "../react/native/hooks/wallets/useLinkProfile.js";
|
|
115
115
|
export { useProfiles } from "../react/native/hooks/wallets/useProfiles.js";
|
|
116
116
|
export { useUnlinkProfile } from "../react/native/hooks/wallets/useUnlinkProfile.js";
|
|
117
|
+
// x402
|
|
118
|
+
export {
|
|
119
|
+
type UseFetchWithPaymentOptions,
|
|
120
|
+
useFetchWithPayment,
|
|
121
|
+
} from "../react/native/hooks/x402/useFetchWithPayment.js";
|
|
117
122
|
export { ThirdwebProvider } from "../react/native/providers/thirdweb-provider.js";
|
|
118
123
|
// Components
|
|
119
124
|
export { AutoConnect } from "../react/native/ui/AutoConnect/AutoConnect.js";
|
package/src/exports/react.ts
CHANGED
|
@@ -131,6 +131,11 @@ export { useAutoConnect } from "../react/web/hooks/wallets/useAutoConnect.js";
|
|
|
131
131
|
export { useLinkProfile } from "../react/web/hooks/wallets/useLinkProfile.js";
|
|
132
132
|
export { useProfiles } from "../react/web/hooks/wallets/useProfiles.js";
|
|
133
133
|
export { useUnlinkProfile } from "../react/web/hooks/wallets/useUnlinkProfile.js";
|
|
134
|
+
// x402
|
|
135
|
+
export {
|
|
136
|
+
type UseFetchWithPaymentOptions,
|
|
137
|
+
useFetchWithPayment,
|
|
138
|
+
} from "../react/web/hooks/x402/useFetchWithPayment.js";
|
|
134
139
|
export { ThirdwebProvider } from "../react/web/providers/thirdweb-provider.js";
|
|
135
140
|
export { AutoConnect } from "../react/web/ui/AutoConnect/AutoConnect.js";
|
|
136
141
|
export type { BuyOrOnrampPrepareResult } from "../react/web/ui/Bridge/BuyWidget.js";
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMutation } from "@tanstack/react-query";
|
|
4
|
+
import type { ThirdwebClient } from "../../../../client/client.js";
|
|
5
|
+
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
|
|
6
|
+
import { wrapFetchWithPayment } from "../../../../x402/fetchWithPayment.js";
|
|
7
|
+
import type { RequestedPaymentRequirements } from "../../../../x402/schemas.js";
|
|
8
|
+
import type { PaymentRequiredResult } from "../../../../x402/types.js";
|
|
9
|
+
import { useActiveWallet } from "../wallets/useActiveWallet.js";
|
|
10
|
+
|
|
11
|
+
export type UseFetchWithPaymentOptions = {
|
|
12
|
+
maxValue?: bigint;
|
|
13
|
+
paymentRequirementsSelector?: (
|
|
14
|
+
paymentRequirements: RequestedPaymentRequirements[],
|
|
15
|
+
) => RequestedPaymentRequirements | undefined;
|
|
16
|
+
parseAs?: "json" | "text" | "raw";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type ShowErrorModalCallback = (data: {
|
|
20
|
+
errorData: PaymentRequiredResult["responseBody"];
|
|
21
|
+
onRetry: () => void;
|
|
22
|
+
onCancel: () => void;
|
|
23
|
+
}) => void;
|
|
24
|
+
|
|
25
|
+
type ShowConnectModalCallback = (data: {
|
|
26
|
+
onConnect: (wallet: Wallet) => void;
|
|
27
|
+
onCancel: () => void;
|
|
28
|
+
}) => void;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Core hook for fetch with payment functionality.
|
|
32
|
+
* This is the platform-agnostic implementation used by both web and native versions.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export function useFetchWithPaymentCore(
|
|
36
|
+
client: ThirdwebClient,
|
|
37
|
+
options?: UseFetchWithPaymentOptions,
|
|
38
|
+
showErrorModal?: ShowErrorModalCallback,
|
|
39
|
+
showConnectModal?: ShowConnectModalCallback,
|
|
40
|
+
) {
|
|
41
|
+
const wallet = useActiveWallet();
|
|
42
|
+
|
|
43
|
+
const mutation = useMutation({
|
|
44
|
+
mutationFn: async ({
|
|
45
|
+
input,
|
|
46
|
+
init,
|
|
47
|
+
}: {
|
|
48
|
+
input: RequestInfo;
|
|
49
|
+
init?: RequestInit;
|
|
50
|
+
}) => {
|
|
51
|
+
// Recursive function that handles fetch + 402 error + retry
|
|
52
|
+
const executeFetch = async (currentWallet = wallet): Promise<unknown> => {
|
|
53
|
+
if (!currentWallet) {
|
|
54
|
+
// If a connect modal handler is provided, show the connect modal
|
|
55
|
+
if (showConnectModal) {
|
|
56
|
+
return new Promise<unknown>((resolve, reject) => {
|
|
57
|
+
showConnectModal({
|
|
58
|
+
onConnect: async (newWallet) => {
|
|
59
|
+
// After connection, retry the fetch with the newly connected wallet
|
|
60
|
+
try {
|
|
61
|
+
const result = await executeFetch(newWallet);
|
|
62
|
+
resolve(result);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
reject(error);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
onCancel: () => {
|
|
68
|
+
reject(new Error("Wallet connection cancelled by user"));
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// If no connect modal handler, throw an error
|
|
75
|
+
throw new Error(
|
|
76
|
+
"No wallet connected. Please connect your wallet to make paid API calls.",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const wrappedFetch = wrapFetchWithPayment(
|
|
81
|
+
globalThis.fetch,
|
|
82
|
+
client,
|
|
83
|
+
currentWallet,
|
|
84
|
+
options,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const response = await wrappedFetch(input, init);
|
|
88
|
+
|
|
89
|
+
// Check if we got a 402 response (payment error)
|
|
90
|
+
if (response.status === 402) {
|
|
91
|
+
try {
|
|
92
|
+
const errorBody =
|
|
93
|
+
(await response.json()) as PaymentRequiredResult["responseBody"];
|
|
94
|
+
|
|
95
|
+
// If a modal handler is provided, show the modal and handle retry/cancel
|
|
96
|
+
if (showErrorModal) {
|
|
97
|
+
return new Promise<unknown>((resolve, reject) => {
|
|
98
|
+
showErrorModal({
|
|
99
|
+
errorData: errorBody,
|
|
100
|
+
onRetry: async () => {
|
|
101
|
+
// Retry the entire fetch+error handling logic recursively
|
|
102
|
+
try {
|
|
103
|
+
const result = await executeFetch();
|
|
104
|
+
resolve(result);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
reject(error);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
onCancel: () => {
|
|
110
|
+
reject(new Error("Payment cancelled by user"));
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// If no modal handler, throw the error with details
|
|
117
|
+
throw new Error(
|
|
118
|
+
errorBody.errorMessage || `Payment failed: ${errorBody.error}`,
|
|
119
|
+
);
|
|
120
|
+
} catch (_parseError) {
|
|
121
|
+
// If we can't parse the error body, throw a generic error
|
|
122
|
+
throw new Error("Payment failed with status 402");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
const errorText = await response.text();
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Payment failed with status ${response.status} ${response.statusText} - ${errorText || "Unknown error"}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const parseAs = options?.parseAs ?? "json";
|
|
134
|
+
return parseResponse(response, parseAs);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Start the fetch process
|
|
138
|
+
return executeFetch();
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
fetchWithPayment: async (input: RequestInfo, init?: RequestInit) => {
|
|
144
|
+
return mutation.mutateAsync({ input, init });
|
|
145
|
+
},
|
|
146
|
+
...mutation,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function parseResponse(response: Response, parseAs: "json" | "text" | "raw") {
|
|
151
|
+
if (parseAs === "json") {
|
|
152
|
+
return response.json();
|
|
153
|
+
} else if (parseAs === "text") {
|
|
154
|
+
return response.text();
|
|
155
|
+
} else if (parseAs === "raw") {
|
|
156
|
+
return response;
|
|
157
|
+
} else {
|
|
158
|
+
throw new Error(`Invalid parseAs option: ${parseAs}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ThirdwebClient } from "../../../../client/client.js";
|
|
4
|
+
import {
|
|
5
|
+
type UseFetchWithPaymentOptions,
|
|
6
|
+
useFetchWithPaymentCore,
|
|
7
|
+
} from "../../../core/hooks/x402/useFetchWithPaymentCore.js";
|
|
8
|
+
|
|
9
|
+
export type { UseFetchWithPaymentOptions };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A React hook that wraps the native fetch API to automatically handle 402 Payment Required responses
|
|
13
|
+
* using the x402 payment protocol with the currently connected wallet.
|
|
14
|
+
*
|
|
15
|
+
* This hook enables you to make API calls that require payment without manually handling the payment flow.
|
|
16
|
+
* Responses are automatically parsed as JSON by default (can be customized with `parseAs` option).
|
|
17
|
+
*
|
|
18
|
+
* When a 402 response is received, it will automatically:
|
|
19
|
+
* 1. Parse the payment requirements
|
|
20
|
+
* 2. Verify the payment amount is within the allowed maximum
|
|
21
|
+
* 3. Create a payment header using the connected wallet
|
|
22
|
+
* 4. Retry the request with the payment header
|
|
23
|
+
*
|
|
24
|
+
* Note: This is the React Native version which does not include modal UI.
|
|
25
|
+
* Payment errors will be thrown and should be handled by your application.
|
|
26
|
+
*
|
|
27
|
+
* @param client - The thirdweb client used to access RPC infrastructure
|
|
28
|
+
* @param options - Optional configuration for payment handling
|
|
29
|
+
* @param options.maxValue - The maximum allowed payment amount in base units
|
|
30
|
+
* @param options.paymentRequirementsSelector - Custom function to select payment requirements from available options
|
|
31
|
+
* @param options.parseAs - How to parse the response: "json" (default), "text", or "raw"
|
|
32
|
+
* @returns An object containing:
|
|
33
|
+
* - `fetchWithPayment`: Function to make fetch requests with automatic payment handling (returns parsed data)
|
|
34
|
+
* - `isPending`: Boolean indicating if a request is in progress
|
|
35
|
+
* - `error`: Any error that occurred during the request
|
|
36
|
+
* - `data`: The parsed response data (JSON by default, or based on `parseAs` option)
|
|
37
|
+
* - Other mutation properties from React Query
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* import { useFetchWithPayment } from "thirdweb/react";
|
|
42
|
+
* import { createThirdwebClient } from "thirdweb";
|
|
43
|
+
*
|
|
44
|
+
* const client = createThirdwebClient({ clientId: "your-client-id" });
|
|
45
|
+
*
|
|
46
|
+
* function MyComponent() {
|
|
47
|
+
* const { fetchWithPayment, isPending, error } = useFetchWithPayment(client);
|
|
48
|
+
*
|
|
49
|
+
* const handleApiCall = async () => {
|
|
50
|
+
* try {
|
|
51
|
+
* // Response is automatically parsed as JSON
|
|
52
|
+
* const data = await fetchWithPayment('https://api.example.com/paid-endpoint');
|
|
53
|
+
* console.log(data);
|
|
54
|
+
* } catch (err) {
|
|
55
|
+
* // Handle payment errors manually in React Native
|
|
56
|
+
* console.error("Payment failed:", err);
|
|
57
|
+
* }
|
|
58
|
+
* };
|
|
59
|
+
*
|
|
60
|
+
* return (
|
|
61
|
+
* <button onClick={handleApiCall} disabled={isPending}>
|
|
62
|
+
* {isPending ? 'Loading...' : 'Make Paid API Call'}
|
|
63
|
+
* </button>
|
|
64
|
+
* );
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* ### Customize response parsing
|
|
69
|
+
* ```tsx
|
|
70
|
+
* const { fetchWithPayment } = useFetchWithPayment(client, {
|
|
71
|
+
* parseAs: "text", // Get response as text instead of JSON
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* const textData = await fetchWithPayment('https://api.example.com/endpoint');
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* ### Customize payment options
|
|
78
|
+
* ```tsx
|
|
79
|
+
* const { fetchWithPayment } = useFetchWithPayment(client, {
|
|
80
|
+
* maxValue: 5000000n, // 5 USDC in base units
|
|
81
|
+
* paymentRequirementsSelector: (requirements) => {
|
|
82
|
+
* // Custom logic to select preferred payment method
|
|
83
|
+
* return requirements[0];
|
|
84
|
+
* }
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* @x402
|
|
89
|
+
*/
|
|
90
|
+
export function useFetchWithPayment(
|
|
91
|
+
client: ThirdwebClient,
|
|
92
|
+
options?: UseFetchWithPaymentOptions,
|
|
93
|
+
) {
|
|
94
|
+
// Native version doesn't show modal, errors bubble up naturally
|
|
95
|
+
return useFetchWithPaymentCore(client, options);
|
|
96
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useContext } from "react";
|
|
4
|
+
import type { ThirdwebClient } from "../../../../client/client.js";
|
|
5
|
+
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
|
|
6
|
+
import type { Theme } from "../../../core/design-system/index.js";
|
|
7
|
+
import {
|
|
8
|
+
type UseFetchWithPaymentOptions,
|
|
9
|
+
useFetchWithPaymentCore,
|
|
10
|
+
} from "../../../core/hooks/x402/useFetchWithPaymentCore.js";
|
|
11
|
+
import { SetRootElementContext } from "../../../core/providers/RootElementContext.js";
|
|
12
|
+
import type { BuyWidgetProps } from "../../ui/Bridge/BuyWidget.js";
|
|
13
|
+
import {
|
|
14
|
+
type UseConnectModalOptions,
|
|
15
|
+
useConnectModal,
|
|
16
|
+
} from "../../ui/ConnectWallet/useConnectModal.js";
|
|
17
|
+
import { PaymentErrorModal } from "../../ui/x402/PaymentErrorModal.js";
|
|
18
|
+
import { SignInRequiredModal } from "../../ui/x402/SignInRequiredModal.js";
|
|
19
|
+
|
|
20
|
+
export type { UseFetchWithPaymentOptions };
|
|
21
|
+
|
|
22
|
+
type UseFetchWithPaymentConfig = UseFetchWithPaymentOptions & {
|
|
23
|
+
/**
|
|
24
|
+
* Whether to show the UI for connection, funding or payment retries.
|
|
25
|
+
* If false, no UI will be shown and errors will have to be handled manually.
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
uiEnabled?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Theme for the payment error modal
|
|
31
|
+
* @default "dark"
|
|
32
|
+
*/
|
|
33
|
+
theme?: Theme | "light" | "dark";
|
|
34
|
+
/**
|
|
35
|
+
* Options to customize the BuyWidget that appears when the user needs to fund their wallet.
|
|
36
|
+
* These options will be merged with default values.
|
|
37
|
+
*/
|
|
38
|
+
fundWalletOptions?: Partial<
|
|
39
|
+
Omit<
|
|
40
|
+
BuyWidgetProps,
|
|
41
|
+
"client" | "chain" | "tokenAddress" | "onSuccess" | "onCancel" | "theme"
|
|
42
|
+
>
|
|
43
|
+
>;
|
|
44
|
+
/**
|
|
45
|
+
* Options to customize the ConnectModal that appears when the user needs to sign in.
|
|
46
|
+
* These options will be merged with the client, theme, and chain from the hook.
|
|
47
|
+
*/
|
|
48
|
+
connectOptions?: Omit<UseConnectModalOptions, "client" | "theme">;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A React hook that wraps the native fetch API to automatically handle 402 Payment Required responses
|
|
53
|
+
* using the x402 payment protocol with the currently connected wallet.
|
|
54
|
+
*
|
|
55
|
+
* This hook enables you to make API calls that require payment without manually handling the payment flow.
|
|
56
|
+
* Responses are automatically parsed as JSON by default (can be customized with `parseAs` option).
|
|
57
|
+
*
|
|
58
|
+
* When a 402 response is received, it will automatically:
|
|
59
|
+
* 1. Parse the payment requirements
|
|
60
|
+
* 2. Verify the payment amount is within the allowed maximum
|
|
61
|
+
* 3. Create a payment header using the connected wallet
|
|
62
|
+
* 4. Retry the request with the payment header
|
|
63
|
+
*
|
|
64
|
+
* If payment fails (e.g. insufficient funds), a modal will be shown to help the user resolve the issue.
|
|
65
|
+
* If no wallet is connected, a sign-in modal will be shown to connect a wallet.
|
|
66
|
+
*
|
|
67
|
+
* @param client - The thirdweb client used to access RPC infrastructure
|
|
68
|
+
* @param options - Optional configuration for payment handling
|
|
69
|
+
* @param options.maxValue - The maximum allowed payment amount in base units
|
|
70
|
+
* @param options.paymentRequirementsSelector - Custom function to select payment requirements from available options
|
|
71
|
+
* @param options.parseAs - How to parse the response: "json" (default), "text", or "raw"
|
|
72
|
+
* @param options.uiEnabled - Whether to show the UI for connection, funding or payment retries (defaults to true). Set to false to handle errors yourself
|
|
73
|
+
* @param options.theme - Theme for the payment error modal (defaults to "dark")
|
|
74
|
+
* @param options.fundWalletOptions - Customize the BuyWidget shown when user needs to fund their wallet
|
|
75
|
+
* @param options.connectOptions - Customize the ConnectModal shown when user needs to sign in
|
|
76
|
+
* @returns An object containing:
|
|
77
|
+
* - `fetchWithPayment`: Function to make fetch requests with automatic payment handling (returns parsed data)
|
|
78
|
+
* - `isPending`: Boolean indicating if a request is in progress
|
|
79
|
+
* - `error`: Any error that occurred during the request
|
|
80
|
+
* - `data`: The parsed response data (JSON by default, or based on `parseAs` option)
|
|
81
|
+
* - Other mutation properties from React Query
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```tsx
|
|
85
|
+
* import { useFetchWithPayment } from "thirdweb/react";
|
|
86
|
+
* import { createThirdwebClient } from "thirdweb";
|
|
87
|
+
*
|
|
88
|
+
* const client = createThirdwebClient({ clientId: "your-client-id" });
|
|
89
|
+
*
|
|
90
|
+
* function MyComponent() {
|
|
91
|
+
* const { fetchWithPayment, isPending } = useFetchWithPayment(client);
|
|
92
|
+
*
|
|
93
|
+
* const handleApiCall = async () => {
|
|
94
|
+
* // Response is automatically parsed as JSON
|
|
95
|
+
* const data = await fetchWithPayment('https://api.example.com/paid-endpoint');
|
|
96
|
+
* console.log(data);
|
|
97
|
+
* };
|
|
98
|
+
*
|
|
99
|
+
* return (
|
|
100
|
+
* <button onClick={handleApiCall} disabled={isPending}>
|
|
101
|
+
* {isPending ? 'Loading...' : 'Make Paid API Call'}
|
|
102
|
+
* </button>
|
|
103
|
+
* );
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* ### Customize response parsing
|
|
108
|
+
* ```tsx
|
|
109
|
+
* const { fetchWithPayment } = useFetchWithPayment(client, {
|
|
110
|
+
* parseAs: "text", // Get response as text instead of JSON
|
|
111
|
+
* });
|
|
112
|
+
*
|
|
113
|
+
* const textData = await fetchWithPayment('https://api.example.com/endpoint');
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* ### Customize payment options
|
|
117
|
+
* ```tsx
|
|
118
|
+
* const { fetchWithPayment } = useFetchWithPayment(client, {
|
|
119
|
+
* maxValue: 5000000n, // 5 USDC in base units
|
|
120
|
+
* theme: "light",
|
|
121
|
+
* paymentRequirementsSelector: (requirements) => {
|
|
122
|
+
* // Custom logic to select preferred payment method
|
|
123
|
+
* return requirements[0];
|
|
124
|
+
* }
|
|
125
|
+
* });
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* ### Customize the fund wallet widget
|
|
129
|
+
* ```tsx
|
|
130
|
+
* const { fetchWithPayment } = useFetchWithPayment(client, {
|
|
131
|
+
* fundWalletOptions: {
|
|
132
|
+
* title: "Add Funds",
|
|
133
|
+
* description: "You need more tokens to complete this payment",
|
|
134
|
+
* buttonLabel: "Get Tokens",
|
|
135
|
+
* }
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* ### Customize the connect modal
|
|
140
|
+
* ```tsx
|
|
141
|
+
* const { fetchWithPayment } = useFetchWithPayment(client, {
|
|
142
|
+
* connectOptions: {
|
|
143
|
+
* wallets: [inAppWallet(), createWallet("io.metamask")],
|
|
144
|
+
* title: "Sign in to continue",
|
|
145
|
+
* }
|
|
146
|
+
* });
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* ### Disable the UI and handle errors yourself
|
|
150
|
+
* ```tsx
|
|
151
|
+
* const { fetchWithPayment, error } = useFetchWithPayment(client, {
|
|
152
|
+
* uiEnabled: false,
|
|
153
|
+
* });
|
|
154
|
+
*
|
|
155
|
+
* // Handle the error manually
|
|
156
|
+
* if (error) {
|
|
157
|
+
* console.error("Payment failed:", error);
|
|
158
|
+
* }
|
|
159
|
+
* ```
|
|
160
|
+
*
|
|
161
|
+
* @x402
|
|
162
|
+
*/
|
|
163
|
+
export function useFetchWithPayment(
|
|
164
|
+
client: ThirdwebClient,
|
|
165
|
+
options?: UseFetchWithPaymentConfig,
|
|
166
|
+
) {
|
|
167
|
+
const setRootEl = useContext(SetRootElementContext);
|
|
168
|
+
const { connect } = useConnectModal();
|
|
169
|
+
const theme = options?.theme || "dark";
|
|
170
|
+
const showModal = options?.uiEnabled !== false; // Default to true
|
|
171
|
+
|
|
172
|
+
const showErrorModal = showModal
|
|
173
|
+
? (data: {
|
|
174
|
+
errorData: Parameters<typeof PaymentErrorModal>[0]["errorData"];
|
|
175
|
+
onRetry: () => void;
|
|
176
|
+
onCancel: () => void;
|
|
177
|
+
}) => {
|
|
178
|
+
setRootEl(
|
|
179
|
+
<PaymentErrorModal
|
|
180
|
+
client={client}
|
|
181
|
+
errorData={data.errorData}
|
|
182
|
+
onCancel={() => {
|
|
183
|
+
setRootEl(null);
|
|
184
|
+
data.onCancel();
|
|
185
|
+
}}
|
|
186
|
+
onRetry={() => {
|
|
187
|
+
setRootEl(null);
|
|
188
|
+
data.onRetry();
|
|
189
|
+
}}
|
|
190
|
+
theme={theme}
|
|
191
|
+
fundWalletOptions={options?.fundWalletOptions}
|
|
192
|
+
paymentRequirementsSelector={options?.paymentRequirementsSelector}
|
|
193
|
+
/>,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
: undefined;
|
|
197
|
+
|
|
198
|
+
const showConnectModal = showModal
|
|
199
|
+
? (data: { onConnect: (wallet: Wallet) => void; onCancel: () => void }) => {
|
|
200
|
+
// First, show the SignInRequiredModal
|
|
201
|
+
setRootEl(
|
|
202
|
+
<SignInRequiredModal
|
|
203
|
+
theme={theme}
|
|
204
|
+
onSignIn={async () => {
|
|
205
|
+
// Close the SignInRequiredModal
|
|
206
|
+
setRootEl(null);
|
|
207
|
+
|
|
208
|
+
// Open the ConnectModal
|
|
209
|
+
try {
|
|
210
|
+
const connectedWallet = await connect({
|
|
211
|
+
client,
|
|
212
|
+
theme,
|
|
213
|
+
...options?.connectOptions,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// On successful connection, trigger onConnect callback with the wallet
|
|
217
|
+
data.onConnect(connectedWallet);
|
|
218
|
+
} catch (_error) {
|
|
219
|
+
// User cancelled the connection
|
|
220
|
+
data.onCancel();
|
|
221
|
+
}
|
|
222
|
+
}}
|
|
223
|
+
onCancel={() => {
|
|
224
|
+
setRootEl(null);
|
|
225
|
+
data.onCancel();
|
|
226
|
+
}}
|
|
227
|
+
/>,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
: undefined;
|
|
231
|
+
|
|
232
|
+
return useFetchWithPaymentCore(
|
|
233
|
+
client,
|
|
234
|
+
options,
|
|
235
|
+
showErrorModal,
|
|
236
|
+
showConnectModal,
|
|
237
|
+
);
|
|
238
|
+
}
|