thirdweb 5.112.3 → 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.
Files changed (98) hide show
  1. package/dist/cjs/exports/react.js +4 -1
  2. package/dist/cjs/exports/react.js.map +1 -1
  3. package/dist/cjs/exports/react.native.js +4 -1
  4. package/dist/cjs/exports/react.native.js.map +1 -1
  5. package/dist/cjs/react/core/hooks/x402/useFetchWithPaymentCore.js +110 -0
  6. package/dist/cjs/react/core/hooks/x402/useFetchWithPaymentCore.js.map +1 -0
  7. package/dist/cjs/react/native/hooks/x402/useFetchWithPayment.js +89 -0
  8. package/dist/cjs/react/native/hooks/x402/useFetchWithPayment.js.map +1 -0
  9. package/dist/cjs/react/web/hooks/x402/useFetchWithPayment.js +168 -0
  10. package/dist/cjs/react/web/hooks/x402/useFetchWithPayment.js.map +1 -0
  11. package/dist/cjs/react/web/ui/components/basic.js +1 -1
  12. package/dist/cjs/react/web/ui/x402/PaymentErrorModal.js +107 -0
  13. package/dist/cjs/react/web/ui/x402/PaymentErrorModal.js.map +1 -0
  14. package/dist/cjs/react/web/ui/x402/SignInRequiredModal.js +28 -0
  15. package/dist/cjs/react/web/ui/x402/SignInRequiredModal.js.map +1 -0
  16. package/dist/cjs/version.js +1 -1
  17. package/dist/cjs/wallets/coinbase/coinbase-web.js +9 -0
  18. package/dist/cjs/wallets/coinbase/coinbase-web.js.map +1 -1
  19. package/dist/cjs/x402/facilitator.js +1 -1
  20. package/dist/cjs/x402/fetchWithPayment.js +9 -7
  21. package/dist/cjs/x402/fetchWithPayment.js.map +1 -1
  22. package/dist/cjs/x402/schemas.js +2 -0
  23. package/dist/cjs/x402/schemas.js.map +1 -1
  24. package/dist/cjs/x402/settle-payment.js +2 -1
  25. package/dist/cjs/x402/settle-payment.js.map +1 -1
  26. package/dist/cjs/x402/verify-payment.js +2 -1
  27. package/dist/cjs/x402/verify-payment.js.map +1 -1
  28. package/dist/esm/exports/react.js +2 -0
  29. package/dist/esm/exports/react.js.map +1 -1
  30. package/dist/esm/exports/react.native.js +2 -0
  31. package/dist/esm/exports/react.native.js.map +1 -1
  32. package/dist/esm/react/core/hooks/x402/useFetchWithPaymentCore.js +107 -0
  33. package/dist/esm/react/core/hooks/x402/useFetchWithPaymentCore.js.map +1 -0
  34. package/dist/esm/react/native/hooks/x402/useFetchWithPayment.js +86 -0
  35. package/dist/esm/react/native/hooks/x402/useFetchWithPayment.js.map +1 -0
  36. package/dist/esm/react/web/hooks/x402/useFetchWithPayment.js +165 -0
  37. package/dist/esm/react/web/hooks/x402/useFetchWithPayment.js.map +1 -0
  38. package/dist/esm/react/web/ui/components/basic.js +1 -1
  39. package/dist/esm/react/web/ui/x402/PaymentErrorModal.js +104 -0
  40. package/dist/esm/react/web/ui/x402/PaymentErrorModal.js.map +1 -0
  41. package/dist/esm/react/web/ui/x402/SignInRequiredModal.js +25 -0
  42. package/dist/esm/react/web/ui/x402/SignInRequiredModal.js.map +1 -0
  43. package/dist/esm/version.js +1 -1
  44. package/dist/esm/wallets/coinbase/coinbase-web.js +9 -0
  45. package/dist/esm/wallets/coinbase/coinbase-web.js.map +1 -1
  46. package/dist/esm/x402/facilitator.js +1 -1
  47. package/dist/esm/x402/fetchWithPayment.js +9 -7
  48. package/dist/esm/x402/fetchWithPayment.js.map +1 -1
  49. package/dist/esm/x402/schemas.js +2 -0
  50. package/dist/esm/x402/schemas.js.map +1 -1
  51. package/dist/esm/x402/settle-payment.js +2 -1
  52. package/dist/esm/x402/settle-payment.js.map +1 -1
  53. package/dist/esm/x402/verify-payment.js +2 -1
  54. package/dist/esm/x402/verify-payment.js.map +1 -1
  55. package/dist/scripts/bridge-widget.js +50 -50
  56. package/dist/types/exports/react.d.ts +1 -0
  57. package/dist/types/exports/react.d.ts.map +1 -1
  58. package/dist/types/exports/react.native.d.ts +1 -0
  59. package/dist/types/exports/react.native.d.ts.map +1 -1
  60. package/dist/types/react/core/hooks/x402/useFetchWithPaymentCore.d.ts +131 -0
  61. package/dist/types/react/core/hooks/x402/useFetchWithPaymentCore.d.ts.map +1 -0
  62. package/dist/types/react/native/hooks/x402/useFetchWithPayment.d.ts +189 -0
  63. package/dist/types/react/native/hooks/x402/useFetchWithPayment.d.ts.map +1 -0
  64. package/dist/types/react/web/hooks/x402/useFetchWithPayment.d.ts +248 -0
  65. package/dist/types/react/web/hooks/x402/useFetchWithPayment.d.ts.map +1 -0
  66. package/dist/types/react/web/ui/x402/PaymentErrorModal.d.ts +20 -0
  67. package/dist/types/react/web/ui/x402/PaymentErrorModal.d.ts.map +1 -0
  68. package/dist/types/react/web/ui/x402/SignInRequiredModal.d.ts +12 -0
  69. package/dist/types/react/web/ui/x402/SignInRequiredModal.d.ts.map +1 -0
  70. package/dist/types/version.d.ts +1 -1
  71. package/dist/types/x402/facilitator.d.ts +1 -1
  72. package/dist/types/x402/fetchWithPayment.d.ts +7 -3
  73. package/dist/types/x402/fetchWithPayment.d.ts.map +1 -1
  74. package/dist/types/x402/schemas.d.ts +8 -2
  75. package/dist/types/x402/schemas.d.ts.map +1 -1
  76. package/dist/types/x402/settle-payment.d.ts +1 -1
  77. package/dist/types/x402/settle-payment.d.ts.map +1 -1
  78. package/dist/types/x402/types.d.ts +2 -0
  79. package/dist/types/x402/types.d.ts.map +1 -1
  80. package/dist/types/x402/verify-payment.d.ts +1 -1
  81. package/dist/types/x402/verify-payment.d.ts.map +1 -1
  82. package/package.json +3 -3
  83. package/src/exports/react.native.ts +5 -0
  84. package/src/exports/react.ts +5 -0
  85. package/src/react/core/hooks/x402/useFetchWithPaymentCore.ts +160 -0
  86. package/src/react/native/hooks/x402/useFetchWithPayment.ts +96 -0
  87. package/src/react/web/hooks/x402/useFetchWithPayment.tsx +238 -0
  88. package/src/react/web/ui/components/basic.tsx +1 -1
  89. package/src/react/web/ui/x402/PaymentErrorModal.tsx +261 -0
  90. package/src/react/web/ui/x402/SignInRequiredModal.tsx +75 -0
  91. package/src/version.ts +1 -1
  92. package/src/wallets/coinbase/coinbase-web.ts +10 -0
  93. package/src/x402/facilitator.ts +1 -1
  94. package/src/x402/fetchWithPayment.ts +19 -12
  95. package/src/x402/schemas.ts +2 -0
  96. package/src/x402/settle-payment.ts +2 -1
  97. package/src/x402/types.ts +2 -0
  98. package/src/x402/verify-payment.ts +2 -1
