thirdweb 5.75.0 → 5.76.0-nightly-015293eb826b346871696183e96d49da44f31393-20241209000527

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 (129) hide show
  1. package/dist/cjs/exports/react.js.map +1 -1
  2. package/dist/cjs/exports/utils.js +5 -1
  3. package/dist/cjs/exports/utils.js.map +1 -1
  4. package/dist/cjs/pay/convert/cryptoToFiat.js.map +1 -1
  5. package/dist/cjs/pay/convert/fiatToCrypto.js +1 -1
  6. package/dist/cjs/pay/convert/fiatToCrypto.js.map +1 -1
  7. package/dist/cjs/pay/convert/type.js +4 -0
  8. package/dist/cjs/pay/convert/type.js.map +1 -0
  9. package/dist/cjs/react/web/ui/ConnectWallet/Details.js +56 -16
  10. package/dist/cjs/react/web/ui/ConnectWallet/Details.js.map +1 -1
  11. package/dist/cjs/react/web/ui/ConnectWallet/screens/ReceiveFunds.js +1 -1
  12. package/dist/cjs/react/web/ui/ConnectWallet/screens/ReceiveFunds.js.map +1 -1
  13. package/dist/cjs/react/web/ui/ConnectWallet/screens/SendFunds.js +3 -2
  14. package/dist/cjs/react/web/ui/ConnectWallet/screens/SendFunds.js.map +1 -1
  15. package/dist/cjs/react/web/ui/components/CopyIcon.js +1 -1
  16. package/dist/cjs/react/web/ui/components/CopyIcon.js.map +1 -1
  17. package/dist/cjs/react/web/ui/components/Skeleton.js +1 -1
  18. package/dist/cjs/react/web/ui/components/Skeleton.js.map +1 -1
  19. package/dist/cjs/react/web/ui/prebuilt/Account/balance.js +114 -26
  20. package/dist/cjs/react/web/ui/prebuilt/Account/balance.js.map +1 -1
  21. package/dist/cjs/react/web/ui/prebuilt/Account/provider.js +3 -0
  22. package/dist/cjs/react/web/ui/prebuilt/Account/provider.js.map +1 -1
  23. package/dist/cjs/utils/formatNumber.js +7 -1
  24. package/dist/cjs/utils/formatNumber.js.map +1 -1
  25. package/dist/cjs/utils/shortenLargeNumber.js +47 -0
  26. package/dist/cjs/utils/shortenLargeNumber.js.map +1 -0
  27. package/dist/cjs/version.js +1 -1
  28. package/dist/cjs/version.js.map +1 -1
  29. package/dist/cjs/wallets/manager/index.js +35 -24
  30. package/dist/cjs/wallets/manager/index.js.map +1 -1
  31. package/dist/esm/exports/react.js.map +1 -1
  32. package/dist/esm/exports/utils.js +2 -0
  33. package/dist/esm/exports/utils.js.map +1 -1
  34. package/dist/esm/pay/convert/cryptoToFiat.js.map +1 -1
  35. package/dist/esm/pay/convert/fiatToCrypto.js +1 -1
  36. package/dist/esm/pay/convert/fiatToCrypto.js.map +1 -1
  37. package/dist/esm/pay/convert/type.js +3 -0
  38. package/dist/esm/pay/convert/type.js.map +1 -0
  39. package/dist/esm/react/web/ui/ConnectWallet/Details.js +57 -23
  40. package/dist/esm/react/web/ui/ConnectWallet/Details.js.map +1 -1
  41. package/dist/esm/react/web/ui/ConnectWallet/screens/ReceiveFunds.js +1 -1
  42. package/dist/esm/react/web/ui/ConnectWallet/screens/ReceiveFunds.js.map +1 -1
  43. package/dist/esm/react/web/ui/ConnectWallet/screens/SendFunds.js +3 -3
  44. package/dist/esm/react/web/ui/ConnectWallet/screens/SendFunds.js.map +1 -1
  45. package/dist/esm/react/web/ui/components/CopyIcon.js +1 -1
  46. package/dist/esm/react/web/ui/components/CopyIcon.js.map +1 -1
  47. package/dist/esm/react/web/ui/components/Skeleton.js +1 -1
  48. package/dist/esm/react/web/ui/components/Skeleton.js.map +1 -1
  49. package/dist/esm/react/web/ui/prebuilt/Account/balance.js +113 -28
  50. package/dist/esm/react/web/ui/prebuilt/Account/balance.js.map +1 -1
  51. package/dist/esm/react/web/ui/prebuilt/Account/provider.js +3 -0
  52. package/dist/esm/react/web/ui/prebuilt/Account/provider.js.map +1 -1
  53. package/dist/esm/utils/formatNumber.js +7 -1
  54. package/dist/esm/utils/formatNumber.js.map +1 -1
  55. package/dist/esm/utils/shortenLargeNumber.js +44 -0
  56. package/dist/esm/utils/shortenLargeNumber.js.map +1 -0
  57. package/dist/esm/version.js +1 -1
  58. package/dist/esm/version.js.map +1 -1
  59. package/dist/esm/wallets/manager/index.js +33 -24
  60. package/dist/esm/wallets/manager/index.js.map +1 -1
  61. package/dist/types/exports/react.d.ts +1 -1
  62. package/dist/types/exports/react.d.ts.map +1 -1
  63. package/dist/types/exports/utils.d.ts +2 -0
  64. package/dist/types/exports/utils.d.ts.map +1 -1
  65. package/dist/types/pay/convert/cryptoToFiat.d.ts +2 -1
  66. package/dist/types/pay/convert/cryptoToFiat.d.ts.map +1 -1
  67. package/dist/types/pay/convert/fiatToCrypto.d.ts +2 -1
  68. package/dist/types/pay/convert/fiatToCrypto.d.ts.map +1 -1
  69. package/dist/types/pay/convert/type.d.ts +7 -0
  70. package/dist/types/pay/convert/type.d.ts.map +1 -0
  71. package/dist/types/react/core/hooks/connection/ConnectButtonProps.d.ts +11 -0
  72. package/dist/types/react/core/hooks/connection/ConnectButtonProps.d.ts.map +1 -1
  73. package/dist/types/react/web/ui/ConnectWallet/Details.d.ts +60 -0
  74. package/dist/types/react/web/ui/ConnectWallet/Details.d.ts.map +1 -1
  75. package/dist/types/react/web/ui/ConnectWallet/screens/ReceiveFunds.d.ts.map +1 -1
  76. package/dist/types/react/web/ui/ConnectWallet/screens/SendFunds.d.ts +15 -0
  77. package/dist/types/react/web/ui/ConnectWallet/screens/SendFunds.d.ts.map +1 -1
  78. package/dist/types/react/web/ui/components/CopyIcon.d.ts.map +1 -1
  79. package/dist/types/react/web/ui/components/Skeleton.d.ts +1 -0
  80. package/dist/types/react/web/ui/components/Skeleton.d.ts.map +1 -1
  81. package/dist/types/react/web/ui/prebuilt/Account/balance.d.ts +58 -9
  82. package/dist/types/react/web/ui/prebuilt/Account/balance.d.ts.map +1 -1
  83. package/dist/types/react/web/ui/prebuilt/Account/provider.d.ts.map +1 -1
  84. package/dist/types/utils/formatNumber.d.ts +7 -1
  85. package/dist/types/utils/formatNumber.d.ts.map +1 -1
  86. package/dist/types/utils/shortenLargeNumber.d.ts +16 -0
  87. package/dist/types/utils/shortenLargeNumber.d.ts.map +1 -0
  88. package/dist/types/version.d.ts +1 -1
  89. package/dist/types/version.d.ts.map +1 -1
  90. package/dist/types/wallets/manager/index.d.ts +4 -0
  91. package/dist/types/wallets/manager/index.d.ts.map +1 -1
  92. package/dist/types/wallets/wallet-connect/types.d.ts +1 -1
  93. package/dist/types/wallets/wallet-connect/types.d.ts.map +1 -1
  94. package/dist/types/wallets/wallet-types.d.ts +1 -1
  95. package/package.json +7 -6
  96. package/src/exports/react.ts +1 -0
  97. package/src/exports/utils.ts +3 -0
  98. package/src/pay/convert/cryptoToFiat.test.ts +21 -1
  99. package/src/pay/convert/cryptoToFiat.ts +2 -1
  100. package/src/pay/convert/fiatToCrypto.test.ts +21 -1
  101. package/src/pay/convert/fiatToCrypto.ts +3 -2
  102. package/src/pay/convert/type.ts +5 -0
  103. package/src/react/core/hooks/connection/ConnectButtonProps.ts +13 -0
  104. package/src/react/web/ui/ConnectWallet/ConnectButton.test.tsx +15 -0
  105. package/src/react/web/ui/ConnectWallet/Details.test.tsx +587 -0
  106. package/src/react/web/ui/ConnectWallet/Details.tsx +174 -67
  107. package/src/react/web/ui/ConnectWallet/screens/PrivateKey.test.tsx +54 -0
  108. package/src/react/web/ui/ConnectWallet/screens/ReceiveFunds.test.tsx +37 -0
  109. package/src/react/web/ui/ConnectWallet/screens/ReceiveFunds.tsx +6 -1
  110. package/src/react/web/ui/ConnectWallet/screens/SendFunds.test.tsx +67 -0
  111. package/src/react/web/ui/ConnectWallet/screens/SendFunds.tsx +3 -2
  112. package/src/react/web/ui/ConnectWallet/screens/StartScreen.test.tsx +71 -0
  113. package/src/react/web/ui/ConnectWallet/screens/formatTokenBalance.test.ts +60 -0
  114. package/src/react/web/ui/ConnectWallet/screens/nativeToken.test.ts +42 -0
  115. package/src/react/web/ui/components/CopyIcon.tsx +5 -1
  116. package/src/react/web/ui/components/Skeleton.tsx +2 -0
  117. package/src/react/web/ui/prebuilt/Account/balance.test.tsx +167 -36
  118. package/src/react/web/ui/prebuilt/Account/balance.tsx +168 -28
  119. package/src/react/web/ui/prebuilt/Account/provider.test.tsx +16 -0
  120. package/src/react/web/ui/prebuilt/Account/provider.tsx +5 -0
  121. package/src/utils/formatNumber.ts +7 -1
  122. package/src/utils/shortenLargeNumber.test.ts +30 -0
  123. package/src/utils/shortenLargeNumber.ts +48 -0
  124. package/src/version.ts +1 -1
  125. package/src/wallets/manager/connection-manager.test.ts +290 -3
  126. package/src/wallets/manager/index.ts +45 -32
  127. package/src/wallets/wallet-connect/receiver/receiver.test.ts +1 -1
  128. package/src/wallets/wallet-connect/types.ts +1 -1
  129. package/src/wallets/wallet-types.ts +1 -1
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { shortenLargeNumber } from "./shortenLargeNumber.js";
3
+
4
+ describe("shortenLargeNumber", () => {
5
+ it("should not affect number below 999", () => {
6
+ expect(shortenLargeNumber(999)).toBe("999");
7
+ });
8
+ it("should toLocaleString number below 10000", () => {
9
+ expect(shortenLargeNumber(1000)).toBe("1,000");
10
+ expect(shortenLargeNumber(9999)).toBe("9,999");
11
+ });
12
+ it("should shorten the number to `k`", () => {
13
+ expect(shortenLargeNumber(10000)).toBe("10k");
14
+ });
15
+ it("should shorten the number to `M`", () => {
16
+ expect(shortenLargeNumber(1_000_000)).toBe("1M");
17
+ });
18
+ it("should shorten the number to `B`", () => {
19
+ expect(shortenLargeNumber(1_000_000_000)).toBe("1B");
20
+ });
21
+ it("should shorten the number to `k`", () => {
22
+ expect(shortenLargeNumber(11100)).toBe("11.1k");
23
+ });
24
+ it("should shorten the number to `M`", () => {
25
+ expect(shortenLargeNumber(1_100_000)).toBe("1.1M");
26
+ });
27
+ it("should shorten the number to `B`", () => {
28
+ expect(shortenLargeNumber(1_100_000_001)).toBe("1.1B");
29
+ });
30
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Shorten the string for large value
3
+ * Mainly used for
4
+ * Examples:
5
+ * 10_000 -> 10k
6
+ * 1_000_000 -> 1M
7
+ * 1_000_000_000 -> 1B
8
+ * @example
9
+ * ```ts
10
+ * import { shortenLargeNumber } from "thirdweb/utils";
11
+ * const numStr = shortenLargeNumber(1_000_000_000, )
12
+ * ```
13
+ * @utils
14
+ */
15
+ export function shortenLargeNumber(value: number) {
16
+ if (value < 1000) {
17
+ return value.toString();
18
+ }
19
+ if (value < 10_000) {
20
+ return value.toLocaleString("en-US");
21
+ }
22
+ if (value < 1_000_000) {
23
+ return formatLargeNumber(value, 1_000, "k");
24
+ }
25
+ if (value < 1_000_000_000) {
26
+ return formatLargeNumber(value, 1_000_000, "M");
27
+ }
28
+ return formatLargeNumber(value, 1_000_000_000, "B");
29
+ }
30
+
31
+ /**
32
+ * Shorten the string for large value (over 4 digits)
33
+ * 1000 -> 1000
34
+ * 10_000 -> 10k
35
+ * 1_000_000 -> 1M
36
+ * 1_000_000_000 -> 1B
37
+ */
38
+ function formatLargeNumber(
39
+ value: number,
40
+ divisor: number,
41
+ suffix: "k" | "M" | "B",
42
+ ) {
43
+ const quotient = value / divisor;
44
+ if (Number.isInteger(quotient)) {
45
+ return Math.floor(quotient) + suffix;
46
+ }
47
+ return quotient.toFixed(1).replace(/\.0$/, "") + suffix;
48
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = "5.75.0";
1
+ export const version = "5.76.0-nightly-015293eb826b346871696183e96d49da44f31393-20241209000527";
@@ -6,7 +6,10 @@ import type { ThirdwebClient } from "../../client/client.js";
6
6
  import type { AsyncStorage } from "../../utils/storage/AsyncStorage.js";
7
7
  import type { Account, Wallet } from "../interfaces/wallet.js";
8
8
  import type { SmartWalletOptions } from "../smart/types.js";
9
- import { createConnectionManager } from "./index.js";
9
+ import {
10
+ createConnectionManager,
11
+ handleSmartWalletConnection,
12
+ } from "./index.js";
10
13
 
11
14
  describe.runIf(process.env.TW_SECRET_KEY)("Connection Manager", () => {
12
15
  let storage: AsyncStorage;
@@ -26,7 +29,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Connection Manager", () => {
26
29
  wallet = {
27
30
  id: "wallet-id",
28
31
  getAccount: vi.fn().mockReturnValue(account),
29
- subscribe: vi.fn(),
32
+ subscribe: vi.fn().mockReturnValue(vi.fn()),
30
33
  disconnect: vi.fn(),
31
34
  switchChain: vi.fn(),
32
35
  getChain: vi.fn().mockReturnValue(sepolia),
@@ -44,7 +47,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Connection Manager", () => {
44
47
  await manager.connect(wallet, { client, onConnect });
45
48
 
46
49
  expect(onConnect).toHaveBeenCalledWith(wallet);
47
- expect(storage.setItem).toHaveBeenCalledWith(expect.any(String), wallet.id);
50
+ expect(storage.setItem).toHaveBeenCalled();
48
51
  });
49
52
 
50
53
  it("handleConnection should connect smart wallet", async () => {
@@ -65,4 +68,288 @@ describe.runIf(process.env.TW_SECRET_KEY)("Connection Manager", () => {
65
68
 
66
69
  expect(manager.connectedWallets.getValue()).toContain(wallet);
67
70
  });
71
+
72
+ it("should disconnect the active wallet when the EOA disconnects", async () => {
73
+ const manager = createConnectionManager(storage);
74
+
75
+ const smartWallet = await manager.handleConnection(wallet, {
76
+ client,
77
+ accountAbstraction: smartWalletOptions,
78
+ });
79
+
80
+ expect(manager.activeWalletStore.getValue()).toBe(smartWallet);
81
+
82
+ await wallet.disconnect();
83
+
84
+ expect(wallet.subscribe).toHaveBeenCalledWith(
85
+ "disconnect",
86
+ expect.any(Function),
87
+ );
88
+ });
89
+
90
+ it("should clear active wallet data on disconnect", async () => {
91
+ const manager = createConnectionManager(storage);
92
+
93
+ await manager.handleConnection(wallet, { client });
94
+
95
+ // Simulate wallet disconnect
96
+ manager.disconnectWallet(wallet);
97
+
98
+ expect(storage.removeItem).toHaveBeenCalledWith(
99
+ "thirdweb:active-wallet-id",
100
+ );
101
+ expect(manager.activeAccountStore.getValue()).toBeUndefined();
102
+ expect(manager.activeWalletChainStore.getValue()).toBeUndefined();
103
+ expect(manager.activeWalletStore.getValue()).toBeUndefined();
104
+ expect(manager.activeWalletConnectionStatusStore.getValue()).toBe(
105
+ "disconnected",
106
+ );
107
+ });
108
+
109
+ it("should throw an error if wallet has no account", async () => {
110
+ const manager = createConnectionManager(storage);
111
+
112
+ wallet = {
113
+ id: "wallet-id",
114
+ getAccount: vi.fn().mockReturnValue(null),
115
+ subscribe: vi.fn(),
116
+ disconnect: vi.fn(),
117
+ switchChain: vi.fn(),
118
+ getChain: vi.fn().mockReturnValue(sepolia),
119
+ getConfig: vi.fn(),
120
+ } as unknown as Wallet;
121
+
122
+ await expect(manager.handleConnection(wallet, { client })).rejects.toThrow(
123
+ "Cannot set a wallet without an account as active",
124
+ );
125
+ });
126
+
127
+ it("should set active wallet and update storage", async () => {
128
+ const manager = createConnectionManager(storage);
129
+
130
+ await manager.setActiveWallet(wallet);
131
+
132
+ expect(manager.activeWalletStore.getValue()).toBe(wallet);
133
+ expect(storage.setItem).toHaveBeenCalledWith(
134
+ "thirdweb:active-wallet-id",
135
+ wallet.id,
136
+ );
137
+ });
138
+
139
+ it("should switch active wallet chain", async () => {
140
+ const manager = createConnectionManager(storage);
141
+
142
+ await manager.handleConnection(wallet, { client });
143
+
144
+ const newChain = {
145
+ id: 2,
146
+ name: "New Chain",
147
+ rpc: "https://rpc.example.com",
148
+ };
149
+
150
+ // Mock the switchChain method to update the activeWalletChainStore
151
+ wallet.switchChain = vi.fn().mockImplementation((chain) => {
152
+ manager.activeWalletChainStore.setValue(chain);
153
+ });
154
+
155
+ await manager.switchActiveWalletChain(newChain);
156
+
157
+ expect(wallet.switchChain).toHaveBeenCalledWith(newChain);
158
+ });
159
+
160
+ it("should define chains", async () => {
161
+ const manager = createConnectionManager(storage);
162
+ await manager.handleConnection(wallet, { client });
163
+
164
+ const chains = [
165
+ { id: 1, name: "Chain 1", rpc: "https://rpc1.example.com" },
166
+ { id: 2, name: "Chain 2", rpc: "https://rpc2.example.com" },
167
+ ];
168
+ manager.defineChains(chains);
169
+
170
+ const activeWalletChain = manager.activeWalletChainStore.getValue();
171
+ expect(activeWalletChain).toBeDefined();
172
+ });
173
+
174
+ it("should remove connected wallet", () => {
175
+ const manager = createConnectionManager(storage);
176
+
177
+ manager.addConnectedWallet(wallet);
178
+ expect(manager.connectedWallets.getValue()).toContain(wallet);
179
+
180
+ manager.removeConnectedWallet(wallet);
181
+ expect(manager.connectedWallets.getValue()).not.toContain(wallet);
182
+ });
183
+
184
+ it("should update active wallet chain to a defined chain", async () => {
185
+ const manager = createConnectionManager(storage);
186
+
187
+ // Connect the wallet to initialize the active wallet chain
188
+ await manager.handleConnection(wallet, { client });
189
+
190
+ // Define a new chain and update the definedChainsStore
191
+ const newChain = {
192
+ id: 11155111,
193
+ name: "New Defined Chain",
194
+ rpc: "https://rpc3.example.com",
195
+ };
196
+ manager.defineChains([newChain]);
197
+
198
+ // Simulate the effect that updates the active wallet chain
199
+ const definedChain = manager.activeWalletChainStore.getValue();
200
+ expect(definedChain).toEqual(newChain);
201
+ });
202
+ });
203
+
204
+ describe("handleSmartWalletConnection", () => {
205
+ let client: ThirdwebClient;
206
+ let eoaWallet: Wallet;
207
+ let smartWalletOptions: SmartWalletOptions;
208
+
209
+ beforeEach(() => {
210
+ client = TEST_CLIENT;
211
+ eoaWallet = {
212
+ id: "eoa-wallet-id",
213
+ getAccount: vi.fn().mockReturnValue(TEST_ACCOUNT_A),
214
+ subscribe: vi.fn().mockReturnValue(vi.fn()),
215
+ disconnect: vi.fn(),
216
+ switchChain: vi.fn(),
217
+ getChain: vi.fn().mockReturnValue(sepolia),
218
+ getConfig: vi.fn(),
219
+ } as unknown as Wallet;
220
+ smartWalletOptions = {
221
+ chain: sepolia,
222
+ } as SmartWalletOptions;
223
+ });
224
+
225
+ it("should connect a smart wallet and subscribe to disconnect event", async () => {
226
+ const onWalletDisconnect = vi.fn();
227
+ const smartWallet = await handleSmartWalletConnection(
228
+ eoaWallet,
229
+ client,
230
+ smartWalletOptions,
231
+ onWalletDisconnect,
232
+ );
233
+
234
+ expect(smartWallet.getAccount()).toBeTruthy();
235
+
236
+ expect(eoaWallet.subscribe).toHaveBeenCalledWith(
237
+ "disconnect",
238
+ expect.any(Function),
239
+ );
240
+ });
241
+
242
+ it("should call onWalletDisconnect when EOA wallet disconnects", async () => {
243
+ const onWalletDisconnect = vi.fn();
244
+ const smartWallet = await handleSmartWalletConnection(
245
+ eoaWallet,
246
+ client,
247
+ smartWalletOptions,
248
+ onWalletDisconnect,
249
+ );
250
+
251
+ // biome-ignore lint/suspicious/noExplicitAny: Mocked function
252
+ const disconnectCallback = (eoaWallet.subscribe as any).mock.calls[0][1];
253
+ disconnectCallback();
254
+
255
+ expect(onWalletDisconnect).toHaveBeenCalledWith(smartWallet);
256
+ });
257
+
258
+ it("should throw an error if EOA wallet has no account", async () => {
259
+ eoaWallet.getAccount = vi.fn().mockReturnValue(null);
260
+
261
+ await expect(
262
+ handleSmartWalletConnection(
263
+ eoaWallet,
264
+ client,
265
+ smartWalletOptions,
266
+ vi.fn(),
267
+ ),
268
+ ).rejects.toThrow("Cannot set a wallet without an account as active");
269
+ });
270
+ });
271
+
272
+ describe("Connection Manager Error Handling", () => {
273
+ let storage: AsyncStorage;
274
+ let client: ThirdwebClient;
275
+ let wallet: Wallet;
276
+
277
+ beforeEach(() => {
278
+ storage = {
279
+ getItem: vi.fn(),
280
+ setItem: vi.fn(),
281
+ removeItem: vi.fn(),
282
+ };
283
+ client = TEST_CLIENT;
284
+ wallet = {
285
+ id: "wallet-id",
286
+ getAccount: vi.fn().mockReturnValue(null), // Simulate wallet without an account
287
+ subscribe: vi.fn(),
288
+ disconnect: vi.fn(),
289
+ switchChain: vi.fn(),
290
+ getChain: vi.fn().mockReturnValue(sepolia),
291
+ getConfig: vi.fn(),
292
+ } as unknown as Wallet;
293
+ });
294
+
295
+ it("should throw an error if trying to set a wallet without an account as active", async () => {
296
+ const manager = createConnectionManager(storage);
297
+
298
+ await expect(manager.setActiveWallet(wallet)).rejects.toThrow(
299
+ "Cannot set a wallet without an account as active",
300
+ );
301
+ });
302
+
303
+ it("should throw an error if handleConnection is called with a wallet without an account", async () => {
304
+ const manager = createConnectionManager(storage);
305
+
306
+ await expect(manager.handleConnection(wallet, { client })).rejects.toThrow(
307
+ "Cannot set a wallet without an account as active",
308
+ );
309
+ });
310
+ });
311
+
312
+ describe("Connection Manager Event Subscriptions", () => {
313
+ let storage: AsyncStorage;
314
+ let client: ThirdwebClient;
315
+ let wallet: Wallet;
316
+ let account: Account;
317
+
318
+ beforeEach(() => {
319
+ storage = {
320
+ getItem: vi.fn(),
321
+ setItem: vi.fn(),
322
+ removeItem: vi.fn(),
323
+ };
324
+ client = TEST_CLIENT;
325
+ account = TEST_ACCOUNT_A;
326
+ wallet = {
327
+ id: "wallet-id",
328
+ getAccount: vi.fn().mockReturnValue(account),
329
+ subscribe: vi.fn().mockReturnValue(vi.fn()),
330
+ disconnect: vi.fn(),
331
+ switchChain: vi.fn(),
332
+ getChain: vi.fn().mockReturnValue(sepolia),
333
+ getConfig: vi.fn(),
334
+ } as unknown as Wallet;
335
+ });
336
+
337
+ it("should subscribe to accountChanged, chainChanged, and disconnect events", async () => {
338
+ const manager = createConnectionManager(storage);
339
+
340
+ await manager.handleConnection(wallet, { client });
341
+
342
+ expect(wallet.subscribe).toHaveBeenCalledWith(
343
+ "accountChanged",
344
+ expect.any(Function),
345
+ );
346
+ expect(wallet.subscribe).toHaveBeenCalledWith(
347
+ "chainChanged",
348
+ expect.any(Function),
349
+ );
350
+ expect(wallet.subscribe).toHaveBeenCalledWith(
351
+ "disconnect",
352
+ expect.any(Function),
353
+ );
354
+ });
68
355
  });
@@ -125,27 +125,26 @@ export function createConnectionManager(storage: AsyncStorage) {
125
125
  ) => {
126
126
  const account = wallet.getAccount();
127
127
  if (!account) {
128
- throw new Error("Can not set a wallet without an account as active");
128
+ throw new Error("Cannot set a wallet without an account as active");
129
129
  }
130
130
 
131
131
  const activeWallet = await (async () => {
132
132
  if (options?.accountAbstraction && !hasSmartAccount(wallet)) {
133
133
  return await handleSmartWalletConnection(
134
- account,
134
+ wallet,
135
135
  options.client,
136
136
  options.accountAbstraction,
137
+ onWalletDisconnect,
137
138
  );
138
139
  } else {
139
140
  return wallet;
140
141
  }
141
142
  })();
142
143
 
143
- // add personal wallet to connected wallets list
144
- addConnectedWallet(wallet);
144
+ await storage.setItem(LAST_ACTIVE_EOA_ID, wallet.id);
145
145
 
146
- if (wallet.id !== "smart") {
147
- await storage.setItem(LAST_ACTIVE_EOA_ID, wallet.id);
148
- }
146
+ // add personal wallet to connected wallets list even if it's not the active one
147
+ addConnectedWallet(wallet);
149
148
 
150
149
  handleSetActiveWallet(activeWallet);
151
150
 
@@ -158,22 +157,6 @@ export function createConnectionManager(storage: AsyncStorage) {
158
157
  return activeWallet;
159
158
  };
160
159
 
161
- const handleSmartWalletConnection = async (
162
- signer: Account,
163
- client: ThirdwebClient,
164
- options: SmartWalletOptions,
165
- ) => {
166
- const wallet = smartWallet(options);
167
-
168
- await wallet.connect({
169
- personalAccount: signer,
170
- client: client,
171
- chain: options.chain,
172
- });
173
-
174
- return wallet;
175
- };
176
-
177
160
  const connect = async (wallet: Wallet, options?: ConnectManagerOptions) => {
178
161
  // connectedWallet can be either wallet or smartWallet
179
162
  const connectedWallet = await handleConnection(wallet, options);
@@ -184,7 +167,7 @@ export function createConnectionManager(storage: AsyncStorage) {
184
167
  const handleSetActiveWallet = (activeWallet: Wallet) => {
185
168
  const account = activeWallet.getAccount();
186
169
  if (!account) {
187
- throw new Error("Can not set a wallet without an account as active");
170
+ throw new Error("Cannot set a wallet without an account as active");
188
171
  }
189
172
 
190
173
  // also add it to connected wallets if it's not already there
@@ -357,11 +340,9 @@ export async function getStoredActiveWalletId(
357
340
  if (value) {
358
341
  return value as WalletId;
359
342
  }
343
+ } catch {}
360
344
 
361
- return null;
362
- } catch {
363
- return null;
364
- }
345
+ return null;
365
346
  }
366
347
 
367
348
  /**
@@ -375,9 +356,41 @@ export async function getLastConnectedChain(
375
356
  if (value) {
376
357
  return JSON.parse(value) as Chain;
377
358
  }
359
+ } catch {}
378
360
 
379
- return null;
380
- } catch {
381
- return null;
382
- }
361
+ return null;
383
362
  }
363
+
364
+ /**
365
+ * @internal
366
+ */
367
+ export const handleSmartWalletConnection = async (
368
+ eoaWallet: Wallet,
369
+ client: ThirdwebClient,
370
+ options: SmartWalletOptions,
371
+ onWalletDisconnect: (wallet: Wallet) => void,
372
+ ) => {
373
+ const signer = eoaWallet.getAccount();
374
+ if (!signer) {
375
+ throw new Error("Cannot set a wallet without an account as active");
376
+ }
377
+
378
+ const wallet = smartWallet(options);
379
+
380
+ await wallet.connect({
381
+ personalAccount: signer,
382
+ client: client,
383
+ chain: options.chain,
384
+ });
385
+
386
+ // Disconnect the active wallet when the EOA disconnects if it the active wallet is a smart wallet
387
+ const disconnectUnsub = eoaWallet.subscribe("disconnect", () => {
388
+ handleDisconnect();
389
+ });
390
+ const handleDisconnect = () => {
391
+ disconnectUnsub();
392
+ onWalletDisconnect(wallet);
393
+ };
394
+
395
+ return wallet;
396
+ };
@@ -27,7 +27,7 @@ const URI_MOCK =
27
27
 
28
28
  const DEFAULT_METADATA = getDefaultAppMetadata();
29
29
 
30
- const listeners: Record<string, (event?: unknown) => Promise<void>> = {};
30
+ const listeners: Record<string, (_event?: unknown) => Promise<void>> = {};
31
31
 
32
32
  const signClientMock = {
33
33
  on: vi.fn((event, listener) => {
@@ -160,7 +160,7 @@ export type WCConnectOptions = {
160
160
  * })
161
161
  * ```
162
162
  */
163
- onDisplayUri?: (uri: string) => void;
163
+ onDisplayUri?: (_uri: string) => void;
164
164
  }
165
165
  >;
166
166
  };
@@ -134,7 +134,7 @@ export type WalletAutoConnectionOption<T extends WalletId> =
134
134
  * @example
135
135
  * ```ts
136
136
  * type X = WalletCreationOptions<'io.metamask'>
137
- * ````
137
+ * ```
138
138
  */
139
139
  export type WalletCreationOptions<T extends WalletId> = T extends "smart"
140
140
  ? SmartWalletOptions