@rango-dev/widget-embedded 0.39.1-next.16 → 0.39.1-next.18

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.
@@ -1,7 +1,6 @@
1
1
  import type { AppStoreState } from './types';
2
2
  import type { Namespace } from '@rango-dev/wallets-core/namespaces/common';
3
3
  import type { Token, WalletDetail } from 'rango-sdk';
4
- import type { StateCreator } from 'zustand';
5
4
 
6
5
  import BigNumber from 'bignumber.js';
7
6
 
@@ -15,7 +14,9 @@ import {
15
14
  WalletEventTypes,
16
15
  WidgetEvents,
17
16
  } from '../../types';
17
+ import { memoizedResult } from '../../utils/common';
18
18
  import { isAccountAndWalletMatched } from '../../utils/wallets';
19
+ import { keepLastUpdated } from '../middlewares/keepLastUpdated';
19
20
  import {
20
21
  createAssetKey,
21
22
  createBalanceKey,
@@ -83,11 +84,12 @@ export interface WalletsSlice {
83
84
  _aggregatedBalances: AggregatedBalanceState;
84
85
  connectedWallets: ConnectedWallet[];
85
86
  fetchingWallets: boolean;
87
+ lastUpdatedAt: number;
86
88
 
87
- setConnectedWalletAsRefetching: (walletType: string) => void;
88
- setConnectedWalletHasError: (walletType: string) => void;
89
+ setConnectedWalletAsRefetching: (accounts: Wallet[]) => void;
90
+ setConnectedWalletHasError: (accounts: Wallet[]) => void;
89
91
  setConnectedWalletRetrievedData: (
90
- walletType: string,
92
+ accounts: Wallet[],
91
93
  walletsDetails: WalletDetail[]
92
94
  ) => void;
93
95
  removeBalancesForWallet: (
@@ -108,10 +110,15 @@ export interface WalletsSlice {
108
110
  accounts: Wallet[],
109
111
  namespace?: Namespace
110
112
  ) => Promise<void>;
113
+ disconnectNamespaces: (walletType: string, namespaces: Namespace[]) => void;
111
114
  /**
112
115
  * Disconnect a wallet and clean up balances after that.
113
116
  */
114
117
  disconnectWallet: (walletType: string) => void;
118
+ _changeSelectedWalletIfNeededOnRemove: (
119
+ walletType: string,
120
+ options?: { namespaces?: Namespace[] }
121
+ ) => void;
115
122
  clearConnectedWallet: () => void;
116
123
  fetchBalances: (
117
124
  accounts: Wallet[],
@@ -128,503 +135,589 @@ export interface WalletsSlice {
128
135
  getConnectedWalletsDetails: () => DeprecatedWalletDetail[];
129
136
  }
130
137
 
131
- export const createWalletsSlice: StateCreator<
132
- AppStoreState,
133
- [],
134
- [],
135
- WalletsSlice
136
- > = (set, get) => ({
137
- _balances: {},
138
- _aggregatedBalances: {},
139
- connectedWallets: [],
140
- fetchingWallets: false,
141
-
142
- // Actions
143
- setConnectedWalletAsRefetching: (walletType: string) => {
144
- set((state) => {
145
- return {
146
- fetchingWallets: true,
147
- connectedWallets: state.connectedWallets.map((connectedWallet) => {
148
- if (connectedWallet.walletType === walletType) {
149
- return {
150
- ...connectedWallet,
151
- loading: true,
152
- error: false,
153
- };
154
- }
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
+ );
155
240
 
156
- return connectedWallet;
157
- }),
158
- };
159
- });
160
- },
161
- setConnectedWalletRetrievedData: (
162
- walletType: string,
163
- walletsDetails: WalletDetail[]
164
- ) => {
165
- set((state) => {
166
- return {
167
- fetchingWallets: false,
168
- connectedWallets: state.connectedWallets.map((connectedWallet) => {
169
- if (connectedWallet.walletType === walletType) {
170
- return {
171
- ...connectedWallet,
172
- loading: false,
173
- error: false,
174
- explorerUrl:
175
- matchWalletDetailsWithConnectedWallet(
176
- connectedWallet,
177
- walletsDetails
178
- )?.explorerUrl || connectedWallet.explorerUrl,
179
- };
180
- }
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
+ );
181
258
 
182
- return connectedWallet;
183
- }),
184
- };
185
- });
186
- },
187
- setConnectedWalletHasError: (walletType: string) => {
188
- set((state) => {
189
- return {
190
- fetchingWallets: false,
191
- connectedWallets: state.connectedWallets.map((connectedWallet) => {
192
- if (connectedWallet.walletType === walletType) {
193
259
  return {
194
- ...connectedWallet,
260
+ address: account.address,
261
+ chain: account.chain,
262
+ explorerUrl: null,
263
+ walletType: account.walletType,
264
+ selected: shouldMarkWalletAsSelected,
265
+ namespace: namespace,
195
266
  loading: false,
196
- error: true,
267
+ error: false,
197
268
  };
198
269
  }
270
+ );
199
271
 
200
- return connectedWallet;
201
- }),
202
- };
203
- });
204
- },
205
- addConnectedWallet: (accounts, namespace) => {
206
- /*
207
- * When we are going to add a new account, there are two thing that can be happens:
208
- * 1. Wallet hasn't add yet.
209
- * 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.
210
- *
211
- * For handling this, we need to only keep not-added-account, then only add those.
212
- *
213
- * Note:
214
- * The second option would be useful for hub particularly.
215
- */
216
- const connectedWallets = get().connectedWallets;
217
- const walletsNeedToBeAdded = accounts.filter(
218
- (account) =>
219
- !connectedWallets.some((connectedWallet) =>
220
- isAccountAndWalletMatched(account, connectedWallet)
221
- )
222
- );
223
-
224
- if (walletsNeedToBeAdded.length > 0) {
225
- const newConnectedWallets: ConnectedWallet[] = walletsNeedToBeAdded.map(
226
- (account) => {
272
+ set((state) => {
227
273
  /*
228
- * 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.
229
- * 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
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.
230
277
  */
231
- const shouldMarkWalletAsSelected = !connectedWallets.some(
232
- (connectedWallet) =>
233
- connectedWallet.chain === account.chain &&
234
- connectedWallet.selected &&
235
- /**
236
- * Sometimes, the connect function can be called multiple times for a particular wallet type when using the auto-connect feature.
237
- * This check is there to make sure the chosen wallet doesn't end up unselected.
238
- */
239
- connectedWallet.walletType !== account.walletType
240
- );
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
+ });
241
287
 
242
288
  return {
243
- address: account.address,
244
- chain: account.chain,
245
- explorerUrl: null,
246
- walletType: account.walletType,
247
- selected: shouldMarkWalletAsSelected,
248
- namespace: namespace,
249
- loading: false,
250
- error: false,
289
+ connectedWallets: [
290
+ ...connectedWalletsWithoutSameWalletAndBlockchain,
291
+ ...newConnectedWallets,
292
+ ],
251
293
  };
252
- }
253
- );
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 };
316
+ }
254
317
 
255
- set((state) => {
256
- /*
257
- * If wallet connected before and only need to update the address we should remove the old value and then add new conncted value.
258
- * This scenario happens when user wants to change account inside the wallet.
259
- * So the assumption here is the wallet has only one active address for a blockchain at the moment.
260
- */
261
- const connectedWalletsWithoutSameWalletAndBlockchain =
262
- state.connectedWallets.filter((currentConnectedWallet) => {
263
- return !newConnectedWallets.some(
264
- (newConnectedWallet) =>
265
- newConnectedWallet.walletType ===
266
- currentConnectedWallet.walletType &&
267
- newConnectedWallet.chain === currentConnectedWallet.chain
268
- );
269
- });
318
+ return connectedWallet;
319
+ });
270
320
 
271
- return {
272
- connectedWallets: [
273
- ...connectedWalletsWithoutSameWalletAndBlockchain,
274
- ...newConnectedWallets,
275
- ],
276
- };
321
+ set({
322
+ connectedWallets: nextConnectedWalletsWithUpdatedSelectedStatus,
277
323
  });
278
- }
279
- },
280
- setWalletsAsSelected: (wallets) => {
281
- const nextConnectedWalletsWithUpdatedSelectedStatus =
282
- get().connectedWallets.map((connectedWallet) => {
283
- const walletSelected = !!wallets.find(
284
- (wallet) =>
285
- wallet.chain === connectedWallet.chain &&
286
- wallet.walletType !== connectedWallet.walletType &&
287
- connectedWallet.selected
288
- );
289
- const walletNotSelected = !!wallets.find(
290
- (wallet) =>
291
- wallet.chain === connectedWallet.chain &&
292
- wallet.walletType === connectedWallet.walletType &&
293
- !connectedWallet.selected
294
- );
295
- if (walletSelected) {
296
- return { ...connectedWallet, selected: false };
297
- } else if (walletNotSelected) {
298
- return { ...connectedWallet, selected: true };
299
- }
324
+ },
325
+ newWalletConnected: async (accounts, namespace) => {
326
+ eventEmitter.emit(WidgetEvents.WalletEvent, {
327
+ type: WalletEventTypes.CONNECT,
328
+ payload: { walletType: accounts[0].walletType, accounts },
329
+ });
330
+
331
+ get().addConnectedWallet(accounts, namespace);
300
332
 
301
- return connectedWallet;
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
+ });
353
+ }
302
354
  });
