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