@rango-dev/widget-embedded 0.39.1-next.8 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/components/SwapDetailsModal/SwapDetailsCompleteModal.d.ts.map +1 -1
  2. package/dist/components/SwapDetailsModal/SwapDetailsModal.styles.d.ts +160 -0
  3. package/dist/components/SwapDetailsModal/SwapDetailsModal.styles.d.ts.map +1 -1
  4. package/dist/constants/profileBanner.d.ts +3 -0
  5. package/dist/constants/profileBanner.d.ts.map +1 -0
  6. package/dist/constants/wallets.d.ts +1 -0
  7. package/dist/constants/wallets.d.ts.map +1 -1
  8. package/dist/containers/Wallets/Wallets.d.ts.map +1 -1
  9. package/dist/containers/Wallets/Wallets.helpers.d.ts +4 -0
  10. package/dist/containers/Wallets/Wallets.helpers.d.ts.map +1 -0
  11. package/dist/containers/Wallets/Wallets.types.d.ts +6 -2
  12. package/dist/containers/Wallets/Wallets.types.d.ts.map +1 -1
  13. package/dist/containers/Wallets/useUpdates.d.ts +13 -0
  14. package/dist/containers/Wallets/useUpdates.d.ts.map +1 -0
  15. package/dist/hooks/useSubscribeToWidgetEvents/useSubscribeToWidgetEvents.d.ts.map +1 -1
  16. package/dist/index.js +2 -2
  17. package/dist/index.js.map +4 -4
  18. package/dist/store/middlewares/keepLastUpdated.d.ts +8 -0
  19. package/dist/store/middlewares/keepLastUpdated.d.ts.map +1 -0
  20. package/dist/store/slices/wallets.d.ts +20 -14
  21. package/dist/store/slices/wallets.d.ts.map +1 -1
  22. package/dist/store/utils/wallets.d.ts +3 -3
  23. package/dist/store/utils/wallets.d.ts.map +1 -1
  24. package/dist/utils/common.d.ts +5 -0
  25. package/dist/utils/common.d.ts.map +1 -1
  26. package/dist/widget-embedded.build.json +1 -1
  27. package/package.json +12 -12
  28. package/src/components/ConfirmWalletsModal/WalletList.tsx +1 -1
  29. package/src/components/SwapDetailsModal/SwapDetailsCompleteModal.tsx +14 -7
  30. package/src/components/SwapDetailsModal/SwapDetailsModal.styles.ts +4 -0
  31. package/src/constants/profileBanner.ts +3 -0
  32. package/src/constants/wallets.ts +2 -0
  33. package/src/containers/Wallets/Wallets.helpers.ts +26 -0
  34. package/src/containers/Wallets/Wallets.tsx +23 -127
  35. package/src/containers/Wallets/Wallets.types.ts +16 -2
  36. package/src/containers/Wallets/useUpdates.ts +240 -0
  37. package/src/hooks/useSubscribeToWidgetEvents/useSubscribeToWidgetEvents.ts +19 -9
  38. package/src/store/middlewares/keepLastUpdated.ts +19 -0
  39. package/src/store/slices/wallets.ts +560 -407
  40. package/src/store/utils/wallets.ts +12 -11
  41. package/src/utils/common.ts +24 -2
@@ -1,10 +1,11 @@
1
1
  import type { AppStoreState } from './types';
2
- import type { Token } from 'rango-sdk';
3
- import type { StateCreator } from 'zustand';
2
+ import type { Namespace } from '@rango-dev/wallets-core/namespaces/common';
3
+ import type { Token, WalletDetail } from 'rango-sdk';
4
4
 
5
5
  import BigNumber from 'bignumber.js';
6
6
 
7
7
  import { ZERO } from '../../constants/numbers';
8
+ import { BALANCE_SEPARATOR } from '../../constants/wallets';
8
9
  import { eventEmitter } from '../../services/eventEmitter';
9
10
  import { httpService } from '../../services/httpService';