303
355
 
304
- set({
305
- connectedWallets: nextConnectedWalletsWithUpdatedSelectedStatus,
306
- });
307
- },
308
- newWalletConnected: async (accounts, namespace) => {
309
- eventEmitter.emit(WidgetEvents.WalletEvent, {
310
- type: WalletEventTypes.CONNECT,
311
- payload: { walletType: accounts[0].walletType, accounts },
312
- });
313
-
314
- get().addConnectedWallet(accounts, namespace);
315
-
316
- void get().fetchBalances(accounts);
317
- },
318
- removeBalancesForWallet: (walletType, options) => {
319
- let walletsNeedsToBeRemoved = get().connectedWallets.filter(
320
- (connectedWallet) => connectedWallet.walletType === walletType
321
- );
322
- /*
323
- * We only need to delete balances where there is no connected wallets with same chain and address for that balance.
324
- * Consider both Metamask and Solana having support for `0xblahblahblahblah` for Ethereum.
325
- * If Phantom is disconnecting, we should keep the balance since Metamask has access to same address yet.
326
- * So we only delete balance when there is no connected wallet that has access to that specific chain and address.
327
- */
328
- get().connectedWallets.forEach((connectedWallet) => {
329
- if (connectedWallet.walletType !== walletType) {
356
+ if (!!options?.chains && options.chains.length > 0) {
330
357
  walletsNeedsToBeRemoved = walletsNeedsToBeRemoved.filter((wallet) => {
331
- const isAnotherWalletHasSameAddressAndChain =
332
- wallet.chain === connectedWallet.chain &&
333
- wallet.address === connectedWallet.address;
334
- return !isAnotherWalletHasSameAddressAndChain;
358
+ return options.chains?.includes(wallet.chain);
335
359
  });
336
360
  }
337
- });
338
361
 
339
- if (!!options?.chains && options.chains.length > 0) {
340
- walletsNeedsToBeRemoved = walletsNeedsToBeRemoved.filter((wallet) => {
341
- return options.chains?.includes(wallet.chain);
342
- });
343
- }
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[];
344
376
 
345
- if (!!options?.namespaces && options.namespaces.length > 0) {
346
- walletsNeedsToBeRemoved = walletsNeedsToBeRemoved.filter((wallet) => {
347
- if (wallet.namespace) {
348
- return options.namespaces?.includes(wallet.namespace);
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];
349
397
  }
350
- return false;
351
398
  });
352
- }
353
399
 
354
- const nextBalancesState: BalanceState = {};
355
- let nextAggregatedBalanceState: AggregatedBalanceState =
356
- get()._aggregatedBalances;
357
- const currentBalancesState = get()._balances;
358
- const balanceKeys = Object.keys(currentBalancesState) as BalanceKey[];
359
-
360
- balanceKeys.forEach((key) => {
361
- const asset = extractAssetFromBalanceKey(key);
362
-
363
- const shouldBalanceBeRemoved = !!walletsNeedsToBeRemoved.find(
364
- (wallet) =>
365
- createBalanceKey(wallet.address, {
366
- address: asset.address,
367
- blockchain: wallet.chain,
368
- symbol: asset.symbol,
369
- }) === key
370
- );
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
+ );
410
+
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
+ });
371
420
 
