@rango-dev/widget-embedded 0.31.1-next.2 → 0.32.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 (193) hide show
  1. package/dist/components/AppRoutes.d.ts.map +1 -1
  2. package/dist/components/BlockchainList/BlockchainList.d.ts.map +1 -1
  3. package/dist/components/BlockchainList/BlockchainList.styles.d.ts +4 -1
  4. package/dist/components/BlockchainList/BlockchainList.styles.d.ts.map +1 -1
  5. package/dist/components/BlockchainList/BlockchainList.types.d.ts +1 -0
  6. package/dist/components/BlockchainList/BlockchainList.types.d.ts.map +1 -1
  7. package/dist/components/BlockchainSelectorButton/BlockchainSelectorButton.d.ts +4 -0
  8. package/dist/components/BlockchainSelectorButton/BlockchainSelectorButton.d.ts.map +1 -0
  9. package/dist/components/BlockchainSelectorButton/BlockchainSelectorButton.styles.d.ts +483 -0
  10. package/dist/components/BlockchainSelectorButton/BlockchainSelectorButton.styles.d.ts.map +1 -0
  11. package/dist/components/BlockchainSelectorButton/BlockchainSelectorButton.types.d.ts +12 -0
  12. package/dist/components/BlockchainSelectorButton/BlockchainSelectorButton.types.d.ts.map +1 -0
  13. package/dist/components/BlockchainSelectorButton/index.d.ts +2 -0
  14. package/dist/components/BlockchainSelectorButton/index.d.ts.map +1 -0
  15. package/dist/components/BlockchainsSection/BlockchainsSection.styles.d.ts +1 -0
  16. package/dist/components/BlockchainsSection/BlockchainsSection.styles.d.ts.map +1 -1
  17. package/dist/components/ConfirmWalletsModal/ConfirmWallets.styles.d.ts +11 -0
  18. package/dist/components/ConfirmWalletsModal/ConfirmWallets.styles.d.ts.map +1 -1
  19. package/dist/components/ConfirmWalletsModal/WalletList.styles.d.ts +3 -0
  20. package/dist/components/ConfirmWalletsModal/WalletList.styles.d.ts.map +1 -1
  21. package/dist/components/CustomCollapsible/CustomCollapsible.styles.d.ts +4 -0
  22. package/dist/components/CustomCollapsible/CustomCollapsible.styles.d.ts.map +1 -1
  23. package/dist/components/CustomDestination/CustomDestination.d.ts.map +1 -1
  24. package/dist/components/CustomDestination/CustomDestination.styles.d.ts +5 -2
  25. package/dist/components/CustomDestination/CustomDestination.styles.d.ts.map +1 -1
  26. package/dist/components/CustomTokenModal/CustomTokenModal.d.ts +4 -0
  27. package/dist/components/CustomTokenModal/CustomTokenModal.d.ts.map +1 -0
  28. package/dist/components/CustomTokenModal/CustomTokenModal.helpers.d.ts +3 -0
  29. package/dist/components/CustomTokenModal/CustomTokenModal.helpers.d.ts.map +1 -0
  30. package/dist/components/CustomTokenModal/CustomTokenModal.styles.d.ts +323 -0
  31. package/dist/components/CustomTokenModal/CustomTokenModal.styles.d.ts.map +1 -0
  32. package/dist/components/CustomTokenModal/CustomTokenModal.types.d.ts +9 -0
  33. package/dist/components/CustomTokenModal/CustomTokenModal.types.d.ts.map +1 -0
  34. package/dist/components/CustomTokenModal/index.d.ts +2 -0
  35. package/dist/components/CustomTokenModal/index.d.ts.map +1 -0
  36. package/dist/components/FilterSelector/FilterSelector.style.d.ts +6 -0
  37. package/dist/components/FilterSelector/FilterSelector.style.d.ts.map +1 -1
  38. package/dist/components/HeaderButtons/HeaderButtons.styles.d.ts +8 -0
  39. package/dist/components/HeaderButtons/HeaderButtons.styles.d.ts.map +1 -1
  40. package/dist/components/HistoryGroupedList/HistoryGroupedList.styles.d.ts +6 -0
  41. package/dist/components/HistoryGroupedList/HistoryGroupedList.styles.d.ts.map +1 -1
  42. package/dist/components/Layout/Layout.styles.d.ts +4 -0
  43. package/dist/components/Layout/Layout.styles.d.ts.map +1 -1
  44. package/dist/components/Layout/PageContainer.d.ts +1 -0
  45. package/dist/components/Layout/PageContainer.d.ts.map +1 -1
  46. package/dist/components/Layout/ScrollableArea.d.ts +1 -0
  47. package/dist/components/Layout/ScrollableArea.d.ts.map +1 -1
  48. package/dist/components/LoadingLiquiditySourceList/LoadingLiquiditySourceList.styles.d.ts +1 -0
  49. package/dist/components/LoadingLiquiditySourceList/LoadingLiquiditySourceList.styles.d.ts.map +1 -1
  50. package/dist/components/LoadingSwapDetails/LoadingSwapDetails.styles.d.ts +14 -0
  51. package/dist/components/LoadingSwapDetails/LoadingSwapDetails.styles.d.ts.map +1 -1
  52. package/dist/components/NoResult/NoResult.styles.d.ts +4 -0
  53. package/dist/components/NoResult/NoResult.styles.d.ts.map +1 -1
  54. package/dist/components/Quote/Quote.styles.d.ts +22 -1
  55. package/dist/components/Quote/Quote.styles.d.ts.map +1 -1
  56. package/dist/components/Quote/QuoteCostDetails.styles.d.ts +5 -0
  57. package/dist/components/Quote/QuoteCostDetails.styles.d.ts.map +1 -1
  58. package/dist/components/Quote/QuoteSummary.styles.d.ts +2 -0
  59. package/dist/components/Quote/QuoteSummary.styles.d.ts.map +1 -1
  60. package/dist/components/QuoteSkeleton/QuoteSkeleton.styles.d.ts +4 -0
  61. package/dist/components/QuoteSkeleton/QuoteSkeleton.styles.d.ts.map +1 -1
  62. package/dist/components/QuoteSkeleton/QuoteSummarySkeleton.styles.d.ts +10 -0
  63. package/dist/components/QuoteSkeleton/QuoteSummarySkeleton.styles.d.ts.map +1 -1
  64. package/dist/components/QuoteSkeleton/StepSkeleton.styles.d.ts +5 -0
  65. package/dist/components/QuoteSkeleton/StepSkeleton.styles.d.ts.map +1 -1
  66. package/dist/components/QuoteWarningsAndErrors/QuoteWarningsAndErrors.styles.d.ts +4 -0
  67. package/dist/components/QuoteWarningsAndErrors/QuoteWarningsAndErrors.styles.d.ts.map +1 -1
  68. package/dist/components/Quotes/Quotes.styles.d.ts +3 -0
  69. package/dist/components/Quotes/Quotes.styles.d.ts.map +1 -1
  70. package/dist/components/RefreshModal/RefreshModal.styles.d.ts +2 -1
  71. package/dist/components/RefreshModal/RefreshModal.styles.d.ts.map +1 -1
  72. package/dist/components/SearchInput/SearchInput.styles.d.ts +1 -0
  73. package/dist/components/SearchInput/SearchInput.styles.d.ts.map +1 -1
  74. package/dist/components/SettingsContainer/SettingsContainer.style.d.ts +3 -0
  75. package/dist/components/SettingsContainer/SettingsContainer.style.d.ts.map +1 -1
  76. package/dist/components/Slippage/Slippage.styles.d.ts +5 -0
  77. package/dist/components/Slippage/Slippage.styles.d.ts.map +1 -1
  78. package/dist/components/SwapDetails/SwapDetails.styles.d.ts +15 -0
  79. package/dist/components/SwapDetails/SwapDetails.styles.d.ts.map +1 -1
  80. package/dist/components/SwapDetailsAlerts/SwapDetailsAlerts.styles.d.ts +3 -0
  81. package/dist/components/SwapDetailsAlerts/SwapDetailsAlerts.styles.d.ts.map +1 -1
  82. package/dist/components/SwapDetailsModal/SwapDetailsModal.styles.d.ts +2 -0
  83. package/dist/components/SwapDetailsModal/SwapDetailsModal.styles.d.ts.map +1 -1
  84. package/dist/components/SwitchFromAndTo/SwitchFromAndTo.styles.d.ts +2 -0
  85. package/dist/components/SwitchFromAndTo/SwitchFromAndTo.styles.d.ts.map +1 -1
  86. package/dist/components/TokenList/TokenList.d.ts.map +1 -1
  87. package/dist/components/TokenList/TokenList.styles.d.ts +18 -0
  88. package/dist/components/TokenList/TokenList.styles.d.ts.map +1 -1
  89. package/dist/components/TokenList/TokenList.types.d.ts +5 -2
  90. package/dist/components/TokenList/TokenList.types.d.ts.map +1 -1
  91. package/dist/components/WalletDerivationPathModal/WalletDerivationPathModal.styles.d.ts +2 -0
  92. package/dist/components/WalletDerivationPathModal/WalletDerivationPathModal.styles.d.ts.map +1 -1
  93. package/dist/components/WalletModal/WalletModalContent.styles.d.ts +4 -0
  94. package/dist/components/WalletModal/WalletModalContent.styles.d.ts.map +1 -1
  95. package/dist/components/WalletNamespacesModal/WalletNamespacesModal.styles.d.ts +1 -0
  96. package/dist/components/WalletNamespacesModal/WalletNamespacesModal.styles.d.ts.map +1 -1
  97. package/dist/constants/customTokens.d.ts +2 -0
  98. package/dist/constants/customTokens.d.ts.map +1 -0
  99. package/dist/constants/navigationRoutes.d.ts +2 -0
  100. package/dist/constants/navigationRoutes.d.ts.map +1 -1
  101. package/dist/constants/searchParams.d.ts +1 -0
  102. package/dist/constants/searchParams.d.ts.map +1 -1
  103. package/dist/containers/App/App.styles.d.ts +1 -0
  104. package/dist/containers/App/App.styles.d.ts.map +1 -1
  105. package/dist/containers/ExpandedQuotes/ExpandedQuotes.styles.d.ts +2 -0
  106. package/dist/containers/ExpandedQuotes/ExpandedQuotes.styles.d.ts.map +1 -1
  107. package/dist/containers/Inputs/Inputs.styles.d.ts +2 -0
  108. package/dist/containers/Inputs/Inputs.styles.d.ts.map +1 -1
  109. package/dist/containers/QuoteInfo/QuoteInfo.styles.d.ts +3 -0
  110. package/dist/containers/QuoteInfo/QuoteInfo.styles.d.ts.map +1 -1
  111. package/dist/containers/Settings/Lists.d.ts.map +1 -1
  112. package/dist/hooks/useFetchCustomToken.d.ts +16 -0
  113. package/dist/hooks/useFetchCustomToken.d.ts.map +1 -0
  114. package/dist/hooks/usePrepareBlockchainList/usePrepareBlockchainList.helpers.d.ts +2 -2
  115. package/dist/hooks/useSwapInput.d.ts.map +1 -1
  116. package/dist/hooks/useSyncUrlAndStore/useSyncUrlAndStore.d.ts.map +1 -1
  117. package/dist/hooks/useTheme.d.ts +1 -0
  118. package/dist/hooks/useTheme.d.ts.map +1 -1
  119. package/dist/index.js +2 -2
  120. package/dist/index.js.map +4 -4
  121. package/dist/pages/AddCustomTokenPage.d.ts +3 -0
  122. package/dist/pages/AddCustomTokenPage.d.ts.map +1 -0
  123. package/dist/pages/CustomTokensPage.d.ts +3 -0
  124. package/dist/pages/CustomTokensPage.d.ts.map +1 -0
  125. package/dist/pages/SelectBlockchainPage.d.ts +2 -1
  126. package/dist/pages/SelectBlockchainPage.d.ts.map +1 -1
  127. package/dist/store/AppStore.d.ts +2 -0
  128. package/dist/store/AppStore.d.ts.map +1 -1
  129. package/dist/store/app.d.ts +2 -0
  130. package/dist/store/app.d.ts.map +1 -1
  131. package/dist/store/quote.d.ts.map +1 -1
  132. package/dist/store/slices/data.d.ts +4 -3
  133. package/dist/store/slices/data.d.ts.map +1 -1
  134. package/dist/store/slices/settings.d.ts +7 -2
  135. package/dist/store/slices/settings.d.ts.map +1 -1
  136. package/dist/store/utils/data.d.ts +4 -1
  137. package/dist/store/utils/data.d.ts.map +1 -1
  138. package/dist/types/config.d.ts +3 -1
  139. package/dist/types/config.d.ts.map +1 -1
  140. package/dist/utils/meta.d.ts +1 -0
  141. package/dist/utils/meta.d.ts.map +1 -1
  142. package/dist/utils/quote.d.ts +1 -0
  143. package/dist/utils/quote.d.ts.map +1 -1
  144. package/dist/utils/settings.d.ts +3 -2
  145. package/dist/utils/settings.d.ts.map +1 -1
  146. package/dist/utils/swap.d.ts.map +1 -1
  147. package/dist/widget-embedded.build.json +1 -1
  148. package/package.json +4 -4
  149. package/src/components/AppRoutes.tsx +30 -0
  150. package/src/components/BlockchainList/BlockchainList.styles.ts +3 -1
  151. package/src/components/BlockchainList/BlockchainList.tsx +26 -17
  152. package/src/components/BlockchainList/BlockchainList.types.ts +1 -0
  153. package/src/components/BlockchainSelectorButton/BlockchainSelectorButton.styles.ts +49 -0
  154. package/src/components/BlockchainSelectorButton/BlockchainSelectorButton.tsx +47 -0
  155. package/src/components/BlockchainSelectorButton/BlockchainSelectorButton.types.ts +11 -0
  156. package/src/components/BlockchainSelectorButton/index.ts +1 -0
  157. package/src/components/CustomDestination/CustomDestination.tsx +5 -3
  158. package/src/components/CustomTokenModal/CustomTokenModal.helpers.ts +19 -0
  159. package/src/components/CustomTokenModal/CustomTokenModal.styles.ts +57 -0
  160. package/src/components/CustomTokenModal/CustomTokenModal.tsx +92 -0
  161. package/src/components/CustomTokenModal/CustomTokenModal.types.ts +9 -0
  162. package/src/components/CustomTokenModal/index.ts +1 -0
  163. package/src/components/TokenList/TokenList.styles.ts +1 -0
  164. package/src/components/TokenList/TokenList.tsx +76 -51
  165. package/src/components/TokenList/TokenList.types.ts +5 -2
  166. package/src/constants/customTokens.ts +6 -0
  167. package/src/constants/navigationRoutes.ts +2 -0
  168. package/src/constants/searchParams.ts +3 -0
  169. package/src/containers/Settings/Lists.tsx +25 -1
  170. package/src/hooks/useFetchCustomToken.ts +114 -0
  171. package/src/hooks/useSwapInput.ts +8 -3
  172. package/src/hooks/useSyncUrlAndStore/useSyncUrlAndStore.ts +6 -1
  173. package/src/hooks/useTheme.ts +5 -1
  174. package/src/pages/AddCustomTokenPage.tsx +210 -0
  175. package/src/pages/CustomTokensPage.tsx +184 -0
  176. package/src/pages/HistoryPage.tsx +2 -2
  177. package/src/pages/LiquiditySourcePage.tsx +2 -2
  178. package/src/pages/SelectBlockchainPage.tsx +15 -8
  179. package/src/pages/SelectSwapItemsPage.tsx +1 -1
  180. package/src/store/app.ts +1 -0
  181. package/src/store/quote.ts +12 -2
  182. package/src/store/slices/data.ts +61 -8
  183. package/src/store/slices/settings.ts +35 -2
  184. package/src/store/utils/data.ts +26 -1
  185. package/src/store/wallets.ts +1 -1
  186. package/src/types/config.ts +4 -1
  187. package/src/utils/meta.ts +12 -1
  188. package/src/utils/quote.ts +16 -3
  189. package/src/utils/settings.ts +13 -1
  190. package/src/utils/swap.ts +7 -7
  191. package/dist/components/ConfirmWalletsModal/ConfirmWallets.helpers.d.ts +0 -3
  192. package/dist/components/ConfirmWalletsModal/ConfirmWallets.helpers.d.ts.map +0 -1
  193. package/src/components/ConfirmWalletsModal/ConfirmWallets.helpers.ts +0 -10
