thirdweb 5.77.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 (207) hide show
  1. package/dist/cjs/exports/react.js +11 -2
  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/exports/wallets/in-app.js +2 -1
  6. package/dist/cjs/exports/wallets/in-app.js.map +1 -1
  7. package/dist/cjs/exports/wallets/in-app.native.js +2 -1
  8. package/dist/cjs/exports/wallets/in-app.native.js.map +1 -1
  9. package/dist/cjs/exports/wallets.js +2 -1
  10. package/dist/cjs/exports/wallets.js.map +1 -1
  11. package/dist/cjs/exports/wallets.native.js +2 -1
  12. package/dist/cjs/exports/wallets.native.js.map +1 -1
  13. package/dist/cjs/extensions/prebuilts/deploy-published.js +22 -16
  14. package/dist/cjs/extensions/prebuilts/deploy-published.js.map +1 -1
  15. package/dist/cjs/react/native/hooks/wallets/useUnlinkProfile.js +58 -0
  16. package/dist/cjs/react/native/hooks/wallets/useUnlinkProfile.js.map +1 -0
  17. package/dist/cjs/react/web/hooks/wallets/useUnlinkProfile.js +58 -0
  18. package/dist/cjs/react/web/hooks/wallets/useUnlinkProfile.js.map +1 -0
  19. package/dist/cjs/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.js +22 -6
  20. package/dist/cjs/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.js.map +1 -1
  21. package/dist/cjs/react/web/ui/MediaRenderer/useResolvedMediaType.js +1 -1
  22. package/dist/cjs/react/web/ui/MediaRenderer/useResolvedMediaType.js.map +1 -1
  23. package/dist/cjs/react/web/ui/prebuilt/Chain/icon.js +1 -1
  24. package/dist/cjs/react/web/ui/prebuilt/Chain/icon.js.map +1 -1
  25. package/dist/cjs/react/web/ui/prebuilt/Chain/name.js +15 -11
  26. package/dist/cjs/react/web/ui/prebuilt/Chain/name.js.map +1 -1
  27. package/dist/cjs/react/web/ui/prebuilt/Chain/provider.js +1 -0
  28. package/dist/cjs/react/web/ui/prebuilt/Chain/provider.js.map +1 -1
  29. package/dist/cjs/react/web/ui/prebuilt/Wallet/icon.js +79 -0
  30. package/dist/cjs/react/web/ui/prebuilt/Wallet/icon.js.map +1 -0
  31. package/dist/cjs/react/web/ui/prebuilt/Wallet/name.js +105 -0
  32. package/dist/cjs/react/web/ui/prebuilt/Wallet/name.js.map +1 -0
  33. package/dist/cjs/react/web/ui/prebuilt/Wallet/provider.js +47 -0
  34. package/dist/cjs/react/web/ui/prebuilt/Wallet/provider.js.map +1 -0
  35. package/dist/cjs/react/web/wallets/in-app/CountrySelector.js +11 -10
  36. package/dist/cjs/react/web/wallets/in-app/CountrySelector.js.map +1 -1
  37. package/dist/cjs/react/web/wallets/in-app/InputSelectionUI.js +3 -1
  38. package/dist/cjs/react/web/wallets/in-app/InputSelectionUI.js.map +1 -1
  39. package/dist/cjs/react/web/wallets/in-app/supported-sms-countries.js.map +1 -1
  40. package/dist/cjs/react/web/wallets/shared/ConnectWalletSocialOptions.js +1 -1
  41. package/dist/cjs/react/web/wallets/shared/ConnectWalletSocialOptions.js.map +1 -1
  42. package/dist/cjs/version.js +1 -1
  43. package/dist/cjs/version.js.map +1 -1
  44. package/dist/cjs/wallets/in-app/core/authentication/linkAccount.js +31 -0
  45. package/dist/cjs/wallets/in-app/core/authentication/linkAccount.js.map +1 -1
  46. package/dist/cjs/wallets/in-app/native/auth/index.js +30 -3
  47. package/dist/cjs/wallets/in-app/native/auth/index.js.map +1 -1
  48. package/dist/cjs/wallets/in-app/native/native-connector.js +8 -0
  49. package/dist/cjs/wallets/in-app/native/native-connector.js.map +1 -1
  50. package/dist/cjs/wallets/in-app/web/lib/auth/index.js +30 -0
  51. package/dist/cjs/wallets/in-app/web/lib/auth/index.js.map +1 -1
  52. package/dist/cjs/wallets/in-app/web/lib/web-connector.js +8 -0
  53. package/dist/cjs/wallets/in-app/web/lib/web-connector.js.map +1 -1
  54. package/dist/esm/exports/react.js +5 -0
  55. package/dist/esm/exports/react.js.map +1 -1
  56. package/dist/esm/exports/react.native.js +1 -0
  57. package/dist/esm/exports/react.native.js.map +1 -1
  58. package/dist/esm/exports/wallets/in-app.js +1 -1
  59. package/dist/esm/exports/wallets/in-app.js.map +1 -1
  60. package/dist/esm/exports/wallets/in-app.native.js +1 -1
  61. package/dist/esm/exports/wallets/in-app.native.js.map +1 -1
  62. package/dist/esm/exports/wallets.js +1 -1
  63. package/dist/esm/exports/wallets.js.map +1 -1
  64. package/dist/esm/exports/wallets.native.js +1 -1
  65. package/dist/esm/exports/wallets.native.js.map +1 -1
  66. package/dist/esm/extensions/prebuilts/deploy-published.js +22 -16
  67. package/dist/esm/extensions/prebuilts/deploy-published.js.map +1 -1
  68. package/dist/esm/react/native/hooks/wallets/useUnlinkProfile.js +55 -0
  69. package/dist/esm/react/native/hooks/wallets/useUnlinkProfile.js.map +1 -0
  70. package/dist/esm/react/web/hooks/wallets/useUnlinkProfile.js +55 -0
  71. package/dist/esm/react/web/hooks/wallets/useUnlinkProfile.js.map +1 -0
  72. package/dist/esm/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.js +22 -6
  73. package/dist/esm/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.js.map +1 -1
  74. package/dist/esm/react/web/ui/MediaRenderer/useResolvedMediaType.js +1 -1
  75. package/dist/esm/react/web/ui/MediaRenderer/useResolvedMediaType.js.map +1 -1
  76. package/dist/esm/react/web/ui/prebuilt/Chain/icon.js +1 -1
  77. package/dist/esm/react/web/ui/prebuilt/Chain/icon.js.map +1 -1
  78. package/dist/esm/react/web/ui/prebuilt/Chain/name.js +14 -11
  79. package/dist/esm/react/web/ui/prebuilt/Chain/name.js.map +1 -1
  80. package/dist/esm/react/web/ui/prebuilt/Chain/provider.js +1 -0
  81. package/dist/esm/react/web/ui/prebuilt/Chain/provider.js.map +1 -1
  82. package/dist/esm/react/web/ui/prebuilt/Wallet/icon.js +75 -0
  83. package/dist/esm/react/web/ui/prebuilt/Wallet/icon.js.map +1 -0
  84. package/dist/esm/react/web/ui/prebuilt/Wallet/name.js +100 -0
  85. package/dist/esm/react/web/ui/prebuilt/Wallet/name.js.map +1 -0
  86. package/dist/esm/react/web/ui/prebuilt/Wallet/provider.js +42 -0
  87. package/dist/esm/react/web/ui/prebuilt/Wallet/provider.js.map +1 -0
  88. package/dist/esm/react/web/wallets/in-app/CountrySelector.js +10 -10
  89. package/dist/esm/react/web/wallets/in-app/CountrySelector.js.map +1 -1
  90. package/dist/esm/react/web/wallets/in-app/InputSelectionUI.js +4 -2
  91. package/dist/esm/react/web/wallets/in-app/InputSelectionUI.js.map +1 -1
  92. package/dist/esm/react/web/wallets/in-app/supported-sms-countries.js.map +1 -1
  93. package/dist/esm/react/web/wallets/shared/ConnectWalletSocialOptions.js +1 -1
  94. package/dist/esm/react/web/wallets/shared/ConnectWalletSocialOptions.js.map +1 -1
  95. package/dist/esm/version.js +1 -1
  96. package/dist/esm/version.js.map +1 -1
  97. package/dist/esm/wallets/in-app/core/authentication/linkAccount.js +30 -0
  98. package/dist/esm/wallets/in-app/core/authentication/linkAccount.js.map +1 -1
  99. package/dist/esm/wallets/in-app/native/auth/index.js +29 -3
  100. package/dist/esm/wallets/in-app/native/auth/index.js.map +1 -1
  101. package/dist/esm/wallets/in-app/native/native-connector.js +9 -1
  102. package/dist/esm/wallets/in-app/native/native-connector.js.map +1 -1
  103. package/dist/esm/wallets/in-app/web/lib/auth/index.js +29 -0
  104. package/dist/esm/wallets/in-app/web/lib/auth/index.js.map +1 -1
  105. package/dist/esm/wallets/in-app/web/lib/web-connector.js +9 -1
  106. package/dist/esm/wallets/in-app/web/lib/web-connector.js.map +1 -1
  107. package/dist/types/exports/react.d.ts +4 -0
  108. package/dist/types/exports/react.d.ts.map +1 -1
  109. package/dist/types/exports/react.native.d.ts +1 -0
  110. package/dist/types/exports/react.native.d.ts.map +1 -1
  111. package/dist/types/exports/wallets/in-app.d.ts +3 -2
  112. package/dist/types/exports/wallets/in-app.d.ts.map +1 -1
  113. package/dist/types/exports/wallets/in-app.native.d.ts +3 -2
  114. package/dist/types/exports/wallets/in-app.native.d.ts.map +1 -1
  115. package/dist/types/exports/wallets.d.ts +3 -2
  116. package/dist/types/exports/wallets.d.ts.map +1 -1
  117. package/dist/types/exports/wallets.native.d.ts +3 -2
  118. package/dist/types/exports/wallets.native.d.ts.map +1 -1
  119. package/dist/types/extensions/prebuilts/deploy-published.d.ts.map +1 -1
  120. package/dist/types/react/native/hooks/wallets/useUnlinkProfile.d.ts +34 -0
  121. package/dist/types/react/native/hooks/wallets/useUnlinkProfile.d.ts.map +1 -0
  122. package/dist/types/react/web/hooks/wallets/useUnlinkProfile.d.ts +34 -0
  123. package/dist/types/react/web/hooks/wallets/useUnlinkProfile.d.ts.map +1 -0
  124. package/dist/types/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.d.ts.map +1 -1
  125. package/dist/types/react/web/ui/prebuilt/Chain/name.d.ts +10 -1
  126. package/dist/types/react/web/ui/prebuilt/Chain/name.d.ts.map +1 -1
  127. package/dist/types/react/web/ui/prebuilt/Chain/provider.d.ts +1 -0
  128. package/dist/types/react/web/ui/prebuilt/Chain/provider.d.ts.map +1 -1
  129. package/dist/types/react/web/ui/prebuilt/Wallet/icon.d.ts +81 -0
  130. package/dist/types/react/web/ui/prebuilt/Wallet/icon.d.ts.map +1 -0
  131. package/dist/types/react/web/ui/prebuilt/Wallet/name.d.ts +111 -0
  132. package/dist/types/react/web/ui/prebuilt/Wallet/name.d.ts.map +1 -0
  133. package/dist/types/react/web/ui/prebuilt/Wallet/provider.d.ts +41 -0
  134. package/dist/types/react/web/ui/prebuilt/Wallet/provider.d.ts.map +1 -0
  135. package/dist/types/react/web/wallets/in-app/CountrySelector.d.ts +2 -0
  136. package/dist/types/react/web/wallets/in-app/CountrySelector.d.ts.map +1 -1
  137. package/dist/types/react/web/wallets/in-app/InputSelectionUI.d.ts +2 -0
  138. package/dist/types/react/web/wallets/in-app/InputSelectionUI.d.ts.map +1 -1
  139. package/dist/types/react/web/wallets/in-app/supported-sms-countries.d.ts +946 -5
  140. package/dist/types/react/web/wallets/in-app/supported-sms-countries.d.ts.map +1 -1
  141. package/dist/types/react/web/wallets/shared/ConnectWalletSocialOptions.d.ts.map +1 -1
  142. package/dist/types/version.d.ts +1 -1
  143. package/dist/types/version.d.ts.map +1 -1
  144. package/dist/types/wallets/ecosystem/types.d.ts +5 -0
  145. package/dist/types/wallets/ecosystem/types.d.ts.map +1 -1
  146. package/dist/types/wallets/in-app/core/authentication/linkAccount.d.ts +13 -0
  147. package/dist/types/wallets/in-app/core/authentication/linkAccount.d.ts.map +1 -1
  148. package/dist/types/wallets/in-app/core/authentication/types.d.ts +5 -0
  149. package/dist/types/wallets/in-app/core/authentication/types.d.ts.map +1 -1
  150. package/dist/types/wallets/in-app/core/interfaces/connector.d.ts +1 -0
  151. package/dist/types/wallets/in-app/core/interfaces/connector.d.ts.map +1 -1
  152. package/dist/types/wallets/in-app/core/wallet/types.d.ts +8 -3
  153. package/dist/types/wallets/in-app/core/wallet/types.d.ts.map +1 -1
  154. package/dist/types/wallets/in-app/native/auth/index.d.ts +27 -4
  155. package/dist/types/wallets/in-app/native/auth/index.d.ts.map +1 -1
  156. package/dist/types/wallets/in-app/native/native-connector.d.ts +4 -3
  157. package/dist/types/wallets/in-app/native/native-connector.d.ts.map +1 -1
  158. package/dist/types/wallets/in-app/web/lib/auth/index.d.ts +27 -1
  159. package/dist/types/wallets/in-app/web/lib/auth/index.d.ts.map +1 -1
  160. package/dist/types/wallets/in-app/web/lib/web-connector.d.ts +4 -3
  161. package/dist/types/wallets/in-app/web/lib/web-connector.d.ts.map +1 -1
  162. package/package.json +3 -2
  163. package/src/exports/react.native.ts +1 -0
  164. package/src/exports/react.ts +15 -0
  165. package/src/exports/wallets/in-app.native.ts +7 -0
  166. package/src/exports/wallets/in-app.ts +7 -0
  167. package/src/exports/wallets.native.ts +7 -0
  168. package/src/exports/wallets.ts +7 -0
  169. package/src/extensions/prebuilts/deploy-published.ts +27 -20
  170. package/src/react/core/hooks/wallets/useAddConnectedWallet.test.tsx +52 -0
  171. package/src/react/core/hooks/wallets/useConnect.test.tsx +105 -0
  172. package/src/react/core/hooks/wallets/useSetActiveWallet.test.tsx +53 -0
  173. package/src/react/native/hooks/wallets/useUnlinkProfile.test.tsx +75 -0
  174. package/src/react/native/hooks/wallets/useUnlinkProfile.ts +62 -0
  175. package/src/react/web/hooks/wallets/useUnlinkProfile.test.tsx +75 -0
  176. package/src/react/web/hooks/wallets/useUnlinkProfile.ts +62 -0
  177. package/src/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.test.tsx +25 -0
  178. package/src/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.tsx +86 -44
  179. package/src/react/web/ui/MediaRenderer/useResolvedMediaType.ts +1 -1
  180. package/src/react/web/ui/prebuilt/Chain/icon.tsx +1 -1
  181. package/src/react/web/ui/prebuilt/Chain/name.test.tsx +19 -1
  182. package/src/react/web/ui/prebuilt/Chain/name.tsx +19 -13
  183. package/src/react/web/ui/prebuilt/Chain/provider.tsx +1 -0
  184. package/src/react/web/ui/prebuilt/Wallet/icon.test.tsx +30 -0
  185. package/src/react/web/ui/prebuilt/Wallet/icon.tsx +120 -0
  186. package/src/react/web/ui/prebuilt/Wallet/name.test.tsx +55 -0
  187. package/src/react/web/ui/prebuilt/Wallet/name.tsx +164 -0
  188. package/src/react/web/ui/prebuilt/Wallet/provider.test.tsx +61 -0
  189. package/src/react/web/ui/prebuilt/Wallet/provider.tsx +65 -0
  190. package/src/react/web/wallets/in-app/CountrySelector.test.tsx +45 -0
  191. package/src/react/web/wallets/in-app/CountrySelector.tsx +16 -13
  192. package/src/react/web/wallets/in-app/InputSelectionUI.test.tsx +45 -0
  193. package/src/react/web/wallets/in-app/InputSelectionUI.tsx +8 -2
  194. package/src/react/web/wallets/in-app/supported-sms-countries.ts +3 -1
  195. package/src/react/web/wallets/shared/ConnectWalletSocialOptions.tsx +3 -0
  196. package/src/version.ts +1 -1
  197. package/src/wallets/ecosystem/types.ts +5 -0
  198. package/src/wallets/in-app/core/authentication/linkAccount.test.ts +160 -0
  199. package/src/wallets/in-app/core/authentication/linkAccount.ts +49 -0
  200. package/src/wallets/in-app/core/authentication/types.ts +6 -0
  201. package/src/wallets/in-app/core/interfaces/connector.ts +1 -0
  202. package/src/wallets/in-app/core/wallet/types.ts +12 -8
  203. package/src/wallets/in-app/native/auth/index.ts +31 -3
  204. package/src/wallets/in-app/native/native-connector.ts +11 -0
  205. package/src/wallets/in-app/web/lib/auth/index.ts +31 -0
  206. package/src/wallets/in-app/web/lib/web-connector.ts +11 -0
  207. package/src/wallets/manager/connection-manager.test.ts +133 -133