372
- // if a balance should be removed, we need to remove its caches in _aggregatedBalances as wel.
373
- if (shouldBalanceBeRemoved) {
374
- nextAggregatedBalanceState = removeBalanceFromAggregatedBalance(
375
- nextAggregatedBalanceState,
376
- 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
+ }
377
437
  );
378
- } else {
379
- nextBalancesState[key] = currentBalancesState[key];
438
+
439
+ set({
440
+ connectedWallets: nextConnectedWallets,
441
+ });
380
442
  }
381
- });
382
-
383
- set({
384
- _balances: nextBalancesState,
385
- _aggregatedBalances: nextAggregatedBalanceState,
386
- });
387
- },
388
- disconnectWallet: (walletType) => {
389
- const isTargetWalletExistsInConnectedWallets = get().connectedWallets.find(
390
- (wallet) => wallet.walletType === walletType
391
- );
392
- 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
+ */
393
454
  eventEmitter.emit(WidgetEvents.WalletEvent, {
394
455
  type: WalletEventTypes.DISCONNECT,
395
456
  payload: { walletType },
396
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);
461
+
462
+ get()._changeSelectedWalletIfNeededOnRemove(walletType);
463
+
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;
397
481
 
398
- // This should be called before updating connectedWallets since we need the old state to remove balances.
399
- get().removeBalancesForWallet(walletType);
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
+ }
400
489
 
401
- let targetWalletWasSelectedForBlockchains = get()
402
- .connectedWallets.filter(
490
+ let targetWalletWasSelectedForBlockchains = connectedWallets
491
+ .filter(
403
492
  (connectedWallet) =>
404
493
  connectedWallet.selected &&
405
494
  connectedWallet.walletType === walletType
406
495
  )
407
496
  .map((connectedWallet) => connectedWallet.chain);
408
497
 
409
- // Remove target wallet from connectedWallets
410
- let nextConnectedWallets = get().connectedWallets.filter(
411
- (connectedWallet) => connectedWallet.walletType !== walletType
412
- );
413
-
414
- /*
415
- * If we are disconnecting a wallet that has `selected` for some blockchains,
416
- * For those blockchains we will fallback to first connected wallet
417
- * which means selected wallet will change.
418
- */
419
498
  if (targetWalletWasSelectedForBlockchains.length > 0) {
420
- nextConnectedWallets = nextConnectedWallets.map((connectedWallet) => {
421
- if (
422
- targetWalletWasSelectedForBlockchains.includes(
423
- connectedWallet.chain
424
- )
425
- ) {
426
- targetWalletWasSelectedForBlockchains =
427
- targetWalletWasSelectedForBlockchains.filter(
428
- (blockchain) => blockchain !== connectedWallet.chain
429
- );
430
- return {
431
- ...connectedWallet,
432
- selected: true,
433
- };
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;
434
517
  }
518
+ );
435
519
 
436
- return connectedWallet;
520
+ set({
521
+ connectedWallets: nextConnectedWallets,
437
522
  });
438
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;
439
529
 
440
- set({
441
- connectedWallets: nextConnectedWallets,
442
- });
443
- }
444
- },
445
- clearConnectedWallet: () => set({ connectedWallets: [] }),
446
- fetchBalances: async (accounts, options) => {
447
- // All the `accounts` have same `walletType` so we can pick the first one.
448
- const walletType = accounts[0].walletType;
449
-
450
- get().setConnectedWalletAsRefetching(walletType);
451
-
452
- const addressesToFetch = accounts.map((account) => ({
453
- address: account.address,
454
- blockchain: account.chain,
455
- }));
456
- const response = await httpService().getWalletsDetails(addressesToFetch);
457
-
458
- const walletsDetails = response.wallets;
459
-
460
- if (walletsDetails) {
461
- const { retryOnFailedBalances = true } = options || {};
462
- if (retryOnFailedBalances) {
463
- const failedWallets: Wallet[] = walletsDetails
464
- .filter((wallet) => wallet.failed)
465
- .map((wallet) => ({
466
- chain: wallet.blockChain,
467
- walletType: walletType,
468
- address: wallet.address,
469
- }));
470
- if (failedWallets.length > 0) {
471
- void get().fetchBalances(failedWallets, {
472
- retryOnFailedBalances: false,
473
- });
474
- }
475
- }
530
+ get().setConnectedWalletAsRefetching(accounts);
476
531
 
477
- let nextBalances: BalanceState = {};
478
- let nextAggregatedBalances: AggregatedBalanceState =
479
- get()._aggregatedBalances;
480
- walletsDetails.forEach((wallet) => {
481
- if (wallet.failed) {
482
- return;
483
- }
532
+ const addressesToFetch = accounts.map((account) => ({
533
+ address: account.address,
534
+ blockchain: account.chain,
535
+ }));
484
536
 
485
- // Remove old balances for current wallet and blockchain
486
- get().removeBalancesForWallet(walletType, {
487
- chains: [wallet.blockChain],
488
- });
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 });
543
+ }
489
544
 
490
- /*
491
- * Check if after fetching balance for an account, the account still exists.
492
- * (It might get disconnected while fetching balances is pending)
493
- */
494
- if (
495
- !get().connectedWallets.find(
496
- (connectedWallet) =>
497
- connectedWallet.walletType === walletType &&
498
- connectedWallet.address === wallet.address &&
499
- connectedWallet.chain === wallet.blockChain
500
- )
501
- ) {
502
- 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
+ }
503
562
  }
504
563
 
505
- 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
+ }
506
571
 
507
- nextAggregatedBalances = updateAggregatedBalanceStateForNewAccount(
508
- nextAggregatedBalances,
509
- balancesForWallet
510
- );
572
+ // Remove old balances for current wallet and blockchain
573
+ get().removeBalancesForWallet(walletType, {
574
+ chains: [wallet.blockChain],
575
+ });
511
576
 
512
- nextBalances = {
513
- ...nextBalances,
514
- ...balancesForWallet,
515
- };
516
- });
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
+ }
517
591
 
