thirdweb 5.106.1 → 5.107.1-nightly-d53f8a25b4978a9f92f494f50955765817e31bad-20250920000329

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 (146) hide show
  1. package/dist/cjs/bridge/Token.js +4 -1
  2. package/dist/cjs/bridge/Token.js.map +1 -1
  3. package/dist/cjs/exports/x402.js +8 -0
  4. package/dist/cjs/exports/x402.js.map +1 -0
  5. package/dist/cjs/react/core/design-system/index.js +1 -0
  6. package/dist/cjs/react/core/design-system/index.js.map +1 -1
  7. package/dist/cjs/react/web/ui/Bridge/BuyWidget.js +1 -1
  8. package/dist/cjs/react/web/ui/Bridge/CheckoutWidget.js +1 -1
  9. package/dist/cjs/react/web/ui/Bridge/TransactionWidget.js +1 -1
  10. package/dist/cjs/react/web/ui/Bridge/swap-widget/SearchInput.js +1 -1
  11. package/dist/cjs/react/web/ui/Bridge/swap-widget/SearchInput.js.map +1 -1
  12. package/dist/cjs/react/web/ui/Bridge/swap-widget/SwapWidget.js +44 -28
  13. package/dist/cjs/react/web/ui/Bridge/swap-widget/SwapWidget.js.map +1 -1
  14. package/dist/cjs/react/web/ui/Bridge/swap-widget/select-chain.js +9 -7
  15. package/dist/cjs/react/web/ui/Bridge/swap-widget/select-chain.js.map +1 -1
  16. package/dist/cjs/react/web/ui/Bridge/swap-widget/select-token-ui.js +82 -46
  17. package/dist/cjs/react/web/ui/Bridge/swap-widget/select-token-ui.js.map +1 -1
  18. package/dist/cjs/react/web/ui/Bridge/swap-widget/swap-ui.js +215 -80
  19. package/dist/cjs/react/web/ui/Bridge/swap-widget/swap-ui.js.map +1 -1
  20. package/dist/cjs/react/web/ui/Bridge/swap-widget/utils.js +6 -0
  21. package/dist/cjs/react/web/ui/Bridge/swap-widget/utils.js.map +1 -1
  22. package/dist/cjs/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.js +1 -1
  23. package/dist/cjs/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.js.map +1 -1
  24. package/dist/cjs/react/web/ui/components/buttons.js +5 -2
  25. package/dist/cjs/react/web/ui/components/buttons.js.map +1 -1
  26. package/dist/cjs/react/web/ui/components/formElements.js +4 -1
  27. package/dist/cjs/react/web/ui/components/formElements.js.map +1 -1
  28. package/dist/cjs/react/web/ui/hooks/useisMobile.js +19 -0
  29. package/dist/cjs/react/web/ui/hooks/useisMobile.js.map +1 -0
  30. package/dist/cjs/stories/Bridge/Swap/SelectChain.stories.js +16 -6
  31. package/dist/cjs/stories/Bridge/Swap/SelectChain.stories.js.map +1 -1
  32. package/dist/cjs/stories/Bridge/Swap/SwapWidget.stories.js +6 -11
  33. package/dist/cjs/stories/Bridge/Swap/SwapWidget.stories.js.map +1 -1
  34. package/dist/cjs/version.js +1 -1
  35. package/dist/cjs/version.js.map +1 -1
  36. package/dist/cjs/x402/facilitator.js +76 -0
  37. package/dist/cjs/x402/facilitator.js.map +1 -0
  38. package/dist/cjs/x402/fetchWithPayment.js +121 -0
  39. package/dist/cjs/x402/fetchWithPayment.js.map +1 -0
  40. package/dist/esm/bridge/Token.js +4 -1
  41. package/dist/esm/bridge/Token.js.map +1 -1
  42. package/dist/esm/exports/x402.js +3 -0
  43. package/dist/esm/exports/x402.js.map +1 -0
  44. package/dist/esm/react/core/design-system/index.js +1 -0
  45. package/dist/esm/react/core/design-system/index.js.map +1 -1
  46. package/dist/esm/react/web/ui/Bridge/BuyWidget.js +1 -1
  47. package/dist/esm/react/web/ui/Bridge/CheckoutWidget.js +1 -1
  48. package/dist/esm/react/web/ui/Bridge/TransactionWidget.js +1 -1
  49. package/dist/esm/react/web/ui/Bridge/swap-widget/SearchInput.js +1 -1
  50. package/dist/esm/react/web/ui/Bridge/swap-widget/SearchInput.js.map +1 -1
  51. package/dist/esm/react/web/ui/Bridge/swap-widget/SwapWidget.js +44 -28
  52. package/dist/esm/react/web/ui/Bridge/swap-widget/SwapWidget.js.map +1 -1
  53. package/dist/esm/react/web/ui/Bridge/swap-widget/select-chain.js +11 -9
  54. package/dist/esm/react/web/ui/Bridge/swap-widget/select-chain.js.map +1 -1
  55. package/dist/esm/react/web/ui/Bridge/swap-widget/select-token-ui.js +83 -47
  56. package/dist/esm/react/web/ui/Bridge/swap-widget/select-token-ui.js.map +1 -1
  57. package/dist/esm/react/web/ui/Bridge/swap-widget/swap-ui.js +218 -83
  58. package/dist/esm/react/web/ui/Bridge/swap-widget/swap-ui.js.map +1 -1
  59. package/dist/esm/react/web/ui/Bridge/swap-widget/utils.js +5 -0
  60. package/dist/esm/react/web/ui/Bridge/swap-widget/utils.js.map +1 -1
  61. package/dist/esm/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.js +2 -2
  62. package/dist/esm/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.js.map +1 -1
  63. package/dist/esm/react/web/ui/components/buttons.js +5 -2
  64. package/dist/esm/react/web/ui/components/buttons.js.map +1 -1
  65. package/dist/esm/react/web/ui/components/formElements.js +5 -2
  66. package/dist/esm/react/web/ui/components/formElements.js.map +1 -1
  67. package/dist/esm/react/web/ui/hooks/useisMobile.js +16 -0
  68. package/dist/esm/react/web/ui/hooks/useisMobile.js.map +1 -0
  69. package/dist/esm/stories/Bridge/Swap/SelectChain.stories.js +12 -4
  70. package/dist/esm/stories/Bridge/Swap/SelectChain.stories.js.map +1 -1
  71. package/dist/esm/stories/Bridge/Swap/SwapWidget.stories.js +7 -12
  72. package/dist/esm/stories/Bridge/Swap/SwapWidget.stories.js.map +1 -1
  73. package/dist/esm/version.js +1 -1
  74. package/dist/esm/version.js.map +1 -1
  75. package/dist/esm/x402/facilitator.js +73 -0
  76. package/dist/esm/x402/facilitator.js.map +1 -0
  77. package/dist/esm/x402/fetchWithPayment.js +118 -0
  78. package/dist/esm/x402/fetchWithPayment.js.map +1 -0
  79. package/dist/types/bridge/Token.d.ts +2 -0
  80. package/dist/types/bridge/Token.d.ts.map +1 -1
  81. package/dist/types/bridge/types/Token.d.ts +2 -0
  82. package/dist/types/bridge/types/Token.d.ts.map +1 -1
  83. package/dist/types/exports/x402.d.ts +3 -0
  84. package/dist/types/exports/x402.d.ts.map +1 -0
  85. package/dist/types/react/core/design-system/index.d.ts +1 -0
  86. package/dist/types/react/core/design-system/index.d.ts.map +1 -1
  87. package/dist/types/react/web/ui/Bridge/BuyWidget.d.ts +1 -1
  88. package/dist/types/react/web/ui/Bridge/CheckoutWidget.d.ts +1 -1
  89. package/dist/types/react/web/ui/Bridge/TransactionWidget.d.ts +1 -1
  90. package/dist/types/react/web/ui/Bridge/swap-widget/SearchInput.d.ts.map +1 -1
  91. package/dist/types/react/web/ui/Bridge/swap-widget/SwapWidget.d.ts +5 -0
  92. package/dist/types/react/web/ui/Bridge/swap-widget/SwapWidget.d.ts.map +1 -1
  93. package/dist/types/react/web/ui/Bridge/swap-widget/select-chain.d.ts +1 -0
  94. package/dist/types/react/web/ui/Bridge/swap-widget/select-chain.d.ts.map +1 -1
  95. package/dist/types/react/web/ui/Bridge/swap-widget/select-token-ui.d.ts.map +1 -1
  96. package/dist/types/react/web/ui/Bridge/swap-widget/swap-ui.d.ts +2 -1
  97. package/dist/types/react/web/ui/Bridge/swap-widget/swap-ui.d.ts.map +1 -1
  98. package/dist/types/react/web/ui/Bridge/swap-widget/utils.d.ts +1 -0
  99. package/dist/types/react/web/ui/Bridge/swap-widget/utils.d.ts.map +1 -1
  100. package/dist/types/react/web/ui/components/basic.d.ts +1 -1
  101. package/dist/types/react/web/ui/components/basic.d.ts.map +1 -1
  102. package/dist/types/react/web/ui/components/buttons.d.ts +1 -0
  103. package/dist/types/react/web/ui/components/buttons.d.ts.map +1 -1
  104. package/dist/types/react/web/ui/components/formElements.d.ts.map +1 -1
  105. package/dist/types/react/web/ui/hooks/useisMobile.d.ts +2 -0
  106. package/dist/types/react/web/ui/hooks/useisMobile.d.ts.map +1 -0
  107. package/dist/types/stories/Bridge/Swap/SelectChain.stories.d.ts +4 -2
  108. package/dist/types/stories/Bridge/Swap/SelectChain.stories.d.ts.map +1 -1
  109. package/dist/types/stories/Bridge/Swap/SwapWidget.stories.d.ts.map +1 -1
  110. package/dist/types/version.d.ts +1 -1
  111. package/dist/types/version.d.ts.map +1 -1
  112. package/dist/types/x402/facilitator.d.ts +48 -0
  113. package/dist/types/x402/facilitator.d.ts.map +1 -0
  114. package/dist/types/x402/fetchWithPayment.d.ts +43 -0
  115. package/dist/types/x402/fetchWithPayment.d.ts.map +1 -0
  116. package/package.json +10 -1
  117. package/src/bridge/Token.ts +6 -0
  118. package/src/bridge/types/Token.ts +2 -0
  119. package/src/exports/x402.ts +5 -0
  120. package/src/react/core/design-system/index.ts +1 -0
  121. package/src/react/web/ui/Bridge/BuyWidget.tsx +1 -1
  122. package/src/react/web/ui/Bridge/CheckoutWidget.tsx +1 -1
  123. package/src/react/web/ui/Bridge/TransactionWidget.tsx +1 -1
  124. package/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx +1 -0
  125. package/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx +62 -45
  126. package/src/react/web/ui/Bridge/swap-widget/select-chain.tsx +25 -12
  127. package/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx +302 -197
  128. package/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx +496 -233
  129. package/src/react/web/ui/Bridge/swap-widget/utils.ts +6 -0
  130. package/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx +10 -10
  131. package/src/react/web/ui/components/basic.tsx +1 -1
  132. package/src/react/web/ui/components/buttons.tsx +6 -2
  133. package/src/react/web/ui/components/formElements.tsx +5 -1
  134. package/src/react/web/ui/hooks/useisMobile.ts +21 -0
  135. package/src/stories/Bridge/Swap/SelectChain.stories.tsx +40 -2
  136. package/src/stories/Bridge/Swap/SwapWidget.stories.tsx +18 -13
  137. package/src/version.ts +1 -1
  138. package/src/x402/facilitator.ts +87 -0
  139. package/src/x402/fetchWithPayment.ts +173 -0
  140. package/dist/cjs/react/web/ui/Bridge/swap-widget/common.js +0 -17
  141. package/dist/cjs/react/web/ui/Bridge/swap-widget/common.js.map +0 -1
  142. package/dist/esm/react/web/ui/Bridge/swap-widget/common.js +0 -14
  143. package/dist/esm/react/web/ui/Bridge/swap-widget/common.js.map +0 -1
  144. package/dist/types/react/web/ui/Bridge/swap-widget/common.d.ts +0 -9
  145. package/dist/types/react/web/ui/Bridge/swap-widget/common.d.ts.map +0 -1
  146. package/src/react/web/ui/Bridge/swap-widget/common.tsx +0 -35