@@ -0,0 +1,6 @@
1
+ import { TransactionType } from 'rango-types';
2
+
3
+ export const ACTIVE_BLOCKCHAINS_FOR_CUSTOM_TOKENS: string[] = [
4
+ TransactionType.EVM,
5
+ TransactionType.SOLANA,
6
+ ];
@@ -4,6 +4,8 @@ export const navigationRoutes = {
4
4
  toSwap: 'to-swap',
5
5
  blockchains: 'blockchains',
6
6
  settings: 'settings',
7
+ customTokens: 'custom-tokens',
8
+ addCustomTokens: 'add-custom-tokens',
7
9
  liquiditySources: 'liquidity-sources',
8
10
  bridges: 'bridges',
9
11
  exchanges: 'exchanges',
@@ -5,6 +5,9 @@ export enum SearchParams {
5
5
  TO_TOKEN = 'toToken',
6
6
  FROM_AMOUNT = 'fromAmount',
7
7
  AUTO_CONNECT = 'autoConnect',
8
+
9
+ // This is for custom tokens
10
+ BLOCKCHAIN = 'blockchain',
8
11
  /*
9
12
  * dApps can transmit liquidity sources as a search parameter,
10
13
  * and these take precedence over widget configurations
@@ -20,6 +20,7 @@ import {
20
20
  styled,
21
21
  Switch,
22
22
  Tabs,
23
+ TargetIcon,
23
24
  Tooltip,
24
25
  Typography,
25
26
  } from '@rango-dev/ui';
@@ -97,9 +98,11 @@ export function SettingsLists() {
97
98
  const {
98
99
  config: { features },
99
100
  } = useAppStore();
101
+ const customTokens = useAppStore().customTokens();
100
102
  const isThemeHidden = isFeatureHidden('theme', features);
101
103
  const isLiquidityHidden = isFeatureHidden('liquiditySource', features);
102
104
  const isLanguageHidden = isFeatureHidden('language', features);
105
+ const isCustomTokensHidden = isFeatureHidden('customTokens', features);
103
106
 
104
107
  const infiniteApprove = useAppStore().infiniteApprove;
105
108
  const toggleInfiniteApprove = useAppStore().toggleInfiniteApprove;
@@ -185,6 +188,25 @@ export function SettingsLists() {
185
188
  onClick: () => navigate(navigationRoutes.exchanges),
186
189
  };
187
190
 
191
+ const customTokensItem = {
192
+ id: 'custom-tokens-item',
193
+ title: (
194
+ <Typography variant="title" size="xmedium">
195
+ {i18n.t('Custom Tokens')}
196
+ </Typography>
197
+ ),
198
+ end: (
199
+ <>
200
+ <Typography variant="body" size="medium">
201
+ {`${customTokens.length}`}
202
+ </Typography>
203
+ <Divider direction="horizontal" size={8} />
204
+ <ChevronRightIcon color="black" />
205
+ </>
206
+ ),
207
+ start: <TargetIcon color="gray" size={16} />,
208
+ onClick: () => navigate(navigationRoutes.customTokens),
209
+ };
188
210
  const languageItem = {
189
211
  id: 'language-item',
190
212
  title: (
@@ -261,7 +283,9 @@ export function SettingsLists() {
261
283
  const settingItems: ListPropTypes['items'] = isLiquidityHidden
262
284
  ? []
263
285
  : [bridgeItem, exchangeItem];
264
-
286
+ if (!isCustomTokensHidden) {
287
+ settingItems.push(customTokensItem);
288
+ }
265
289
  if (!isLanguageHidden) {
266
290
  settingItems.push(languageItem);
267
291
  }
@@ -0,0 +1,114 @@
1
+ import type { Token } from 'rango-sdk';
2
+
3
+ import { i18n } from '@lingui/core';
4
+ import { useState } from 'react';
5
+
6
+ import { useAppStore } from '../store/AppStore';
7
+ import { getConfig } from '../utils/configs';
8
+
9
+ type ErrorType = {
10
+ title: string;
11
+ message: string;
12
+ };
13
+
14
+ interface UseFetchCustomToken {
15
+ fetchCustomToken: ({
16
+ blockchain,
17
+ tokenAddress,
18
+ }: {
19
+ blockchain: string;
20
+ tokenAddress: string;
21
+ }) => Promise<Token>;
22
+ loading: boolean;
23
+ error?: ErrorType;
24
+ }
25
+
26
+ export function useFetchCustomToken(): UseFetchCustomToken {
27
+ const [loading, setLoading] = useState(false);
28
+ const [error, setError] = useState<undefined | ErrorType>(undefined);
29
+ const { findToken } = useAppStore();
30
+ const customTokens = useAppStore().customTokens();
31
+
32
+ function produceErrorMessage(
33
+ type: 'duplicated' | 'not-found' | 'token-exist',
34
+ blockchain?: string
35
+ ) {
36
+ switch (type) {
37
+ case 'duplicated':
38
+ return {
39
+ title: i18n.t('Duplicate Token'),
40
+ message: i18n.t(
41
+ 'The address you entered is duplicate, please enter a new address.'
42
+ ),
43
+ };
44
+ case 'token-exist':
45
+ return {
46
+ title: i18n.t('Token Already Exists'),
47
+ message: i18n.t(
48
+ `There's no need to add this token again because it already exists and is supported by us.`
49
+ ),
50
+ };
51
+ case 'not-found':
52
+ return {
53
+ title: i18n.t('Token Not Found'),
54
+ message: i18n.t(
55
+ `Sorry, no token was found on ${blockchain} blockchain with the provided address. please make sure you have entered the right token address.`
56
+ ),
57
+ };
58
+ }
59
+ }
60
+
61
+ const fetchCustomToken: UseFetchCustomToken['fetchCustomToken'] = async ({
62
+ blockchain,
63
+ tokenAddress,
64
+ }) => {
65
+ setLoading(true);
66
+ try {
67
+ // Check for duplicate token in customTokens
68
+ const isDuplicate = customTokens.some(
69
+ (token) => token.address?.toLowerCase() === tokenAddress.toLowerCase()
70
+ );
71
+ if (isDuplicate) {
72
+ const errorMessage = produceErrorMessage('duplicated');
73
+ setError(errorMessage);
74
+ return undefined;
75
+ }
76
+
77
+ const res = await fetch(
78
+ `${getConfig('BASE_URL')}/meta/token?apiKey=${getConfig(
79
+ 'API_KEY'
80
+ )}&blockchain=${blockchain}&address=${tokenAddress}`
81
+ );
82
+ const contentType = res.headers.get('content-type');
83
+ const response =
84
+ contentType &&
85
+ contentType.includes('application/json') &&
86
+ (await res.json());
87
+ if (!response || response.error) {
88
+ const errorMessage = produceErrorMessage('not-found', blockchain);
89
+ setError(errorMessage);
90
+ return undefined;
91
+ }
92
+
93
+ // Check if token is already in the system
94
+ const isTokenFound = findToken({
95
+ blockchain: response.blockchain,
96
+ address: response.address,
97
+ symbol: response.symbol,
98
+ });
99
+ if (isTokenFound) {
100
+ const errorMessage = produceErrorMessage('token-exist');
101
+ setError(errorMessage);
102
+ return undefined;
103
+ }
104
+
105
+ return response;
106
+ } catch (error: any) {
107
+ setError(undefined);
108
+ throw new Error(error.message);
109
+ } finally {
110
+ setLoading(false);
111
+ }
112
+ };
113
+ return { fetchCustomToken, loading, error };
114
+ }
@@ -11,6 +11,8 @@ import { isPositiveNumber } from '../utils/numbers';
11
11
  import {
12
12
  generateQuoteWarnings,
13
13
  getDefaultQuote,
14
+ getQuoteFromTokenUsdPrice,
15
+ getQuoteToTokenUsdPrice,
14
16
  sortQuotesBy,
15
17
  } from '../utils/quote';
16
18
  import { isFeatureEnabled } from '../utils/settings';
@@ -146,12 +148,15 @@ export function useSwapInput({
146
148
  requestId: quote?.requestId || '',
147
149
  swaps: quote?.swaps,
148
150
  });
149
-
151
+ const outputUsdValue =
152
+ getQuoteToTokenUsdPrice(quote) || toToken?.usdPrice;
153
+ const inputUsdValue =
154
+ getQuoteFromTokenUsdPrice(quote) || fromToken?.usdPrice;
150
155
  const quoteWarning =
151
156
  quote &&
152
157
  generateQuoteWarnings(quote, {
153
- fromToken,
154
- toToken,
158
+ fromToken: { ...fromToken, usdPrice: inputUsdValue },
159
+ toToken: { ...toToken, usdPrice: outputUsdValue },
155
160
  userSlippage,
156
161
  findToken,
157
162
  });
@@ -54,6 +54,8 @@ export function useSyncUrlAndStore() {
54
54
  const clientUrl = searchParams.get(SearchParams.CLIENT_URL);
55
55
  const liquiditySources = searchParams.get(SearchParams.LIQUIDITY_SOURCES);
56
56
 
57
+ const blockchain = searchParams.get(SearchParams.BLOCKCHAIN);
58
+
57
59
  return {
58
60
  fromAmount,
59
61
  fromBlockchain,
@@ -64,6 +66,7 @@ export function useSyncUrlAndStore() {
64
66
  clientUrl,
65
67
  liquiditySources,
66
68
  utmQueryParams,
69
+ blockchain,
67
70
  };
68
71
  };
69
72
 
@@ -79,7 +82,8 @@ export function useSyncUrlAndStore() {
79
82
  };
80
83
 
81
84
  useEffect(() => {
82
- const { autoConnect, clientUrl, utmQueryParams } = getUrlSearchParams();
85
+ const { autoConnect, clientUrl, utmQueryParams, blockchain } =
86
+ getUrlSearchParams();
83
87
  if (isInRouterContext && fetchMetaStatus === 'success') {
84
88
  updateUrlSearchParams({
85
89
  [SearchParams.FROM_BLOCKCHAIN]: fromBlockchain?.name,
@@ -89,6 +93,7 @@ export function useSyncUrlAndStore() {
89
93
  [SearchParams.FROM_AMOUNT]: inputAmount,
90
94
  [SearchParams.AUTO_CONNECT]: autoConnect ?? undefined,
91
95
  [SearchParams.CLIENT_URL]: clientUrl ?? undefined,
96
+ [SearchParams.BLOCKCHAIN]: blockchain ?? undefined,
92
97
  [SearchParams.LIQUIDITY_SOURCES]: campaignMode
93
98
  ? liquiditySourcesParamsRef.current
94
99
  : undefined,
@@ -93,8 +93,12 @@ export function useTheme(props: WidgetTheme) {
93
93
  if (theme === 'auto') {
94
94
  return OSTheme === 'dark' ? darkClassNames : lightClassNames;
95
95
  }
96
+
96
97
  return theme === 'dark' ? darkClassNames : lightClassNames;
97
98
  };
98
99
 
99
- return { activeTheme: getActiveTheme };
100
+ return {
101
+ activeTheme: getActiveTheme,
102
+ mode: theme === 'auto' ? OSTheme : theme,
103
+ };
100
104
  }
@@ -0,0 +1,210 @@
1
+ import { i18n } from '@lingui/core';
2
+ import {
3
+ Alert,
4
+ Button,
5
+ darkTheme,
6
+ Divider,
7
+ DoneIcon,
8
+ MessageBox,
9
+ styled,
10
+ TextField,
11
+ Typography,
12
+ } from '@rango-dev/ui';
13
+ import { type Token } from 'rango-sdk';
14
+ import React, { useEffect, useState } from 'react';
15
+ import { useNavigate, useSearchParams } from 'react-router-dom';
16
+
17
+ import { BlockchainSelectorButton } from '../components/BlockchainSelectorButton';
18
+ import { WatermarkedModal } from '../components/common/WatermarkedModal';
19
+ import { CustomTokenModal } from '../components/CustomTokenModal';
20
+ import { Layout, PageContainer } from '../components/Layout';
21
+ import { navigationRoutes } from '../constants/navigationRoutes';
22
+ import { SearchParams } from '../constants/searchParams';
23
+ import { useFetchCustomToken } from '../hooks/useFetchCustomToken';
24
+ import { useNavigateBack } from '../hooks/useNavigateBack';
25
+ import { useAppStore } from '../store/AppStore';
26
+ import { getContainer } from '../utils/common';
27
+ import { findBlockchain, isValidTokenAddress } from '../utils/meta';
28
+
29
+ const Content = styled('div', {
30
+ display: 'flex',
31
+ justifyContent: 'space-between',
32
+ flexDirection: 'column',
33
+ flex: 1,
34
+ '& ._text-field': {
35
+ padding: '$4 $10',
36
+ backgroundColor: '$neutral300',
37
+ [`.${darkTheme} &`]: {
38
+ backgroundColor: '$neutral400',
39
+ },
40
+ borderRadius: '$sm',
41
+ height: '$40',
42
+ },
43
+ });
44
+ const CUSTOM_TOKEN_REFRESH_DELAY = 1000;
45
+
46
+ export function AddCustomTokenPage() {
47
+ const navigate = useNavigate();
48
+ const [searchParams] = useSearchParams();
49
+ const navigateBack = useNavigateBack();
50
+ const { setCustomToken } = useAppStore();
51
+ const blockchains = useAppStore().blockchains();
52
+
53
+ const blockchainName = searchParams.get(SearchParams.BLOCKCHAIN) || '';
54
+ const blockchain = findBlockchain(blockchainName, blockchains);
55
+ const [address, setAddress] = useState('');
56
+ const [isOpenErrorModal, setIsOpenErrorModal] = useState<boolean>(false);
57
+ const [token, setToken] = useState<Token>();
58
+ const { fetchCustomToken, loading, error } = useFetchCustomToken();
59
+ const [networkError, setNetworkError] = useState('');
60
+ const isValidAddress =
61
+ !!blockchain && isValidTokenAddress(blockchain, address);
62
+ const isImportDisabled = !blockchain || !address || !isValidAddress;
63
+
64
+ const getCustomToken = async () => {
65
+ if (blockchain) {
66
+ try {
67
+ const res = await fetchCustomToken({
68
+ blockchain: blockchainName,
69
+ tokenAddress: address,
70
+ });
71
+ setNetworkError('');
72
+ if (!!res) {
73
+ setToken(res);
74
+ }
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
+ } catch (error: any) {
77
+ if (error.message === 'Failed to fetch') {
78
+ setNetworkError(i18n.t('Network Error'));
79
+ setIsOpenErrorModal(true);
80
+ }
81
+ }
82
+ }
83
+ };
84
+
85
+ const closeErrorModal = () => {
86
+ if (!!error) {
87
+ setAddress('');
88
+ }
89
+ setIsOpenErrorModal(false);
90
+ };
91
+
92
+ const handleErrorModalButtonClick = async () => {
93
+ if (!!networkError) {
94
+ setTimeout(async () => {
95
+ await getCustomToken();
96
+ }, CUSTOM_TOKEN_REFRESH_DELAY);
97
+ }
98
+ closeErrorModal();
99
+ };
100
+
101
+ useEffect(() => {
102
+ if (!!error) {
103
+ setIsOpenErrorModal(true);
104
+ }
105
+ }, [error]);
106
+ return (
107
+ <Layout
108
+ header={{
109
+ title: i18n.t('Add Custom Token'),
110
+ }}>
111
+ <PageContainer>
112
+ <Content>
113
+ <div>
114
+ <BlockchainSelectorButton
115
+ onClick={() =>
116
+ navigate(navigationRoutes.blockchains, { replace: true })
117
+ }
118
+ hasLogo={!!blockchain?.logo}
119
+ value={
120
+ !!blockchain
121
+ ? {
122
+ name: blockchain.displayName,
123
+ logo: blockchain.logo,
124
+ }
125
+ : undefined
126
+ }
127
+ title={i18n.t('Select chain')}
128
+ placeholder={i18n.t('Select chain')}
129
+ />
130
+ <Divider size={'24'} />
131
+ <Typography size="large" variant="label">
132
+ {i18n.t('Enter Address')}
133
+ </Typography>
134
+ <Divider size={10} />
135
+ <TextField
136
+ fullWidth
137
+ disabled={!blockchain}
138
+ variant="contained"
139
+ placeholder={i18n.t('Enter token address')}
140
+ size="large"
141
+ value={address}
142
+ suffix={
143
+ !!address &&
144
+ isValidAddress && <DoneIcon color="success" size={12} />
145
+ }
146
+ onChange={(e) => setAddress(e.target.value)}
147
+ />
148
+ {!isValidAddress && !!address && (
149
+ <>
150
+ <Divider size={4} />
151
+ <Alert type="error" variant="alarm" title="Invalid Address" />
152
+ </>
153
+ )}
154
+ </div>
155
+
156
+ <Button
157
+ disabled={isImportDisabled}
158
+ type="primary"
159
+ variant="contained"
160
+ loading={loading}
161
+ size="large"
162
+ onClick={getCustomToken}>
163
+ {i18n.t('Import')}
164
+ </Button>
165
+ </Content>
166
+ <WatermarkedModal
167
+ open={isOpenErrorModal}
168
+ dismissible
169
+ onClose={closeErrorModal}
170
+ container={getContainer()}>
171
+ <MessageBox
172
+ title={error?.title || networkError}
173
+ type="error"
174
+ description={
175
+ error?.message || i18n.t('Failed Network, Please retry.')
176
+ }>
177
+ <Divider size={40} />
178
+ <Divider size={10} />
179
+
180
+ <Button
181
+ variant="contained"
182
+ size="large"
183
+ type="primary"
184
+ fullWidth
185
+ onClick={handleErrorModalButtonClick}>
186
+ {networkError
187
+ ? i18n.t('Retry')
188
+ : i18n.t('Add another custom token')}
189
+ </Button>
190
+ </MessageBox>
191
+ </WatermarkedModal>
192
+ {blockchain && token && (
193
+ <CustomTokenModal
194
+ blockchain={blockchain}
195
+ token={token}
196
+ onSubmitClick={() => {
197
+ if (token) {
198
+ setCustomToken(token);
199
+ setToken(undefined);
200
+ navigateBack();
201
+ }
202
+ }}
203
+ onClose={() => setToken(undefined)}
204
+ open={!!blockchain && !!token}
205
+ />
206
+ )}
207
+ </PageContainer>
208
+ </Layout>
209
+ );
210
+ }
@@ -0,0 +1,184 @@
1
+ import type { Token } from 'rango-sdk';
2
+
3
+ import { i18n } from '@lingui/core';
4
+ import {
5
+ Button,
6
+ CustomTokensZeroStateDarkIcon,
7
+ CustomTokensZeroStateIcon,
8
+ DeleteIcon,
9
+ Divider,
10
+ IconButton,
11
+ MessageBox,
12
+ NotFound,
13
+ styled,
14
+ } from '@rango-dev/ui';
15
+ import React, { useState } from 'react';
16
+ import { useNavigate } from 'react-router-dom';
17
+
18
+ import { WatermarkedModal } from '../components/common/WatermarkedModal';
19
+ import { Layout, PageContainer } from '../components/Layout';
20
+ import { SearchInput } from '../components/SearchInput';
21
+ import { TokenList } from '../components/TokenList';
22
+ import { navigationRoutes } from '../constants/navigationRoutes';
23
+ import { useTheme } from '../hooks/useTheme';
24
+ import { useAppStore } from '../store/AppStore';
25
+ import { useQuoteStore } from '../store/quote';
26
+ import { containsText, getContainer } from '../utils/common';
27
+ import { createTokenHash } from '../utils/meta';
28
+
29
+ const Content = styled('div', {
30
+ display: 'flex',
31
+ justifyContent: 'space-between',
32
+ flexDirection: 'column',
33
+ flex: 1,
34
+ });
35
+ const NotFoundContent = styled('div', {
36
+ display: 'flex',
37
+ flexDirection: 'column',
38
+ alignItems: 'center',
39
+ justifyContent: 'center',
40
+ flex: '0.75',
41
+ });
42
+ const DeleteIconButton = styled(IconButton, {
43
+ '&:hover': {
44
+ '& svg': {
45
+ color: '$secondary550',
46
+ },
47
+ },
48
+ });
49
+ export function CustomTokensPage() {
50
+ const [searchedFor, setSearchedFor] = useState<string>('');
51
+ const { deleteCustomToken } = useAppStore();
52
+ const customTokens = useAppStore().customTokens();
53
+ const { fromToken, toToken, setFromToken, setToToken } = useQuoteStore();
54
+ const { mode } = useTheme({});
55
+ const navigate = useNavigate();
56
+ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
57
+ const [selectedToken, setSelectedToken] = useState<Token>();
58
+ const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
59
+ const value = event.target.value;
60
+ setSearchedFor(value);
61
+ };
62
+ const isDarkTheme = mode === 'dark';
63
+
64
+ const customTokensResults = customTokens.filter(
65
+ (token) =>
66
+ containsText(token.symbol, searchedFor) ||
67
+ containsText(token.address || '', searchedFor) ||
68
+ containsText(token.name || '', searchedFor)
69
+ );
70
+
71
+ const handleDeleteCustomToken = () => {
72
+ if (selectedToken) {
73
+ const toTokenHash = toToken ? createTokenHash(toToken) : null;
74
+ const fromTokenHash = fromToken ? createTokenHash(fromToken) : null;
75
+ const selectedTokenHash = createTokenHash(selectedToken);
76
+
77
+ if (toTokenHash === selectedTokenHash) {
78
+ setToToken({ token: null });
79
+ } else if (fromTokenHash === selectedTokenHash) {
80
+ setFromToken({ token: null });
81
+ }
82
+ deleteCustomToken(selectedToken);
83
+ }
84
+ setIsDeleteModalOpen(false);
85
+ };
86
+
87
+ return (
88
+ <Layout
89
+ header={{
90
+ title: i18n.t('Custom Tokens'),
91
+ }}>
92
+ <PageContainer>
93
+ <Content>
94
+ {!!customTokens.length ? (
95
+ <>
96
+ <SearchInput
97
+ value={searchedFor}
98
+ setValue={setSearchedFor}
99
+ fullWidth
100
+ color="light"
101
+ variant="contained"
102
+ placeholder={i18n.t('Search Token')}
103
+ onChange={handleSearch}
104
+ />
105
+ <Divider size={16} />
106
+ <TokenList
107
+ list={customTokensResults}
108
+ type="custom-token"
109
+ searchedFor={searchedFor}
110
+ showTitle={false}
111
+ action={(token) => (
112
+ <DeleteIconButton
113
+ variant="ghost"
114
+ onClick={() => {
115
+ setIsDeleteModalOpen(true);
116
+ setSelectedToken(token);
117
+ }}>
118
+ <DeleteIcon size={12} color="gray" />
119
+ </DeleteIconButton>
120
+ )}
121
+ />
122
+ </>
123
+ ) : (
124
+ <NotFoundContent>
125
+ <NotFound
126
+ icon={
127
+ isDarkTheme ? (
128
+ <CustomTokensZeroStateDarkIcon size={200} />
129
+ ) : (
130
+ <CustomTokensZeroStateIcon size={200} />
131
+ )
132
+ }
133
+ title={i18n.t('No custom tokens')}
134
+ description={i18n.t(
135
+ 'press the button to add your custom token'
136
+ )}
137
+ />
138
+ </NotFoundContent>
139
+ )}
140
+ <Divider size={20} />
141
+
142
+ <Button
143
+ type="primary"
144
+ variant="contained"
145
+ size="large"
146
+ onClick={() => navigate(navigationRoutes.addCustomTokens)}>
147
+ {i18n.t('Add Custom Token')}
148
+ </Button>
149
+ </Content>
150
+ <WatermarkedModal
151
+ open={isDeleteModalOpen}
152
+ dismissible
153
+ onClose={() => setIsDeleteModalOpen(false)}
154
+ container={getContainer()}>
155
+ <MessageBox
156
+ title={i18n.t('Delete Custom Token')}
157
+ type="error"
158
+ description={i18n.t('Are you sure you want to Delete this Token?')}>
159
+ <Divider size={40} />
160
+ <Divider size={10} />
161
+
162
+ <Button
163
+ fullWidth
164
+ variant="contained"
165
+ type="primary"
166
+ size="large"
167
+ onClick={handleDeleteCustomToken}>
168
+ {i18n.t('Yes, Delete it')}
169
+ </Button>
170
+ <Divider size={12} />
171
+ <Button
172
+ fullWidth
173
+ variant="outlined"
174
+ type="primary"
175
+ size="large"
176
+ onClick={() => setIsDeleteModalOpen(false)}>
177
+ {i18n.t('No, Continue')}
178
+ </Button>
179
+ </MessageBox>
180
+ </WatermarkedModal>
181
+ </PageContainer>
182
+ </Layout>
183
+ );
184
+ }
@@ -87,7 +87,7 @@ export function HistoryPage() {
87
87
  const loading = !state.loadedFromPersistor;
88
88
  const [filterBy, setFilterBy] = useState('');
89
89
  const [openClearModal, setOpenClearModal] = useState(false);
90
- const searchHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
90
+ const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
91
91
  const value = event.target.value;
92
92
  setSearchedFor(value);
93
93
  };
@@ -159,7 +159,7 @@ export function HistoryPage() {
159
159
  variant="contained"
160
160
  placeholder={i18n.t('Search Transaction')}
161
161
  autoFocus
162
- onChange={searchHandler}
162
+ onChange={handleSearch}
163
163
  style={{ height: 36 }}
164
164
  value={searchedFor}
165
165
  />