518
- set((state) => ({
519
- _balances: {
520
- ...state._balances,
521
- ...nextBalances,
522
- },
523
- _aggregatedBalances: nextAggregatedBalances,
524
- }));
592
+ const balancesForWallet = createBalanceStateForNewAccount(
593
+ wallet,
594
+ get
595
+ );
525
596
 
526
- get().setConnectedWalletRetrievedData(walletType, walletsDetails);
527
- } else {
528
- get().setConnectedWalletHasError(walletType);
529
- throw new Error(
530
- `We couldn't fetch your account balances. Seem there is no information on blockchain for them yet.`
531
- );
532
- }
533
- },
534
- getBalances: () => {
535
- return get()._balances;
536
- },
537
- getBalanceFor: (token) => {
538
- const balances = get().getBalances();
597
+ nextAggregatedBalances = updateAggregatedBalanceStateForNewAccount(
598
+ nextAggregatedBalances,
599
+ balancesForWallet
600
+ );
539
601
 
540
- /*
541
- * The old implementation wasn't considering user's address.
542
- * it can be problematic when two separate address has same token, both of them will override on same key.
543
- *
544
- * For keeping the same behavior, here we pick the most amount and also will not consider user's address in key.
545
- */
602
+ nextBalances = {
603
+ ...nextBalances,
604
+ ...balancesForWallet,
605
+ };
606
+ });
546
607
 