@@ -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
+ }
@@ -17,7 +17,7 @@ export const ScreenBottomContainer = /* @__PURE__ */ StyledDiv((_) => {
17
17
  borderTop: `1px solid ${theme.colors.separatorLine}`,
18
18
  display: "flex",
19
19
  flexDirection: "column",
20
- gap: spacing.lg,
20
+ gap: spacing.md,
21
21
  padding: spacing.lg,
22
22
  };
23
23
  });
@@ -0,0 +1,261 @@
1
+ "use client";
2
+ import { useState } from "react";
3
+ import { getCachedChain } from "../../../../chains/utils.js";
4
+ import type { ThirdwebClient } from "../../../../client/client.js";
5
+ import {
6
+ extractEvmChainId,
7
+ networkToCaip2ChainId,
8
+ type RequestedPaymentRequirements,
9
+ } from "../../../../x402/schemas.js";
10
+ import type { PaymentRequiredResult } from "../../../../x402/types.js";
11
+ import { CustomThemeProvider } from "../../../core/design-system/CustomThemeProvider.js";
12
+ import type { Theme } from "../../../core/design-system/index.js";
13
+ import { spacing } from "../../../core/design-system/index.js";
14
+ import { useActiveWallet } from "../../../core/hooks/wallets/useActiveWallet.js";
15
+ import { BuyWidget, type BuyWidgetProps } from "../Bridge/BuyWidget.js";
16
+ import {
17
+ Container,
18
+ ModalHeader,
19
+ ScreenBottomContainer,
20
+ } from "../components/basic.js";
21
+ import { Button } from "../components/buttons.js";
22
+ import { Modal } from "../components/Modal.js";
23
+ import { Text } from "../components/text.js";
24
+
25
+ type PaymentErrorModalProps = {
26
+ client: ThirdwebClient;
27
+ errorData: PaymentRequiredResult["responseBody"];
28
+ onRetry: () => void;
29
+ onCancel: () => void;
30
+ theme: Theme | "light" | "dark";
31
+ fundWalletOptions?: Partial<
32
+ Omit<
33
+ BuyWidgetProps,
34
+ | "client"
35
+ | "chain"
36
+ | "tokenAddress"
37
+ | "amount"
38
+ | "onSuccess"
39
+ | "onCancel"
40
+ | "theme"
41
+ >
42
+ >;
43
+ paymentRequirementsSelector?: (
44
+ paymentRequirements: RequestedPaymentRequirements[],
45
+ ) => RequestedPaymentRequirements | undefined;
46
+ };
47
+
48
+ type Screen = "error" | "buy-widget";
49
+
50
+ /**
51
+ * @internal
52
+ */
53
+ export function PaymentErrorModal(props: PaymentErrorModalProps) {
54
+ const {
55
+ client,
56
+ errorData,
57
+ onRetry,
58
+ onCancel,
59
+ theme,
60
+ fundWalletOptions,
61
+ paymentRequirementsSelector,
62
+ } = props;
63
+ const [screen, setScreen] = useState<Screen>("error");
64
+ const isInsufficientFunds = errorData.error === "insufficient_funds";
65
+ const wallet = useActiveWallet();
66
+
67
+ // Extract chain and token info from errorData for BuyWidget
68
+ const getBuyWidgetConfig = () => {
69
+ if (!errorData.accepts || errorData.accepts.length === 0) {
70
+ return null;
71
+ }
72
+
73
+ // Get payment requirements from errorData
74
+ const parsedPaymentRequirements = errorData.accepts;
75
+
76
+ // Get the current chain from wallet
77
+ const currentChain = wallet?.getChain();
78
+ const currentChainId = currentChain?.id;
79
+
80
+ // Select payment requirement using the same logic as wrapFetchWithPayment
81
+ const selectedRequirement = paymentRequirementsSelector
82
+ ? paymentRequirementsSelector(parsedPaymentRequirements)
83
+ : defaultPaymentRequirementsSelector(
84
+ parsedPaymentRequirements,
85
+ currentChainId,
86
+ "exact",
87
+ errorData.error,
88
+ );
89
+
90
+ if (!selectedRequirement) return null;
91
+
92
+ const caip2ChainId = networkToCaip2ChainId(selectedRequirement.network);
93
+ const chainId = extractEvmChainId(caip2ChainId);
94
+
95
+ if (!chainId) return null;
96
+
97
+ const chain = getCachedChain(chainId);
98
+ const tokenAddress = selectedRequirement.asset as `0x${string}`;
99
+
100
+ return {
101
+ chain,
102
+ tokenAddress,
103
+ amount: undefined,
104
+ };
105
+ };
106
+
107
+ const buyWidgetConfig = isInsufficientFunds ? getBuyWidgetConfig() : null;
108
+
109
+ if (screen === "buy-widget" && buyWidgetConfig) {
110
+ return (
111
+ <CustomThemeProvider theme={theme}>
112
+ <Modal
113
+ className="tw-payment-error-modal"
114
+ hideCloseIcon={false}
115
+ open={true}
116
+ setOpen={(open) => {
117
+ if (!open) {
118
+ onCancel();
119
+ }
120
+ }}
121
+ size="compact"
122
+ title="Top up your wallet"
123
+ crossContainerStyles={{
124
+ position: "absolute",
125
+ right: spacing.lg,
126
+ top: spacing.lg,
127
+ zIndex: 1,
128
+ }}
129
+ >
130
+ {/* BuyWidget without padding */}
131
+ <BuyWidget
132
+ client={client}
133
+ theme={theme}
134
+ chain={buyWidgetConfig.chain}
135
+ tokenAddress={buyWidgetConfig.tokenAddress}
136
+ amount={buyWidgetConfig.amount}
137
+ style={{
138
+ border: "none",
139
+ width: "100%",
140
+ }}
141
+ buttonLabel="Continue"
142
+ title="Get funds"
143
+ description="Top up your wallet to complete your payment."
144
+ {...fundWalletOptions}
145
+ onSuccess={() => {
146
+ // Close modal and retry the payment
147
+ onRetry();
148
+ }}
149
+ onCancel={() => {
150
+ // Go back to error screen
151
+ setScreen("error");
152
+ }}
153
+ />
154
+ </Modal>
155
+ </CustomThemeProvider>
156
+ );
157
+ }
158
+
159
+ // Error screen (default)
160
+ return (
161
+ <CustomThemeProvider theme={theme}>
162
+ <Modal
163
+ className="tw-payment-error-modal"
164
+ hideCloseIcon={true}
165
+ open={true}
166
+ setOpen={(open) => {
167
+ if (!open) {
168
+ onCancel();
169
+ }
170
+ }}
171
+ size="compact"
172
+ title="Payment Failed"
173
+ >
174
+ <Container p="lg">
175
+ <ModalHeader title="Payment failed" />
176
+
177
+ <Container
178
+ flex="column"
179
+ gap="lg"
180
+ style={{
181
+ paddingTop: spacing.lg,
182
+ }}
183
+ >
184
+ {/* Error Message */}
185
+ <Text
186
+ size="sm"
187
+ style={{
188
+ color: "inherit",
189
+ lineHeight: 1.5,
190
+ }}
191
+ >
192
+ {isInsufficientFunds
193
+ ? "Your wallet doesn't have enough funds to complete this payment. Please top up your wallet and try again."
194
+ : errorData.errorMessage ||
195
+ "An error occurred while processing your payment."}
196
+ </Text>
197
+ </Container>
198
+ </Container>
199
+
200
+ {/* Action Buttons */}
201
+ <ScreenBottomContainer>
202
+ {isInsufficientFunds && buyWidgetConfig ? (
203
+ <>
204
+ <Button
205
+ fullWidth
206
+ gap="xs"
207
+ onClick={() => setScreen("buy-widget")}
208
+ variant="accent"
209
+ >
210
+ Top up your wallet
211
+ </Button>
212
+ <Button fullWidth gap="xs" onClick={onCancel} variant="secondary">
213
+ Cancel
214
+ </Button>
215
+ </>
216
+ ) : (
217
+ <>
218
+ <Button fullWidth gap="xs" onClick={onRetry} variant="accent">
219
+ Try Again
220
+ </Button>
221
+ <Button fullWidth gap="xs" onClick={onCancel} variant="secondary">
222
+ Cancel
223
+ </Button>
224
+ </>
225
+ )}
226
+ </ScreenBottomContainer>
227
+ </Modal>
228
+ </CustomThemeProvider>
229
+ );
230
+ }
231
+
232
+ // Default payment requirement selector - same logic as in fetchWithPayment.ts
233
+ function defaultPaymentRequirementsSelector(
234
+ paymentRequirements: RequestedPaymentRequirements[],
235
+ chainId: number | undefined,
236
+ scheme: "exact",
237
+ _error?: string,
238
+ ): RequestedPaymentRequirements | undefined {
239
+ if (!paymentRequirements.length) {
240
+ return undefined;
241
+ }
242
+
243
+ // If we have a chainId, find matching payment requirements
244
+ if (chainId !== undefined) {
245
+ const matchingPaymentRequirements = paymentRequirements.find(
246
+ (x) =>
247
+ extractEvmChainId(networkToCaip2ChainId(x.network)) === chainId &&
248
+ x.scheme === scheme,
249
+ );
250
+
251
+ if (matchingPaymentRequirements) {
252
+ return matchingPaymentRequirements;
253
+ }
254
+ }
255
+
256
+ // If no matching payment requirements, use the first payment requirement
257
+ const firstPaymentRequirement = paymentRequirements.find(
258
+ (x) => x.scheme === scheme,
259
+ );
260
+ return firstPaymentRequirement;
261
+ }
@@ -0,0 +1,75 @@
1
+ "use client";
2
+ import { CustomThemeProvider } from "../../../core/design-system/CustomThemeProvider.js";
3
+ import type { Theme } from "../../../core/design-system/index.js";
4
+ import { spacing } from "../../../core/design-system/index.js";
5
+ import {
6
+ Container,
7
+ ModalHeader,
8
+ ScreenBottomContainer,
9
+ } from "../components/basic.js";
10
+ import { Button } from "../components/buttons.js";
11
+ import { Modal } from "../components/Modal.js";
12
+ import { Text } from "../components/text.js";
13
+
14
+ type SignInRequiredModalProps = {
15
+ theme: Theme | "light" | "dark";
16
+ onSignIn: () => void;
17
+ onCancel: () => void;
18
+ };
19
+
20
+ /**
21
+ * @internal
22
+ */
23
+ export function SignInRequiredModal(props: SignInRequiredModalProps) {
24
+ const { theme, onSignIn, onCancel } = props;
25
+
26
+ return (
27
+ <CustomThemeProvider theme={theme}>
28
+ <Modal
29
+ className="tw-signin-required-modal"
30
+ hideCloseIcon={true}
31
+ open={true}
32
+ setOpen={(open) => {
33
+ if (!open) {
34
+ onCancel();
35
+ }
36
+ }}
37
+ size="compact"
38
+ title="Sign in required"
39
+ >
40
+ <Container p="lg">
41
+ <ModalHeader title="Sign in required" />
42
+
43
+ <Container
44
+ flex="column"
45
+ gap="lg"
46
+ style={{
47
+ paddingTop: spacing.lg,
48
+ }}
49
+ >
50
+ {/* Description */}
51
+ <Text
52
+ size="sm"
53
+ style={{
54
+ color: "inherit",
55
+ lineHeight: 1.5,
56
+ }}
57
+ >
58
+ Account required to complete payment, please sign in to continue.
59
+ </Text>
60
+ </Container>
61
+ </Container>
62
+
63
+ {/* Action Buttons */}
64
+ <ScreenBottomContainer>
65
+ <Button fullWidth gap="xs" onClick={onSignIn} variant="accent">
66
+ Sign in
67
+ </Button>
68
+ <Button fullWidth gap="xs" onClick={onCancel} variant="secondary">
69
+ Cancel
70
+ </Button>
71
+ </ScreenBottomContainer>
72
+ </Modal>
73
+ </CustomThemeProvider>
74
+ );
75
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = "5.112.3";
1
+ export const version = "5.113.0";
@@ -474,6 +474,16 @@ async function switchChainCoinbaseWalletSDK(
474
474
  provider: ProviderInterface,
475
475
  chain: Chain,
476
476
  ) {
477
+ // check if chain is already connected
478
+ const connectedChainId = (await provider.request({
479
+ method: "eth_chainId",
480
+ })) as string | number;
481
+ const connectedChain = getCachedChain(normalizeChainId(connectedChainId));
482
+ if (connectedChain?.id === chain.id) {
483
+ // chain is already connected, no need to switch
484
+ return;
485
+ }
486
+
477
487
  const chainIdHex = numberToHex(chain.id);
478
488
 
479
489
  try {
@@ -107,7 +107,7 @@ const DEFAULT_BASE_URL = "https://api.thirdweb.com/v1/payments/x402";
107
107
 
108
108
  * ```
109
109
  *
110
- * @bridge x402
110
+ * @x402
111
111
  */
112
112
  export function facilitator(
113
113
  config: ThirdwebX402FacilitatorConfig,
@@ -23,7 +23,7 @@ import { createPaymentHeader } from "./sign.js";
23
23
  * @param fetch - The fetch function to wrap (typically globalThis.fetch)
24
24
  * @param client - The thirdweb client used to access RPC infrastructure
25
25
  * @param wallet - The wallet used to sign payment messages
26
- * @param maxValue - The maximum allowed payment amount in base units (defaults to 1 USDC)
26
+ * @param maxValue - The maximum allowed payment amount in base units
27
27
  * @returns A wrapped fetch function that handles 402 responses automatically
28
28
  *
29
29
  * @example
@@ -46,13 +46,18 @@ import { createPaymentHeader } from "./sign.js";
46
46
  * @throws {Error} If a payment has already been attempted for this request
47
47
  * @throws {Error} If there's an error creating the payment header
48
48
  *
49
- * @bridge x402
49
+ * @x402
50
50
  */
51
51
  export function wrapFetchWithPayment(
52
52
  fetch: typeof globalThis.fetch,
53
53
  client: ThirdwebClient,
54
54
  wallet: Wallet,
55
- maxValue?: bigint,
55
+ options?: {
56
+ maxValue?: bigint;
57
+ paymentRequirementsSelector?: (
58
+ paymentRequirements: RequestedPaymentRequirements[],
59
+ ) => RequestedPaymentRequirements | undefined;
60
+ },
56
61
  ) {
57
62
  return async (input: RequestInfo, init?: RequestInit) => {
58
63
  const response = await fetch(input, init);
@@ -78,12 +83,14 @@ export function wrapFetchWithPayment(
78
83
  "Wallet not connected. Please connect your wallet to continue.",
79
84
  );
80
85
  }
81
- const selectedPaymentRequirements = defaultPaymentRequirementsSelector(
82
- parsedPaymentRequirements,
83
- chain.id,
84
- "exact",
85
- error,
86
- );
86
+ const selectedPaymentRequirements = options?.paymentRequirementsSelector
87
+ ? options.paymentRequirementsSelector(parsedPaymentRequirements)
88
+ : defaultPaymentRequirementsSelector(
89
+ parsedPaymentRequirements,
90
+ chain.id,
91
+ "exact",
92
+ error,
93
+ );
87
94
 
88
95
  if (!selectedPaymentRequirements) {
89
96
  throw new Error(
@@ -92,11 +99,11 @@ export function wrapFetchWithPayment(
92
99
  }
93
100
 
94
101
  if (
95
- maxValue &&
96
- BigInt(selectedPaymentRequirements.maxAmountRequired) > maxValue
102
+ options?.maxValue &&
103
+ BigInt(selectedPaymentRequirements.maxAmountRequired) > options.maxValue
97
104
  ) {
98
105
  throw new Error(
99
- `Payment amount exceeds maximum allowed (currently set to ${maxValue} in base units)`,
106
+ `Payment amount exceeds maximum allowed (currently set to ${options.maxValue} in base units)`,
100
107
  );
101
108
  }
102
109
 
@@ -41,6 +41,7 @@ export type RequestedPaymentRequirements = z.infer<
41
41
  const FacilitatorSettleResponseSchema = SettleResponseSchema.extend({
42
42
  network: FacilitatorNetworkSchema,
43
43
  errorMessage: z.string().optional(),
44
+ fundWalletLink: z.string().optional(),
44
45
  });
45
46
  export type FacilitatorSettleResponse = z.infer<
46
47
  typeof FacilitatorSettleResponseSchema
@@ -48,6 +49,7 @@ export type FacilitatorSettleResponse = z.infer<
48
49
 
49
50
  const FacilitatorVerifyResponseSchema = VerifyResponseSchema.extend({
50
51
  errorMessage: z.string().optional(),
52
+ fundWalletLink: z.string().optional(),
51
53
  });
52
54
 
53
55
  export type FacilitatorVerifyResponse = z.infer<