diff --git a/src/components/SearchModal/CurrencyList.tsx b/src/components/SearchModal/CurrencyList.tsx index 43e39fdadf..aaea0b1631 100644 --- a/src/components/SearchModal/CurrencyList.tsx +++ b/src/components/SearchModal/CurrencyList.tsx @@ -136,18 +136,15 @@ export function CurrencyRow({ return false } - if (isTokenNative(currency, currency.chainId)) { - return !!favoriteTokens.includeNativeToken - } - if (currency.isToken) { const addr = (currency as Token).address ?? '' - const addresses = favoriteTokens?.addresses ?? [] + const addresses = favoriteTokens ?? [] return !!addresses?.includes(addr) || !!addresses?.includes(addr.toLowerCase()) } return false })() + const balanceComponent = hideBalance ? ( '******' ) : currencyBalance ? ( diff --git a/src/components/SearchModal/CurrencySearch.tsx b/src/components/SearchModal/CurrencySearch.tsx index d7529a1f03..7a77c1bdfa 100644 --- a/src/components/SearchModal/CurrencySearch.tsx +++ b/src/components/SearchModal/CurrencySearch.tsx @@ -76,7 +76,6 @@ const ButtonClear = styled.div` gap: 5px; cursor: pointer; ` -const MAX_FAVORITE_PAIR = 12 interface CurrencySearchProps { isOpen: boolean @@ -254,32 +253,15 @@ export function CurrencySearch({ const handleClickFavorite = useCallback( (e: React.MouseEvent, currency: any) => { e.stopPropagation() - const address = currency?.wrapped?.address || currency.address if (!address) return - const currentList = favoriteTokens?.addresses || [] - const isAddFavorite = isTokenNative(currency, currency.chainId) - ? !favoriteTokens?.includeNativeToken - : !currentList.find(el => el === address) // else remove favorite - const curTotal = - currentList.filter(address => !!defaultTokens[address]).length + (favoriteTokens?.includeNativeToken ? 1 : 0) - if (isAddFavorite && curTotal === MAX_FAVORITE_PAIR) return - - if (isTokenNative(currency, currency.chainId)) { - toggleFavoriteToken({ - chainId, - isNative: true, - }) - return - } - toggleFavoriteToken({ chainId, address, }) }, - [chainId, favoriteTokens, toggleFavoriteToken, defaultTokens], + [chainId, toggleFavoriteToken], ) // menu ui @@ -292,20 +274,30 @@ export function CurrencySearch({ if (!Object.keys(defaultTokens).length) return setLoadingCommon(true) let result: (Token | Currency)[] = [] - if (favoriteTokens?.includeNativeToken) { - result.push(NativeCurrencies[chainId]) - } const addressesToFetch: string[] = [] - favoriteTokens?.addresses.forEach(address => { - if (defaultTokens[address]) { - result.push(defaultTokens[address]) + + favoriteTokens?.forEach(address => { + let token + Object.entries(defaultTokens).forEach(([add, t]) => { + if (add.toLowerCase() === address.toLowerCase()) { + token = t + } + }) + if (token) { + result.push(token) return } addressesToFetch.push(address) }) + if (addressesToFetch.length) { const tokens = await fetchListTokenByAddresses(addressesToFetch, chainId) - result = result.concat(tokens) + // Sort the returned token list to match the order of the passed address list + result = result.concat( + tokens.sort((x, y) => { + return addressesToFetch.indexOf(x.wrapped.address) - addressesToFetch.indexOf(y.wrapped.address) + }), + ) } setCommonTokens(result) } catch (error) { @@ -393,14 +385,13 @@ export function CurrencySearch({ const removeImportedToken = useCallback( (token: Token) => { removeToken(chainId, token.address) - if (favoriteTokens?.addresses?.includes(token.address)) - // remove in favorite too - toggleFavoriteToken({ - chainId, - address: token.address, - }) + + toggleFavoriteToken({ + chainId, + address: token.address, + }) }, - [chainId, toggleFavoriteToken, removeToken, favoriteTokens?.addresses], + [chainId, toggleFavoriteToken, removeToken], ) const removeAllImportToken = () => { diff --git a/src/constants/networks.ts b/src/constants/networks.ts index b7c8da1862..952e8f8291 100644 --- a/src/constants/networks.ts +++ b/src/constants/networks.ts @@ -164,6 +164,7 @@ export const STATIC_FEE_OPTIONS: { [chainId: number]: number[] | undefined } = { [ChainId.GÖRLI]: [8, 10, 50, 300, 500, 1000], [ChainId.ZKSYNC]: [8, 10, 50, 300, 500, 1000], [ChainId.LINEA_TESTNET]: [8, 10, 50, 300, 500, 1000], + [ChainId.LINEA]: [8, 10, 50, 300, 500, 1000], } export const ONLY_STATIC_FEE_CHAINS = [ @@ -175,6 +176,7 @@ export const ONLY_STATIC_FEE_CHAINS = [ ChainId.GÖRLI, ChainId.ZKSYNC, ChainId.LINEA_TESTNET, + ChainId.LINEA, ] // hardcode for unavailable subgraph diff --git a/src/constants/networks/linea.ts b/src/constants/networks/linea.ts index fe95642892..cd9ea39456 100644 --- a/src/constants/networks/linea.ts +++ b/src/constants/networks/linea.ts @@ -8,7 +8,7 @@ const EMPTY = '' const EMPTY_ARRAY: any[] = [] const NOT_SUPPORT = null -const lineaTestnetInfo: EVMNetworkInfo = { +const lineaInfo: EVMNetworkInfo = { chainId: ChainId.LINEA, route: 'linea', ksSettingRoute: 'linea', @@ -70,4 +70,4 @@ const lineaTestnetInfo: EVMNetworkInfo = { geckoTermialId: NOT_SUPPORT, } -export default lineaTestnetInfo +export default lineaInfo diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 2aa6c33bf5..3477844850 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -5,7 +5,7 @@ import * as Sentry from '@sentry/react' import { Suspense, lazy, useEffect } from 'react' import { isMobile } from 'react-device-detect' import { AlertTriangle } from 'react-feather' -import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom' +import { Navigate, Route, Routes, useLocation, useNavigate, useParams } from 'react-router-dom' import { useNetwork, usePrevious } from 'react-use' import { Flex, Text } from 'rebass' import styled from 'styled-components' @@ -14,6 +14,7 @@ import snow from 'assets/images/snow.png' import Popups from 'components/Announcement/Popups' import TopBanner from 'components/Announcement/Popups/TopBanner' import AppHaveUpdate from 'components/AppHaveUpdate' +import { ButtonPrimary } from 'components/Button' import ModalConfirm from 'components/ConfirmModal' import ErrorBoundary from 'components/ErrorBoundary' import Footer from 'components/Footer/Footer' @@ -45,6 +46,22 @@ import ElasticLegacyNotice from './ElasticLegacy/ElasticLegacyNotice' import Icons from './Icons' import VerifyAuth from './Verify/VerifyAuth' +// THIS IS ONLY TEMPORARY, WILL REMOVE IN NEXT VERSION +const CommingSoonModal = () => { + const navigate = useNavigate() + return ( + navigate('/')}> + + Our pools and farms will be available soon! + + navigate('/')}> + Ok + + + + ) +} + // test page for swap only through elastic const ElasticSwap = lazy(() => import('./ElasticSwap')) const SwapV2 = lazy(() => import('./SwapV2')) @@ -331,19 +348,28 @@ export default function App() { <> {/* Pools Routes */} } /> - } /> + : } + /> <> {/* Farms Routes */} } /> - } /> + : } + /> <> {/* My Pools Routes */} } /> - } /> + : } + /> <> diff --git a/src/services/ksSetting.ts b/src/services/ksSetting.ts index 6936e57584..ee7ca0648a 100644 --- a/src/services/ksSetting.ts +++ b/src/services/ksSetting.ts @@ -16,6 +16,7 @@ export type KyberSwapConfig = { elasticClient: ApolloClient readProvider: AppJsonRpcProvider | undefined connection: Connection | undefined + commonTokens?: string[] } export type KyberSwapConfigResponse = { @@ -25,6 +26,7 @@ export type KyberSwapConfigResponse = { blockSubgraph: string classicSubgraph: string elasticSubgraph: string + commonTokens?: string[] } export type KyberswapConfigurationResponse = { diff --git a/src/state/application/hooks.ts b/src/state/application/hooks.ts index bda0ac3309..bdad33a5c6 100644 --- a/src/state/application/hooks.ts +++ b/src/state/application/hooks.ts @@ -434,6 +434,7 @@ function getDefaultConfig(chainId: ChainId): KyberSwapConfigResponse { blockSubgraph: (evm ? NETWORKS_INFO[chainId] : ethereumInfo).defaultBlockSubgraph, elasticSubgraph: (evm ? NETWORKS_INFO[chainId] : ethereumInfo).elastic.defaultSubgraph, classicSubgraph: (evm ? NETWORKS_INFO[chainId] : ethereumInfo).classic.defaultSubgraph, + commonTokens: undefined, } } @@ -469,11 +470,13 @@ export const useKyberSwapConfig = (customChainId?: ChainId): KyberSwapConfig => elasticClient, classicClient, connection: isSolana(chainId) ? new Connection(config.rpc, { commitment: 'confirmed' }) : undefined, + commonTokens: config.commonTokens, } }, [ config.rpc, config.isEnableBlockService, config.prochart, + config.commonTokens, readProvider, blockClient, elasticClient, diff --git a/src/state/application/reducer.ts b/src/state/application/reducer.ts index 5de933880a..019d850f59 100644 --- a/src/state/application/reducer.ts +++ b/src/state/application/reducer.ts @@ -197,6 +197,7 @@ export default createReducer(initialState, builder => blockSubgraph, elasticSubgraph, classicSubgraph, + commonTokens: data.commonTokens, }, } }), diff --git a/src/state/index.ts b/src/state/index.ts index 20fc52ee46..8dd826d9e0 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -41,12 +41,31 @@ import tokenPrices from './tokenPrices' import topTokens from './topTokens' import transactions from './transactions/reducer' import tutorial from './tutorial/reducer' -import user from './user/reducer' +import user, { UserState } from './user/reducer' import vesting from './vesting/reducer' const PERSISTED_KEYS: string[] = ['user', 'transactions', 'profile'] ENV_LEVEL < ENV_TYPE.PROD && PERSISTED_KEYS.push('customizeDexes') +// Migrate from old version to new version, prevent lost favorite tokens of user +const preloadedState: any = load({ states: PERSISTED_KEYS }) +if ('user' in preloadedState) { + const userState: UserState = preloadedState.user + if (userState.favoriteTokensByChainId) { + userState.favoriteTokensByChainIdv2 = Object.entries(userState.favoriteTokensByChainId).reduce( + (acc, [chainId, obj]) => { + acc[chainId] = {} + obj.addresses.forEach((address: string) => { + acc[chainId][address.toLowerCase()] = true + }) + return acc + }, + {} as any, + ) + userState.favoriteTokensByChainId = undefined + } +} + const store = configureStore({ devTools: process.env.NODE_ENV !== 'production', reducer: { @@ -110,7 +129,7 @@ const store = configureStore({ .concat(earningApi.middleware) .concat(socialApi.middleware) .concat(tokenApi.middleware), - preloadedState: load({ states: PERSISTED_KEYS }), + preloadedState, }) const PREFIX_REDUX_PERSIST = 'redux_localstorage_simple_' diff --git a/src/state/user/actions.ts b/src/state/user/actions.ts index 0a3fe1802c..23186d7acd 100644 --- a/src/state/user/actions.ts +++ b/src/state/user/actions.ts @@ -49,7 +49,9 @@ export const toggleTopTrendingTokens = createAction('user/toggleTopTrendin export type ToggleFavoriteTokenPayload = { chainId: ChainId -} & ({ isNative?: false; address: string } | { isNative: true; address?: never }) + address: string + newValue?: boolean +} export const toggleFavoriteToken = createAction('user/toggleFavoriteToken') export const updateChainId = createAction('user/updateChainId') export const updateTokenAnalysisSettings = createAction('user/updateTokenAnalysisSettings') diff --git a/src/state/user/hooks.tsx b/src/state/user/hooks.tsx index 6e4275450d..74fa4ac391 100644 --- a/src/state/user/hooks.tsx +++ b/src/state/user/hooks.tsx @@ -3,6 +3,7 @@ import { useCallback, useMemo } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useGetParticipantInfoQuery } from 'services/kyberAISubscription' +import { SUGGESTED_BASES } from 'constants/bases' import { DEFAULT_SLIPPAGE_TESTNET, TERM_FILES_PATH } from 'constants/index' import { SupportedLocale } from 'constants/locales' import { PINNED_PAIRS } from 'constants/tokens' @@ -16,6 +17,7 @@ import { import useDebounce from 'hooks/useDebounce' import { ParticipantInfo, ParticipantStatus } from 'pages/TrueSightV2/types' import { AppDispatch, AppState } from 'state' +import { useKyberSwapConfig } from 'state/application/hooks' import { useIsConnectingWallet, useSessionInfo } from 'state/authen/hooks' import { useAppDispatch, useAppSelector } from 'state/hooks' import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo' @@ -49,9 +51,11 @@ import { updateUserSlippageTolerance, updateUserSlippageToleranceForLineaTestnet, } from 'state/user/actions' -import { CROSS_CHAIN_SETTING_DEFAULT, CrossChainSetting, VIEW_MODE, getFavoriteTokenDefault } from 'state/user/reducer' +import { CROSS_CHAIN_SETTING_DEFAULT, CrossChainSetting, VIEW_MODE } from 'state/user/reducer' import { isAddress, isChristmasTime } from 'utils' +const MAX_FAVORITE_LIMIT = 12 + function serializeToken(token: Token | WrappedTokenInfo): SerializedToken { return { chainId: token.chainId, @@ -395,18 +399,35 @@ export function useToggleTopTrendingTokens(): () => void { export const useUserFavoriteTokens = (chainId: ChainId) => { const dispatch = useDispatch() - const { favoriteTokensByChainId } = useSelector((state: AppState) => state.user) + const { favoriteTokensByChainIdv2: favoriteTokensByChainId } = useSelector((state: AppState) => state.user) + const { commonTokens } = useKyberSwapConfig(chainId) + const defaultTokens = useMemo(() => { + return commonTokens || SUGGESTED_BASES[chainId || ChainId.MAINNET].map(e => e.address) + }, [commonTokens, chainId]) const favoriteTokens = useMemo(() => { if (!chainId) return undefined - return favoriteTokensByChainId - ? favoriteTokensByChainId[chainId] || getFavoriteTokenDefault(chainId) - : getFavoriteTokenDefault(chainId) - }, [chainId, favoriteTokensByChainId]) + const favoritedTokens = favoriteTokensByChainId?.[chainId] || {} + const favoritedTokenAddresses = defaultTokens + .filter(address => favoritedTokens[address.toLowerCase()] !== false) + .concat(Object.keys(favoritedTokens).filter(address => favoritedTokens[address])) + + return [...new Set(favoritedTokenAddresses.map(a => a.toLowerCase()))] + }, [chainId, favoriteTokensByChainId, defaultTokens]) const toggleFavoriteToken = useCallback( - (payload: ToggleFavoriteTokenPayload) => dispatch(toggleFavoriteTokenAction(payload)), - [dispatch], + (payload: ToggleFavoriteTokenPayload) => { + if (!favoriteTokens) return + const address = payload.address.toLowerCase() + // Is adding favorite and reached max limit + if (favoriteTokens.indexOf(address) < 0 && favoriteTokens.length >= MAX_FAVORITE_LIMIT) { + return + } + const newValue = favoriteTokens.indexOf(address) < 0 + + dispatch(toggleFavoriteTokenAction({ ...payload, newValue })) + }, + [dispatch, favoriteTokens], ) return { favoriteTokens, toggleFavoriteToken } diff --git a/src/state/user/reducer.ts b/src/state/user/reducer.ts index 7cd19ea5da..5adcce2af2 100644 --- a/src/state/user/reducer.ts +++ b/src/state/user/reducer.ts @@ -60,7 +60,7 @@ export type CrossChainSetting = { enableExpressExecution: boolean } -interface UserState { +export interface UserState { // the timestamp of the last updateVersion action lastUpdateVersionTimestamp?: number @@ -101,7 +101,7 @@ interface UserState { kyberAIDisplaySettings: { [k: string]: boolean } - favoriteTokensByChainId: Partial< + favoriteTokensByChainId?: Partial< Record< ChainId, { @@ -110,6 +110,14 @@ interface UserState { } > > + favoriteTokensByChainIdv2: Partial< + Record< + ChainId, + { + [address: string]: boolean + } + > + > readonly chainId: ChainId acceptedTermVersion: number | null viewMode: VIEW_MODE @@ -181,6 +189,7 @@ const initialState: UserState = { liquidationsOnCEX: true, }, favoriteTokensByChainId: {}, + favoriteTokensByChainIdv2: {}, chainId: ChainId.MAINNET, acceptedTermVersion: null, viewMode: VIEW_MODE.GRID, @@ -296,31 +305,19 @@ export default createReducer(initialState, builder => .addCase(toggleKyberAIBanner, state => { state.showKyberAIBanner = !state.showKyberAIBanner }) - .addCase(toggleFavoriteToken, (state, { payload: { chainId, isNative, address } }) => { - if (!state.favoriteTokensByChainId) { - state.favoriteTokensByChainId = {} + .addCase(toggleFavoriteToken, (state, { payload: { chainId, address, newValue } }) => { + if (!state.favoriteTokensByChainIdv2) { + state.favoriteTokensByChainIdv2 = {} } - let favoriteTokens = state.favoriteTokensByChainId[chainId] - if (!favoriteTokens) { - favoriteTokens = getFavoriteTokenDefault(chainId) - state.favoriteTokensByChainId[chainId] = favoriteTokens + if (!state.favoriteTokensByChainIdv2[chainId]) { + state.favoriteTokensByChainIdv2[chainId] = {} } - if (isNative) { - const previousValue = favoriteTokens.includeNativeToken - favoriteTokens.includeNativeToken = !previousValue - return - } - - if (address) { - // this is intentionally added, to remove compiler error - const index = favoriteTokens.addresses.findIndex(addr => addr === address) - if (index === -1) { - favoriteTokens.addresses.push(address) - return - } - favoriteTokens.addresses.splice(index, 1) + const favoriteTokens = state.favoriteTokensByChainIdv2[chainId] + const lowercaseAddress = address.toLowerCase() + if (favoriteTokens) { + favoriteTokens[lowercaseAddress] = newValue !== undefined ? newValue : !favoriteTokens[lowercaseAddress] } }) .addCase(updateChainId, (state, { payload: chainId }) => {