@rango-dev/widget-embedded 0.40.2-next.9 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/components/CustomTokenModal/CustomTokenModal.constants.d.ts +2 -0
  2. package/dist/components/CustomTokenModal/CustomTokenModal.constants.d.ts.map +1 -0
  3. package/dist/components/CustomTokenModal/CustomTokenModal.d.ts.map +1 -1
  4. package/dist/components/CustomTokenModal/CustomTokenModal.types.d.ts +1 -0
  5. package/dist/components/CustomTokenModal/CustomTokenModal.types.d.ts.map +1 -1
  6. package/dist/components/ImportCustomToken/ImportCustomToken.d.ts +4 -0
  7. package/dist/components/ImportCustomToken/ImportCustomToken.d.ts.map +1 -0
  8. package/dist/components/ImportCustomToken/ImportCustomToken.types.d.ts +15 -0
  9. package/dist/components/ImportCustomToken/ImportCustomToken.types.d.ts.map +1 -0
  10. package/dist/components/ImportCustomToken/index.d.ts +2 -0
  11. package/dist/components/ImportCustomToken/index.d.ts.map +1 -0
  12. package/dist/components/Quote/Quote.styles.d.ts +2 -2
  13. package/dist/components/SearchInput/SearchInput.d.ts.map +1 -1
  14. package/dist/components/SearchInput/SearchInput.types.d.ts +4 -2
  15. package/dist/components/SearchInput/SearchInput.types.d.ts.map +1 -1
  16. package/dist/components/SwapDetailsModal/SwapDetailsModal.types.d.ts +2 -2
  17. package/dist/components/SwapDetailsModal/SwapDetailsModal.types.d.ts.map +1 -1
  18. package/dist/components/TokenList/TokenList.d.ts.map +1 -1
  19. package/dist/components/TokenList/TokenList.styles.d.ts +323 -1
  20. package/dist/components/TokenList/TokenList.styles.d.ts.map +1 -1
  21. package/dist/components/TokenList/TokenList.types.d.ts +6 -1
  22. package/dist/components/TokenList/TokenList.types.d.ts.map +1 -1
  23. package/dist/constants/customTokens.d.ts +1 -0
  24. package/dist/constants/customTokens.d.ts.map +1 -1
  25. package/dist/containers/Inputs/Inputs.d.ts.map +1 -1
  26. package/dist/hooks/useFetchCustomToken.d.ts +8 -4
  27. package/dist/hooks/useFetchCustomToken.d.ts.map +1 -1
  28. package/dist/hooks/usePrepareBlockchainList/usePrepareBlockchainList.helpers.d.ts +2 -2
  29. package/dist/hooks/useSearchCustomTokens/index.d.ts +2 -0
  30. package/dist/hooks/useSearchCustomTokens/index.d.ts.map +1 -0
  31. package/dist/hooks/useSearchCustomTokens/types.d.ts +9 -0
  32. package/dist/hooks/useSearchCustomTokens/types.d.ts.map +1 -0
  33. package/dist/hooks/useSearchCustomTokens/useSearchCustomTokens.constants.d.ts +2 -0
  34. package/dist/hooks/useSearchCustomTokens/useSearchCustomTokens.constants.d.ts.map +1 -0
  35. package/dist/hooks/useSearchCustomTokens/useSearchCustomTokens.d.ts +3 -0
  36. package/dist/hooks/useSearchCustomTokens/useSearchCustomTokens.d.ts.map +1 -0
  37. package/dist/hooks/useSyncStoresWithConfig.d.ts.map +1 -1
  38. package/dist/hooks/useSyncUrlAndStore/useSyncUrlAndStore.d.ts.map +1 -1
  39. package/dist/index.js +2 -2
  40. package/dist/index.js.map +4 -4
  41. package/dist/pages/AddCustomTokenPage.d.ts.map +1 -1
  42. package/dist/pages/CustomTokensPage.d.ts.map +1 -1
  43. package/dist/pages/SelectSwapItemPage/SelectSwapItemPage.constants.d.ts +3 -0
  44. package/dist/pages/SelectSwapItemPage/SelectSwapItemPage.constants.d.ts.map +1 -0
  45. package/dist/pages/SelectSwapItemPage/SelectSwapItemPage.helpers.d.ts +4 -0
  46. package/dist/pages/SelectSwapItemPage/SelectSwapItemPage.helpers.d.ts.map +1 -0
  47. package/dist/pages/SelectSwapItemPage/SelectSwapItemsPage.d.ts.map +1 -0
  48. package/dist/pages/SelectSwapItemPage/index.d.ts +2 -0
  49. package/dist/pages/SelectSwapItemPage/index.d.ts.map +1 -0
  50. package/dist/store/app.d.ts +1 -1
  51. package/dist/store/app.d.ts.map +1 -1
  52. package/dist/store/quote.d.ts +9 -6
  53. package/dist/store/quote.d.ts.map +1 -1
  54. package/dist/store/slices/settings.d.ts +4 -2
  55. package/dist/store/slices/settings.d.ts.map +1 -1
  56. package/dist/store/slices/wallets.d.ts +5 -1
  57. package/dist/store/slices/wallets.d.ts.map +1 -1
  58. package/dist/store/utils/wallets.d.ts +2 -1
  59. package/dist/store/utils/wallets.d.ts.map +1 -1
  60. package/dist/utils/quote.d.ts +1 -1
  61. package/dist/utils/quote.d.ts.map +1 -1
  62. package/dist/utils/settings.d.ts +1 -1
  63. package/dist/widget-embedded.build.json +1 -1
  64. package/package.json +12 -12
  65. package/src/components/AppRoutes.tsx +1 -1
  66. package/src/components/CustomTokenModal/CustomTokenModal.constants.ts +2 -0
  67. package/src/components/CustomTokenModal/CustomTokenModal.tsx +26 -5
  68. package/src/components/CustomTokenModal/CustomTokenModal.types.ts +1 -0
  69. package/src/components/ImportCustomToken/ImportCustomToken.tsx +121 -0
  70. package/src/components/ImportCustomToken/ImportCustomToken.types.ts +18 -0
  71. package/src/components/ImportCustomToken/index.ts +1 -0
  72. package/src/components/NotificationContent/NotificationContent.tsx +2 -2
  73. package/src/components/Quote/QuoteTrigger.tsx +5 -5
  74. package/src/components/QuoteSkeleton/QuoteSummarySkeleton.tsx +2 -2
  75. package/src/components/QuoteSkeleton/StepSkeleton.tsx +2 -2
  76. package/src/components/SearchInput/SearchInput.tsx +13 -10
  77. package/src/components/SearchInput/SearchInput.types.ts +4 -2
  78. package/src/components/SwapDetailsModal/SwapDetailsModal.types.ts +2 -2
  79. package/src/components/TokenList/TokenList.styles.ts +40 -0
  80. package/src/components/TokenList/TokenList.tsx +134 -22
  81. package/src/components/TokenList/TokenList.types.ts +5 -1
  82. package/src/constants/customTokens.ts +3 -0
  83. package/src/containers/Inputs/Inputs.tsx +6 -4
  84. package/src/hooks/useFetchCustomToken.ts +39 -16
  85. package/src/hooks/useSearchCustomTokens/index.ts +1 -0
  86. package/src/hooks/useSearchCustomTokens/types.ts +9 -0
  87. package/src/hooks/useSearchCustomTokens/useSearchCustomTokens.constants.ts +1 -0
  88. package/src/hooks/useSearchCustomTokens/useSearchCustomTokens.ts +81 -0
  89. package/src/hooks/useSyncStoresWithConfig.ts +2 -3
  90. package/src/hooks/useSyncUrlAndStore/useSyncUrlAndStore.ts +1 -3
  91. package/src/pages/AddCustomTokenPage.tsx +24 -94
  92. package/src/pages/CustomTokensPage.tsx +1 -0
  93. package/src/pages/SelectSwapItemPage/SelectSwapItemPage.constants.ts +2 -0
  94. package/src/pages/SelectSwapItemPage/SelectSwapItemPage.helpers.ts +51 -0
  95. package/src/pages/{SelectSwapItemsPage.tsx → SelectSwapItemPage/SelectSwapItemsPage.tsx} +54 -13
  96. package/src/pages/SelectSwapItemPage/index.ts +1 -0
  97. package/src/store/app.ts +12 -1
  98. package/src/store/quote.ts +6 -3
  99. package/src/store/slices/settings.ts +11 -4
  100. package/src/store/slices/wallets.ts +105 -2
  101. package/src/store/utils/wallets.ts +31 -2
  102. package/src/utils/quote.ts +1 -1
  103. package/dist/pages/SelectSwapItemsPage.d.ts.map +0 -1
  104. /package/dist/pages/{SelectSwapItemsPage.d.ts → SelectSwapItemPage/SelectSwapItemsPage.d.ts} +0 -0
