thirdweb 5.78.0 → 5.79.0-nightly-f91f6310e9396918d0ffc5217eeb4a44cef0b8c8-20241215000412

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 (97) hide show
  1. package/dist/cjs/exports/react.js +8 -1
  2. package/dist/cjs/exports/react.js.map +1 -1
  3. package/dist/cjs/exports/wallets/in-app.js.map +1 -1
  4. package/dist/cjs/exports/wallets/in-app.native.js.map +1 -1
  5. package/dist/cjs/exports/wallets.js.map +1 -1
  6. package/dist/cjs/exports/wallets.native.js.map +1 -1
  7. package/dist/cjs/extensions/prebuilts/deploy-published.js +22 -16
  8. package/dist/cjs/extensions/prebuilts/deploy-published.js.map +1 -1
  9. package/dist/cjs/react/web/ui/MediaRenderer/useResolvedMediaType.js +1 -1
  10. package/dist/cjs/react/web/ui/MediaRenderer/useResolvedMediaType.js.map +1 -1
  11. package/dist/cjs/react/web/ui/prebuilt/Chain/icon.js +1 -1
  12. package/dist/cjs/react/web/ui/prebuilt/Chain/icon.js.map +1 -1
  13. package/dist/cjs/react/web/ui/prebuilt/Chain/name.js +15 -11
  14. package/dist/cjs/react/web/ui/prebuilt/Chain/name.js.map +1 -1
  15. package/dist/cjs/react/web/ui/prebuilt/Chain/provider.js +1 -0
  16. package/dist/cjs/react/web/ui/prebuilt/Chain/provider.js.map +1 -1
  17. package/dist/cjs/react/web/ui/prebuilt/Wallet/icon.js +79 -0
  18. package/dist/cjs/react/web/ui/prebuilt/Wallet/icon.js.map +1 -0
  19. package/dist/cjs/react/web/ui/prebuilt/Wallet/name.js +105 -0
  20. package/dist/cjs/react/web/ui/prebuilt/Wallet/name.js.map +1 -0
  21. package/dist/cjs/react/web/ui/prebuilt/Wallet/provider.js +47 -0
  22. package/dist/cjs/react/web/ui/prebuilt/Wallet/provider.js.map +1 -0
  23. package/dist/cjs/version.js +1 -1
  24. package/dist/cjs/version.js.map +1 -1
  25. package/dist/esm/exports/react.js +4 -0
  26. package/dist/esm/exports/react.js.map +1 -1
  27. package/dist/esm/exports/wallets/in-app.js.map +1 -1
  28. package/dist/esm/exports/wallets/in-app.native.js.map +1 -1
  29. package/dist/esm/exports/wallets.js.map +1 -1
  30. package/dist/esm/exports/wallets.native.js.map +1 -1
  31. package/dist/esm/extensions/prebuilts/deploy-published.js +22 -16
  32. package/dist/esm/extensions/prebuilts/deploy-published.js.map +1 -1
  33. package/dist/esm/react/web/ui/MediaRenderer/useResolvedMediaType.js +1 -1
  34. package/dist/esm/react/web/ui/MediaRenderer/useResolvedMediaType.js.map +1 -1
  35. package/dist/esm/react/web/ui/prebuilt/Chain/icon.js +1 -1
  36. package/dist/esm/react/web/ui/prebuilt/Chain/icon.js.map +1 -1
  37. package/dist/esm/react/web/ui/prebuilt/Chain/name.js +14 -11
  38. package/dist/esm/react/web/ui/prebuilt/Chain/name.js.map +1 -1
  39. package/dist/esm/react/web/ui/prebuilt/Chain/provider.js +1 -0
  40. package/dist/esm/react/web/ui/prebuilt/Chain/provider.js.map +1 -1
  41. package/dist/esm/react/web/ui/prebuilt/Wallet/icon.js +75 -0
  42. package/dist/esm/react/web/ui/prebuilt/Wallet/icon.js.map +1 -0
  43. package/dist/esm/react/web/ui/prebuilt/Wallet/name.js +100 -0
  44. package/dist/esm/react/web/ui/prebuilt/Wallet/name.js.map +1 -0
  45. package/dist/esm/react/web/ui/prebuilt/Wallet/provider.js +42 -0
  46. package/dist/esm/react/web/ui/prebuilt/Wallet/provider.js.map +1 -0
  47. package/dist/esm/version.js +1 -1
  48. package/dist/esm/version.js.map +1 -1
  49. package/dist/types/exports/react.d.ts +3 -0
  50. package/dist/types/exports/react.d.ts.map +1 -1
  51. package/dist/types/exports/wallets/in-app.d.ts +2 -1
  52. package/dist/types/exports/wallets/in-app.d.ts.map +1 -1
  53. package/dist/types/exports/wallets/in-app.native.d.ts +2 -1
  54. package/dist/types/exports/wallets/in-app.native.d.ts.map +1 -1
  55. package/dist/types/exports/wallets.d.ts +2 -1
  56. package/dist/types/exports/wallets.d.ts.map +1 -1
  57. package/dist/types/exports/wallets.native.d.ts +2 -1
  58. package/dist/types/exports/wallets.native.d.ts.map +1 -1
  59. package/dist/types/extensions/prebuilts/deploy-published.d.ts.map +1 -1
  60. package/dist/types/react/web/ui/prebuilt/Chain/name.d.ts +10 -1
  61. package/dist/types/react/web/ui/prebuilt/Chain/name.d.ts.map +1 -1
  62. package/dist/types/react/web/ui/prebuilt/Chain/provider.d.ts +1 -0
  63. package/dist/types/react/web/ui/prebuilt/Chain/provider.d.ts.map +1 -1
  64. package/dist/types/react/web/ui/prebuilt/Wallet/icon.d.ts +81 -0
  65. package/dist/types/react/web/ui/prebuilt/Wallet/icon.d.ts.map +1 -0
  66. package/dist/types/react/web/ui/prebuilt/Wallet/name.d.ts +111 -0
  67. package/dist/types/react/web/ui/prebuilt/Wallet/name.d.ts.map +1 -0
  68. package/dist/types/react/web/ui/prebuilt/Wallet/provider.d.ts +41 -0
  69. package/dist/types/react/web/ui/prebuilt/Wallet/provider.d.ts.map +1 -0
  70. package/dist/types/version.d.ts +1 -1
  71. package/dist/types/version.d.ts.map +1 -1
  72. package/dist/types/wallets/in-app/core/wallet/types.d.ts +3 -3
  73. package/dist/types/wallets/in-app/core/wallet/types.d.ts.map +1 -1
  74. package/package.json +3 -2
  75. package/src/exports/react.ts +14 -0
  76. package/src/exports/wallets/in-app.native.ts +6 -0
  77. package/src/exports/wallets/in-app.ts +6 -0
  78. package/src/exports/wallets.native.ts +6 -0
  79. package/src/exports/wallets.ts +6 -0
  80. package/src/extensions/prebuilts/deploy-published.ts +27 -20
  81. package/src/react/core/hooks/wallets/useAddConnectedWallet.test.tsx +52 -0
  82. package/src/react/core/hooks/wallets/useConnect.test.tsx +105 -0
  83. package/src/react/core/hooks/wallets/useSetActiveWallet.test.tsx +53 -0
  84. package/src/react/web/ui/MediaRenderer/useResolvedMediaType.ts +1 -1
  85. package/src/react/web/ui/prebuilt/Chain/icon.tsx +1 -1
  86. package/src/react/web/ui/prebuilt/Chain/name.test.tsx +19 -1
  87. package/src/react/web/ui/prebuilt/Chain/name.tsx +19 -13
  88. package/src/react/web/ui/prebuilt/Chain/provider.tsx +1 -0
  89. package/src/react/web/ui/prebuilt/Wallet/icon.test.tsx +30 -0
  90. package/src/react/web/ui/prebuilt/Wallet/icon.tsx +120 -0
  91. package/src/react/web/ui/prebuilt/Wallet/name.test.tsx +55 -0
  92. package/src/react/web/ui/prebuilt/Wallet/name.tsx +164 -0
  93. package/src/react/web/ui/prebuilt/Wallet/provider.test.tsx +61 -0
  94. package/src/react/web/ui/prebuilt/Wallet/provider.tsx +65 -0
  95. package/src/version.ts +1 -1
  96. package/src/wallets/in-app/core/wallet/types.ts +7 -8
  97. package/src/wallets/manager/connection-manager.test.ts +133 -133