@@ -1,3 +1,9 @@
1
1
  export function cleanedChainName(name: string) {
2
2
  return name.replace("Mainnet", "");
3
3
  }
4
+
5
+ export const tokenAmountFormatter = new Intl.NumberFormat("en-US", {
6
+ notation: "compact",
7
+ maximumFractionDigits: 5,
8
+ minimumFractionDigits: 2,
9
+ });
@@ -3,21 +3,21 @@ import type { IconFC } from "./types.js";
3
3
  export const ArrowUpDownIcon: IconFC = (props) => {
4
4
  return (
5
5
  <svg
6
- xmlns="http://www.w3.org/2000/svg"
7
- viewBox="0 0 24 24"
6
+ viewBox="0 0 16 16"
8
7
  fill="none"
9
- stroke="currentColor"
10
- strokeWidth="2"
11
- strokeLinecap="round"
12
- strokeLinejoin="round"
8
+ xmlns="http://www.w3.org/2000/svg"
13
9
  role="presentation"
14
10
  width={props.size}
15
11
  height={props.size}
16
12
  >
17
- <path d="m21 16-4 4-4-4" />
18
- <path d="M17 20V4" />
19
- <path d="m3 8 4-4 4 4" />
20
- <path d="M7 4v16" />
13
+ <g
14
+ stroke="currentColor"
15
+ strokeWidth="1"
16
+ strokeLinecap="round"
17
+ strokeLinejoin="round"
18
+ >
19
+ <path d="M9.599 2.4v11.2l3.2-3.476M6.4 13.6V2.4L3.2 5.875"></path>
20
+ </g>
21
21
  </svg>
22
22
  );
23
23
  };