@@ -1,25 +1,33 @@
1
1
  /* eslint-disable @typescript-eslint/no-magic-numbers */
2
- import type { PropTypes, RenderDescProps } from './TokenList.types';
3
- import type { BlockchainMeta, Token } from 'rango-sdk';
2
+ import type { PropTypes, RenderDescProps, TokenData } from './TokenList.types';
4
3
 
5
4
  import { i18n } from '@lingui/core';
6
5
  import {
6
+ Button,
7
+ CustomTokenWarning,
7
8
  darkTheme,
8
9
  Divider,
9
10
  ExternalLinkIcon,
10
11
  Image,
11
- ListItemButton,
12
+ ListItem,
12
13
  NotFound,
13
14
  PinIcon,
14
15
  Skeleton,
15
16
  Typography,
16
17
  VirtualizedList,
17
18
  } from '@rango-dev/ui';
18
- import React from 'react';
19
+ import React, { useState } from 'react';
20
+ import { useTranslation } from 'react-i18next';
19
21
 
22
+ import { DEFAULT_TOKEN_IMAGE_SRC } from '../../constants/customTokens';
23
+ import { useNavigateBack } from '../../hooks/useNavigateBack';
20
24
  import { useAppStore } from '../../store/AppStore';
25
+ import { useQuoteStore } from '../../store/quote';
21
26
  import { createTintsAndShades } from '../../utils/colors';