@@ -43,6 +43,6 @@ export function useResolvedMediaType(
43
43
 
44
44
  return {
45
45
  mediaInfo: { url: resolvedUrl, mimeType: resolvedMimeType.data },
46
- isFetched: resolvedMimeType.isFetched,
46
+ isFetched: resolvedMimeType.isFetched || !!mimeType,
47
47
  };
48
48
  }
@@ -143,7 +143,7 @@ export function ChainIcon({
143
143
  }
144
144
  // Check if the chain object already has "icon"
145
145
  if (chain.icon?.url) {
146
- return chain.icon.url;
146
+ return resolveScheme({ uri: chain.icon.url, client });
147
147
  }
148
148
  const possibleUrl = await getChainMetadata(chain).then(
149
149
  (data) => data.icon?.url,
@@ -2,7 +2,8 @@ import { describe, expect, it } from "vitest";
2
2
  import { render, screen, waitFor } from "~test/react-render.js";
3
3
  import { ethereum } from "../../../../../chains/chain-definitions/ethereum.js";
4
4
  import { defineChain } from "../../../../../chains/utils.js";
5
- import { ChainName, fetchChainName } from "./name.js";
5
+ import { getFunctionId } from "../../../../../utils/function-id.js";
6
+ import { ChainName, fetchChainName, getQueryKeys } from "./name.js";
6
7
  import { ChainProvider } from "./provider.js";
7
8
 
8
9
  describe.runIf(process.env.TW_SECRET_KEY)("ChainName component", () => {
@@ -97,4 +98,21 @@ describe.runIf(process.env.TW_SECRET_KEY)("ChainName component", () => {
97
98
  });
98
99
  expect(res).toBe("eth_mainnet");
99
100
  });
101
+
102
+ it("getQueryKeys should work without nameResolver", () => {
103
+ expect(getQueryKeys({ chainId: 1 })).toStrictEqual([
104
+ "_internal_chain_name_",
105
+ 1,
106
+ ]);
107
+ });
108
+
109
+ it("getQueryKeys should work WITH nameResolver", () => {
110
+ const nameResolver = () => "tw";
111
+ const fnId = getFunctionId(nameResolver);
112
+ expect(getQueryKeys({ chainId: 1, nameResolver })).toStrictEqual([
113
+ "_internal_chain_name_",
114
+ 1,
115
+ { resolver: fnId },
116
+ ]);
117
+ });
100
118
  });