@@ -84,7 +84,7 @@ export function Container(props: {
84
84
  expand?: boolean;
85
85
  center?: "x" | "y" | "both";
86
86
  gap?: keyof typeof spacing;
87
- children: React.ReactNode;
87
+ children?: React.ReactNode;
88
88
  style?: React.CSSProperties;
89
89
  p?: keyof typeof spacing;
90
90
  px?: keyof typeof spacing;
@@ -21,6 +21,7 @@ type ButtonProps = {
21
21
  fullWidth?: boolean;
22
22
  gap?: keyof typeof spacing;
23
23
  bg?: keyof Theme["colors"];
24
+ hoverBg?: keyof Theme["colors"];
24
25
  };
25
26
 
26
27
  export const Button = /* @__PURE__ */ StyledButton((props: ButtonProps) => {
@@ -95,6 +96,9 @@ export const Button = /* @__PURE__ */ StyledButton((props: ButtonProps) => {
95
96
  transition: "border 200ms ease",
96
97
  WebkitTapHighlightColor: "transparent",
97
98
  width: props.fullWidth ? "100%" : undefined,
99
+ "&:hover": {
100
+ background: props.hoverBg ? theme.colors[props.hoverBg] : undefined,
101
+ },
98
102
  ...(() => {
99
103
  if (props.variant === "outline") {
100
104
  return {
@@ -120,7 +124,7 @@ export const Button = /* @__PURE__ */ StyledButton((props: ButtonProps) => {
120
124
  if (props.variant === "ghost-solid") {
121
125
  return {
122
126
  "&:hover": {
123
- background: theme.colors.tertiaryBg,
127
+ background: theme.colors[props.hoverBg || "tertiaryBg"],
124
128
  },
125
129
  border: "1px solid transparent",
126
130
  };
@@ -137,7 +141,7 @@ export const Button = /* @__PURE__ */ StyledButton((props: ButtonProps) => {
137
141
  if (props.variant === "secondary") {
138
142
  return {
139
143
  "&:hover": {
140
- background: theme.colors.secondaryButtonHoverBg,
144
+ background: theme.colors[props.hoverBg || "secondaryButtonHoverBg"],
141
145
  },
142
146
  };
143
147
  }
@@ -2,6 +2,7 @@
2
2
  import { useCustomTheme } from "../../../core/design-system/CustomThemeProvider.js";
3
3
  import {
4
4
  fontSize,
5
+ media,
5
6
  radius,
6
7
  spacing,
7
8
  type Theme,
@@ -97,11 +98,14 @@ export const Input = /* @__PURE__ */ StyledInput<InputProps>((props) => {
97
98
  color: theme.colors.primaryText,
98
99
  display: "block",
99
100
  fontFamily: "inherit",
100
- fontSize: fontSize.md,
101
+ fontSize: fontSize.sm,
101
102
  outline: "none",
102
103
  padding: props.sm ? spacing.sm : fontSize.sm,
103
104
  WebkitAppearance: "none",
104
105
  width: "100%",
106
+ [media.mobile]: {
107
+ fontSize: fontSize.md,
108
+ },
105
109
  };
106
110
  });
107
111
 
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+
3
+ const MOBILE_BREAKPOINT = 640;
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
7
+ undefined,
8
+ );
9
+
10
+ React.useEffect(() => {
11
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
12
+ const onChange = () => {
13
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
14
+ };
15
+ mql.addEventListener("change", onChange);
16
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
17
+ return () => mql.removeEventListener("change", onChange);
18
+ }, []);
19
+
20
+ return !!isMobile;
21
+ }
@@ -16,13 +16,14 @@ const meta = {
16
16
  } satisfies Meta<typeof SelectBridgeChain>;
17
17
  export default meta;
18
18
 
19
- export function WithData() {
19
+ export function WithDataDesktop() {
20
20
  const [selectedChain, setSelectedChain] = useState<BridgeChain | undefined>(
21
21
  undefined,
22
22
  );
23
23
  return (
24
24
  <SwapWidgetContainer theme="dark" className="w-full">
25
25
  <SelectBridgeChain
26
+ isMobile={false}
26
27
  client={storyClient}
27
28
  onSelectChain={setSelectedChain}
28
29
  onBack={() => {}}
@@ -32,13 +33,50 @@ export function WithData() {
32
33
  );
33
34
  }
34
35
 
35
- export function Loading() {
36
+ export function LoadingDesktop() {
36
37
  const [selectedChain, setSelectedChain] = useState<BridgeChain | undefined>(
37
38
  undefined,
38
39
  );
39
40
  return (
40
41
  <SwapWidgetContainer theme="dark" className="w-full">
41
42
  <SelectBridgeChainUI
43
+ isMobile={false}
44
+ client={storyClient}
45
+ onSelectChain={setSelectedChain}
46
+ onBack={() => {}}
47
+ isPending={true}
48
+ chains={[]}
49
+ selectedChain={selectedChain}
50
+ />
51
+ </SwapWidgetContainer>
52
+ );
53
+ }
54
+
55
+ export function WithDataMobile() {
56
+ const [selectedChain, setSelectedChain] = useState<BridgeChain | undefined>(
57
+ undefined,
58
+ );
59
+ return (
60
+ <SwapWidgetContainer theme="dark" className="w-full">
61
+ <SelectBridgeChain
62
+ isMobile={true}
63
+ client={storyClient}
64
+ onSelectChain={setSelectedChain}
65
+ onBack={() => {}}
66
+ selectedChain={selectedChain}
67
+ />
68
+ </SwapWidgetContainer>
69
+ );
70
+ }
71
+
72
+ export function LoadingMobile() {
73
+ const [selectedChain, setSelectedChain] = useState<BridgeChain | undefined>(
74
+ undefined,
75
+ );
76
+ return (
77
+ <SwapWidgetContainer theme="dark" className="w-full">
78
+ <SelectBridgeChainUI
79
+ isMobile={true}
42
80
  client={storyClient}
43
81
  onSelectChain={setSelectedChain}
44
82
  onBack={() => {}}
@@ -1,7 +1,6 @@
1
1
  import type { Meta } from "@storybook/react";
2
2
  import { lightTheme } from "../../../react/core/design-system/index.js";
3
3
  import { SwapWidget } from "../../../react/web/ui/Bridge/swap-widget/SwapWidget.js";
4
- import { ConnectButton } from "../../../react/web/ui/ConnectWallet/ConnectButton.js";
5
4
  import { storyClient } from "../../utils.js";
6
5
 
7
6
  const meta: Meta<typeof SwapWidget> = {
@@ -14,15 +13,6 @@ const meta: Meta<typeof SwapWidget> = {
14
13
  return (
15
14
  <div>
16
15
  <Story />
17
- <div
18
- style={{
19
- position: "absolute",
20
- bottom: "20px",
21
- right: "20px",
22
- }}
23
- >
24
- <ConnectButton client={storyClient} />
25
- </div>
26
16
  </div>
27
17
  );
28
18
  },
@@ -31,15 +21,28 @@ const meta: Meta<typeof SwapWidget> = {
31
21
  export default meta;
32
22
 
33
23
  export function BasicUsage() {
34
- return <SwapWidget client={storyClient} />;
24
+ return <SwapWidget client={storyClient} persistTokenSelections={false} />;
35
25
  }
36
26
 
37
27
  export function CurrencySet() {
38
- return <SwapWidget client={storyClient} currency="JPY" />;
28
+ return (
29
+ <SwapWidget
30
+ client={storyClient}
31
+ currency="JPY"
32
+ persistTokenSelections={false}
33
+ />
34
+ );
39
35
  }
40
36
 
41
37
  export function LightMode() {
42
- return <SwapWidget client={storyClient} currency="JPY" theme="light" />;
38
+ return (
39
+ <SwapWidget
40
+ client={storyClient}
41
+ currency="JPY"
42
+ theme="light"
43
+ persistTokenSelections={false}
44
+ />
45
+ );
43
46
  }
44
47
 
45
48
  export function NoThirdwebBranding() {
@@ -48,6 +51,7 @@ export function NoThirdwebBranding() {
48
51
  client={storyClient}
49
52
  currency="JPY"
50
53
  showThirdwebBranding={false}
54
+ persistTokenSelections={false}
51
55
  />
52
56
  );
53
57
  }
@@ -57,6 +61,7 @@ export function CustomTheme() {
57
61
  <SwapWidget
58
62
  client={storyClient}
59
63
  currency="JPY"
64
+ persistTokenSelections={false}
60
65
  theme={lightTheme({
61
66
  colors: {
62
67
  modalBg: "#FFFFF0",
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = "5.106.1";
1
+ export const version = "5.107.1-nightly-d53f8a25b4978a9f92f494f50955765817e31bad-20250920000329";
@@ -0,0 +1,87 @@
1
+ import type { FacilitatorConfig } from "x402/types";
2
+ import type { ThirdwebClient } from "../client/client.js";
3
+
4
+ export type ThirdwebX402FacilitatorConfig = {
5
+ client: ThirdwebClient;
6
+ serverWalletAddress: string;
7
+ vaultAccessToken?: string;
8
+ baseUrl?: string;
9
+ };
10
+
11
+ const DEFAULT_BASE_URL = "https://api.thirdweb.com/v1/payments/x402";
12
+
13
+ /**
14
+ * Creates a facilitator for the x402 payment protocol.
15
+ * Use this with any x402 middleware to enable settling transactions with your thirdweb server wallet.
16
+ *
17
+ * @param config - The configuration for the facilitator
18
+ * @returns a x402 compatible FacilitatorConfig
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { facilitator } from "thirdweb/x402";
23
+ * import { createThirdwebClient } from "thirdweb";
24
+ *
25
+ * const client = createThirdwebClient({
26
+ * secretKey: "your-secret-key",
27
+ * });
28
+ * const thirdwebX402Facilitator = facilitator({
29
+ * client: client,
30
+ * serverWalletAddress: "0x1234567890123456789012345678901234567890",
31
+ * });
32
+ *
33
+ * // add the facilitator to any x402 payment middleware
34
+ * const middleware = paymentMiddleware(
35
+ * "0x1234567890123456789012345678901234567890",
36
+ * {
37
+ * "/api/paywall": {
38
+ * price: "$0.01",
39
+ * network: "base-sepolia",
40
+ * config: {
41
+ * description: "Access to paid content",
42
+ * },
43
+ * },
44
+ * },
45
+ * thirdwebX402Facilitator,
46
+ * );
47
+ * ```
48
+ *
49
+ * @bridge x402
50
+ */
51
+ export function facilitator(
52
+ config: ThirdwebX402FacilitatorConfig,
53
+ ): FacilitatorConfig {
54
+ const secretKey = config.client.secretKey;
55
+ if (!secretKey) {
56
+ throw new Error("Client secret key is required for the x402 facilitator");
57
+ }
58
+ const serverWalletAddress = config.serverWalletAddress;
59
+ if (!serverWalletAddress) {
60
+ throw new Error(
61
+ "Server wallet address is required for the x402 facilitator",
62
+ );
63
+ }
64
+ return {
65
+ url: (config.baseUrl ?? DEFAULT_BASE_URL) as `${string}://${string}`,
66
+ createAuthHeaders: async () => {
67
+ return {
68
+ verify: {
69
+ "x-secret-key": secretKey,
70
+ },
71
+ settle: {
72
+ "x-secret-key": secretKey,
73
+ "x-settlement-wallet-address": serverWalletAddress,
74
+ ...(config.vaultAccessToken
75
+ ? { "x-vault-access-token": config.vaultAccessToken }
76
+ : {}),
77
+ },
78
+ supported: {
79
+ "x-secret-key": secretKey,
80
+ },
81
+ list: {
82
+ "x-secret-key": secretKey,
83
+ },
84
+ };
85
+ },
86
+ };
87
+ }
@@ -0,0 +1,173 @@
1
+ import { createPaymentHeader } from "x402/client";
2
+ import {
3
+ ChainIdToNetwork,
4
+ EvmNetworkToChainId,
5
+ type PaymentRequirements,
6
+ PaymentRequirementsSchema,
7
+ type Signer,
8
+ } from "x402/types";
9
+ import { viemAdapter } from "../adapters/viem.js";
10
+ import { getCachedChain } from "../chains/utils.js";
11
+ import type { ThirdwebClient } from "../client/client.js";
12
+ import type { Wallet } from "../wallets/interfaces/wallet.js";
13
+
14
+ /**
15
+ * Enables the payment of APIs using the x402 payment protocol.
16
+ *
17
+ * This function wraps the native fetch API to automatically handle 402 Payment Required responses
18
+ * by creating and sending a payment header. It will:
19
+ * 1. Make the initial request
20
+ * 2. If a 402 response is received, parse the payment requirements
21
+ * 3. Verify the payment amount is within the allowed maximum
22
+ * 4. Create a payment header using the provided wallet client
23
+ * 5. Retry the request with the payment header
24
+ *
25
+ * @param fetch - The fetch function to wrap (typically globalThis.fetch)
26
+ * @param client - The thirdweb client used to access RPC infrastructure
27
+ * @param wallet - The wallet used to sign payment messages
28
+ * @param maxValue - The maximum allowed payment amount in base units (defaults to 1 USDC)
29
+ * @returns A wrapped fetch function that handles 402 responses automatically
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { wrapFetchWithPayment } from "thirdweb/x402";
34
+ * import { createThirdwebClient } from "thirdweb";
35
+ * import { createWallet } from "thirdweb/wallets";
36
+ *
37
+ * const client = createThirdwebClient({ clientId: "your-client-id" });
38
+ * const wallet = createWallet("io.metamask");
39
+ * await wallet.connect({ client })
40
+ *
41
+ * const fetchWithPay = wrapFetchWithPayment(fetch, client, wallet);
42
+ *
43
+ * // Make a request that may require payment
44
+ * const response = await fetchWithPay('https://api.example.com/paid-endpoint');
45
+ * ```
46
+ *
47
+ * @throws {Error} If the payment amount exceeds the maximum allowed value
48
+ * @throws {Error} If a payment has already been attempted for this request
49
+ * @throws {Error} If there's an error creating the payment header
50
+ *
51
+ * @bridge x402
52
+ */
53
+ export function wrapFetchWithPayment(
54
+ fetch: typeof globalThis.fetch,
55
+ client: ThirdwebClient,
56
+ wallet: Wallet,
57
+ maxValue: bigint = BigInt(1 * 10 ** 6), // Default to 1 USDC
58
+ ) {
59
+ return async (input: RequestInfo, init?: RequestInit) => {
60
+ const response = await fetch(input, init);
61
+
62
+ if (response.status !== 402) {
63
+ return response;
64
+ }
65
+
66
+ const { x402Version, accepts } = (await response.json()) as {
67
+ x402Version: number;
68
+ accepts: unknown[];
69
+ };
70
+ const parsedPaymentRequirements = accepts
71
+ .map((x) => PaymentRequirementsSchema.parse(x))
72
+ .filter((x) => x.scheme === "exact"); // TODO (402): accept other schemes
73
+
74
+ const account = wallet.getAccount();
75
+ let chain = wallet.getChain();
76
+
77
+ if (!account || !chain) {
78
+ throw new Error(
79
+ "Wallet not connected. Please connect your wallet to continue.",
80
+ );
81
+ }
82
+ const selectedPaymentRequirements = defaultPaymentRequirementsSelector(
83
+ parsedPaymentRequirements,
84
+ chain.id,
85
+ "exact",
86
+ );
87
+
88
+ if (BigInt(selectedPaymentRequirements.maxAmountRequired) > maxValue) {
89
+ throw new Error("Payment amount exceeds maximum allowed");
90
+ }
91
+
92
+ const paymentChainId = EvmNetworkToChainId.get(
93
+ selectedPaymentRequirements.network,
94
+ );
95
+
96
+ if (!paymentChainId) {
97
+ throw new Error(
98
+ `No chain found for the selected payment requirement: ${selectedPaymentRequirements.network}`,
99
+ );
100
+ }
101
+
102
+ // switch to the payment chain if it's not the current chain
103
+ if (paymentChainId !== chain.id) {
104
+ await wallet.switchChain(getCachedChain(paymentChainId));
105
+ chain = wallet.getChain();
106
+ if (!chain) {
107
+ throw new Error(`Failed to switch chain (${paymentChainId})`);
108
+ }
109
+ }
110
+
111
+ const walletClient = viemAdapter.wallet.toViem({
112
+ wallet: wallet,
113
+ chain,
114
+ client,
115
+ }) as Signer;
116
+
117
+ const paymentHeader = await createPaymentHeader(
118
+ walletClient,
119
+ x402Version,
120
+ selectedPaymentRequirements,
121
+ );
122
+
123
+ const initParams = init || {};
124
+
125
+ if ((initParams as { __is402Retry?: boolean }).__is402Retry) {
126
+ throw new Error("Payment already attempted");
127
+ }
128
+
129
+ const newInit = {
130
+ ...initParams,
131
+ headers: {
132
+ ...(initParams.headers || {}),
133
+ "X-PAYMENT": paymentHeader,
134
+ "Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE",
135
+ },
136
+ __is402Retry: true,
137
+ };
138
+
139
+ const secondResponse = await fetch(input, newInit);
140
+ return secondResponse;
141
+ };
142
+ }
143
+
144
+ function defaultPaymentRequirementsSelector(
145
+ paymentRequirements: PaymentRequirements[],
146
+ chainId: number,
147
+ scheme: "exact",
148
+ ) {
149
+ if (!paymentRequirements.length) {
150
+ throw new Error(
151
+ "No valid payment requirements found in server 402 response",
152
+ );
153
+ }
154
+ const currentWalletNetwork = ChainIdToNetwork[chainId];
155
+ // find the payment requirements matching the connected wallet chain
156
+ const matchingPaymentRequirements = paymentRequirements.find(
157
+ (x) => x.network === currentWalletNetwork && x.scheme === scheme,
158
+ );
159
+
160
+ if (matchingPaymentRequirements) {
161
+ return matchingPaymentRequirements;
162
+ } else {
163
+ // if no matching payment requirements, use the first payment requirement
164
+ // and switch the wallet to that chain
165
+ const firstPaymentRequirement = paymentRequirements.find(
166
+ (x) => x.scheme === scheme,
167
+ );
168
+ if (!firstPaymentRequirement) {
169
+ throw new Error("No suitable payment requirements found");
170
+ }
171
+ return firstPaymentRequirement;
172
+ }
173
+ }
@@ -1,17 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DecimalRenderer = DecimalRenderer;
4
- const jsx_runtime_1 = require("react/jsx-runtime");
5
- const text_js_1 = require("../../components/text.js");
6
- function DecimalRenderer(props) {
7
- if (Number(props.value) > 1000) {
8
- return ((0, jsx_runtime_1.jsx)(text_js_1.Text, { size: props.integerSize, color: props.color, weight: props.weight, children: compactFormatter.format(Number(props.value)) }));
9
- }
10
- const [integerPart, fractionPart] = props.value.split(".");
11
- return ((0, jsx_runtime_1.jsxs)("div", { style: { display: "flex", alignItems: "baseline" }, children: [(0, jsx_runtime_1.jsx)(text_js_1.Text, { size: props.integerSize, color: props.color, weight: props.weight, children: integerPart }), (0, jsx_runtime_1.jsxs)(text_js_1.Text, { size: props.fractionSize, color: props.color, weight: props.weight, children: [".", fractionPart || "00"] })] }));
12
- }
13
- const compactFormatter = new Intl.NumberFormat("en-US", {
14
- notation: "compact",
15
- maximumFractionDigits: 2,
16
- });
17
- //# sourceMappingURL=common.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"common.js","sourceRoot":"","sources":["../../../../../../../src/react/web/ui/Bridge/swap-widget/common.tsx"],"names":[],"mappings":";;AAGA,0CA0BC;;AA5BD,sDAAgD;AAEhD,SAAgB,eAAe,CAAC,KAM/B;IACC,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,CAAC;QAC/B,OAAO,CACL,uBAAC,cAAI,IAAC,IAAI,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,YACpE,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GACxC,CACR,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE3D,OAAO,CACL,iCAAK,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,aACrD,uBAAC,cAAI,IAAC,IAAI,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,YACpE,WAAW,GACP,EACP,wBAAC,cAAI,IAAC,IAAI,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,kBACpE,YAAY,IAAI,IAAI,IACjB,IACH,CACP,CAAC;AACJ,CAAC;AAED,MAAM,gBAAgB,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE;IACtD,QAAQ,EAAE,SAAS;IACnB,qBAAqB,EAAE,CAAC;CACzB,CAAC,CAAC"}
@@ -1,14 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Text } from "../../components/text.js";
3
- export function DecimalRenderer(props) {
4
- if (Number(props.value) > 1000) {
5
- return (_jsx(Text, { size: props.integerSize, color: props.color, weight: props.weight, children: compactFormatter.format(Number(props.value)) }));
6
- }
7
- const [integerPart, fractionPart] = props.value.split(".");
8
- return (_jsxs("div", { style: { display: "flex", alignItems: "baseline" }, children: [_jsx(Text, { size: props.integerSize, color: props.color, weight: props.weight, children: integerPart }), _jsxs(Text, { size: props.fractionSize, color: props.color, weight: props.weight, children: [".", fractionPart || "00"] })] }));
9
- }
10
- const compactFormatter = new Intl.NumberFormat("en-US", {
11
- notation: "compact",
12
- maximumFractionDigits: 2,
13
- });
14
- //# sourceMappingURL=common.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"common.js","sourceRoot":"","sources":["../../../../../../../src/react/web/ui/Bridge/swap-widget/common.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAEhD,MAAM,UAAU,eAAe,CAAC,KAM/B;IACC,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,CAAC;QAC/B,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,YACpE,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GACxC,CACR,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE3D,OAAO,CACL,eAAK,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,aACrD,KAAC,IAAI,IAAC,IAAI,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,YACpE,WAAW,GACP,EACP,MAAC,IAAI,IAAC,IAAI,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,kBACpE,YAAY,IAAI,IAAI,IACjB,IACH,CACP,CAAC;AACJ,CAAC;AAED,MAAM,gBAAgB,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE;IACtD,QAAQ,EAAE,SAAS;IACnB,qBAAqB,EAAE,CAAC;CACzB,CAAC,CAAC"}
@@ -1,9 +0,0 @@
1
- import type { Theme } from "../../../../core/design-system/index.js";
2
- export declare function DecimalRenderer(props: {
3
- value: string;
4
- color: keyof Theme["colors"];
5
- weight: 400 | 500 | 600 | 700;
6
- integerSize: "md" | "sm";
7
- fractionSize: "sm" | "xs";
8
- }): import("react/jsx-runtime").JSX.Element;
9
- //# sourceMappingURL=common.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../../../../../src/react/web/ui/Bridge/swap-widget/common.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yCAAyC,CAAC;AAGrE,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC7B,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAC9B,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;CAC3B,2CAoBA"}
@@ -1,35 +0,0 @@
1
- import type { Theme } from "../../../../core/design-system/index.js";
2
- import { Text } from "../../components/text.js";
3
-
4
- export function DecimalRenderer(props: {
5
- value: string;
6
- color: keyof Theme["colors"];
7
- weight: 400 | 500 | 600 | 700;
8
- integerSize: "md" | "sm";
9
- fractionSize: "sm" | "xs";
10
- }) {
11
- if (Number(props.value) > 1000) {
12
- return (
13
- <Text size={props.integerSize} color={props.color} weight={props.weight}>
14
- {compactFormatter.format(Number(props.value))}
15
- </Text>
16
- );
17
- }
18
- const [integerPart, fractionPart] = props.value.split(".");
19
-
20
- return (
21
- <div style={{ display: "flex", alignItems: "baseline" }}>
22
- <Text size={props.integerSize} color={props.color} weight={props.weight}>
23
- {integerPart}
24
- </Text>
25
- <Text size={props.fractionSize} color={props.color} weight={props.weight}>
26
- .{fractionPart || "00"}
27
- </Text>
28
- </div>
29
- );
30
- }
31
-
32
- const compactFormatter = new Intl.NumberFormat("en-US", {
33
- notation: "compact",
34
- maximumFractionDigits: 2,
35
- });