@@ -0,0 +1,105 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import type { ReactNode } from "react";
3
+ import { beforeEach, describe, expect, it } from "vitest";
4
+ import { MockStorage } from "../../../../../test/src/mocks/storage.js";
5
+ import { TEST_CLIENT } from "../../../../../test/src/test-clients.js";
6
+ import { TEST_ACCOUNT_A } from "../../../../../test/src/test-wallets.js";
7
+ import { createWalletAdapter } from "../../../../adapters/wallet-adapter.js";
8
+ import { ethereum } from "../../../../chains/chain-definitions/ethereum.js";
9
+ import {
10
+ type ConnectionManager,
11
+ createConnectionManager,
12
+ } from "../../../../wallets/manager/index.js";
13
+ import { ConnectionManagerCtx } from "../../providers/connection-manager.js";
14
+ import { useActiveWalletConnectionStatus } from "./useActiveWalletConnectionStatus.js";
15
+ import { useConnect } from "./useConnect.js";
16
+
17
+ describe("useAddConnectedWallet", () => {
18
+ // Mock the connection manager
19
+ const mockStorage = new MockStorage();
20
+ let manager: ConnectionManager;
21
+
22
+ // Create a wrapper component with the mocked context
23
+ const wrapper = ({ children }: { children: ReactNode }) => {
24
+ return (
25
+ <ConnectionManagerCtx.Provider value={manager}>
26
+ {children}
27
+ </ConnectionManagerCtx.Provider>
28
+ );
29
+ };
30
+
31
+ const wallet = createWalletAdapter({
32
+ adaptedAccount: TEST_ACCOUNT_A,
33
+ client: TEST_CLIENT,
34
+ chain: ethereum,
35
+ onDisconnect: () => {},
36
+ switchChain: () => {},
37
+ });
38
+
39
+ beforeEach(() => {
40
+ manager = createConnectionManager(mockStorage);
41
+ });
42
+
43
+ it("should connect a wallet to the connection manager", async () => {
44
+ const { result: statusResult } = renderHook(
45
+ () => useActiveWalletConnectionStatus(),
46
+ {
47
+ wrapper,
48
+ },
49
+ );
50
+ const { result } = renderHook(() => useConnect(), { wrapper });
51
+ expect(statusResult.current).toEqual("disconnected");
52
+ await result.current.connect(async () => wallet);
53
+ expect(statusResult.current).toEqual("connected");
54
+
55
+ // should add to connected wallets
56
+ expect(manager.connectedWallets.getValue()).toHaveLength(1);
57
+ expect(manager.connectedWallets.getValue()[0]).toEqual(wallet);
58
+ // should set the active wallet
59
+ expect(manager.activeWalletStore.getValue()).toEqual(wallet);
60
+ });
61
+
62
+ it("should handle a function that returns a wallet", async () => {
63
+ const { result: statusResult } = renderHook(
64
+ () => useActiveWalletConnectionStatus(),
65
+ {
66
+ wrapper,
67
+ },
68
+ );
69
+ const { result } = renderHook(() => useConnect(), { wrapper });
70
+ expect(statusResult.current).toEqual("disconnected");
71
+ await result.current.connect(async () => wallet);
72
+ expect(statusResult.current).toEqual("connected");
73
+
74
+ // should add to connected wallets
75
+ expect(manager.connectedWallets.getValue()).toHaveLength(1);
76
+ expect(manager.connectedWallets.getValue()[0]).toEqual(wallet);
77
+ // should set the active wallet
78
+ expect(manager.activeWalletStore.getValue()).toEqual(wallet);
79
+ });
80
+
81
+ it("should handle an error when connecting a wallet", async () => {
82
+ const { result: statusResult } = renderHook(
83
+ () => useActiveWalletConnectionStatus(),
84
+ {
85
+ wrapper,
86
+ },
87
+ );
88
+ expect(statusResult.current).toEqual("disconnected");
89
+ const { result } = renderHook(() => useConnect(), { wrapper });
90
+ await result.current.connect(async () => {
91
+ throw new Error("test");
92
+ });
93
+
94
+ expect(statusResult.current).toEqual("disconnected");
95
+ // should set the active wallet
96
+ expect(manager.activeWalletStore.getValue()).toEqual(undefined);
97
+ });
98
+
99
+ it("should throw an error when used outside of ThirdwebProvider", () => {
100
+ // Render the hook without a provider
101
+ expect(() => {
102
+ renderHook(() => useConnect());
103
+ }).toThrow("useConnect must be used within <ThirdwebProvider>");
104
+ });
105
+ });
@@ -0,0 +1,53 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import type { ReactNode } from "react";
3
+ import { describe, expect, it } from "vitest";
4
+ import { MockStorage } from "../../../../../test/src/mocks/storage.js";
5
+ import { TEST_CLIENT } from "../../../../../test/src/test-clients.js";
6
+ import { TEST_ACCOUNT_A } from "../../../../../test/src/test-wallets.js";
7
+ import { createWalletAdapter } from "../../../../adapters/wallet-adapter.js";
8
+ import { ethereum } from "../../../../chains/chain-definitions/ethereum.js";
9
+ import { createConnectionManager } from "../../../../wallets/manager/index.js";
10
+ import { ConnectionManagerCtx } from "../../providers/connection-manager.js";
11
+ import { useSetActiveWallet } from "./useSetActiveWallet.js";
12
+
13
+ describe("useAddConnectedWallet", () => {
14
+ // Mock the connection manager
15
+ const mockStorage = new MockStorage();
16
+ const manager = createConnectionManager(mockStorage);
17
+
18
+ // Create a wrapper component with the mocked context
19
+ const wrapper = ({ children }: { children: ReactNode }) => {
20
+ return (
21
+ <ConnectionManagerCtx.Provider value={manager}>
22
+ {children}
23
+ </ConnectionManagerCtx.Provider>
24
+ );
25
+ };
26
+
27
+ const wallet = createWalletAdapter({
28
+ adaptedAccount: TEST_ACCOUNT_A,
29
+ client: TEST_CLIENT,
30
+ chain: ethereum,
31
+ onDisconnect: () => {},
32
+ switchChain: () => {},
33
+ });
34
+
35
+ it("should add a wallet to the connection manager", async () => {
36
+ // Render the hook
37
+ const { result } = renderHook(() => useSetActiveWallet(), { wrapper });
38
+ result.current(wallet);
39
+
40
+ // should add to connected wallets
41
+ expect(manager.connectedWallets.getValue()).toHaveLength(1);
42
+ expect(manager.connectedWallets.getValue()[0]).toEqual(wallet);
43
+ // should set the active wallet
44
+ expect(manager.activeWalletStore.getValue()).toEqual(wallet);
45
+ });
46
+
47
+ it("should throw an error when used outside of ThirdwebProvider", () => {
48
+ // Render the hook without a provider
49
+ expect(() => {
50
+ renderHook(() => useSetActiveWallet());
51
+ }).toThrow("useSetActiveWallet must be used within <ThirdwebProvider>");
52
+ });
53
+ });
@@ -0,0 +1,75 @@
1
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2
+ import { act, renderHook } from "@testing-library/react";
3
+ import type React from "react";
4
+ import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { TEST_CLIENT } from "~test/test-clients.js";
6
+ import { useConnectedWallets } from "../../../../react/core/hooks/wallets/useConnectedWallets.js";
7
+ import type { Profile } from "../../../../wallets/in-app/core/authentication/types.js";
8
+ import { unlinkProfile } from "../../../../wallets/in-app/web/lib/auth/index.js";
9
+ import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
10
+ import { useUnlinkProfile } from "./useUnlinkProfile.js";
11
+
12
+ vi.mock("../../../../wallets/in-app/web/lib/auth/index.js");
13
+ vi.mock("../../../core/hooks/wallets/useConnectedWallets.js");
14
+
15
+ describe("useUnlinkProfile", () => {
16
+ const queryClient = new QueryClient();
17
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
18
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
19
+ );
20
+
21
+ beforeEach(() => {
22
+ vi.clearAllMocks();
23
+ vi.spyOn(queryClient, "invalidateQueries");
24
+ });
25
+
26
+ const mockProfile = {} as unknown as Profile;
27
+ it("should call unlinkProfile with correct parameters", async () => {
28
+ vi.mocked(useConnectedWallets).mockReturnValue([]);
29
+
30
+ const { result } = renderHook(() => useUnlinkProfile(), {
31
+ wrapper,
32
+ });
33
+ const mutationFn = result.current.mutateAsync;
34
+
35
+ await act(async () => {
36
+ await mutationFn({ client: TEST_CLIENT, profileToUnlink: mockProfile });
37
+ });
38
+
39
+ expect(unlinkProfile).toHaveBeenCalledWith({
40
+ client: TEST_CLIENT,
41
+ ecosystem: undefined,
42
+ profileToUnlink: mockProfile,
43
+ });
44
+ expect(queryClient.invalidateQueries).toHaveBeenCalledWith({
45
+ queryKey: ["profiles"],
46
+ });
47
+ });
48
+
49
+ it("should include ecosystem if ecosystem wallet is found", async () => {
50
+ const mockWallet = {
51
+ id: "ecosystem.wallet-id",
52
+ getConfig: () => ({ partnerId: "partner-id" }),
53
+ } as unknown as Wallet;
54
+ vi.mocked(useConnectedWallets).mockReturnValue([mockWallet]);
55
+
56
+ const { result } = renderHook(() => useUnlinkProfile(), {
57
+ wrapper,
58
+ });
59
+ const mutationFn = result.current.mutateAsync;
60
+
61
+ await act(async () => {
62
+ await mutationFn({ client: TEST_CLIENT, profileToUnlink: mockProfile });
63
+ });
64
+
65
+ expect(unlinkProfile).toHaveBeenCalledWith({
66
+ client: TEST_CLIENT,
67
+ ecosystem: {
68
+ id: mockWallet.id,
69
+ partnerId: (mockWallet as Wallet<`ecosystem.${string}`>).getConfig()
70
+ ?.partnerId,
71
+ },
72
+ profileToUnlink: mockProfile,
73
+ });
74
+ });
75
+ });
@@ -0,0 +1,62 @@
1
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
2
+ import type { ThirdwebClient } from "../../../../client/client.js";
3
+ import { isEcosystemWallet } from "../../../../wallets/ecosystem/is-ecosystem-wallet.js";
4
+ import type { Profile } from "../../../../wallets/in-app/core/authentication/types.js";
5
+ import type { Ecosystem } from "../../../../wallets/in-app/core/wallet/types.js";
6
+ import { unlinkProfile } from "../../../../wallets/in-app/web/lib/auth/index.js";
7
+ import { useConnectedWallets } from "../../../core/hooks/wallets/useConnectedWallets.js";
8
+
9
+ /**
10
+ * Unlinks a web2 or web3 profile currently connected in-app or ecosystem account.
11
+ * **When a profile is unlinked from the account, it will no longer be able to be used to sign into the account.**
12
+ *
13
+ * @example
14
+ *
15
+ * ### Unlinking an email account
16
+ *
17
+ * ```jsx
18
+ * import { useUnlinkProfile } from "thirdweb/react";
19
+ *
20
+ * const { data: connectedProfiles, isLoading } = useProfiles({
21
+ * client: props.client,
22
+ * });
23
+ * const { mutate: unlinkProfile } = useUnlinkProfile();
24
+ *
25
+ * const onClick = () => {
26
+ * unlinkProfile({
27
+ * client,
28
+ * // Select any other profile you want to unlink
29
+ * profileToUnlink: connectedProfiles[1]
30
+ * });
31
+ * };
32
+ * ```
33
+ *
34
+ * @wallet
35
+ */
36
+ export function useUnlinkProfile() {
37
+ const wallets = useConnectedWallets();
38
+ const queryClient = useQueryClient();
39
+ return useMutation({
40
+ mutationFn: async ({
41
+ client,
42
+ profileToUnlink,
43
+ }: { client: ThirdwebClient; profileToUnlink: Profile }) => {
44
+ const ecosystemWallet = wallets.find((w) => isEcosystemWallet(w));
45
+ const ecosystem: Ecosystem | undefined = ecosystemWallet
46
+ ? {
47
+ id: ecosystemWallet.id,
48
+ partnerId: ecosystemWallet.getConfig()?.partnerId,
49
+ }
50
+ : undefined;
51
+
52
+ await unlinkProfile({
53
+ client,
54
+ ecosystem,
55
+ profileToUnlink,
56
+ });
57
+ },
58
+ onSuccess: () => {
59
+ queryClient.invalidateQueries({ queryKey: ["profiles"] });
60
+ },
61
+ });
62
+ }
@@ -0,0 +1,75 @@
1
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2
+ import { act, renderHook } from "@testing-library/react";
3
+ import type React from "react";
4
+ import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { TEST_CLIENT } from "~test/test-clients.js";
6
+ import { useConnectedWallets } from "../../../../react/core/hooks/wallets/useConnectedWallets.js";
7
+ import type { Profile } from "../../../../wallets/in-app/core/authentication/types.js";
8
+ import { unlinkProfile } from "../../../../wallets/in-app/web/lib/auth/index.js";
9
+ import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
10
+ import { useUnlinkProfile } from "./useUnlinkProfile.js";
11
+
12
+ vi.mock("../../../../wallets/in-app/web/lib/auth/index.js");
13
+ vi.mock("../../../core/hooks/wallets/useConnectedWallets.js");
14
+
15
+ describe("useUnlinkProfile", () => {
16
+ const queryClient = new QueryClient();
17
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
18
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
19
+ );
20
+
21
+ beforeEach(() => {
22
+ vi.clearAllMocks();
23
+ vi.spyOn(queryClient, "invalidateQueries");
24
+ });
25
+
26
+ const mockProfile = {} as unknown as Profile;
27
+ it("should call unlinkProfile with correct parameters", async () => {
28
+ vi.mocked(useConnectedWallets).mockReturnValue([]);
29
+
30
+ const { result } = renderHook(() => useUnlinkProfile(), {
31
+ wrapper,
32
+ });
33
+ const mutationFn = result.current.mutateAsync;
34
+
35
+ await act(async () => {
36
+ await mutationFn({ client: TEST_CLIENT, profileToUnlink: mockProfile });
37
+ });
38
+
39
+ expect(unlinkProfile).toHaveBeenCalledWith({
40
+ client: TEST_CLIENT,
41
+ ecosystem: undefined,
42
+ profileToUnlink: mockProfile,
43
+ });
44
+ expect(queryClient.invalidateQueries).toHaveBeenCalledWith({
45
+ queryKey: ["profiles"],
46
+ });
47
+ });
48
+
49
+ it("should include ecosystem if ecosystem wallet is found", async () => {
50
+ const mockWallet = {
51
+ id: "ecosystem.wallet-id",
52
+ getConfig: () => ({ partnerId: "partner-id" }),
53
+ } as unknown as Wallet;
54
+ vi.mocked(useConnectedWallets).mockReturnValue([mockWallet]);
55
+
56
+ const { result } = renderHook(() => useUnlinkProfile(), {
57
+ wrapper,
58
+ });
59
+ const mutationFn = result.current.mutateAsync;
60
+
61
+ await act(async () => {
62
+ await mutationFn({ client: TEST_CLIENT, profileToUnlink: mockProfile });
63
+ });
64
+
65
+ expect(unlinkProfile).toHaveBeenCalledWith({
66
+ client: TEST_CLIENT,
67
+ ecosystem: {
68
+ id: mockWallet.id,
69
+ partnerId: (mockWallet as Wallet<`ecosystem.${string}`>).getConfig()
70
+ ?.partnerId,
71
+ },
72
+ profileToUnlink: mockProfile,
73
+ });
74
+ });
75
+ });
@@ -0,0 +1,62 @@
1
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
2
+ import type { ThirdwebClient } from "../../../../client/client.js";
3
+ import { isEcosystemWallet } from "../../../../wallets/ecosystem/is-ecosystem-wallet.js";
4
+ import type { Profile } from "../../../../wallets/in-app/core/authentication/types.js";
5
+ import type { Ecosystem } from "../../../../wallets/in-app/core/wallet/types.js";
6
+ import { unlinkProfile } from "../../../../wallets/in-app/web/lib/auth/index.js";
7
+ import { useConnectedWallets } from "../../../core/hooks/wallets/useConnectedWallets.js";
8
+
9
+ /**
10
+ * Unlinks a web2 or web3 profile currently connected in-app or ecosystem account.
11
+ * **When a profile is unlinked from the account, it will no longer be able to be used to sign into the account.**
12
+ *
13
+ * @example
14
+ *
15
+ * ### Unlinking an email account
16
+ *
17
+ * ```jsx
18
+ * import { useUnlinkProfile } from "thirdweb/react";
19
+ *
20
+ * const { data: connectedProfiles, isLoading } = useProfiles({
21
+ * client: props.client,
22
+ * });
23
+ * const { mutate: unlinkProfile } = useUnlinkProfile();
24
+ *
25
+ * const onClick = () => {
26
+ * unlinkProfile({
27
+ * client,
28
+ * // Select any other profile you want to unlink
29
+ * profileToUnlink: connectedProfiles[1]
30
+ * });
31
+ * };
32
+ * ```
33
+ *
34
+ * @wallet
35
+ */
36
+ export function useUnlinkProfile() {
37
+ const wallets = useConnectedWallets();
38
+ const queryClient = useQueryClient();
39
+ return useMutation({
40
+ mutationFn: async ({
41
+ client,
42
+ profileToUnlink,
43
+ }: { client: ThirdwebClient; profileToUnlink: Profile }) => {
44
+ const ecosystemWallet = wallets.find((w) => isEcosystemWallet(w));
45
+ const ecosystem: Ecosystem | undefined = ecosystemWallet
46
+ ? {
47
+ id: ecosystemWallet.id,
48
+ partnerId: ecosystemWallet.getConfig()?.partnerId,
49
+ }
50
+ : undefined;
51
+
52
+ await unlinkProfile({
53
+ client,
54
+ ecosystem,
55
+ profileToUnlink,
56
+ });
57
+ },
58
+ onSuccess: () => {
59
+ queryClient.invalidateQueries({ queryKey: ["profiles"] });
60
+ },
61
+ });
62
+ }
@@ -131,5 +131,30 @@ describe("LinkedProfilesScreen", () => {
131
131
  render(<LinkedProfilesScreen {...mockProps} />);
132
132
  expect(screen.queryByText("Guest")).not.toBeInTheDocument();
133
133
  });
