@rango-dev/widget-embedded 0.30.1-next.0 → 0.30.1-next.10

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 (87) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/components/ConfirmWalletsModal/ConfirmWallets.styles.d.ts +1 -653
  3. package/dist/components/ConfirmWalletsModal/ConfirmWallets.styles.d.ts.map +1 -1
  4. package/dist/components/ConfirmWalletsModal/ConfirmWalletsModal.d.ts.map +1 -1
  5. package/dist/components/ConfirmWalletsModal/WalletList.d.ts.map +1 -1
  6. package/dist/components/CustomDestination/CustomDestination.d.ts +4 -0
  7. package/dist/components/CustomDestination/CustomDestination.d.ts.map +1 -0
  8. package/dist/components/CustomDestination/CustomDestination.styles.d.ts +495 -0
  9. package/dist/components/CustomDestination/CustomDestination.styles.d.ts.map +1 -0
  10. package/dist/components/CustomDestination/CustomDestination.types.d.ts +7 -0
  11. package/dist/components/CustomDestination/CustomDestination.types.d.ts.map +1 -0
  12. package/dist/components/Quote/Quote.styles.d.ts +3 -3
  13. package/dist/components/RefreshModal/RefreshModal.styles.d.ts +3 -3
  14. package/dist/components/SwapDetailsModal/SwapDetailsModal.WalletState.d.ts.map +1 -1
  15. package/dist/components/WalletDerivationPathModal/WalletDerivationPathModal.d.ts.map +1 -1
  16. package/dist/components/WalletNamespacesModal/WalletNamespacesModal.d.ts.map +1 -1
  17. package/dist/containers/Wallets/Wallets.d.ts.map +1 -1
  18. package/dist/hooks/usePrepareBlockchainList/usePrepareBlockchainList.helpers.d.ts +2 -2
  19. package/dist/hooks/useTheme.d.ts.map +1 -1
  20. package/dist/index.d.ts +4 -4
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -2
  23. package/dist/index.js.map +4 -4
  24. package/dist/pages/Home.d.ts.map +1 -1
  25. package/dist/pages/WalletsPage.d.ts.map +1 -1
  26. package/dist/services/cacheService.d.ts +21 -0
  27. package/dist/services/cacheService.d.ts.map +1 -0
  28. package/dist/store/notification.d.ts.map +1 -1
  29. package/dist/store/slices/config.d.ts +2 -1
  30. package/dist/store/slices/config.d.ts.map +1 -1
  31. package/dist/store/slices/data.d.ts +2 -1
  32. package/dist/store/slices/data.d.ts.map +1 -1
  33. package/dist/store/utils/data.d.ts +20 -0
  34. package/dist/store/utils/data.d.ts.map +1 -0
  35. package/dist/store/utils/data.test.d.ts +2 -0
  36. package/dist/store/utils/data.test.d.ts.map +1 -0
  37. package/dist/store/utils/index.d.ts +2 -0
  38. package/dist/store/utils/index.d.ts.map +1 -0
  39. package/dist/test-utils/fixtures.d.ts +1 -0
  40. package/dist/test-utils/fixtures.d.ts.map +1 -1
  41. package/dist/types/config.d.ts +5 -0
  42. package/dist/types/config.d.ts.map +1 -1
  43. package/dist/types/event.d.ts +21 -15
  44. package/dist/types/event.d.ts.map +1 -1
  45. package/dist/types/notification.d.ts +1 -0
  46. package/dist/types/notification.d.ts.map +1 -1
  47. package/dist/types/wallets.d.ts +1 -1
  48. package/dist/types/wallets.d.ts.map +1 -1
  49. package/dist/utils/events.d.ts +8 -0
  50. package/dist/utils/events.d.ts.map +1 -0
  51. package/dist/utils/settings.d.ts +1 -1
  52. package/dist/utils/swap.d.ts +1 -1
  53. package/dist/utils/swap.d.ts.map +1 -1
  54. package/dist/widget-embedded.build.json +1 -1
  55. package/package.json +10 -10
  56. package/src/components/ConfirmWalletsModal/ConfirmWallets.styles.ts +1 -49
  57. package/src/components/ConfirmWalletsModal/ConfirmWalletsModal.tsx +59 -178
  58. package/src/components/ConfirmWalletsModal/WalletList.tsx +16 -4
  59. package/src/components/CustomDestination/CustomDestination.styles.ts +45 -0
  60. package/src/components/CustomDestination/CustomDestination.tsx +148 -0
  61. package/src/components/CustomDestination/CustomDestination.types.ts +7 -0
  62. package/src/components/SwapDetailsModal/SwapDetailsModal.WalletState.tsx +28 -13
  63. package/src/components/WalletDerivationPathModal/WalletDerivationPathModal.tsx +1 -1
  64. package/src/components/WalletNamespacesModal/WalletNamespacesModal.tsx +1 -1
  65. package/src/containers/Wallets/Wallets.tsx +7 -2
  66. package/src/hooks/useTheme.ts +11 -5
  67. package/src/index.ts +4 -0
  68. package/src/pages/Home.tsx +6 -3
  69. package/src/pages/WalletsPage.tsx +25 -11
  70. package/src/services/cacheService.ts +35 -0
  71. package/src/store/notification.ts +13 -1
  72. package/src/store/slices/config.ts +39 -1
  73. package/src/store/slices/data.test.ts +84 -0
  74. package/src/store/slices/data.ts +44 -24
  75. package/src/store/utils/data.test.ts +175 -0
  76. package/src/store/utils/data.ts +117 -0
  77. package/src/store/utils/index.ts +1 -0
  78. package/src/test-utils/fixtures.ts +14 -7
  79. package/src/types/config.ts +3 -1
  80. package/src/types/event.ts +32 -16
  81. package/src/types/notification.ts +1 -0
  82. package/src/types/wallets.ts +1 -1
  83. package/src/utils/events.ts +27 -0
  84. package/src/utils/swap.ts +4 -4
  85. package/dist/constants/namespaces.d.ts +0 -8
  86. package/dist/constants/namespaces.d.ts.map +0 -1
  87. package/src/constants/namespaces.ts +0 -62