27
+ import { getContainer } from '../../utils/common';
28
+ import { findBlockchain } from '../../utils/meta';
22
29
  import { formatBalance } from '../../utils/wallets';
30
+ import { ImportCustomToken } from '../ImportCustomToken';
23
31
 
24
32
  import { LoadingTokenList } from './LoadingTokenList';
25
33
  import {
@@ -29,8 +37,10 @@ import {
29
37
  End,
30
38
  ImageSection,
31
39
  List,
40
+ ListItemContainer,
32
41
  Pin,
33
42
  StyledLink,
43
+ StyledListItemButton,
34
44
  Tag,
35
45
  TagTitle,
36
46
  Title,
@@ -97,14 +107,57 @@ export function TokenList(props: PropTypes) {
97
107
  selectedBlockchain,
98
108
  showTitle = true,
99
109
  action,
110
+ type,
111
+ showWarning = true,
100
112
  } = props;
101
113
 
102
114
  const fetchStatus = useAppStore().fetchStatus;
103
115
  const blockchains = useAppStore().blockchains();
104
116
  const { getBalanceFor, fetchingWallets: loadingWallet } = useAppStore();
105
117
  const { isTokenPinned } = useAppStore();
118
+ const { setFromToken, setToToken } = useQuoteStore();
119
+ const { t } = useTranslation();
120
+ const navigateBack = useNavigateBack();
106
121
 
107
- const endRenderer = (token: Token) => {
122
+ const [customToken, setCustomToken] = useState<TokenData | null>(null);
123
+
124
+ const updateToken = () => {
125
+ if (type === 'source') {
126
+ setFromToken({ token: customToken, meta: { blockchains } });
127
+ } else {
128
+ setToToken({ token: customToken, meta: { blockchains } });
129
+ }
130
+ };
131
+
132
+ const handleImportToken = () => {
133
+ updateToken();
134
+ navigateBack();
135
+ };
136
+
137
+ const endRenderer = (token: TokenData) => {
138
+ if (token.customToken) {
139
+ const { customToken, ...otherProps } = token;
140
+ const handleClick: React.MouseEventHandler<HTMLButtonElement> = (
141
+ event
142
+ ) => {
143
+ event.stopPropagation();
144
+ setCustomToken({ ...otherProps, warning: true });
145
+ };
146
+
147
+ return (
148
+ // eslint-disable-next-line jsx-id-attribute-enforcement/missing-ids
149
+ <Button
150
+ variant="contained"
151
+ type="primary"
152
+ size="small"
153
+ className="widget-token-list-item-import-btn"
154
+ onClick={handleClick}>
155
+ <Typography variant="body" size="xsmall" color="background">
156
+ {t('import')}
157
+ </Typography>
158
+ </Button>
159
+ );
160
+ }
108
161
  const tokenBalance = formatBalance(getBalanceFor(token));
109
162
 
110
163
  if (action) {
@@ -144,10 +197,42 @@ export function TokenList(props: PropTypes) {
144
197
  <VirtualizedList
145
198
  itemContent={(index) => {
146
199
  const token = tokens[index];
200
+ if (token === 'skeleton') {
201
+ return (
202
+ <ListItem
203
+ hasDivider
204
+ start={<Skeleton variant="circular" width={35} height={35} />}
205
+ end={
206
+ <End>
207
+ <Skeleton variant="text" size="large" width={70} />
208
+ <Divider size={4} />
209
+ <Skeleton variant="text" size="medium" width={50} />
210
+ </End>
211
+ }
212
+ title={
213
+ <div>
214
+ <Skeleton variant="text" size="large" width={90} />
215
+ <Divider size={4} />
216
+ <Skeleton variant="text" size="medium" width={90} />
217
+ </div>
218
+ }
219
+ />
220
+ );
221
+ }
147
222
  const address = token.address || '';
148
223
  const blockchain = blockchains.find(
149
224
  (blockchain) => blockchain.name === token.blockchain
150
- ) as BlockchainMeta;
225
+ );
226
+
227
+ /**
228
+ * This block is added to satisfy TypeScript without using assertions and to prevent any errors that could break the app.
229
+ * Be cautious, as Virtuoso warns us if we return empty elements.
230
+ * If you need to exclude any items, do so before passing them to the virtual list.
231
+ */
232
+ if (!blockchain) {
233
+ return null;
234
+ }
235
+
151
236
  const colors = createTintsAndShades(blockchain.color, 'main');
152
237
  const customCssForTag = {
153
238
  $$color: colors.main150,
@@ -165,26 +250,32 @@ export function TokenList(props: PropTypes) {
165
250
  color: '$$color',
166
251
  };
167
252
 
253
+ const handleClick = () => {
254
+ if (typeof token !== 'string' && !token.customToken) {
255
+ onChange?.(token);
256
+ }
257
+ };
258
+
168
259
  return (
169
- <div
170
- style={{
171
- paddingRight: 5,
172
- }}>
173
- <ListItemButton
174
- style={{
175
- width: '100%',
176
- overflow: 'hidden',
177
- height: '60px',
178
- }}
260
+ <ListItemContainer>
261
+ <StyledListItemButton
179
262
  tab-index={index}
180
263
  key={`${token.symbol}${token.address}`}
181
264
  id={`${token.symbol}${token.address}`}
182
- className={`widget-token-list-item-btn`}
265
+ className="widget-token-list-item-btn"
183
266
  hasDivider
184
- onClick={() => onChange && onChange(tokens[index])}
267
+ customToken={token.customToken}
268
+ onClick={handleClick}
185
269
  start={
186
270
  <ImageSection>
187
- <Image src={token.image} size={30} />
271
+ <Image
272
+ src={
273
+ token.image === ''
274
+ ? DEFAULT_TOKEN_IMAGE_SRC
275
+ : token.image
276
+ }
277
+ size={30}
278
+ />
188
279
  {props.type !== 'custom-token' &&
189
280
  isTokenPinned(token, props.type) && (
190
281
  <Pin>
@@ -210,16 +301,23 @@ export function TokenList(props: PropTypes) {
210
301
  {token.blockchain}
211
302
  </TagTitle>
212
303
  </Tag>
304
+ {showWarning && token.warning && (
305
+ <>
306
+ <Divider direction="horizontal" size={4} />
307
+ <CustomTokenWarning container={getContainer()} />
308
+ </>
309
+ )}
213
310
  </Title>
214
311
  ) : undefined
215
312
  }
216
313
  description={
314
+ typeof token !== 'string' &&
217
315
  !!blockchain?.info &&
218
316
  !!address &&
219
317
  blockchain.type !== 'COSMOS'
220
318
  ? renderDesc({
221
319
  address,
222
- token: tokens[index],
320
+ token,
223
321
  customCssForTag,
224
322
  customCssForTagTitle,
225
323
  name: token.name,
@@ -231,7 +329,7 @@ export function TokenList(props: PropTypes) {
231
329
  }
232
330
  end={endRenderer(token)}
233
331
  />
234
- </div>
332
+ </ListItemContainer>
235
333
  );
236
334
  }}
237
335
  totalCount={tokens.length}
@@ -240,6 +338,10 @@ export function TokenList(props: PropTypes) {
240
338
  );
241
339
  };
242
340
 
341
+ const customTokenBlockchain = customToken
342
+ ? findBlockchain(customToken?.blockchain, blockchains)
343
+ : null;
344
+
243
345
  return (
244
346
  <>
245
347
  {showTitle && (
@@ -256,7 +358,17 @@ export function TokenList(props: PropTypes) {
256
358
  {fetchStatus === 'loading' && <LoadingTokenList size={PAGE_SIZE} />}
257
359
  {fetchStatus === 'success' &&
258
360
  (tokens.length ? (
259
- <List as="ul">{renderList()}</List>
361
+ <>
362
+ <ImportCustomToken
363
+ token={customToken}
364
+ address={customToken?.address ?? ''}
365
+ blockchain={customTokenBlockchain ?? undefined}
366
+ onImport={handleImportToken}
367
+ onExitErrorModal={() => setCustomToken(null)}
368
+ onExitImportModal={() => setCustomToken(null)}
369
+ />
370
+ <List as="ul">{renderList()}</List>
371
+ </>
260
372
  ) : (
261
373
  !!searchedFor && (
262
374
  <NotFound
@@ -1,14 +1,18 @@
1
1
  import type { Token } from 'rango-sdk';
2
2
  import type { ReactElement } from 'react';
3
3
 
4
+ export type TokenData = Token &
5
+ (Token & { customToken?: boolean; warning?: boolean });
6
+
4
7
  export interface PropTypes {
5
- list: Token[];
8
+ list: (TokenData | 'skeleton')[];
6
9
  searchedFor?: string;
7
10
  onChange?: (token: Token) => void;
8
11
  selectedBlockchain?: string;
9
12
  type: 'source' | 'destination' | 'custom-token';
10
13
  action?: (token: Token) => ReactElement;
11
14
  showTitle?: boolean;
15
+ showWarning?: boolean;
12
16
  }
13
17
 
14
18
  export interface LoadingTokenListProps {
@@ -4,3 +4,6 @@ export const ACTIVE_BLOCKCHAINS_FOR_CUSTOM_TOKENS: string[] = [
4
4
  TransactionType.EVM,
5
5
  TransactionType.SOLANA,
6
6
  ];
7
+
8
+ export const DEFAULT_TOKEN_IMAGE_SRC =
9
+ 'https://raw.githubusercontent.com/rango-exchange/assets/refs/heads/main/common/unknown-image.png';
@@ -82,11 +82,12 @@ export function Inputs(props: PropTypes) {
82
82
  balance={fromTokenFormattedBalance}
83
83
  chain={{
84
84
  displayName: fromBlockchain?.displayName || '',
85
- image: fromBlockchain?.logo || '',
85
+ image: fromBlockchain?.logo,
86
86
  }}
87
87
  token={{
88
88
  displayName: fromToken?.symbol || '',
89
- image: fromToken?.image || '',
89
+ image: fromToken?.image,
90
+ securityWarning: !!fromToken?.warning,
90
91
  }}
91
92
  onClickToken={() => onClickToken('from')}
92
93
  price={{
@@ -133,11 +134,12 @@ export function Inputs(props: PropTypes) {
133
134
  fetchingQuote={fetchingQuote}
134
135
  chain={{
135
136
  displayName: toBlockchain?.displayName || '',
136
- image: toBlockchain?.logo || '',
137
+ image: toBlockchain?.logo,
137
138
  }}
138
139
  token={{
139
140
  displayName: toToken?.symbol || '',
140
- image: toToken?.image || '',
141
+ image: toToken?.image,
142
+ securityWarning: !!toToken?.warning,
141
143
  }}
142
144
  percentageChange={numberToString(
143
145
  getPriceImpact(inputUsdValue, outputUsdValue),
@@ -1,3 +1,4 @@
1
+ import type { TokenData } from '../components/TokenList/TokenList.types';
1
2
  import type { Token } from 'rango-sdk';
2
3
 
3
4
  import { i18n } from '@lingui/core';
@@ -6,36 +7,49 @@ import { useState } from 'react';
6
7
  import { httpService } from '../services/httpService';
7
8
  import { useAppStore } from '../store/AppStore';
8
9
 
9
- type ErrorType = {
10
+ type ErrorType = 'duplicated' | 'not-found' | 'token-exist' | 'network-error';
11
+
12
+ export type CustomTokenError = {
13
+ type: ErrorType;
10
14
  title: string;
11
15
  message: string;
12
16
  };
13
17
 
14
- interface UseFetchCustomToken {
18
+ export interface UseFetchCustomToken {
15
19
  fetchCustomToken: ({
16
20
  blockchain,
17
21
  tokenAddress,
18
22
  }: {
19
23
  blockchain: string;
20
24
  tokenAddress: string;
21
- }) => Promise<Token | undefined>;
25
+ }) => Promise<void>;
26
+ token: Token | null;
22
27
  loading: boolean;
23
- error?: ErrorType;
28
+ error: CustomTokenError | null;
29
+ resetState: () => void;
24
30
  }
25
31
 
26
32
  export function useFetchCustomToken(): UseFetchCustomToken {
27
33
  const [loading, setLoading] = useState(false);
28
- const [error, setError] = useState<undefined | ErrorType>(undefined);
34
+ const [error, setError] = useState<CustomTokenError | null>(null);
35
+ const [token, setToken] = useState<TokenData | null>(null);
29
36
  const { findToken } = useAppStore();
30
37
  const customTokens = useAppStore().customTokens();
31
38
 
39
+ const resetState = () => {
40
+ setToken(null);
41
+ setLoading(false);
42
+ setError(null);
43
+ };
44
+
32
45
  function produceErrorMessage(
33
- type: 'duplicated' | 'not-found' | 'token-exist',
46
+ type: ErrorType,
34
47
  blockchain?: string
35
- ) {
48
+ ): CustomTokenError {
36
49
  switch (type) {
37
50
  case 'duplicated':
38
51
  return {
52
+ type,
39
53
  title: i18n.t('Duplicate Token'),
40
54
  message: i18n.t(
41
55
  'The address you entered is duplicate, please enter a new address.'
@@ -43,6 +57,7 @@ export function useFetchCustomToken(): UseFetchCustomToken {
43
57
  };
44
58
  case 'token-exist':
45
59
  return {
60
+ type,
46
61
  title: i18n.t('Token Already Exists'),
47
62
  message: i18n.t(
48
63
  `There's no need to add this token again because it already exists and is supported by us.`
@@ -50,6 +65,7 @@ export function useFetchCustomToken(): UseFetchCustomToken {
50
65
  };
51
66
  case 'not-found':
52
67
  return {
68
+ type,
53
69
  title: i18n.t('Token Not Found'),
54
70
  message: i18n.t({
55
71
  id: 'Sorry, no token was found on {blockchain} chain with the provided address. please make sure you have entered the right token address.',
@@ -58,6 +74,12 @@ export function useFetchCustomToken(): UseFetchCustomToken {
58
74
  },
59
75
  }),
60
76
  };
77
+ case 'network-error':
78
+ return {
79
+ type,
80
+ title: i18n.t('Network error'),
81
+ message: i18n.t('An error occurred while retrieving token data.'),
82
+ };
61
83
  }
62
84
  }
63
85
 
@@ -74,7 +96,7 @@ export function useFetchCustomToken(): UseFetchCustomToken {
74
96
  if (isDuplicate) {
75
97
  const errorMessage = produceErrorMessage('duplicated');
76
98
  setError(errorMessage);
77
- return undefined;
99
+ return;
78
100
  }
79
101
 
80
102
  const response = await httpService().getCustomToken({
@@ -85,7 +107,7 @@ export function useFetchCustomToken(): UseFetchCustomToken {
85
107
  if (!response || !response.token || response.error) {
86
108
  const errorMessage = produceErrorMessage('not-found', blockchain);
87
109
  setError(errorMessage);
88
- return undefined;
110
+ return;
89
111
  }
90
112
 
91
113
  // Check if token is already in the system
@@ -98,21 +120,22 @@ export function useFetchCustomToken(): UseFetchCustomToken {
98
120
  if (isTokenFound) {
99
121
  const errorMessage = produceErrorMessage('token-exist');
100
122
  setError(errorMessage);
101
- return undefined;
123
+ return;
102
124
  }
103
125
 
104
- return token;
126
+ return setToken({ ...token, warning: true });
127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
128
  } catch (error: any) {
106
- if (error?.code === 'ERR_BAD_REQUEST') {
129
+ if (error.code === 'ERR_BAD_REQUEST') {
107
130
  const errorMessage = produceErrorMessage('not-found', blockchain);
108
131
  setError(errorMessage);
109
- return undefined;
132
+ return;
110
133
  }
111
- setError(undefined);
112
- throw new Error(error.message);
134
+ setError(produceErrorMessage('network-error'));
135
+ return;
113
136
  } finally {
114
137
  setLoading(false);
115
138
  }
116
139
  };
117
- return { fetchCustomToken, loading, error };
140
+ return { fetchCustomToken, token, loading, error, resetState };
118
141
  }
@@ -0,0 +1 @@
1
+ export { useSearchCustomTokens } from './useSearchCustomTokens';
@@ -0,0 +1,9 @@
1
+ import type { Token } from 'rango-sdk';
2
+
3
+ export type UseSearchCustomTokens = {
4
+ fetch: (query: string, blockchain?: string) => void;
5
+ cancel: () => void;
6
+ loading: boolean;
7
+ tokens: Token[];
8
+ error: string | null;
9
+ };
@@ -0,0 +1 @@
1
+ export const DEBOUNCE_DELAY = 600;
@@ -0,0 +1,81 @@
1
+ import type { UseSearchCustomTokens } from './types';
2
+ import type { Token } from 'rango-sdk';
3
+
4
+ import { useCallback, useRef, useState } from 'react';
5
+
6
+ import { httpService } from '../../services/httpService';
7
+ import { useAppStore } from '../../store/AppStore';
8
+ import { createAssetKey } from '../../store/utils/wallets';
9
+ import { debounce } from '../../utils/common';
10
+
11
+ import { DEBOUNCE_DELAY } from './useSearchCustomTokens.constants';
12
+
13
+ export function useSearchCustomTokens(): UseSearchCustomTokens {
14
+ const blockchains = useAppStore().blockchains();
15
+ const abortControllerRef = useRef<AbortController | null>(null);
16
+ const { customTokens } = useAppStore();
17
+ const [loading, setLoading] = useState(false);
18
+ const [tokens, setTokens] = useState<Token[]>([]);
19
+ const [error, setError] = useState<string | null>(null);
20
+
21
+ const fetch = async (query: string, blockchain?: string) => {
22
+ setError(null);
23
+ setLoading(true);
24
+ setTokens([]);
25
+
26
+ try {
27
+ const response = await httpService().searchCustomTokens(
28
+ { query, blockchain },
29
+ { signal: abortControllerRef.current?.signal }
30
+ );
31
+
32
+ const customTokensSet = new Set(
33
+ customTokens().map((token) => createAssetKey(token))
34
+ );
35
+ const blockchainsSet = new Set(
36
+ blockchains.map((blockchain) => blockchain.name)
37
+ );
38
+ const filteredTokens = response.tokens.filter(
39
+ (token) =>
40
+ blockchainsSet.has(token.blockchain) &&
41
+ !customTokensSet.has(createAssetKey(token))
42
+ );
43
+
44
+ setTokens(filteredTokens);
45
+ setLoading(false);
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ } catch (error: any) {
48
+ setError(error instanceof Error ? error.message : 'something went wrong');
49
+ setTokens([]);
50
+ if (error?.name !== 'CanceledError') {
51
+ setLoading(false);
52
+ }
53
+ }
54
+ };
55
+
56
+ const debouncedFetch = useCallback(
57
+ debounce((query: string, blockchain?: string) => {
58
+ if (abortControllerRef.current?.signal.aborted) {
59
+ return;
60
+ }
61
+ void fetch(query, blockchain);
62
+ }, DEBOUNCE_DELAY),
63
+ [blockchains.length]
64
+ );
65
+
66
+ const cancel = () => {
67
+ abortControllerRef.current?.abort();
68
+ };
69
+ return {
70
+ fetch: (query: string, blockchain?: string) => {
71
+ abortControllerRef.current = new AbortController();
72
+ setTokens([]);
73
+ setLoading(true);
74
+ debouncedFetch(query, blockchain);
75
+ },
76
+ cancel,
77
+ loading,
78
+ tokens,
79
+ error,
80
+ };
81
+ }
@@ -26,7 +26,6 @@ export function useSyncStoresWithConfig() {
26
26
  const config = useAppStore().config;
27
27
  const fetchMetaStatus = useAppStore().fetchStatus;
28
28
  const blockchains = useAppStore().blockchains();
29
- const tokens = useAppStore().tokens();
30
29
  const { findToken } = useAppStore();
31
30
 
32
31
  const { setAffiliateRef, setAffiliatePercent, setAffiliateWallets } =
@@ -61,7 +60,7 @@ export function useSyncStoresWithConfig() {
61
60
  }
62
61
 
63
62
  if (token) {
64
- setFromToken({ token, meta: { blockchains, tokens } });
63
+ setFromToken({ token, meta: { blockchains } });
65
64
  } else if (!token && prevConfigFromToken.current) {
66
65
  setFromToken({ token: null });
67
66
  }
@@ -111,7 +110,7 @@ export function useSyncStoresWithConfig() {
111
110
  }
112
111
 
113
112
  if (token) {
114
- setToToken({ token, meta: { blockchains, tokens } });
113
+ setToToken({ token, meta: { blockchains } });
115
114
  } else if (!token && prevConfigToToken.current) {
116
115
  setToToken({ token: null });
117
116
  }
@@ -31,7 +31,6 @@ export function useSyncUrlAndStore() {
31
31
  } = useQuoteStore();
32
32
  const fetchMetaStatus = useAppStore().fetchStatus;
33
33
  const blockchains = useAppStore().blockchains();
34
- const tokens = useAppStore().tokens();
35
34
  const isInRouterContext = useInRouterContext();
36
35
  const { updateIframe, updateCampaignMode } = useAppStore();
37
36
  const campaignMode = useAppStore().isInCampaignMode();
@@ -157,7 +156,6 @@ export function useSyncUrlAndStore() {
157
156
  token: fromToken,
158
157
  meta: {
159
158
  blockchains: blockchains,
160
- tokens: tokens,
161
159
  },
162
160
  });
163
161
  }
@@ -168,7 +166,7 @@ export function useSyncUrlAndStore() {
168
166
  if (!!toToken) {
169
167
  setToToken({
170
168
  token: toToken,
171
- meta: { blockchains: blockchains, tokens: tokens },
169
+ meta: { blockchains: blockchains },
172
170
  });
173
171
  }
174
172
  }