134
+
135
+ it("should render unlink button when there are multiple profiles", () => {
136
+ vi.mocked(useProfiles).mockReturnValue({
137
+ data: [
138
+ { type: "email", details: { email: "test@example.com" } },
139
+ { type: "google", details: { email: "google@example.com" } },
140
+ ],
141
+ isLoading: false,
142
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking data
143
+ } as any);
144
+
145
+ render(<LinkedProfilesScreen {...mockProps} />);
146
+ expect(screen.getAllByLabelText("Unlink")).toHaveLength(2);
147
+ });
148
+
149
+ it("should not render unlink button when there is only one profile", () => {
150
+ vi.mocked(useProfiles).mockReturnValue({
151
+ data: [{ type: "email", details: { email: "test@example.com" } }],
152
+ isLoading: false,
153
+ // biome-ignore lint/suspicious/noExplicitAny: Mocking data
154
+ } as any);
155
+
156
+ render(<LinkedProfilesScreen {...mockProps} />);
157
+ expect(screen.queryByLabelText("Unlink")).not.toBeInTheDocument();
158
+ });
134
159
  });
135
160
  });
@@ -1,5 +1,7 @@
1
1
  "use client";
2
+ import { Cross2Icon } from "@radix-ui/react-icons";
2
3
  import type { ThirdwebClient } from "../../../../../client/client.js";