@@ -68,6 +68,33 @@ export const WalletStateContent = (props: WalletStateContentProps) => {
68
68
  }
69
69
  };
70
70
 
71
+ const handleWalletItemClick = () => {
72
+ if (!!walletType && !!walletInfo) {
73
+ if (!!walletInfo.namespaces?.length) {
74
+ if (walletInfo.namespaces.length > 1) {
75
+ setNamespacesModalState({
76
+ open: true,
77
+ providerType: walletType,
78
+ providerImage: walletInfo.img,
79
+ availableNamespaces: walletInfo.namespaces,
80
+ singleNamespace: walletInfo.singleNamespace,
81
+ });
82
+ } else if (walletInfo.needsDerivationPath) {
83
+ setDerivationPathModalState({
84
+ open: true,
85
+ providerType: walletType,
86
+ providerImage: walletInfo.img,
87
+ namespace: walletInfo.namespaces[0],
88
+ });
89
+ } else {
90
+ connect(walletType).catch((error) => debug(error));
91
+ }
92
+ } else {
93
+ connect(walletType).catch((error) => debug(error));
94
+ }
95
+ }
96
+ };
97
+
71
98
  const handleConfirmNamespaces = (selectedNamespaces: Namespace[]) => {
72
99
  if (
73
100
  !!walletInfo?.needsDerivationPath &&
@@ -117,19 +144,7 @@ export const WalletStateContent = (props: WalletStateContentProps) => {
117
144
  link={walletInfo.installLink}
118
145
  disabled={walletButtonDisabled}
119
146
  // TODO we need to show an error modal when user reject the connection
120
- onClick={async () => {
121
- if (walletInfo.namespaces) {
122
- setNamespacesModalState({
123
- open: true,
124
- providerType: walletType,
125
- providerImage: walletInfo.img,
126
- availableNamespaces: walletInfo.namespaces,
127
- singleNamespace: walletInfo.singleNamespace,
128
- });
129
- } else {
130
- connect(walletType).catch((error) => debug(error));
131
- }
132
- }}
147
+ onClick={handleWalletItemClick}
133
148
  />
134
149
  </WalletContainer>
135
150
  )}
@@ -10,10 +10,10 @@ import {
10
10
  Select,
11
11
  TextField,
12
12
  } from '@rango-dev/ui';
13
+ import { namespaces } from '@rango-dev/wallets-shared';
13
14
  import React, { useEffect, useState } from 'react';
14
15
 
15
16
  import { WIDGET_UI_ID } from '../../constants';
16
- import { namespaces } from '../../constants/namespaces';
17
17
  import { WatermarkedModal } from '../common/WatermarkedModal';
18
18
  import {
19
19
  LogoContainer,
@@ -13,10 +13,10 @@ import {
13
13
  Radio,
14
14
  RadioRoot,
15
15
  } from '@rango-dev/ui';
16
+ import { namespaces } from '@rango-dev/wallets-shared';
16
17
  import React, { useMemo, useState } from 'react';
17
18
 
18
19
  import { WIDGET_UI_ID } from '../../constants';
19
- import { namespaces } from '../../constants/namespaces';
20
20
  import { useAppStore } from '../../store/AppStore';
21
21
  import { WatermarkedModal } from '../common/WatermarkedModal';
22
22
  import { WalletImageContainer } from '../HeaderButtons/HeaderButtons.styles';
@@ -31,7 +31,12 @@ export const WidgetContext = createContext<WidgetContextInterface>({
31
31
  });
32
32
 
33
33
  function Main(props: PropsWithChildren<PropTypes>) {
34
- const { updateConfig, updateSettings, fetch: fetchMeta } = useAppStore();
34
+ const {
35
+ updateConfig,
36
+ updateSettings,
37
+ fetch: fetchMeta,
38
+ fetchStatus,
39
+ } = useAppStore();
35
40
  const blockchains = useAppStore().blockchains();
36
41
  const { findToken } = useAppStore();
37
42
  const config = useAppStore().config;
@@ -61,7 +66,7 @@ function Main(props: PropsWithChildren<PropTypes>) {
61
66
  dappConfig: props.config,
62
67
  };
63
68
  }
64
- }, [props.config]);
69
+ }, [props.config, fetchStatus]);
65
70
 