10
11
  import {
@@ -13,7 +14,9 @@ import {
13
14
  WalletEventTypes,
14
15
  WidgetEvents,
15
16
  } from '../../types';
17
+ import { memoizedResult } from '../../utils/common';
16
18
  import { isAccountAndWalletMatched } from '../../utils/wallets';
19
+ import { keepLastUpdated } from '../middlewares/keepLastUpdated';
17
20
  import {
18
21
  createAssetKey,
19
22
  createBalanceKey,
@@ -27,11 +30,12 @@ type WalletAddress = string;
27
30
  type TokenAddress = string;
28
31
  type TokenSymbol = string;
29
32
  type BlockchainId = string;
30
- /** format: `BlockchainId-TokenAddress-TokenSymbol` */
31
- export type AssetKey = `${BlockchainId}-${TokenAddress}-${TokenSymbol}`;
32
- /** format: `BlockchainId-TokenAddress-TokenSymbol-WalletAddress` */
33
+ /** format: `BlockchainId${BALANCE_SEPARATOR}TokenAddress${BALANCE_SEPARATOR}TokenSymbol` */
34
+ export type AssetKey =
35
+ `${BlockchainId}${typeof BALANCE_SEPARATOR}${TokenAddress}${typeof BALANCE_SEPARATOR}${TokenSymbol}`;
36
+ /** format: `BlockchainId${BALANCE_SEPARATOR}TokenAddress${BALANCE_SEPARATOR}TokenSymbol${BALANCE_SEPARATOR}WalletAddress` */
33
37
  export type BalanceKey =
34
- `${BlockchainId}-${TokenAddress}-${TokenSymbol}-${WalletAddress}`;
38
+ `${BlockchainId}${typeof BALANCE_SEPARATOR}${TokenAddress}${typeof BALANCE_SEPARATOR}${TokenSymbol}${typeof BALANCE_SEPARATOR}${WalletAddress}`;
35
39
 
36
40
  export type BalanceState = {
37
41
  [key: BalanceKey]: Balance;
@@ -45,6 +49,7 @@ export interface ConnectedWallet extends Wallet {
45
49
  selected: boolean;
46
50
  loading: boolean;
47
51
  error: boolean;
52
+ namespace?: Namespace;
48
53
  }
49
54
 
50
55
  interface DeprecatedTokenBalance {
@@ -63,33 +68,57 @@ export interface DeprecatedWalletDetail extends ConnectedWallet {
63
68
  balances: DeprecatedTokenBalance[] | null;
64
69
  }
65
70
 
71
+ function matchWalletDetailsWithConnectedWallet(
72
+ connectedWallet: ConnectedWallet,
73
+ walletsDetails: WalletDetail[]
74
+ ): WalletDetail | undefined {
75
+ return walletsDetails.find(
76
+ (walletDetails) =>
77
+ connectedWallet.address === walletDetails.address &&
78
+ connectedWallet.chain === walletDetails.blockChain
79
+ );
80
+ }
81
+
66
82
  export interface WalletsSlice {
67
83
  _balances: BalanceState;
68
84
  _aggregatedBalances: AggregatedBalanceState;
69
85
  connectedWallets: ConnectedWallet[];
70
86
  fetchingWallets: boolean;
87
+ lastUpdatedAt: number;
71
88
 
72
- setConnectedWalletAsRefetching: (walletType: string) => void;
73
- setConnectedWalletHasError: (walletType: string) => void;
74
- setConnectedWalletRetrievedData: (walletType: string) => void;
89
+ setConnectedWalletAsRefetching: (accounts: Wallet[]) => void;
90
+ setConnectedWalletHasError: (accounts: Wallet[]) => void;
91
+ setConnectedWalletRetrievedData: (
92
+ accounts: Wallet[],
93
+ walletsDetails: WalletDetail[]
94
+ ) => void;
75
95
  removeBalancesForWallet: (
76
96
  walletType: string,
77
97
  options?: {
78
98
  chains?: string[];
99
+ namespaces?: Namespace[];
79
100
  }
80
101
  ) => void;
81
- addConnectedWallet: (accounts: Wallet[]) => void;
102
+ addConnectedWallet: (accounts: Wallet[], namespace?: Namespace) => void;
82
103
  setWalletsAsSelected: (
83
104
  wallets: { walletType: string; chain: string }[]
84
105
  ) => void;
85
106
  /**
86
107
  * Add new accounts to store and fetch balances for them.
87
108
  */
88
- newWalletConnected: (accounts: Wallet[]) => Promise<void>;
109
+ newWalletConnected: (
110
+ accounts: Wallet[],
111
+ namespace?: Namespace
112
+ ) => Promise<void>;
113
+ disconnectNamespaces: (walletType: string, namespaces: Namespace[]) => void;
89
114
  /**
90
115
  * Disconnect a wallet and clean up balances after that.
91
116
  */
92
117
  disconnectWallet: (walletType: string) => void;
118
+ _changeSelectedWalletIfNeededOnRemove: (
119
+ walletType: string,
120
+ options?: { namespaces?: Namespace[] }
121
+ ) => void;
93
122
  clearConnectedWallet: () => void;
94
123
  fetchBalances: (
95
124
  accounts: Wallet[],
@@ -106,465 +135,589 @@ export interface WalletsSlice {
106
135
  getConnectedWalletsDetails: () => DeprecatedWalletDetail[];
107
136
  }
108
137
 
109
- export const createWalletsSlice: StateCreator<
110
- AppStoreState,
111
- [],
112
- [],
113
- WalletsSlice
114
- > = (set, get) => ({
115
- _balances: {},
116
- _aggregatedBalances: {},
117
- connectedWallets: [],
118
- fetchingWallets: false,
119
-
120
- // Actions
121
- setConnectedWalletAsRefetching: (walletType: string) => {
122
- set((state) => {
123
- return {
124
- fetchingWallets: true,
125
- connectedWallets: state.connectedWallets.map((connectedWallet) => {
126
- if (connectedWallet.walletType === walletType) {
127
- return {
128
- ...connectedWallet,
129
- loading: true,
130
- error: false,
131
- };
132
- }
138
+ const memoizedConnectedWalletsWithNestedBalances = memoizedResult();
139
+ export const createWalletsSlice = keepLastUpdated<AppStoreState, WalletsSlice>(
140
+ (set, get) => ({
141
+ _balances: {},
142
+ _aggregatedBalances: {},
143
+ connectedWallets: [],
144
+ fetchingWallets: false,
145
+ lastUpdatedAt: +new Date(),
146
+
147
+ // Actions
148
+ setConnectedWalletAsRefetching: (accounts: Wallet[]) => {
149
+ set((state) => {
150
+ return {
151
+ fetchingWallets: true,
152
+ connectedWallets: state.connectedWallets.map((connectedWallet) => {
153
+ if (
154
+ accounts.find((account) =>
155
+ isAccountAndWalletMatched(account, connectedWallet)
156
+ )
157
+ ) {
158
+ return {
159
+ ...connectedWallet,
160
+ loading: true,
161
+ error: false,
162
+ };
163
+ }
164
+
165
+ return connectedWallet;
166
+ }),
167
+ };
168
+ });
169
+ },
170
+ setConnectedWalletRetrievedData: (
171
+ accounts: Wallet[],
172
+ walletsDetails: WalletDetail[]
173
+ ) => {
174
+ set((state) => {
175
+ return {
176
+ fetchingWallets: false,
177
+ connectedWallets: state.connectedWallets.map((connectedWallet) => {
178
+ if (
179
+ accounts.find((account) =>
180
+ isAccountAndWalletMatched(account, connectedWallet)
181
+ )
182
+ ) {
183
+ return {
184
+ ...connectedWallet,
185
+ loading: false,
186
+ error: false,
187
+ explorerUrl:
188
+ matchWalletDetailsWithConnectedWallet(
189
+ connectedWallet,
190
+ walletsDetails
191
+ )?.explorerUrl || connectedWallet.explorerUrl,
192
+ };
193
+ }
194
+
195
+ return connectedWallet;
196
+ }),
197
+ };
198
+ });
199
+ },
200
+ setConnectedWalletHasError: (accounts: Wallet[]) => {
201
+ set((state) => {
202
+ return {
203
+ fetchingWallets: false,
204
+ connectedWallets: state.connectedWallets.map((connectedWallet) => {
205
+ if (
206
+ accounts.find((account) =>
207
+ isAccountAndWalletMatched(account, connectedWallet)
208
+ )
209
+ ) {
210
+ return {
211
+ ...connectedWallet,
212
+ loading: false,
213
+ error: true,
214
+ };
215
+ }
216
+
217
+ return connectedWallet;
218
+ }),
219
+ };
220
+ });
221
+ },
222
+ addConnectedWallet: (accounts, namespace) => {
223
+ /*
224
+ * When we are going to add a new account, there are two thing that can be happens:
225
+ * 1. Wallet hasn't add yet.
226
+ * 2. Wallet has added, and there are some more account that needs to added to connected wallet. consider we've added an ETH and Pol account, then we need to add Arb account later as well.
227
+ *
228
+ * For handling this, we need to only keep not-added-account, then only add those.
229
+ *
230
+ * Note:
231
+ * The second option would be useful for hub particularly.
232
+ */
233
+ const connectedWallets = get().connectedWallets;
234
+ const walletsNeedToBeAdded = accounts.filter(
235
+ (account) =>
236
+ !connectedWallets.some((connectedWallet) =>
237
+ isAccountAndWalletMatched(account, connectedWallet)
238
+ )
239
+ );
240
+
241
+ if (walletsNeedToBeAdded.length > 0) {
242
+ const newConnectedWallets: ConnectedWallet[] = walletsNeedToBeAdded.map(
243
+ (account) => {
244
+ /*
245
+ * When a wallet is connecting, we will check if there is any `selected` wallet before, if not, we will consider this new wallet as connected.
246
+ * In this way, when user tries to swap, we selected a wallet by default and don't need to do an extra click in ConfirmWalletModal
247
+ */
248
+ const shouldMarkWalletAsSelected = !connectedWallets.some(
249
+ (connectedWallet) =>
250
+ connectedWallet.chain === account.chain &&
251
+ connectedWallet.selected &&
252
+ /**
253
+ * Sometimes, the connect function can be called multiple times for a particular wallet type when using the auto-connect feature.
254
+ * This check is there to make sure the chosen wallet doesn't end up unselected.
255
+ */
256
+ connectedWallet.walletType !== account.walletType
257
+ );
133
258
 
134
- return connectedWallet;
135
- }),
136
- };
137
- });
138
- },
139
- setConnectedWalletRetrievedData: (walletType: string) => {
140
- set((state) => {
141
- return {
142
- fetchingWallets: false,
143
- connectedWallets: state.connectedWallets.map((connectedWallet) => {
144
- if (connectedWallet.walletType === walletType) {
145
259
  return {
146
- ...connectedWallet,
260
+ address: account.address,
261
+ chain: account.chain,
262
+ explorerUrl: null,
263
+ walletType: account.walletType,
264
+ selected: shouldMarkWalletAsSelected,
265
+ namespace: namespace,
147
266
  loading: false,
148
267
  error: false,
149
268
  };
150
269
  }
270
+ );
151
271
 
152
- return connectedWallet;
153
- }),
154
- };
155
- });
156
- },
157
- setConnectedWalletHasError: (walletType: string) => {
158
- set((state) => {
159
- return {
160
- fetchingWallets: false,
161
- connectedWallets: state.connectedWallets.map((connectedWallet) => {
162
- if (connectedWallet.walletType === walletType) {
163
- return {
164
- ...connectedWallet,
165
- loading: false,
166
- error: true,
167
- };
272
+ set((state) => {
273
+ /*
274
+ * If wallet connected before and only need to update the address we should remove the old value and then add new conncted value.
275
+ * This scenario happens when user wants to change account inside the wallet.
276
+ * So the assumption here is the wallet has only one active address for a blockchain at the moment.
277
+ */
278
+ const connectedWalletsWithoutSameWalletAndBlockchain =
279
+ state.connectedWallets.filter((currentConnectedWallet) => {
280
+ return !newConnectedWallets.some(
281
+ (newConnectedWallet) =>
282
+ newConnectedWallet.walletType ===
283
+ currentConnectedWallet.walletType &&
284
+ newConnectedWallet.chain === currentConnectedWallet.chain
285
+ );
286
+ });
287
+
288
+ return {
289
+ connectedWallets: [
290
+ ...connectedWalletsWithoutSameWalletAndBlockchain,
291
+ ...newConnectedWallets,
292
+ ],
293
+ };
294
+ });
295
+ }
296
+ },
297
+ setWalletsAsSelected: (wallets) => {
298
+ const nextConnectedWalletsWithUpdatedSelectedStatus =
299
+ get().connectedWallets.map((connectedWallet) => {
300
+ const walletSelected = !!wallets.find(
301
+ (wallet) =>
302
+ wallet.chain === connectedWallet.chain &&
303
+ wallet.walletType !== connectedWallet.walletType &&
304
+ connectedWallet.selected
305
+ );
306
+ const walletNotSelected = !!wallets.find(
307
+ (wallet) =>
308
+ wallet.chain === connectedWallet.chain &&
309
+ wallet.walletType === connectedWallet.walletType &&
310
+ !connectedWallet.selected
311
+ );
312
+ if (walletSelected) {
313
+ return { ...connectedWallet, selected: false };
314
+ } else if (walletNotSelected) {
315
+ return { ...connectedWallet, selected: true };
168
316
  }
169
317
 
170
318
  return connectedWallet;
171
- }),
172
- };
173
- });
174
- },
175
- addConnectedWallet: (accounts: Wallet[]) => {
176
- /*
177
- * When we are going to add a new account, there are two thing that can be happens:
178
- * 1. Wallet hasn't add yet.
179
- * 2. Wallet has added, and there are some more account that needs to added to connected wallet. consider we've added an ETH and Pol account, then we need to add Arb account later as well.
180
- *
181
- * For handling this, we need to only keep not-added-account, then only add those.
182
- *
183
- * Note:
184
- * The second option would be useful for hub particularly.
185
- */
186
- const connectedWallets = get().connectedWallets;
187
- const walletsNeedToBeAdded = accounts.filter(
188
- (account) =>
189
- !connectedWallets.some((connectedWallet) =>
190
- isAccountAndWalletMatched(account, connectedWallet)
191
- )
192
- );
193
-
194
- if (walletsNeedToBeAdded.length > 0) {
195
- const newConnectedWallets = walletsNeedToBeAdded.map((account) => {
196
- /*
197
- * When a wallet is connecting, we will check if there is any `selected` wallet before, if not, we will consider this new wallet as connected.
198
- * In this way, when user tries to swap, we selected a wallet by default and don't need to do an extra click in ConfirmWalletModal
199
- */
200
- const shouldMarkWalletAsSelected = !connectedWallets.some(
201
- (connectedWallet) =>
202
- connectedWallet.chain === account.chain &&
203
- connectedWallet.selected &&
204
- /**
205
- * Sometimes, the connect function can be called multiple times for a particular wallet type when using the auto-connect feature.
206
- * This check is there to make sure the chosen wallet doesn't end up unselected.
207
- */
208
- connectedWallet.walletType !== account.walletType
209
- );
319
+ });
210
320
 
211
- return {
212
- address: account.address,
213
- chain: account.chain,
214
- explorerUrl: null,
215
- walletType: account.walletType,
216
- selected: shouldMarkWalletAsSelected,
217
-
218
- loading: false,
219
- error: false,
220
- };
321
+ set({
322
+ connectedWallets: nextConnectedWalletsWithUpdatedSelectedStatus,
323
+ });
324
+ },
325
+ newWalletConnected: async (accounts, namespace) => {
326
+ eventEmitter.emit(WidgetEvents.WalletEvent, {
327
+ type: WalletEventTypes.CONNECT,
328
+ payload: { walletType: accounts[0].walletType, accounts },
221
329
  });
222
330
 
223
- set((state) => {
224
- /*
225
- * If wallet connected before and only need to update the address we should remove the old value and then add new conncted value.
226
- * This scenario happens when user wants to change account inside the wallet.
227
- * So the assumption here is the wallet has only one active address for a blockchain at the moment.
228
- */
229
- const connectedWalletsWithoutSameWalletAndBlockchain =
230
- state.connectedWallets.filter((currentConnectedWallet) => {
231
- return !newConnectedWallets.some(
232
- (newConnectedWallet) =>
233
- newConnectedWallet.walletType ===
234
- currentConnectedWallet.walletType &&
235
- newConnectedWallet.chain === currentConnectedWallet.chain
236
- );
237
- });
331
+ get().addConnectedWallet(accounts, namespace);
238
332
 
239
- return {
240
- connectedWallets: [
241
- ...connectedWalletsWithoutSameWalletAndBlockchain,
242
- ...newConnectedWallets,
243
- ],
244
- };
245
- });
246
- }
247
- },
248
- setWalletsAsSelected: (wallets) => {
249
- const nextConnectedWalletsWithUpdatedSelectedStatus =
250
- get().connectedWallets.map((connectedWallet) => {
251
- const walletSelected = !!wallets.find(
252
- (wallet) =>
253
- wallet.chain === connectedWallet.chain &&
254
- wallet.walletType !== connectedWallet.walletType &&
255
- connectedWallet.selected
256
- );
257
- const walletNotSelected = !!wallets.find(
258
- (wallet) =>
259
- wallet.chain === connectedWallet.chain &&
260
- wallet.walletType === connectedWallet.walletType &&
261
- !connectedWallet.selected
262
- );
263
- if (walletSelected) {
264
- return { ...connectedWallet, selected: false };
265
- } else if (walletNotSelected) {
266
- return { ...connectedWallet, selected: true };
333
+ void get().fetchBalances(accounts);
334
+ },
335
+ removeBalancesForWallet: (walletType, options) => {
336
+ let walletsNeedsToBeRemoved = get().connectedWallets.filter(
337
+ (connectedWallet) => connectedWallet.walletType === walletType
338
+ );
339
+ /*
340
+ * We only need to delete balances where there is no connected wallets with same chain and address for that balance.
341
+ * Consider both Metamask and Solana having support for `0xblahblahblahblah` for Ethereum.
342
+ * If Phantom is disconnecting, we should keep the balance since Metamask has access to same address yet.
343
+ * So we only delete balance when there is no connected wallet that has access to that specific chain and address.
344
+ */
345
+ get().connectedWallets.forEach((connectedWallet) => {
346
+ if (connectedWallet.walletType !== walletType) {
347
+ walletsNeedsToBeRemoved = walletsNeedsToBeRemoved.filter((wallet) => {
348
+ const isAnotherWalletHasSameAddressAndChain =
349
+ wallet.chain === connectedWallet.chain &&
350
+ wallet.address === connectedWallet.address;
351
+ return !isAnotherWalletHasSameAddressAndChain;
352
+ });
267
353
  }
268
-
269
- return connectedWallet;
270
354
  });
271
355
 
272
- set({
273
- connectedWallets: nextConnectedWalletsWithUpdatedSelectedStatus,
274
- });
275
- },
276
- newWalletConnected: async (accounts) => {
277
- eventEmitter.emit(WidgetEvents.WalletEvent, {
278
- type: WalletEventTypes.CONNECT,
279
- payload: { walletType: accounts[0].walletType, accounts },
280
- });
281
-
282
- get().addConnectedWallet(accounts);
283
-
284
- void get().fetchBalances(accounts);
285
- },
286
- removeBalancesForWallet: (walletType, options) => {
287
- let walletsNeedsToBeRemoved = get().connectedWallets.filter(
288
- (connectedWallet) => connectedWallet.walletType === walletType
289
- );
290
- /*
291
- * We only need to delete balances where there is no connected wallets with same chain and address for that balance.
292
- * Consider both Metamask and Solana having support for `0xblahblahblahblah` for Ethereum.
293
- * If Phantom is disconnecting, we should keep the balance since Metamask has access to same address yet.
294
- * So we only delete balance when there is no connected wallet that has access to that specific chain and address.
295
- */
296
- get().connectedWallets.forEach((connectedWallet) => {
297
- if (connectedWallet.walletType !== walletType) {
356
+ if (!!options?.chains && options.chains.length > 0) {
298
357
  walletsNeedsToBeRemoved = walletsNeedsToBeRemoved.filter((wallet) => {
299
- const isAnotherWalletHasSameAddressAndChain =
300
- wallet.chain === connectedWallet.chain &&
301
- wallet.address === connectedWallet.address;
302
- return !isAnotherWalletHasSameAddressAndChain;
358
+ return options.chains?.includes(wallet.chain);
303
359
  });
304
360
  }
305
- });
306
361
 
307
- if (!!options?.chains && options.chains.length > 0) {
308
- walletsNeedsToBeRemoved = walletsNeedsToBeRemoved.filter((wallet) => {
309
- return options.chains?.includes(wallet.chain);
362
+ if (!!options?.namespaces && options.namespaces.length > 0) {
363
+ walletsNeedsToBeRemoved = walletsNeedsToBeRemoved.filter((wallet) => {
364
+ if (wallet.namespace) {
365
+ return options.namespaces?.includes(wallet.namespace);
366
+ }
367
+ return false;
368
+ });
369
+ }
370
+
371
+ const nextBalancesState: BalanceState = {};
372
+ let nextAggregatedBalanceState: AggregatedBalanceState =
373
+ get()._aggregatedBalances;
374
+ const currentBalancesState = get()._balances;
375
+ const balanceKeys = Object.keys(currentBalancesState) as BalanceKey[];
376
+
377
+ balanceKeys.forEach((key) => {
378
+ const asset = extractAssetFromBalanceKey(key);
379
+
380
+ const shouldBalanceBeRemoved = !!walletsNeedsToBeRemoved.find(
381
+ (wallet) =>
382
+ createBalanceKey(wallet.address, {
383
+ address: asset.address,
384
+ blockchain: wallet.chain,
385
+ symbol: asset.symbol,
386
+ }) === key
387
+ );
388
+
389
+ // if a balance should be removed, we need to remove its caches in _aggregatedBalances as wel.
390
+ if (shouldBalanceBeRemoved) {
391
+ nextAggregatedBalanceState = removeBalanceFromAggregatedBalance(
392
+ nextAggregatedBalanceState,
393
+ key
394
+ );
395
+ } else {
396
+ nextBalancesState[key] = currentBalancesState[key];
397
+ }
310
398
  });
311
- }
312
399
 
313
- const nextBalancesState: BalanceState = {};
314
- let nextAggregatedBalanceState: AggregatedBalanceState =
315
- get()._aggregatedBalances;
316
- const currentBalancesState = get()._balances;
317
- const balanceKeys = Object.keys(currentBalancesState) as BalanceKey[];
318
-
319
- balanceKeys.forEach((key) => {
320
- const asset = extractAssetFromBalanceKey(key);
321
-
322
- const shouldBalanceBeRemoved = !!walletsNeedsToBeRemoved.find(
323
- (wallet) =>
324
- createBalanceKey(wallet.address, {
325
- address: asset.address,
326
- blockchain: wallet.chain,
327
- symbol: asset.symbol,
328
- }) === key
329
- );
400
+ set({
401
+ _balances: nextBalancesState,
402
+ _aggregatedBalances: nextAggregatedBalanceState,
403
+ });
404
+ },
405
+ disconnectNamespaces: (walletType, requestedNamesapces) => {
406
+ const isTargetWalletExistsInConnectedWallets =
407
+ get().connectedWallets.find(
408
+ (wallet) => wallet.walletType === walletType
409
+ );
330
410
 
331
- if (!shouldBalanceBeRemoved) {
332
- nextBalancesState[key] = currentBalancesState[key];
333
- }
411
+ if (isTargetWalletExistsInConnectedWallets) {
412
+ // This should be called before updating connectedWallets since we need the old state to remove balances.
413
+ get().removeBalancesForWallet(walletType, {
414
+ namespaces: requestedNamesapces,
415
+ });
416
+
417
+ get()._changeSelectedWalletIfNeededOnRemove(walletType, {
418
+ namespaces: requestedNamesapces,
419
+ });
334
420
 
335
- // if a balance should be removed, we need to remove its caches in _aggregatedBalances as wel.
336
- if (shouldBalanceBeRemoved) {
337
- nextAggregatedBalanceState = removeBalanceFromAggregatedBalance(
338
- nextAggregatedBalanceState,
339
- key
421
+ const nextConnectedWallets = get().connectedWallets.filter(
422
+ (connectedWallet) => {
423
+ if (connectedWallet.namespace) {
424
+ const targetWalletAndNamespace =
425
+ connectedWallet.walletType === walletType &&
426
+ requestedNamesapces.includes(connectedWallet.namespace);
427
+
428
+ // If the wallet and namespace matched, we should filter it out.
429
+ return !targetWalletAndNamespace;
430
+ }
431
+ /*
432
+ * if a connected wallet hasn't `namesapce`, it means it legacy.
433
+ * legacy will not reach this method, but for we check anyways
434
+ */
435
+ return true;
436
+ }
340
437
  );
438
+
439
+ set({
440
+ connectedWallets: nextConnectedWallets,
441
+ });
341
442
  }
342
- });
343
- set({
344
- _balances: nextBalancesState,
345
- _aggregatedBalances: nextAggregatedBalanceState,
346
- });
347
- },
348
- disconnectWallet: (walletType) => {
349
- const isTargetWalletExistsInConnectedWallets = get().connectedWallets.find(
350
- (wallet) => wallet.walletType === walletType
351
- );
352
- if (isTargetWalletExistsInConnectedWallets) {
443
+ },
444
+ disconnectWallet: (walletType) => {
445
+ const isTargetWalletExistsInConnectedWallets =
446
+ get().connectedWallets.find(
447
+ (wallet) => wallet.walletType === walletType
448
+ );
449
+ /*
450
+ * Previously DISCONNECT event was being emitted if target wallet existed in connected wallets.
451
+ * Considering that connected wallets get clear on namespace disconnect in hub,
452
+ * now emitting this event is done without checking for connected wallets to be compatible with hub.
453
+ */
353
454
  eventEmitter.emit(WidgetEvents.WalletEvent, {
354
455
  type: WalletEventTypes.DISCONNECT,
355
456
  payload: { walletType },
356
457
  });
458
+ if (isTargetWalletExistsInConnectedWallets) {
459
+ // This should be called before updating connectedWallets since we need the old state to remove balances.
460
+ get().removeBalancesForWallet(walletType);
357
461
 
358
- // This should be called before updating connectedWallets since we need the old state to remove balances.
359
- get().removeBalancesForWallet(walletType);
462
+ get()._changeSelectedWalletIfNeededOnRemove(walletType);
360
463
 
361
- let targetWalletWasSelectedForBlockchains = get()
362
- .connectedWallets.filter(
464
+ // Remove target wallet from connectedWallets
465
+ const nextConnectedWallets = get().connectedWallets.filter(
466
+ (connectedWallet) => connectedWallet.walletType !== walletType
467
+ );
468
+
469
+ set({
470
+ connectedWallets: nextConnectedWallets,
471
+ });
472
+ }
473
+ },
474
+ /*
475
+ * If we are disconnecting a wallet that has `selected` for some blockchains,
476
+ * For those blockchains we will fallback to first connected wallet
477
+ * which means selected wallet will change.
478
+ */
479
+ _changeSelectedWalletIfNeededOnRemove: (walletType, options) => {
480
+ let connectedWallets = get().connectedWallets;
481
+
482
+ if (options?.namespaces && options.namespaces.length > 0) {
483
+ connectedWallets = connectedWallets.filter(
484
+ (connectedWallet) =>
485
+ !!connectedWallet.namespace &&
486
+ options.namespaces?.includes(connectedWallet.namespace)
487
+ );
488
+ }
489
+
490
+ let targetWalletWasSelectedForBlockchains = connectedWallets
491
+ .filter(
363
492
  (connectedWallet) =>
364
493
  connectedWallet.selected &&
365
494
  connectedWallet.walletType === walletType
366
495
  )
367
496
  .map((connectedWallet) => connectedWallet.chain);
368
497
 
369
- // Remove target wallet from connectedWallets
370
- let nextConnectedWallets = get().connectedWallets.filter(
371
- (connectedWallet) => connectedWallet.walletType !== walletType
372
- );
373
-
374
- /*
375
- * If we are disconnecting a wallet that has `selected` for some blockchains,
376
- * For those blockchains we will fallback to first connected wallet
377
- * which means selected wallet will change.
378
- */
379
498
  if (targetWalletWasSelectedForBlockchains.length > 0) {
380
- nextConnectedWallets = nextConnectedWallets.map((connectedWallet) => {
381
- if (
382
- targetWalletWasSelectedForBlockchains.includes(
383
- connectedWallet.chain
384
- )
385
- ) {
386
- targetWalletWasSelectedForBlockchains =
387
- targetWalletWasSelectedForBlockchains.filter(
388
- (blockchain) => blockchain !== connectedWallet.chain
389
- );
390
- return {
391
- ...connectedWallet,
392
- selected: true,
393
- };
499
+ const nextConnectedWallets = get().connectedWallets.map(
500
+ (connectedWallet) => {
501
+ if (
502
+ targetWalletWasSelectedForBlockchains.includes(
503
+ connectedWallet.chain
504
+ )
505
+ ) {
506
+ targetWalletWasSelectedForBlockchains =
507
+ targetWalletWasSelectedForBlockchains.filter(
508
+ (blockchain) => blockchain !== connectedWallet.chain
509
+ );
510
+ return {
511
+ ...connectedWallet,
512
+ selected: true,
513
+ };
514
+ }
515
+
516
+ return connectedWallet;
394
517
  }
518
+ );
395
519
 
396
- return connectedWallet;
520
+ set({
521
+ connectedWallets: nextConnectedWallets,
397
522
  });
398
523
  }
524
+ },
525
+ clearConnectedWallet: () => set({ connectedWallets: [] }),
526
+ fetchBalances: async (accounts, options) => {
527
+ // All the `accounts` have same `walletType` so we can pick the first one.
528
+ const walletType = accounts[0].walletType;
399
529
 
400
- set({
401
- connectedWallets: nextConnectedWallets,
402
- });
403
- }
404
- },
405
- clearConnectedWallet: () => set({ connectedWallets: [] }),
406
- fetchBalances: async (accounts, options) => {
407
- // All the `accounts` have same `walletType` so we can pick the first one.
408
- const walletType = accounts[0].walletType;
409
-
410
- get().setConnectedWalletAsRefetching(walletType);
411
-
412
- const addressesToFetch = accounts.map((account) => ({
413
- address: account.address,
414
- blockchain: account.chain,
415
- }));
416
- const response = await httpService().getWalletsDetails(addressesToFetch);
417
-
418
- const listWalletsWithBalances = response.wallets;
419
-
420
- if (listWalletsWithBalances) {
421
- const { retryOnFailedBalances = true } = options || {};
422
- if (retryOnFailedBalances) {
423
- const failedWallets: Wallet[] = listWalletsWithBalances
424
- .filter((wallet) => wallet.failed)
425
- .map((wallet) => ({
426
- chain: wallet.blockChain,
427
- walletType: walletType,
428
- address: wallet.address,
429
- }));
430
- if (failedWallets.length > 0) {
431
- void get().fetchBalances(failedWallets, {
432
- retryOnFailedBalances: false,
433
- });
434
- }
530
+ get().setConnectedWalletAsRefetching(accounts);
531
+
532
+ const addressesToFetch = accounts.map((account) => ({
533
+ address: account.address,
534
+ blockchain: account.chain,
535
+ }));
536
+
537
+ let response;
538
+ try {
539
+ response = await httpService().getWalletsDetails(addressesToFetch);
540
+ } catch (e) {
541
+ get().setConnectedWalletHasError(accounts);
542
+ throw new Error(`Request for fetching balances failed.`, { cause: e });
435
543
  }
436
544
 
437
- let nextBalances: BalanceState = {};
438
- let nextAggregatedBalances: AggregatedBalanceState =
439
- get()._aggregatedBalances;
440
- listWalletsWithBalances.forEach((wallet) => {
441
- if (wallet.failed) {
442
- return;
545
+ const walletsDetails = response.wallets;
546
+
547
+ if (walletsDetails) {
548
+ const { retryOnFailedBalances = true } = options || {};
549
+ if (retryOnFailedBalances) {
550
+ const failedWallets: Wallet[] = walletsDetails
551
+ .filter((wallet) => wallet.failed)
552
+ .map((wallet) => ({
553
+ chain: wallet.blockChain,
554
+ walletType: walletType,
555
+ address: wallet.address,
556
+ }));
557
+ if (failedWallets.length > 0) {
558
+ void get().fetchBalances(failedWallets, {
559
+ retryOnFailedBalances: false,
560
+ });
561
+ }
443
562
  }
444
563
 
445
- const balancesForWallet = createBalanceStateForNewAccount(wallet, get);
564
+ let nextBalances: BalanceState = {};
565
+ let nextAggregatedBalances: AggregatedBalanceState =
566
+ get()._aggregatedBalances;
567
+ walletsDetails.forEach((wallet) => {
568
+ if (wallet.failed) {
569
+ return;
570
+ }
446
571
 
447
- nextAggregatedBalances = updateAggregatedBalanceStateForNewAccount(
448
- nextAggregatedBalances,
449
- balancesForWallet
450
- );
572
+ // Remove old balances for current wallet and blockchain
573
+ get().removeBalancesForWallet(walletType, {
574
+ chains: [wallet.blockChain],
575
+ });
451
576
 
452
- nextBalances = {
453
- ...nextBalances,
454
- ...balancesForWallet,
455
- };
456
- });
577
+ /*
578
+ * Check if after fetching balance for an account, the account still exists.
579
+ * (It might get disconnected while fetching balances is pending)
580
+ */
581
+ if (
582
+ !get().connectedWallets.find(
583
+ (connectedWallet) =>
584
+ connectedWallet.walletType === walletType &&
585
+ connectedWallet.address === wallet.address &&
586
+ connectedWallet.chain === wallet.blockChain
587
+ )
588
+ ) {
589
+ return;
590
+ }
457
591
 
458
- set((state) => ({
459
- _balances: {
460
- ...state._balances,
461
- ...nextBalances,
462
- },
463
- _aggregatedBalances: nextAggregatedBalances,
464
- }));
592
+ const balancesForWallet = createBalanceStateForNewAccount(
593
+ wallet,
594
+ get
595
+ );
465
596
 
466
- get().setConnectedWalletRetrievedData(walletType);
467
- } else {
468
- get().setConnectedWalletHasError(walletType);
469
- throw new Error(
470
- `We couldn't fetch your account balances. Seem there is no information on blockchain for them yet.`
471
- );
472
- }
473
- },
474
- getBalances: () => {
475
- return get()._balances;
476
- },
477
- getBalanceFor: (token) => {
478
- const balances = get().getBalances();
597
+ nextAggregatedBalances = updateAggregatedBalanceStateForNewAccount(
598
+ nextAggregatedBalances,
599
+ balancesForWallet
600
+ );
479
601
 
480
- /*
481
- * The old implementation wasn't considering user's address.
482
- * it can be problematic when two separate address has same token, both of them will override on same key.
483
- *
484
- * For keeping the same behavior, here we pick the most amount and also will not consider user's address in key.
485
- */
602
+ nextBalances = {
603
+ ...nextBalances,
604
+ ...balancesForWallet,
605
+ };
606
+ });
486
607
 
487
- // Note: balance key is created using asset key + wallet address
488
- const assetKey = createAssetKey(token);
489
- const targetBalanceKeys = get()._aggregatedBalances[assetKey] || [];
608
+ set((state) => ({
609
+ _balances: {
610
+ ...state._balances,
611
+ ...nextBalances,
612
+ },
613
+ _aggregatedBalances: nextAggregatedBalances,
614
+ }));
615
+
616
+ get().setConnectedWalletRetrievedData(accounts, walletsDetails);
617
+ } else {
618
+ get().setConnectedWalletHasError(accounts);
619
+ throw new Error(
620
+ `We couldn't fetch your account balances. Seem there is no information on blockchain for them yet.`
621
+ );
622
+ }
623
+ },
624
+ getBalances: () => {
625
+ return get()._balances;
626
+ },
627
+ getBalanceFor: (token) => {
628
+ const balances = get().getBalances();
490
629
 
491
- if (targetBalanceKeys.length === 0) {
492
- return null;
493
- } else if (targetBalanceKeys.length === 1) {
494
- const targetKey = targetBalanceKeys[0];
495
- return balances[targetKey];
496
- }
630
+ /*
631
+ * The old implementation wasn't considering user's address.
632
+ * it can be problematic when two separate address has same token, both of them will override on same key.
633
+ *
634
+ * For keeping the same behavior, here we pick the most amount and also will not consider user's address in key.
635
+ */
497
636
 
498
- // If there are multiple balances for an specific token, we pick the maximum.
499
- const firstTargetBalance = balances[targetBalanceKeys[0]];
500
- let maxBalance: Balance = firstTargetBalance;
501
- targetBalanceKeys.forEach((targetBalanceKey) => {
502
- const currentBalance = balances[targetBalanceKey];
503
- const currentBalanceAmount = new BigNumber(currentBalance.amount);
504
- const prevBalanceAmount = new BigNumber(maxBalance.amount);
637
+ // Note: balance key is created using asset key + wallet address
638
+ const assetKey = createAssetKey(token);
639
+ const targetBalanceKeys = get()._aggregatedBalances[assetKey] || [];
505
640
 
506
- if (currentBalanceAmount.isGreaterThan(prevBalanceAmount)) {
507
- maxBalance = currentBalance;
641
+ if (targetBalanceKeys.length === 0) {
642
+ return null;
643
+ } else if (targetBalanceKeys.length === 1) {
644
+ const targetKey = targetBalanceKeys[0];
645
+ return balances[targetKey];
508
646
  }
509
- });
510
- return maxBalance;
511
- },
512
- getBalancesForWalletAddress: (address: string) => {
513
- const balances = get().getBalances();
514
- const balanceKeys = Object.keys(balances) as BalanceKey[];
515
-
516
- const balancesForTargetWalletAddress = balanceKeys.reduce(
517
- (output, balanceKey) => {
518
- const balance = balances[balanceKey];
519
-
520
- const [, , , balanceWalletAddreess] = balanceKey.split('-');
521
- if (balanceWalletAddreess === address) {
522
- output[balanceKey] = balance;
523
- }
524
-
525
- return output;
526
- },
527
- {} as BalanceState
528
- );
529
647
 
530
- return balancesForTargetWalletAddress;
531
- },
648
+ // If there are multiple balances for an specific token, we pick the maximum.
649
+ const firstTargetBalance = balances[targetBalanceKeys[0]];
650
+ let maxBalance: Balance = firstTargetBalance;
651
+ targetBalanceKeys.forEach((targetBalanceKey) => {
652
+ const currentBalance = balances[targetBalanceKey];
653
+ const currentBalanceAmount = new BigNumber(currentBalance.amount);
654
+ const prevBalanceAmount = new BigNumber(maxBalance.amount);
532
655
 
533
- getConnectedWalletsDetails: () => {
534
- const connectedWallets = get().connectedWallets;
535
- return connectedWallets.map((wallet) => {
536
- const balances = get().getBalancesForWalletAddress(wallet.address);
537
- const balancesKeys = Object.keys(balances) as BalanceKey[];
538
-
539
- return {
540
- ...wallet,
541
- balances: balancesKeys.reduce((output, balanceKey) => {
656
+ if (currentBalanceAmount.isGreaterThan(prevBalanceAmount)) {
657
+ maxBalance = currentBalance;
658
+ }
659
+ });
660
+ return maxBalance;
661
+ },
662
+ getBalancesForWalletAddress: (address: string) => {
663
+ const balances = get().getBalances();
664
+ const balanceKeys = Object.keys(balances) as BalanceKey[];
665
+
666
+ const balancesForTargetWalletAddress = balanceKeys.reduce(
667
+ (output, balanceKey) => {
542
668
  const balance = balances[balanceKey];
543
- const asset = extractAssetFromBalanceKey(balanceKey);
544
-
545
- if (asset.blockchain === wallet.chain) {
546
- const token = get().findToken(asset);
547
-
548
- const amount = balance.amount
549
- ? new BigNumber(balance.amount).shiftedBy(-balance.decimals)
550
- : ZERO;
551
669
 
552
- output.push({
553
- chain: wallet.chain,
554
- symbol: asset.symbol,
555
- ticker: asset.symbol,
556
- address: asset.address,
557
- rawAmount: balance.amount,
558
- decimal: balance.decimals,
559
- amount: amount.toString(),
560
- logo: token?.image || null,
561
- usdPrice: token?.usdPrice || null,
562
- });
670
+ const [, , , balanceWalletAddreess] =
671
+ balanceKey.split(BALANCE_SEPARATOR);
672
+ if (balanceWalletAddreess === address) {
673
+ output[balanceKey] = balance;
563
674
  }
564
675
 
565
676
  return output;
566
- }, [] as DeprecatedTokenBalance[]),
567
- };
568
- });
569
- },
570
- });
677
+ },
678
+ {} as BalanceState
679
+ );
680
+
681
+ return balancesForTargetWalletAddress;
682
+ },
683
+ getConnectedWalletsDetails: () => {
684
+ return memoizedConnectedWalletsWithNestedBalances(() => {
685
+ const connectedWallets = get().connectedWallets;
686
+ return connectedWallets.map((wallet) => {
687
+ const balances = get().getBalancesForWalletAddress(wallet.address);
688
+ const balancesKeys = Object.keys(balances) as BalanceKey[];
689
+
690
+ return {
691
+ ...wallet,
692
+ balances: balancesKeys.reduce((output, balanceKey) => {
693
+ const balance = balances[balanceKey];
694
+ const asset = extractAssetFromBalanceKey(balanceKey);
695
+
696
+ if (asset.blockchain === wallet.chain) {
697
+ const token = get().findToken(asset);
698
+
699
+ const amount = balance.amount
700
+ ? new BigNumber(balance.amount).shiftedBy(-balance.decimals)
701
+ : ZERO;
702
+
703
+ output.push({
704
+ chain: wallet.chain,
705
+ symbol: asset.symbol,
706
+ ticker: asset.symbol,
707
+ address: asset.address,
708
+ rawAmount: balance.amount,
709
+ decimal: balance.decimals,
710
+ amount: amount.toString(),
711
+ logo: token?.image || null,
712
+ usdPrice: token?.usdPrice || null,
713
+ });
714
+ }
715
+
716
+ return output;
717
+ }, [] as DeprecatedTokenBalance[]),
718
+ };
719
+ });
720
+ }, get().lastUpdatedAt);
721
+ },
722
+ })
723
+ );