4
+ import { useUnlinkProfile } from "../../../../../react/web/hooks/wallets/useUnlinkProfile.js";
3
5
  import { shortenAddress } from "../../../../../utils/address.js";
4
6
  import type { Profile } from "../../../../../wallets/in-app/core/authentication/types.js";
5
7
  import { fontSize, iconSize } from "../../../../core/design-system/index.js";
@@ -10,6 +12,7 @@ import { LoadingScreen } from "../../../wallets/shared/LoadingScreen.js";
10
12
  import { Img } from "../../components/Img.js";
11
13
  import { Spacer } from "../../components/Spacer.js";
12
14
  import { Container, Line, ModalHeader } from "../../components/basic.js";
15
+ import { IconButton } from "../../components/buttons.js";
13
16
  import { Text } from "../../components/text.js";
14
17
  import { Blobbie } from "../Blobbie.js";
15
18
  import { MenuButton } from "../MenuButton.js";
@@ -70,57 +73,61 @@ export function LinkedProfilesScreen(props: {
70
73
  />
71
74
  </Container>
72
75
  <Line />
73
- {isLoading ? (
74
- <LoadingScreen />
75
- ) : (
76
- <Container
77
- scrollY
78
- style={{
79
- height: "300px",
80
- }}
81
- >
82
- <Spacer y="md" />
83
- <Container px="sm">
84
- <MenuButton
85
- onClick={() => {
86
- props.setScreen("link-profile");
87
- }}
88
- style={{
89
- fontSize: fontSize.sm,
90
- }}
91
- >
92
- <AddUserIcon size={iconSize.lg} />
93
- <Text color="primaryText">
94
- {props.locale.manageWallet.linkProfile}
95
- </Text>
96
- </MenuButton>
97
- <Spacer y="xs" />
98
- {/* Exclude guest as a profile */}
99
- {connectedProfiles
100
- ?.filter((profile) => profile.type !== "guest")
101
- .map((profile) => (
102
- <LinkedProfile
103
- key={`${profile.type}-${getProfileDisplayName(profile)}`}
104
- profile={profile}
105
- client={props.client}
106
- />
107
- ))}
108
- </Container>
109
- <Spacer y="md" />
76
+
77
+ <Container
78
+ scrollY
79
+ style={{
80
+ height: "300px",
81
+ }}
82
+ >
83
+ <Spacer y="md" />
84
+ <Container px="sm">
85
+ <MenuButton
86
+ onClick={() => {
87
+ props.setScreen("link-profile");
88
+ }}
89
+ style={{
90
+ fontSize: fontSize.sm,
91
+ }}
92
+ >
93
+ <AddUserIcon size={iconSize.lg} />
94
+ <Text color="primaryText">
95
+ {props.locale.manageWallet.linkProfile}
96
+ </Text>
97
+ </MenuButton>
98
+ <Spacer y="xs" />
99
+ {/* Exclude guest as a profile */}
100
+ {connectedProfiles
101
+ ?.filter((profile) => profile.type !== "guest")
102
+ .map((profile) => (
103
+ <LinkedProfile
104
+ key={`${JSON.stringify(profile)}`}
105
+ enableUnlinking={connectedProfiles.length > 1}
106
+ profile={profile}
107
+ client={props.client}
108
+ />
109
+ ))}
110
110
  </Container>
111
- )}
111
+ <Spacer y="md" />
112
+ </Container>
112
113
  </Container>
113
114
  );
114
115
  }