66
71
  const evmBasedChainNames = blockchains
67
72
  .filter(isEvmBlockchain)
@@ -15,6 +15,13 @@ import {
15
15
  } from '../utils/configs';
16
16
  import { customizedThemeTokens } from '../utils/ui';
17
17
 
18
+ function doesWindowPreferDarkColorScheme() {
19
+ return (
20
+ window.matchMedia &&
21
+ window.matchMedia('(prefers-color-scheme: dark)').matches
22
+ );
23
+ }
24
+
18
25
  export function useTheme(props: WidgetTheme) {
19
26
  const {
20
27
  colors,
@@ -23,7 +30,9 @@ export function useTheme(props: WidgetTheme) {
23
30
  secondaryBorderRadius = DEFAULT_SECONDARY_RADIUS,
24
31
  } = props;
25
32
 
26
- const [OSTheme, setOSTheme] = useState('light');
33
+ const [OSTheme, setOSTheme] = useState(
34
+ doesWindowPreferDarkColorScheme() ? 'dark' : 'light'
35
+ );
27
36
 
28
37
  const { theme } = useAppStore();
29
38
 
@@ -64,10 +73,7 @@ export function useTheme(props: WidgetTheme) {
64
73
  }
65
74
  };
66
75
 
67
- if (
68
- window.matchMedia &&
69
- window.matchMedia('(prefers-color-scheme: dark)').matches
70
- ) {
76
+ if (doesWindowPreferDarkColorScheme()) {
71
77
  setOSTheme('dark');
72
78
  }
73
79
 
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import type {
4
4
  BlockchainAndTokenConfig,
5
5
  QuoteEventData,
6
6
  Tokens,
7
+ UiEventData,
7
8
  WalletEventData,
8
9
  WalletInfoWithExtra,
9
10
  WidgetColors,
@@ -71,6 +72,7 @@ import { widgetEventEmitter } from './services/eventEmitter';
71
72
  import {
72
73
  WidgetEvents as MainEvents,
73
74
  QuoteEventTypes,
75
+ UiEventTypes,
74
76
  WalletEventTypes,
75
77
  WidgetEvents,
76
78
  } from './types';
@@ -108,6 +110,7 @@ export type {
108
110
  WidgetVariant,
109
111
  WalletEventData,
110
112
  QuoteEventData,
113
+ UiEventData,
111
114
  WalletInfoWithExtra,
112
115
  };
113
116
  export {
@@ -133,6 +136,7 @@ export {
133
136
  MainEvents,
134
137
  QuoteEventTypes,
135
138
  WalletEventTypes,
139
+ UiEventTypes,
136
140
  RouteEventType,
137
141
  StepEventType,
138
142
  StepExecutionEventStatus,
@@ -19,7 +19,9 @@ import { useAppStore } from '../store/AppStore';
19
19
  import { useQuoteStore } from '../store/quote';
20
20
  import { useUiStore } from '../store/ui';
21
21
  import { useWalletsStore } from '../store/wallets';
22
+ import { UiEventTypes } from '../types';
22
23
  import { isVariantExpandable } from '../utils/configs';
24
+ import { emitPreventableEvent } from '../utils/events';
23
25
  import { getSwapButtonState, isTokensIdentical } from '../utils/swap';
24
26
 
25
27
  const MainContainer = styled('div', {
@@ -115,14 +117,12 @@ export function Home() {
115
117
  navigate(route);
116
118
  }
117
119
  };
118
-
119
120
  const onClickOnQuote = (quote: SelectedQuote) => {
120
121
  if (selectedQuote?.requestId !== quote.requestId) {
121
122
  setShowQuoteWarningModal(false);
122
123
  setSelectedQuote(quote);
123
124
  }
124
125
  };
125
-
126
126
  return (
127
127
  <MainContainer>
128
128
  <Layout
@@ -138,7 +138,10 @@ export function Home() {
138
138
  fullWidth
139
139
  onClick={() => {
140
140
  if (swapButtonState.action === 'connect-wallet') {
141
- onHandleNavigation(navigationRoutes.wallets);
141
+ emitPreventableEvent(
142
+ { type: UiEventTypes.CLICK_CONNECT_WALLET },
143
+ () => onHandleNavigation(navigationRoutes.wallets)
144
+ );
142
145
  } else if (swapButtonState.action === 'confirm-warning') {
143
146
  setShowQuoteWarningModal(true);
144
147
  } else {
@@ -126,21 +126,35 @@ export function WalletsPage() {
126
126
 
127
127
  const filteredWallets = filterWalletsByCategory(list, blockchainCategory);
128
128
 
129
- const handleWalletItemClick = (type: string, wallet: WalletInfoWithExtra) => {
129
+ const handleWalletItemClick = (wallet: WalletInfoWithExtra) => {
130
130
  if (isSingleWalletActive(list, config.multiWallets)) {
131
131
  return;
132
132
  }
133
133
 
134
- if (!!wallet.namespaces && wallet.state === WalletState.DISCONNECTED) {
135
- setNamespacesModalState({
136
- open: true,
137
- providerType: type,
138
- providerImage: wallet.image,
139
- availableNamespaces: wallet.namespaces,
140
- singleNamespace: wallet.singleNamespace,
141
- });
134
+ if (
135
+ !!wallet.namespaces?.length &&
136
+ wallet.state === WalletState.DISCONNECTED
137
+ ) {
138
+ if (wallet.namespaces.length > 1) {
139
+ setNamespacesModalState({
140
+ open: true,
141
+ providerType: wallet.type,
142
+ providerImage: wallet.image,
143
+ availableNamespaces: wallet.namespaces,
144
+ singleNamespace: wallet.singleNamespace,
145
+ });
146
+ } else if (wallet.needsDerivationPath) {
147
+ setDerivationPathModalState({
148
+ open: true,
149
+ providerType: wallet.type,
150
+ providerImage: wallet.image,
151
+ namespace: wallet.namespaces[0],
152
+ });
153
+ } else {
154
+ void handleClick(wallet.type);
155
+ }
142
156
  } else {
143
- void handleClick(type);
157
+ void handleClick(wallet.type);
144
158
  }
145
159
  };
146
160
 
@@ -208,7 +222,7 @@ export function WalletsPage() {
208
222
  key={key}
209
223
  {...wallet}
210
224
  container={getContainer()}
211
- onClick={(type) => handleWalletItemClick(type, wallet)}
225
+ onClick={() => handleWalletItemClick(wallet)}
212
226
  isLoading={fetchMetaStatus === 'loading'}
213
227
  disabled={!isActiveTab}
214
228
  />
@@ -0,0 +1,35 @@
1
+ import type { Token } from 'rango-sdk';
2
+
3
+ interface CacheServiceInterface<V> {
4
+ get<K extends keyof V>(key: K): V[K] | undefined;
5
+ set<K extends keyof V>(key: K, value: V[K]): void;
6
+ remove<K extends keyof V>(key: K): void;
7
+ clear(): void;
8
+ }
9
+
10
+ class CacheService<T> implements CacheServiceInterface<T> {
11
+ #cache: Map<string, T[keyof T]> = new Map();
12
+
13
+ get<K extends keyof T>(key: K): T[K] | undefined {
14
+ return this.#cache.get(key as string) as T[K];
15
+ }
16
+
17
+ set<K extends keyof T>(key: K, value: T[K]): void {
18
+ this.#cache.set(key as string, value);
19
+ }
20
+
21
+ remove<K extends keyof T>(key: K): void {
22
+ this.#cache.delete(key as string);
23
+ }
24
+
25
+ clear(): void {
26
+ this.#cache.clear();
27
+ }
28
+ }
29
+
30
+ export type CachedEntries = {
31
+ supportedSourceTokens: Token[];
32
+ supportedDestinationTokens: Token[];
33
+ };
34
+
35
+ export const cacheService = new CacheService<CachedEntries>();
@@ -36,6 +36,7 @@ export const useNotificationStore = createSelectors(
36
36
  creationTime: Date.now(),
37
37
  requestId: route.requestId,
38
38
  route: {
39
+ creationTime: parseInt(route.creationTime),
39
40
  from: {
40
41
  blockchain: fromStep.fromBlockchain,
41
42
  address: fromStep.fromSymbolAddress,
@@ -67,7 +68,18 @@ export const useNotificationStore = createSelectors(
67
68
  getNotifications: () => {
68
69
  const { isSynced, notifications } = get();
69
70
  return isSynced
70
- ? notifications.sort((a, b) => b.creationTime - a.creationTime)
71
+ ? notifications.sort((a, b) => {
72
+ if (a.route.creationTime && !b.route.creationTime) {
73
+ return -1;
74
+ }
75
+ if (!a.route.creationTime && b.route.creationTime) {
76
+ return 1;
77
+ }
78
+ if (!a.route.creationTime && !b.route.creationTime) {
79
+ return b.creationTime - a.creationTime;
80
+ }
81
+ return b.route.creationTime - a.route.creationTime;
82
+ })
71
83
  : [];
72
84
  },
73
85
  syncNotifications: (swaps) => {
@@ -1,7 +1,11 @@
1
+ import type { DataSlice } from './data';
1
2
  import type { SettingsSlice } from './settings';
2
3
  import type { WidgetConfig } from '../../types';
3
4
  import type { StateCreatorWithInitialData } from '../app';
4
5
 
6
+ import { cacheService } from '../../services/cacheService';
7
+ import { matchTokensFromConfigWithMeta } from '../utils';
8
+
5
9
  export const DEFAULT_CONFIG: WidgetConfig = {
6
10
  apiKey: '',
7
11
  title: undefined,
@@ -54,7 +58,7 @@ export interface ConfigSlice {
54
58
 
55
59
  export const createConfigSlice: StateCreatorWithInitialData<
56
60
  WidgetConfig,
57
- ConfigSlice & SettingsSlice,
61
+ ConfigSlice & SettingsSlice & DataSlice,
58
62
  ConfigSlice
59
63
  > = (initialData, set, get) => {
60
64
  return {
@@ -87,6 +91,40 @@ export const createConfigSlice: StateCreatorWithInitialData<
87
91
  // Actions
88
92
  updateConfig: (nextConfig: WidgetConfig) => {
89
93
  const currentConfig = get().config;
94
+ const {
95
+ _tokensMapByTokenHash: tokensMapByTokenHash,
96
+ _tokensMapByBlockchainName: tokensMapByBlockchainName,
97
+ } = get();
98
+
99
+ const supportedSourceTokens = matchTokensFromConfigWithMeta({
100
+ type: 'source',
101
+ config: {
102
+ blockchains: nextConfig.from?.blockchains,
103
+ tokens: nextConfig.from?.tokens,
104
+ },
105
+ meta: {
106
+ tokensMapByBlockchainName,
107
+ tokensMapByTokenHash,
108
+ },
109
+ });
110
+
111
+ const supportedDestinationTokens = matchTokensFromConfigWithMeta({
112
+ type: 'destination',
113
+ config: {
114
+ blockchains: nextConfig.to?.blockchains,
115
+ tokens: nextConfig.to?.tokens,
116
+ },
117
+ meta: {
118
+ tokensMapByBlockchainName,
119
+ tokensMapByTokenHash,
120
+ },
121
+ });
122
+
123
+ cacheService.set('supportedSourceTokens', supportedSourceTokens);
124
+ cacheService.set(
125
+ 'supportedDestinationTokens',
126
+ supportedDestinationTokens
127
+ );
90
128
 
91
129
  set({
92
130
  config: {
@@ -3,6 +3,7 @@ import type { EvmBlockchainMeta, Token } from 'rango-sdk';
3
3
 
4
4
  import { assert, beforeEach, describe, expect, test } from 'vitest';
5
5
 
6
+ import { cacheService } from '../../services/cacheService';
6
7
  import {
7
8
  createEvmBlockchain,
8
9
  createInitialAppStore,
@@ -17,6 +18,7 @@ let customTokens: [Token, Token, Token];
17
18
  let rangoBlockchain: EvmBlockchainMeta;
18
19
 
19
20
  beforeEach(() => {
21
+ cacheService.clear();
20
22
  rangoBlockchain = createEvmBlockchain();
21
23
 
22
24
  customTokens = [
@@ -52,6 +54,10 @@ beforeEach(() => {
52
54
  customTokens.forEach((token) => {
53
55
  const tokenHash = createTokenHash(token);
54
56
  initData._tokensMapByTokenHash.set(tokenHash, token);
57
+ if (!initData._tokensMapByBlockchainName[token.blockchain]) {
58
+ initData._tokensMapByBlockchainName[token.blockchain] = [];
59
+ }
60
+ initData._tokensMapByBlockchainName[token.blockchain].push(tokenHash);
55
61
  });
56
62
 
57
63
  const appStore = createAppStore();
@@ -351,3 +357,81 @@ describe('search in tokens', () => {
351
357
  expect(secondResult).toBe('RNG');
352
358
  });
353
359
  });
360
+
361
+ describe('supported tokens from config', () => {
362
+ test('Should ensure tokens include only tokens from the config', () => {
363
+ const rangoToken = customTokens[0];
364
+ const djangoToken = customTokens[1];
365
+
366
+ const configTokens = [rangoToken, djangoToken];
367
+
368
+ appStoreState = updateAppStoreConfig(appStoreState, {
369
+ from: {
370
+ tokens: configTokens,
371
+ },
372
+ to: { tokens: configTokens },
373
+ });
374
+
375
+ let sourceTokens = appStoreState.tokens({
376
+ type: 'source',
377
+ });
378
+
379
+ let destinationTokens = appStoreState.tokens({
380
+ type: 'destination',
381
+ });
382
+
383
+ expect(configTokens).toMatchObject(sourceTokens);
384
+ expect(configTokens).toMatchObject(destinationTokens);
385
+
386
+ appStoreState = updateAppStoreConfig(appStoreState, {
387
+ from: {
388
+ blockchains: [rangoBlockchain.name],
389
+ tokens: {
390
+ [rangoBlockchain.name]: {
391
+ tokens: configTokens,
392
+ isExcluded: false,
393
+ },
394
+ },
395
+ },
396
+ to: {
397
+ blockchains: [rangoBlockchain.name],
398
+ tokens: {
399
+ [rangoBlockchain.name]: {
400
+ tokens: configTokens,
401
+ isExcluded: false,
402
+ },
403
+ },
404
+ },
405
+ });
406
+
407
+ sourceTokens = appStoreState.tokens({
408
+ type: 'source',
409
+ });
410
+
411
+ destinationTokens = appStoreState.tokens({
412
+ type: 'destination',
413
+ });
414
+
415
+ expect(sourceTokens).toMatchObject(sourceTokens);
416
+ expect(configTokens).toMatchObject(destinationTokens);
417
+ });
418
+
419
+ test('Check tokens calculation is caching', () => {
420
+ const rangoToken = customTokens[0];
421
+ const djangoToken = customTokens[1];
422
+
423
+ appStoreState = updateAppStoreConfig(appStoreState, {
424
+ from: {
425
+ tokens: [rangoToken, djangoToken],
426
+ },
427
+ });
428
+
429
+ expect(cacheService.get('supportedSourceTokens')?.length ?? 0).toBe(0);
430
+
431
+ appStoreState.tokens({
432
+ type: 'source',
433
+ });
434
+
435
+ expect(cacheService.get('supportedSourceTokens')?.length ?? 0).toBe(2);
436
+ });
437
+ });
@@ -1,16 +1,18 @@
1
1
  // We keep all the received data from server in this slice
2
2
 
3
3
  import type { ConfigSlice } from './config';
4
- import type { Balance, TokenHash } from '../../types';
4
+ import type { CachedEntries } from '../../services/cacheService';
5
+ import type { Balance, Blockchain, TokenHash } from '../../types';
5
6
  import type { Asset, BlockchainMeta, SwapperMeta, Token } from 'rango-sdk';
6
7
  import type { StateCreator } from 'zustand';
7
8
 
9
+ import { cacheService } from '../../services/cacheService';
8
10
  import { httpService as sdk } from '../../services/httpService';
9
11
  import { compareWithSearchFor, containsText } from '../../utils/common';
10
- import { isTokenExcludedInConfig } from '../../utils/configs';
11
12
  import { createTokenHash, isTokenNative } from '../../utils/meta';
12
13
  import { sortLiquiditySourcesByGroupTitle } from '../../utils/settings';
13
14
  import { areTokensEqual, compareTokenBalance } from '../../utils/wallets';
15
+ import { matchTokensFromConfigWithMeta } from '../utils';
14
16
 
15
17
  type BlockchainOptions = {
16
18
  type?: 'source' | 'destination';
@@ -30,6 +32,7 @@ export type FetchStatus = 'loading' | 'success' | 'failed';
30
32
  export interface DataSlice {
31
33
  _blockchainsMapByName: Map<string, BlockchainMeta>;
32
34
  _tokensMapByTokenHash: Map<TokenHash, Token>;
35
+ _tokensMapByBlockchainName: Record<Blockchain, TokenHash[]>;
33
36
  _popularTokens: Token[];
34
37
  _swappers: SwapperMeta[];
35
38
  fetchStatus: FetchStatus;
@@ -48,8 +51,9 @@ export const createDataSlice: StateCreator<
48
51
  DataSlice
49
52
  > = (set, get) => ({
50
53
  // State
51
- _blockchainsMapByName: new Map<string, BlockchainMeta>(),
52
- _tokensMapByTokenHash: new Map<TokenHash, Token>(),
54
+ _blockchainsMapByName: new Map(),
55
+ _tokensMapByTokenHash: new Map(),
56
+ _tokensMapByBlockchainName: {},
53
57
  _popularTokens: [],
54
58
  _swappers: [],
55
59
  fetchStatus: 'loading',
@@ -84,31 +88,41 @@ export const createDataSlice: StateCreator<
84
88
  return list;
85
89
  },
86
90
  tokens: (options) => {
87
- const tokensMapByHashToken = get()._tokensMapByTokenHash;
88
- const tokensFromState = Array.from(tokensMapByHashToken?.values() || []);
91
+ const { _tokensMapByTokenHash, _tokensMapByBlockchainName, config } = get();
92
+ const tokensFromState = Array.from(_tokensMapByTokenHash.values());
89
93
  const blockchainsMapByName = get()._blockchainsMapByName;
90
-
91
- if (!options || !options?.type) {
94
+ if (!options || !options.type) {
92
95
  return tokensFromState;
93
96
  }
94
97
 
95
- const config = get().config;
96
- const supportedTokensConfig =
97
- (options.type === 'source' ? config.from?.tokens : config.to?.tokens) ??
98
- {};
98
+ const configType = options.type === 'source' ? 'from' : 'to';
99
+ const cacheKey: keyof CachedEntries =
100
+ options.type === 'source'
101
+ ? 'supportedSourceTokens'
102
+ : 'supportedDestinationTokens';
103
+
104
+ let supportedTokens = cacheService.get(cacheKey);
105
+ if (!supportedTokens) {
106
+ supportedTokens = matchTokensFromConfigWithMeta({
107
+ type: options.type,
108
+ config: {
109
+ blockchains: config[configType]?.blockchains,
110
+ tokens: config[configType]?.tokens,
111
+ },
112
+ meta: {
113
+ tokensMapByTokenHash: _tokensMapByTokenHash,
114
+ tokensMapByBlockchainName: _tokensMapByBlockchainName,
115
+ },
116
+ });
117
+ cacheService.set(cacheKey, supportedTokens);
118
+ }
119
+
99
120
  const blockchains = get().blockchains({
100
121
  type: options.type,
101
122
  });
102
123
 
103
- const list = tokensFromState
124
+ const list = supportedTokens
104
125
  .filter((token) => {
105
- if (
106
- supportedTokensConfig &&
107
- isTokenExcludedInConfig(token, supportedTokensConfig)
108
- ) {
109
- return false;
110
- }
111
-
112
126
  // If a specific blockchain has passed, we only keep that blockchain's tokens.
113
127
  if (!!options.blockchain && token.blockchain !== options.blockchain) {
114
128
  return false;
@@ -261,17 +275,18 @@ export const createDataSlice: StateCreator<
261
275
  const response = await sdk().getAllMetadata({
262
276
  enableCentralizedSwappers,
263
277
  });
264
-
265
278
  set({ fetchStatus: 'success' });
266
279
  const blockchainsMapByName: Map<string, BlockchainMeta> = new Map<
267
280
  string,
268
281
  BlockchainMeta
269
282
  >();
270
283
 
271
- const tokensMapByHashToken: Map<TokenHash, Token> = new Map<
284
+ const tokensMapByTokenHash: Map<TokenHash, Token> = new Map<
272
285
  TokenHash,
273
286
  Token
274
287
  >();
288
+ const tokensMapByBlockchainName: DataSlice['_tokensMapByBlockchainName'] =
289
+ {};
275
290
  const tokens: Token[] = [];
276
291
  const popularTokens: Token[] = response.popularTokens;
277
292
  const swappers: SwapperMeta[] = response.swappers;
@@ -299,12 +314,17 @@ export const createDataSlice: StateCreator<
299
314
 
300
315
  tokens.forEach((token) => {
301
316
  const tokenHash = createTokenHash(token);
302
- tokensMapByHashToken.set(tokenHash, token);
317
+ if (!tokensMapByBlockchainName[token.blockchain]) {
318
+ tokensMapByBlockchainName[token.blockchain] = [];
319
+ }
320
+ tokensMapByTokenHash.set(tokenHash, token);
321
+ tokensMapByBlockchainName[token.blockchain].push(tokenHash);
303
322
  });
304
323
 
305
324
  set({
306
325
  _blockchainsMapByName: blockchainsMapByName,
307
- _tokensMapByTokenHash: tokensMapByHashToken,
326
+ _tokensMapByTokenHash: tokensMapByTokenHash,
327
+ _tokensMapByBlockchainName: tokensMapByBlockchainName,
308
328
  _popularTokens: popularTokens,
309
329
  _swappers: swappers,
310
330
  });