547
- // Note: balance key is created using asset key + wallet address
548
- const assetKey = createAssetKey(token);
549
- const targetBalanceKeys = get()._aggregatedBalances[assetKey] || [];
608
+ set((state) => ({
609
+ _balances: {
610
+ ...state._balances,
611
+ ...nextBalances,
612
+ },
613
+ _aggregatedBalances: nextAggregatedBalances,
614
+ }));
550
615
 
551
- if (targetBalanceKeys.length === 0) {
552
- return null;
553
- } else if (targetBalanceKeys.length === 1) {
554
- const targetKey = targetBalanceKeys[0];
555
- return balances[targetKey];
556
- }
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();
629
+
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
+ */
557
636
 
558
- // If there are multiple balances for an specific token, we pick the maximum.
559
- const firstTargetBalance = balances[targetBalanceKeys[0]];
560
- let maxBalance: Balance = firstTargetBalance;
561
- targetBalanceKeys.forEach((targetBalanceKey) => {
562
- const currentBalance = balances[targetBalanceKey];
563
- const currentBalanceAmount = new BigNumber(currentBalance.amount);
564
- 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] || [];
565
640
 
566
- if (currentBalanceAmount.isGreaterThan(prevBalanceAmount)) {
567
- 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];
568
646
  }
569
- });
570
- return maxBalance;
571
- },
572
- getBalancesForWalletAddress: (address: string) => {
573
- const balances = get().getBalances();
574
- const balanceKeys = Object.keys(balances) as BalanceKey[];
575
-
576
- const balancesForTargetWalletAddress = balanceKeys.reduce(
577
- (output, balanceKey) => {
578
- const balance = balances[balanceKey];
579
-
580
- const [, , , balanceWalletAddreess] =
581
- balanceKey.split(BALANCE_SEPARATOR);
582
- if (balanceWalletAddreess === address) {
583
- output[balanceKey] = balance;
584
- }
585
-
586
- return output;
587
- },
588
- {} as BalanceState
589
- );
590
-
591
- return balancesForTargetWalletAddress;
592
- },
593
- getConnectedWalletsDetails: () => {
594
- const connectedWallets = get().connectedWallets;
595
- return connectedWallets.map((wallet) => {
596
- const balances = get().getBalancesForWalletAddress(wallet.address);
597
- const balancesKeys = Object.keys(balances) as BalanceKey[];
598
-
599
- return {
600
- ...wallet,
601
- balances: balancesKeys.reduce((output, balanceKey) => {
602
- const balance = balances[balanceKey];
603
- const asset = extractAssetFromBalanceKey(balanceKey);
604
647
 
605
- if (asset.blockchain === wallet.chain) {
606
- const token = get().findToken(asset);
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);
607
655
 
608
- const amount = balance.amount
609
- ? new BigNumber(balance.amount).shiftedBy(-balance.decimals)
610
- : ZERO;
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) => {
668
+ const balance = balances[balanceKey];
611
669
 
612
- output.push({
613
- chain: wallet.chain,
614
- symbol: asset.symbol,
615
- ticker: asset.symbol,
616
- address: asset.address,
617
- rawAmount: balance.amount,
618
- decimal: balance.decimals,
619
- amount: amount.toString(),
620
- logo: token?.image || null,
621
- usdPrice: token?.usdPrice || null,
622
- });
670
+ const [, , , balanceWalletAddreess] =
671
+ balanceKey.split(BALANCE_SEPARATOR);
672
+ if (balanceWalletAddreess === address) {
673
+ output[balanceKey] = balance;
623
674
  }
624
675
 
625
676
  return output;
626
- }, [] as DeprecatedTokenBalance[]),
627
- };
628
- });
629
- },
630
- });
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
+ );