@@ -48,7 +48,7 @@ export interface ChainNameProps
48
48
  * name was not fetched succesfully
49
49
  * @example
50
50
  * ```tsx
51
- * <ChainName fallbackComponent={"Failed to load"}
51
+ * <ChainName fallbackComponent={<span>Failed to load</span>}
52
52
  * />
53
53
  * ```
54
54
  */
@@ -157,18 +157,7 @@ export function ChainName({
157
157
  }: ChainNameProps) {
158
158
  const { chain } = useChainContext();
159
159
  const nameQuery = useQuery({
160
- queryKey: [
161
- "_internal_chain_name_",
162
- chain.id,
163
- {
164
- resolver:
165
- typeof nameResolver === "string"
166
- ? nameResolver
167
- : typeof nameResolver === "function"
168
- ? getFunctionId(nameResolver)
169
- : undefined,
170
- },
171
- ] as const,
160
+ queryKey: getQueryKeys({ chainId: chain.id, nameResolver }),
172
161
  queryFn: async () => fetchChainName({ chain, nameResolver }),
173
162
  ...queryOptions,
174
163
  });
@@ -205,3 +194,20 @@ export async function fetchChainName(props: {
205
194
  }
206
195
  return getChainMetadata(chain).then((data) => data.name);
207
196
  }
197
+
198
+ /**
199
+ * @internal Exported for tests
200
+ */
201
+ export function getQueryKeys(props: {
202
+ chainId: number;
203
+ nameResolver?: string | (() => string) | (() => Promise<string>);
204
+ }) {
205
+ if (typeof props.nameResolver === "function") {
206
+ return [
207
+ "_internal_chain_name_",
208
+ props.chainId,
209
+ { resolver: getFunctionId(props.nameResolver) },
210
+ ] as const;
211
+ }
212
+ return ["_internal_chain_name_", props.chainId] as const;
213
+ }
@@ -48,6 +48,7 @@ const ChainProviderContext = /* @__PURE__ */ createContext<
48
48
  * ```
49
49
  * @component
50
50
  * @chain
51
+ * @beta
51
52
  */
52
53
  export function ChainProvider(
53
54
  props: React.PropsWithChildren<ChainProviderProps>,
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { render, waitFor } from "~test/react-render.js";
3
+ import { WalletIcon, fetchWalletImage } from "./icon.js";
4
+ import { WalletProvider } from "./provider.js";
5
+
6
+ describe("WalletIcon", () => {
7
+ it("should fetch wallet image", async () => {
8
+ expect(await fetchWalletImage({ id: "io.metamask" })).toBe(
9
+ "data:image/webp;base64,UklGRsgFAABXRUJQVlA4ILwFAAAwHgCdASqAAIAAPm0ylEWkIyIXTGzMQAbEoAzQSPDffG8qhx7LpfeDUfnDee/tP6gP2u9Yf0a/7L0/+pc3nivc0JlTXBvMVYW1Wt7JbdRJ1a5UhpH9+sdhAvGug20WzG+wRD+P/7/raQ1NqZ5EfcOwosQsP7NRVflPNArBLCbFJWE3EvTLNOmMfR2f+r+E2y6y1lWanc3n9RtF4lYbcW38t3UG/W2jXng4loXFOy3Vjm/r/1KHwXlpAuhAIzK/XF+ChayRFhBD+lbRrgQ3CSVjdmXw6agYPoMQ+tpjPUN/3qdGcvnN1sKWt57OGozQVSjMu0aA58MPKVWiAP77l9VfptKhZ7in3+r1DqZZQEYqO1p+MENah/WS/QQDvPuew8P0OXfahcjbCg7h4QEEEjDyfoFUbQHdw+meAsydryxVs/Ij+eB619Uj/sMgtwIifIyTieCT/hdsDmpLqGD+vOIArZfzSkordRkngojxVgvWRht84IaCHCs5Dn208UHFmyQE4KCxHef1iLYeAEPxnIAovGHIl7rCjiYE12obAM3ZCnt+RFqcT3q2rorCswc4/8f8TOhCSo63/dszpMkNQonGwtyhAFnm1EKqLPDcy99ggKQL95VYePoHcv8sHOG5zJ7lircX7VPpxkloNVWnlp/drJjQrp0h5BsOnqYn756+wcFw5qapAOnYCKUHujsoSz2BlLs1702rFpvi1iVczo2aO9GN1TaM3zBqMX0NN0YEU8pz/3+xwkK5M5q+Qb4FNJeugjdOp2kcYoOCbyAg/0OyGswYHPwDN/opVaDHWj0FcKzLq43uEjUZMG8t7O6BkoK0FmpYOeTQrinuF5F6W6ENly78daVmGPYTU22R9No5+xr8F2ESYTaJzOR3UoouY15xHrsxhDCukfebOiHljS6jUjF0TWaIAb58k57DZ3gdjkpxnJDyEmCsCOlASNWMn1ay3G8PDriQOdwsL7bgx/jZXN6XZHVx2hst/6Qcljnnn4um8eD1NcP1rTV7HMZzAb/d7ntki9zU1IFl695zPs3YT+h0PS4JuQYLzYoxpmMUQ35sVx/3KkL4Pm/TlvuNpnopM0+l2b/0OS6+5GwJYEMpzM4peYZS3qZkz/kxWNPTLv3UcCguDqpGxZ39l/VQJhSImKPZBa1RWCtSj29VCzCEZlX/eAdAKgZwhMKR4C5osznEbxjfRdHHPxjZwstCwugFa5w/WykOTFGAzk0sLSuvxO9kw/f++0WvG5La11GQaqbDDJ/ks7bcELqEGtvLHiJgxOXW9PqCk5Ap9EYelPTmdhHZgzWxtz4idN+JX+DGyYtJYjtW+Ay4kJ4Hol+Iavfje1f/22S69z1XSZ8OOcrDHKQdO1JlGaTkKQmCg5Tr05Qy9NQSfh67Vpui6OyMu38BnbG+cfWPRx/MjGQITY8w8sb1GUGd5hD9eVqycVtz8yFlYrXYQSBkAxMYgkBaiWe6kk24SoOCgt74nNs3pyTWUERw9ENESB6PyO9HjcsUZWIh97RKlf6thPtqxdlfr9i3McON9zvI7M2TtYcneXhOnqN8V4wqqII2J3DQ6/DZFNj5eNkCp2ijt7UWuKduhEmbZlLahfxD8eqBHAZR/H5rulzc4oVlyXr6qPXf9LCEuDRSDE8VNFY4NuTcaRTZO33RrWmWzbXAwpEKeH/Xf78XMoeynLBSyB+pS6y8Fqh7ExdmnvtW2gW3pwNrLc5lXlZJW8VcBzaSpy1Dmqj6Xll9BS8RyWqNx0O3fY8NJpf1exbNYMWA8juddzn2d9lHypEbiym0ASxI/jgGkbis5fecZ60QtJusJgdC8HAJEh64A9EuOCAnnISc5CElwpsvJTdEZhYv3t7MtLDvHp20lAtylt5l9yxqfCy/sqC6qZ/8+tsTAHzziGF6NKoaD0FVhZ2CER5AonJdBz5sg0rcD7arFe96uzujFDCQAAAA",
10
+ );
11
+ });
12
+
13
+ it("should throw error if WalletId is not supported", async () => {
14
+ await expect(() =>
15
+ // @ts-ignore For test
16
+ fetchWalletImage({ id: "__undefined__" }),
17
+ ).rejects.toThrowError("Wallet with id __undefined__ not found");
18
+ });
19
+
20
+ it("should render an image", async () => {
21
+ const { container } = render(
22
+ <WalletProvider id="io.cosmostation">
23
+ <WalletIcon />
24
+ </WalletProvider>,
25
+ );
26
+ await waitFor(() => {
27
+ expect(container.querySelector("img")).not.toBe(null);
28
+ });
29
+ });
30
+ });
@@ -0,0 +1,120 @@
1
+ "use client";
2
+
3
+ import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
4
+ import type { JSX } from "react";
5
+ import { getWalletInfo } from "../../../../../wallets/__generated__/getWalletInfo.js";
6
+ import type { WalletId } from "../../../../../wallets/wallet-types.js";
7
+ import { useWalletContext } from "./provider.js";
8
+
9
+ export interface WalletIconProps
10
+ extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src"> {
11
+ /**
12
+ * This component will be shown while the icon of the wallet is being fetched
13
+ * If not passed, the component will return `null`.
14
+ *
15
+ * You can/should pass a loading sign or spinner to this prop.
16
+ * @example
17
+ * ```tsx
18
+ * <WalletIcon loadingComponent={<Spinner />} />
19
+ * ```
20
+ */
21
+ loadingComponent?: JSX.Element;
22
+ /**
23
+ * This component will be shown if the icon fails to be retreived
24
+ * If not passed, the component will return `null`.
25
+ *
26
+ * You can/should pass a descriptive text/component to this prop, indicating that the
27
+ * icon was not fetched succesfully
28
+ * @example
29
+ * ```tsx
30
+ * <WalletIcon fallbackComponent={<span>Failed to load</span>}
31
+ * />
32
+ * ```
33
+ */
34
+ fallbackComponent?: JSX.Element;
35
+ /**
36
+ * Optional `useQuery` params
37
+ */
38
+ queryOptions?: Omit<UseQueryOptions<string>, "queryFn" | "queryKey">;
39
+ }
40
+
41
+ /**
42
+ * This component tries to resolve the icon of a given wallet, then return an image.
43
+ * @returns an <img /> with the src of the wallet icon
44
+ *
45
+ * @example
46
+ * ### Basic usage
47
+ * ```tsx
48
+ * import { WalletProvider, WalletIcon } from "thirdweb/react";
49
+ *
50
+ * <WalletProvider id="io.metamask">
51
+ * <WalletIcon />
52
+ * </WalletProvider>
53
+ * ```
54
+ *
55
+ * Result: An <img /> component with the src of the icon
56
+ * ```html
57
+ * <img src="metamask-icon.png" />
58
+ * ```
59
+ *
60
+ * ### Show a loading sign while the icon is being loaded
61
+ * ```tsx
62
+ * <WalletIcon loadingComponent={<Spinner />} />
63
+ * ```
64
+ *
65
+ * ### Fallback to a dummy image if the wallet icon fails to resolve
66
+ * ```tsx
67
+ * <WalletIcon fallbackComponent={<img src="blank-image.png" />} />
68
+ * ```
69
+ *
70
+ * ### Usage with queryOptions
71
+ * WalletIcon uses useQuery() from tanstack query internally.
72
+ * It allows you to pass a custom queryOptions of your choice for more control of the internal fetching logic
73
+ * ```tsx
74
+ * <WalletIcon queryOptions={{ enabled: someLogic, retry: 3, }} />
75
+ * ```
76
+ *
77
+ * @component
78
+ * @wallet
79
+ * @beta
80
+ */
81
+ export function WalletIcon({
82
+ loadingComponent,
83
+ fallbackComponent,
84
+ queryOptions,
85
+ ...restProps
86
+ }: WalletIconProps) {
87
+ const imageQuery = useWalletIcon({ queryOptions });
88
+ if (imageQuery.isLoading) {
89
+ return loadingComponent || null;
90
+ }
91
+ if (!imageQuery.data) {
92
+ return fallbackComponent || null;
93
+ }
94
+ return <img src={imageQuery.data} {...restProps} alt={restProps.alt} />;
95
+ }
96
+
97
+ /**
98
+ * @internal
99
+ */
100
+ function useWalletIcon(props: {
101
+ queryOptions?: Omit<UseQueryOptions<string>, "queryFn" | "queryKey">;
102
+ }) {
103
+ const { id } = useWalletContext();
104
+ const imageQuery = useQuery({
105
+ queryKey: ["walletIcon", id],
106
+ queryFn: async () => fetchWalletImage({ id }),
107
+ ...props.queryOptions,
108
+ });
109
+ return imageQuery;
110
+ }
111
+
112
+ /**
113
+ * @internal Exported for tests only
114
+ */
115
+ export async function fetchWalletImage(props: {
116
+ id: WalletId;
117
+ }) {
118
+ const image_src = await getWalletInfo(props.id, true);
119
+ return image_src;
120
+ }
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { render, waitFor } from "~test/react-render.js";
3
+ import { getFunctionId } from "../../../../../utils/function-id.js";
4
+ import { WalletName, fetchWalletName, getQueryKeys } from "./name.js";
5
+ import { WalletProvider } from "./provider.js";
6
+
7
+ describe.runIf(process.env.TW_SECRET_KEY)("WalletName", () => {
8
+ it("fetchWalletName: should fetch wallet name from id", async () => {
9
+ const name = await fetchWalletName({ id: "io.metamask" });
10
+ expect(name).toBe("MetaMask");
11
+ });
12
+
13
+ it("fetchWalletName should throw error if failed to get name", async () => {
14
+ // @ts-ignore for test
15
+ await expect(() => fetchWalletName({ id: "test___" })).rejects.toThrowError(
16
+ "Wallet with id test___ not found",
17
+ );
18
+ });
19
+
20
+ it("fetchWalletName should work with formatFn", async () => {
21
+ const formatFn = (str: string) => `${str} Wallet`;
22
+ expect(await fetchWalletName({ id: "io.metamask", formatFn })).toBe(
23
+ "MetaMask Wallet",
24
+ );
25
+ });
26
+
27
+ it("getQueryKeys should work without a formatFn", () => {
28
+ expect(getQueryKeys({ id: "ai.hacken" })).toStrictEqual([
29
+ "walletName",
30
+ "ai.hacken",
31
+ ]);
32
+ });
33
+
34
+ it("getQueryKeys should work WITH a formatFn", () => {
35
+ const fn = (str: string) => `test:${str}`;
36
+ const fnId = getFunctionId(fn);
37
+ expect(getQueryKeys({ id: "ai.hacken", formatFn: fn })).toStrictEqual([
38
+ "walletName",
39
+ "ai.hacken",
40
+ { resolver: fnId },
41
+ ]);
42
+ });
43
+
44
+ it("should render a span", async () => {
45
+ const { container } = render(
46
+ <WalletProvider id="io.metamask">
47
+ <WalletName />
48
+ </WalletProvider>,
49
+ );
50
+
51
+ await waitFor(() => {
52
+ expect(container.querySelector("span")).not.toBe(null);
53
+ });
54
+ });
55
+ });
@@ -0,0 +1,164 @@
1
+ "use client";
2
+
3
+ import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
4
+ import type { JSX } from "react";
5
+ import { getFunctionId } from "../../../../../utils/function-id.js";
6
+ import { getWalletInfo } from "../../../../../wallets/__generated__/getWalletInfo.js";
7
+ import type { WalletId } from "../../../../../wallets/wallet-types.js";
8
+ import { useWalletContext } from "./provider.js";
9
+
10
+ /**
11
+ * Props for the WalletName component
12
+ * @component
13
+ * @wallet
14
+ */
15
+ export interface WalletNameProps
16
+ extends Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> {
17
+ /**
18
+ * This component will be shown while the name of the wallet name is being fetched
19
+ * If not passed, the component will return `null`.
20
+ *
21
+ * You can/should pass a loading sign or spinner to this prop.
22
+ * @example
23
+ * ```tsx
24
+ * <WalletName loadingComponent={<Spinner />} />
25
+ * ```
26
+ */
27
+ loadingComponent?: JSX.Element;
28
+ /**
29
+ * This component will be shown if the name fails to be retreived
30
+ * If not passed, the component will return `null`.
31
+ *
32
+ * You can/should pass a descriptive text/component to this prop, indicating that the
33
+ * name was not fetched succesfully
34
+ * @example
35
+ * ```tsx
36
+ * <WalletName fallbackComponent={<span>Failed to load</span>}
37
+ * />
38
+ * ```
39
+ */
40
+ fallbackComponent?: JSX.Element;
41
+ /**
42
+ * Optional `useQuery` params
43
+ */
44
+ queryOptions?: Omit<UseQueryOptions<string>, "queryFn" | "queryKey">;
45
+ /**
46
+ * A function to format the name's display value
47
+ * ```tsx
48
+ * <WalletName formatFn={(str: string) => doSomething()} />
49
+ * ```
50
+ */
51
+ formatFn?: (str: string) => string;
52
+ }
53
+
54
+ /**
55
+ * This component fetches then shows the name of a wallet.
56
+ * It inherits all the attributes of a HTML <span> component, hence you can style it just like how you would style a normal <span>
57
+ *
58
+ * @example
59
+ * ### Basic usage
60
+ * ```tsx
61
+ * import { WalletProvider, WalletName } from "thirdweb/react";
62
+ *
63
+ * <WalletProvider id="io.metamask">
64
+ * <WalletName />
65
+ * </WalletProvider>
66
+ * ```
67
+ * Result:
68
+ * ```html
69
+ * <span>MetaMask</span>
70
+ * ```
71
+ *
72
+ * ### Show a loading sign when the name is being fetched
73
+ * ```tsx
74
+ * import { WalletProvider, WalletName } from "thirdweb/react";
75
+ *
76
+ * <WalletProvider {...props}>
77
+ * <WalletName loadingComponent={<Spinner />} />
78
+ * </WalletProvider>
79
+ * ```
80
+ *
81
+ * ### Fallback to something when the name fails to resolve
82
+ * ```tsx
83
+ * <WalletProvider {...props}>
84
+ * <WalletName fallbackComponent={<span>Failed to load</span>} />
85
+ * </WalletProvider>
86
+ * ```
87
+ *
88
+ * ### Custom query options for useQuery
89
+ * This component uses `@tanstack-query`'s useQuery internally.
90
+ * You can use the `queryOptions` prop for more fine-grained control
91
+ * ```tsx
92
+ * <WalletName
93
+ * queryOptions={{
94
+ * enabled: isEnabled,
95
+ * retry: 4,
96
+ * }}
97
+ * />
98
+ * @component
99
+ * @beta
100
+ * @wallet
101
+ */
102
+ export function WalletName({
103
+ loadingComponent,
104
+ fallbackComponent,
105
+ queryOptions,
106
+ formatFn,
107
+ ...restProps
108
+ }: WalletNameProps) {
109
+ const nameQuery = useWalletName({ queryOptions, formatFn });
110
+ if (nameQuery.isLoading) {
111
+ return loadingComponent || null;
112
+ }
113
+ if (!nameQuery.data) {
114
+ return fallbackComponent || null;
115
+ }
116
+ return <span {...restProps}>{nameQuery.data}</span>;
117
+ }
118
+
119
+ /**
120
+ * @internal
121
+ */
122
+ function useWalletName(props: {
123
+ formatFn?: (str: string) => string;
124
+ queryOptions?: Omit<UseQueryOptions<string>, "queryFn" | "queryKey">;
125
+ }) {
126
+ const { id } = useWalletContext();
127
+ const nameQuery = useQuery({
128
+ queryKey: getQueryKeys({ id, formatFn: props.formatFn }),
129
+ queryFn: async () => fetchWalletName({ id, formatFn: props.formatFn }),
130
+ ...props.queryOptions,
131
+ });
132
+ return nameQuery;
133
+ }
134
+
135
+ /**
136
+ * @internal Exported for tests only
137
+ */
138
+ export function getQueryKeys(props: {
139
+ id: WalletId;
140
+ formatFn?: (str: string) => string;
141
+ }) {
142
+ if (typeof props.formatFn === "function") {
143
+ return [
144
+ "walletName",
145
+ props.id,
146
+ { resolver: getFunctionId(props.formatFn) },
147
+ ] as const;
148
+ }
149
+ return ["walletName", props.id] as const;
150
+ }
151
+
152
+ /**
153
+ * @internal Exported for tests only
154
+ */
155
+ export async function fetchWalletName(props: {
156
+ id: WalletId;
157
+ formatFn?: (str: string) => string;
158
+ }) {
159
+ const info = await getWalletInfo(props.id);
160
+ if (typeof props.formatFn === "function") {
161
+ return props.formatFn(info.name);
162
+ }
163
+ return info.name;
164
+ }
@@ -0,0 +1,61 @@
1
+ import { type FC, useContext } from "react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { render, renderHook, screen } from "~test/react-render.js";
4
+ import {
5
+ WalletProvider,
6
+ WalletProviderContext,
7
+ useWalletContext,
8
+ } from "./provider.js";
9
+
10
+ describe.runIf(process.env.TW_SECRET_KEY)("WalletProvider", () => {
11
+ it("useWalletContext should throw an error when used outside of WalletProvider", () => {
12
+ const consoleErrorSpy = vi
13
+ .spyOn(console, "error")
14
+ .mockImplementation(() => {});
15
+
16
+ expect(() => {
17
+ renderHook(() => useWalletContext());
18
+ }).toThrow(
19
+ "WalletProviderContext not found. Make sure you are using WalletIcon, WalletName, etc. inside a <WalletProvider /> component",
20
+ );
21
+
22
+ consoleErrorSpy.mockRestore();
23
+ });
24
+
25
+ it("useWalletContext should return the context value when used within WalletProvider", () => {
26
+ const wrapper: FC = ({ children }: React.PropsWithChildren) => (
27
+ <WalletProvider id="io.metamask">{children}</WalletProvider>
28
+ );
29
+
30
+ const { result } = renderHook(() => useWalletContext(), { wrapper });
31
+
32
+ expect(result.current.id).toStrictEqual("io.metamask");
33
+ });
34
+
35
+ it("should render children correctly", () => {
36
+ render(
37
+ <WalletProvider id="io.metamask">
38
+ <div>Child Component</div>
39
+ </WalletProvider>,
40
+ );
41
+
42
+ expect(screen.getByText("Child Component")).toBeInTheDocument();
43
+ });
44
+
45
+ it("should provide context values to children", () => {
46
+ function WalletConsumer() {
47
+ const context = useContext(WalletProviderContext);
48
+ if (!context) {
49
+ return <div>No context</div>;
50
+ }
51
+ return <div>{String(context.id)}</div>;
52
+ }
53
+ render(
54
+ <WalletProvider id="io.metamask">
55
+ <WalletConsumer />
56
+ </WalletProvider>,
57
+ );
58
+
59
+ expect(screen.getByText("io.metamask")).toBeInTheDocument();
60
+ });
61
+ });
@@ -0,0 +1,65 @@
1
+ "use client";
2
+
3
+ import type React from "react";
4
+ import { createContext, useContext } from "react";
5
+ import type { WalletId } from "../../../../../wallets/wallet-types.js";
6
+
7
+ /**
8
+ * Props for the WalletProvider component
9
+ * @component
10
+ * @wallet
11
+ */
12
+ export type WalletProviderProps = {
13
+ id: WalletId;
14
+ };
15
+
16
+ /**
17
+ * @internal Exported for tests only
18
+ */
19
+ export const WalletProviderContext = /* @__PURE__ */ createContext<
20
+ WalletProviderProps | undefined
21
+ >(undefined);
22
+
23
+ /**
24
+ /**
25
+ * A React context provider component that supplies Wallet-related data to its child components.
26
+ *
27
+ * This component serves as a wrapper around the `WalletProviderContext.Provider` and passes
28
+ * the provided wallet data down to all of its child components through the context API.
29
+ *
30
+ * @example
31
+ * ### Basic usage
32
+ * ```tsx
33
+ * import { WalletProvider, WalletIcon, WalletName } from "thirdweb/react";
34
+ *
35
+ * <WalletProvider id="io.metamask">
36
+ * <WalletIcon />
37
+ * <WalletName />
38
+ * </WalletProvider>
39
+ * ```
40
+ * @beta
41
+ * @component
42
+ * @wallet
43
+ */
44
+ export function WalletProvider(
45
+ props: React.PropsWithChildren<WalletProviderProps>,
46
+ ) {
47
+ return (
48
+ <WalletProviderContext.Provider value={props}>
49
+ {props.children}
50
+ </WalletProviderContext.Provider>
51
+ );
52
+ }
53
+
54
+ /**
55
+ * @internal
56
+ */
57
+ export function useWalletContext() {
58
+ const ctx = useContext(WalletProviderContext);
59
+ if (!ctx) {
60
+ throw new Error(
61
+ "WalletProviderContext not found. Make sure you are using WalletIcon, WalletName, etc. inside a <WalletProvider /> component",
62
+ );
63
+ }
64
+ return ctx;
65
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = "5.78.0";
1
+ export const version = "5.79.0-nightly-f91f6310e9396918d0ffc5217eeb4a44cef0b8c8-20241215000412";
@@ -1,6 +1,7 @@
1
1
  import type { Chain } from "../../../../chains/types.js";
2
2
  import type { ThirdwebClient } from "../../../../client/client.js";
3
3
  import type { SupportedSmsCountry } from "../../../../react/web/wallets/in-app/supported-sms-countries.js";
4
+ import type { Prettify } from "../../../../utils/type-utils.js";
4
5
  import type { SmartWalletOptions } from "../../../smart/types.js";
5
6
  import type {
6
7
  AuthOption,
@@ -20,14 +21,12 @@ export type Ecosystem = {
20
21
  partnerId?: string;
21
22
  };
22
23
 
23
- export type InAppWalletConnectionOptions = (
24
- | MultiStepAuthArgsType
25
- | SingleStepAuthArgsType
26
- ) & {
27
- client: ThirdwebClient;
28
- chain?: Chain;
29
- redirect?: boolean;
30
- };
24
+ export type InAppWalletConnectionOptions = Prettify<
25
+ (MultiStepAuthArgsType | SingleStepAuthArgsType) & {
26
+ client: ThirdwebClient;
27
+ chain?: Chain;
28
+ }
29
+ >;
31
30
 
32
31
  export type InAppWalletAutoConnectOptions = {
33
32
  client: ThirdwebClient;