115
116
 
116
117
  function LinkedProfile({
117
118
  profile,
119
+ enableUnlinking,
118
120
  client,
119
- }: { profile: Profile; client: ThirdwebClient }) {
121
+ }: {
122
+ profile: Profile;
123
+ enableUnlinking: boolean;
124
+ client: ThirdwebClient;
125
+ }) {
120
126
  const { data: socialProfiles } = useSocialProfiles({
121
127
  client,
122
128
  address: profile.details.address,
123
129
  });
130
+ const { mutate: unlinkProfileMutation, isPending } = useUnlinkProfile();
124
131
 
125
132
  return (
126
133
  <MenuButton
@@ -128,6 +135,7 @@ function LinkedProfile({
128
135
  fontSize: fontSize.sm,
129
136
  cursor: "default",
130
137
  }}
138
+ as={"div"}
131
139
  disabled // disabled until we have more data to show on a dedicated profile screen
132
140
  >
133
141
  {socialProfiles?.some((p) => p.avatar) ? (
@@ -180,12 +188,46 @@ function LinkedProfile({
180
188
  {socialProfiles?.find((p) => p.avatar)?.name ||
181
189
  getProfileDisplayName(profile)}
182
190
  </Text>
183
- {socialProfiles?.find((p) => p.avatar)?.name &&
184
- profile.details.address && (
185
- <Text color="secondaryText" size="sm">
186
- {shortenAddress(profile.details.address, 4)}
187
- </Text>
191
+ <div
192
+ style={{
193
+ display: "flex",
194
+ flexDirection: "row",
195
+ alignItems: "center",
196
+ gap: "8px",
197
+ }}
198
+ >
199
+ {socialProfiles?.find((p) => p.avatar)?.name &&
200
+ profile.details.address && (
201
+ <Text color="secondaryText" size="sm">
202
+ {shortenAddress(profile.details.address, 4)}
203
+ </Text>
204
+ )}
205
+ {enableUnlinking && (
206
+ <IconButton
207
+ autoFocus
208
+ type="button"
209
+ aria-label="Unlink"
210
+ onClick={() =>
211
+ unlinkProfileMutation({
212
+ client,
213
+ profileToUnlink: profile,
214
+ })
215
+ }
216
+ style={{
217
+ pointerEvents: "auto",
218
+ }}
219
+ disabled={isPending}
220
+ >
221
+ <Cross2Icon
222
+ width={iconSize.md}
223
+ height={iconSize.md}
224
+ style={{
225
+ color: "inherit",
226
+ }}
227
+ />
228
+ </IconButton>
188
229
  )}
230
+ </div>
189
231
  </div>
190
232
  </MenuButton>
191
233
  );
@@ -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,