From 5f1cb766d9008d2783af813a0fbff6c6877b0ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Ho=C3=A0i=20Danh?= <33005392+nguyenhoaidanh@users.noreply.github.com> Date: Wed, 9 Aug 2023 15:09:03 +0700 Subject: [PATCH 01/10] update: border radius logo crosschain (#2153) --- src/components/Logo/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Logo/index.tsx b/src/components/Logo/index.tsx index d9744071db..409090cd37 100644 --- a/src/components/Logo/index.tsx +++ b/src/components/Logo/index.tsx @@ -65,7 +65,7 @@ export function TokenLogoWithChain(data: any) { style={{ width: size, height: size, - borderRadius: '50%', + borderRadius: '4px', }} /> Date: Wed, 9 Aug 2023 23:56:47 +0700 Subject: [PATCH 02/10] ci: auto generate release note (#2155) --- .github/workflows/release.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 096e94b99d..150434c177 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -165,3 +165,4 @@ jobs: tag_name: ${{ env.VERSION_TAG }} prerelease: false title: 'KyberSwap Interface - ${{ env.VERSION_TAG }}' + generate_release_notes: true From b4eb15394591cec8166d9d198c5af2110af88157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Ho=C3=A0i=20Danh?= <33005392+nguyenhoaidanh@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:14:44 +0700 Subject: [PATCH 03/10] fix: crosschain native amount wrong (#2154) --- src/components/SearchModal/CurrencySearch.tsx | 12 +++++----- src/hooks/bridge/index.ts | 9 ++++++++ .../TransfersHistory/HistoryTable/Desktop.tsx | 22 ------------------- 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/src/components/SearchModal/CurrencySearch.tsx b/src/components/SearchModal/CurrencySearch.tsx index 7a77c1bdfa..7a5cc0acef 100644 --- a/src/components/SearchModal/CurrencySearch.tsx +++ b/src/components/SearchModal/CurrencySearch.tsx @@ -385,13 +385,13 @@ export function CurrencySearch({ const removeImportedToken = useCallback( (token: Token) => { removeToken(chainId, token.address) - - toggleFavoriteToken({ - chainId, - address: token.address, - }) + if (favoriteTokens?.some(el => el.toLowerCase() === token.address.toLowerCase())) + toggleFavoriteToken({ + chainId, + address: token.address, + }) }, - [chainId, toggleFavoriteToken, removeToken], + [chainId, toggleFavoriteToken, removeToken, favoriteTokens], ) const removeAllImportToken = () => { diff --git a/src/hooks/bridge/index.ts b/src/hooks/bridge/index.ts index 0b86c2cf50..92ee2ceacc 100644 --- a/src/hooks/bridge/index.ts +++ b/src/hooks/bridge/index.ts @@ -17,7 +17,9 @@ export const useEthBalanceOfAnotherChain = (chainId: ChainId | undefined) => { const { readProvider } = useKyberSwapConfig(chainId) const { account } = useActiveWeb3React() const [balance, setBalance] = useState>() + useEffect(() => { + const controller = new AbortController() async function getBalance() { try { if (!readProvider || !account || !chainId) { @@ -25,12 +27,19 @@ export const useEthBalanceOfAnotherChain = (chainId: ChainId | undefined) => { return } const balance = await readProvider.getBalance(account) + if (controller.signal.aborted) { + return + } setBalance(CurrencyAmount.fromRawAmount(NativeCurrencies[chainId], JSBI.BigInt(balance))) } catch (error) { + if (controller.signal.aborted) { + return + } setBalance(undefined) } } getBalance() + return () => controller.abort() }, [chainId, readProvider, account]) return balance diff --git a/src/pages/CrossChain/TransfersHistory/HistoryTable/Desktop.tsx b/src/pages/CrossChain/TransfersHistory/HistoryTable/Desktop.tsx index 1c285440e3..819a285f5a 100644 --- a/src/pages/CrossChain/TransfersHistory/HistoryTable/Desktop.tsx +++ b/src/pages/CrossChain/TransfersHistory/HistoryTable/Desktop.tsx @@ -2,7 +2,6 @@ import { Trans } from '@lingui/macro' import { Flex } from 'rebass' import styled, { css } from 'styled-components' -import { ITEMS_PER_PAGE } from 'pages/Bridge/consts' import TransactionItem from 'pages/CrossChain/TransfersHistory/HistoryTable/TransactionItem' import { Props } from './index' @@ -46,26 +45,6 @@ export const TableRow = styled.div` ` const Desktop: React.FC = ({ transfers }) => { - const renderInvisibleRows = () => { - // don't need invisible rows for upToExtraSmall screens - if (transfers.length === ITEMS_PER_PAGE) { - return null - } - - return Array(ITEMS_PER_PAGE - transfers.length) - .fill(0) - .map((_, i) => { - return ( - - ) - }) - } - return ( @@ -88,7 +67,6 @@ const Desktop: React.FC = ({ transfers }) => { {transfers.map(transfer => ( ))} - {renderInvisibleRows()} ) } From 8c9a5eb0b221c697402a370cdd9a835a35fdc3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Ho=C3=A0i=20Danh?= <33005392+nguyenhoaidanh@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:16:10 +0700 Subject: [PATCH 04/10] update: integrate stable coin from api (#2136) --- src/components/KyberAITokenBanner/index.tsx | 11 +- src/components/PoolList/index.tsx | 7 +- .../SwapForm/hooks/useCheckStablePairSwap.ts | 11 +- src/constants/tokens.ts | 125 ------------------ src/hooks/Tokens.ts | 18 +++ src/pages/ProAmmPools/index.tsx | 6 +- src/pages/SwapV2/index.tsx | 12 +- .../useUpdateSlippageInStableCoinSwap.tsx | 30 ++--- src/services/ksSetting.ts | 12 +- src/state/swap/hooks.ts | 13 +- src/state/topTokens/hooks.ts | 11 +- 11 files changed, 64 insertions(+), 192 deletions(-) diff --git a/src/components/KyberAITokenBanner/index.tsx b/src/components/KyberAITokenBanner/index.tsx index 2729469d6c..5ee3825895 100644 --- a/src/components/KyberAITokenBanner/index.tsx +++ b/src/components/KyberAITokenBanner/index.tsx @@ -14,8 +14,9 @@ import CurrencyLogo from 'components/CurrencyLogo' import ApeIcon from 'components/Icons/ApeIcon' import Row, { RowBetween, RowFit } from 'components/Row' import { APP_PATHS } from 'constants/index' -import { KNC, NativeCurrencies, STABLE_COINS_ADDRESS } from 'constants/tokens' +import { KNC, NativeCurrencies } from 'constants/tokens' import { useActiveWeb3React } from 'hooks' +import { useStableCoins } from 'hooks/Tokens' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import useTheme from 'hooks/useTheme' import KyberScoreMeter from 'pages/TrueSightV2/components/KyberScoreMeter' @@ -44,6 +45,8 @@ const KyberAITokenBanner = ({ const token0 = currencyIn?.wrapped const token1 = currencyOut?.wrapped + const { isStableCoin } = useStableCoins(chainId) + const { data: tokenInputOverview } = useTokenDetailQuery( { address: token0?.address, chain }, { skip: !token0?.address || !account || !isWhiteList || !chain, refetchOnMountOrArgChange: true }, @@ -87,11 +90,7 @@ const KyberAITokenBanner = ({ if (!token && !staticMode) return null - if ( - staticMode && - STABLE_COINS_ADDRESS[chainId].some(value => value.toLowerCase() === currencyIn?.wrapped.address.toLowerCase()) - ) - return null + if (staticMode && isStableCoin(currencyIn?.wrapped.address.toLowerCase())) return null const staticModeCurrency = !currencyIn || KNC[chainId].equals(currencyIn) ? NativeCurrencies[chainId] : currencyIn const color = staticMode ? theme.primary : calculateValueToColor(token?.kyberScore || 0, theme) return ( diff --git a/src/components/PoolList/index.tsx b/src/components/PoolList/index.tsx index 1c94b602f0..ac2be52b6a 100644 --- a/src/components/PoolList/index.tsx +++ b/src/components/PoolList/index.tsx @@ -15,8 +15,8 @@ import ListItem from 'components/PoolList/ListItem' import ShareModal from 'components/ShareModal' import { ClickableText } from 'components/YieldPools/styleds' import { AMP_HINT, AMP_LIQUIDITY_HINT, MAX_ALLOW_APY } from 'constants/index' -import { STABLE_COINS_ADDRESS } from 'constants/tokens' import { useActiveWeb3React } from 'hooks' +import { useStableCoins } from 'hooks/Tokens' import { SelectPairInstructionWrapper } from 'pages/Pools/styleds' import { ApplicationModal } from 'state/application/actions' import { useModalOpen, useOpenModal } from 'state/application/hooks' @@ -309,6 +309,7 @@ const PoolList = ({ currencies, searchValue, isShowOnlyActiveFarmPools, onlyShow const { data: farms } = useActiveAndUniqueFarmsData() const [currentPage, setCurrentPage] = useState(1) + const { stableCoins } = useStableCoins(chainId) const sortedFilteredSubgraphPoolsData = useMemo(() => { let res = [...subgraphPoolsData] @@ -347,7 +348,7 @@ const PoolList = ({ currencies, searchValue, isShowOnlyActiveFarmPools, onlyShow }) if (onlyShowStable) { - const stableList = isEVM ? STABLE_COINS_ADDRESS[chainId]?.map(item => item.toLowerCase()) || [] : [] + const stableList = isEVM ? stableCoins?.map(item => item.address.toLowerCase()) || [] : [] res = res.filter(poolData => { return ( stableList.includes(poolData.token0.id.toLowerCase()) && stableList.includes(poolData.token1.id.toLowerCase()) @@ -364,8 +365,8 @@ const PoolList = ({ currencies, searchValue, isShowOnlyActiveFarmPools, onlyShow onlyShowStable, farms, searchValue, - chainId, isEVM, + stableCoins, ]) const startIndex = (currentPage - 1) * ITEM_PER_PAGE diff --git a/src/components/SwapForm/hooks/useCheckStablePairSwap.ts b/src/components/SwapForm/hooks/useCheckStablePairSwap.ts index 13ada4bab7..fa5b794e14 100644 --- a/src/components/SwapForm/hooks/useCheckStablePairSwap.ts +++ b/src/components/SwapForm/hooks/useCheckStablePairSwap.ts @@ -1,15 +1,10 @@ import { Currency } from '@kyberswap/ks-sdk-core' -import { STABLE_COINS_ADDRESS } from 'constants/tokens' +import { useStableCoins } from 'hooks/Tokens' const useCheckStablePairSwap = (currencyIn: Currency | undefined, currencyOut: Currency | undefined) => { - const isStablePairSwap = Boolean( - currencyIn && - currencyOut && - STABLE_COINS_ADDRESS[currencyIn.chainId].includes(currencyIn.wrapped.address) && - STABLE_COINS_ADDRESS[currencyOut.chainId].includes(currencyOut.wrapped.address), - ) - + const { isStableCoin } = useStableCoins(currencyIn?.chainId) + const isStablePairSwap = isStableCoin(currencyIn?.wrapped?.address) && isStableCoin(currencyOut?.wrapped?.address) return isStablePairSwap } diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index 70561933f4..a133595a66 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -25,131 +25,6 @@ export const NativeCurrencies = new Proxy(NativeCurrenciesLocal, { }, }) -export const STABLE_COINS_ADDRESS: { [chainId in ChainId]: string[] } = { - [ChainId.MAINNET]: [ - '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI - '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC - '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - '0x4Fabb145d64652a948d72533023f6E7A623C7C53', // BUSD - '0x8D6CeBD76f18E1558D4DB88138e2DeFB3909fAD6', // MAI - '0xB0B195aEFA3650A6908f15CdaC7D92F8a5791B0B', // BOB - '0x99D8a9C45b2ecA8864373A26D1459e3Dff1e17F3', // MIM - ], - [ChainId.MATIC]: [ - '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063', // DAI - '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // usdc - '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', // usdt - '0xa3Fa99A148fA48D14Ed51d610c367C61876997F1', // MAI - '0xB0B195aEFA3650A6908f15CdaC7D92F8a5791B0B', // BOB - '0x49a0400587A7F65072c87c4910449fDcC5c47242', // MIM - ], - [ChainId.BSCMAINNET]: [ - '0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3', // dai - '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', // usdc - '0x55d398326f99059fF775485246999027B3197955', // usdt - '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56', // busd - '0x3F56e0c36d275367b8C502090EDF38289b3dEa0d', // MAI - '0xfE19F0B51438fd612f6FD59C1dbB3eA319f433Ba', // MIM - '0xB0B195aEFA3650A6908f15CdaC7D92F8a5791B0B', // BOB - ], - [ChainId.AVAXMAINNET]: [ - '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7', // USDt - '0xc7198437980c041c805A1EDcbA50c1Ce5db95118', // usdt.e - '0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664', // usdc.e - '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', // usdc - '0xd586E7F844cEa2F87f50152665BCbc2C279D8d70', // dai.e - '0x3B55E45fD6bd7d4724F5c47E0d1bCaEdd059263e', // MAI - '0x5c49b268c9841AFF1Cc3B0a418ff5c3442eE3F3b', // MAI - '0x130966628846BFd36ff31a822705796e8cb8C18D', // MIM - '0x111111111111ed1D73f860F57b2798b683f2d325', // YUSD - ], - [ChainId.FANTOM]: [ - '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', // dai - '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75', // usdc - '0x049d68029688eAbF473097a2fC38ef61633A3C7A', // fusdt - '0xfB98B335551a418cD0737375a2ea0ded62Ea213b', // MAI - '0x82f0B8B456c1A451378467398982d4834b6829c1', // MIM - ], - [ChainId.CRONOS]: [ - '0xF2001B145b43032AAF5Ee2884e456CCd805F677D', // dai - '0xc21223249CA28397B4B6541dfFaEcC539BfF0c59', // usdc - '0x66e428c3f67a68878562e79A0234c1F83c208770', // usdt - '0xC74D59A548ecf7fc1754bb7810D716E9Ac3e3AE5', // busd - '0x2Ae35c8E3D4bD57e8898FF7cd2bBff87166EF8cb', // MAI - ], - [ChainId.ARBITRUM]: [ - '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', // dai - '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8', // usdc - '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', // usdt - '0x3F56e0c36d275367b8C502090EDF38289b3dEa0d', // MAI - '0xFEa7a6a0B346362BF88A9e4A88416B77a57D6c2A', // MIM - ], - [ChainId.BTTC]: [ - '0x9B5F27f6ea9bBD753ce3793a07CbA3C74644330d', // usdt_b - '0xdB28719F7f938507dBfe4f0eAe55668903D34a15', // usdt_t - '0xE887512ab8BC60BcC9224e1c3b5Be68E26048B8B', // usdt_e - '0x935faA2FCec6Ab81265B301a30467Bbc804b43d3', // usdc_t - '0xCa424b845497f7204D9301bd13Ff87C0E2e86FCF', // usdc_b - '0xAE17940943BA9440540940DB0F1877f101D39e8b', // usdc_e - '0xe7dC549AE8DB61BDE71F22097BEcc8dB542cA100', // dai_e - '0x17F235FD5974318E4E2a5e37919a209f7c37A6d1', // usdd_t - ], - [ChainId.VELAS]: [ - '0xe2C120f188eBd5389F71Cf4d9C16d05b62A58993', // usdc - '0x01445C31581c354b7338AC35693AB2001B50b9aE', // usdt - '0xc111c29A988AE0C0087D97b33C6E6766808A3BD3', // busd - ], - [ChainId.AURORA]: [ - '0xe3520349F477A5F6EB06107066048508498A291b', // Dai - '0xB12BFcA5A55806AaF64E99521918A4bf0fC40802', // usdc - '0x4988a896b1227218e4A686fdE5EabdcAbd91571f', // usdt - '0xdFA46478F9e5EA86d57387849598dbFB2e964b02', // MAI - ], - [ChainId.OASIS]: [ - '0x80A16016cC4A2E6a2CACA8a4a498b1699fF0f844', // usdc - '0xdC19A122e268128B5eE20366299fc7b5b199C8e3', // usdtet - '0x639A647fbe20b6c8ac19E48E2de44ea792c62c5C', // busd - '0x6Cb9750a92643382e020eA9a170AbB83Df05F30B', // usdt - '0x5a4Ba16C2AeB295822A95280A7c7149E87769E6A', // ceDAI - '0x81ECac0D6Be0550A00FF064a4f9dd2400585FE9c', // ceUSDC - '0x4Bf769b05E832FCdc9053fFFBC78Ca889aCb5E1E', // ceUSDT - ], - [ChainId.OPTIMISM]: [ - '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', // Dai - '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58', // usdt - '0x7F5c764cBc14f9669B88837ca1490cCa17c31607', // usdc - '0xdFA46478F9e5EA86d57387849598dbFB2e964b02', // MAI - '0xB0B195aEFA3650A6908f15CdaC7D92F8a5791B0B', // BOB - ], - [ChainId.ZKSYNC]: [ - '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', // USDC - '0x8E86e46278518EFc1C5CEd245cBA2C7e3ef11557', // USD+ - '0x2039bb4116B4EFc145Ec4f0e2eA75012D6C0f181', // BUSD - '0x493257fD37EDB34451f62EDf8D2a0C418852bA4C', // USDT - ], - [ChainId.LINEA_TESTNET]: [ - '0xf56dc6695cF1f5c364eDEbC7Dc7077ac9B586068', // USDC - '0x1990BC6dfe2ef605Bfc08f5A23564dB75642Ad73', // USDT - '0x8741Ba6225A6BF91f9D73531A98A89807857a2B3', // DAI - '0x7d43AABC515C356145049227CeE54B608342c0ad', // BUSD - ], - [ChainId.LINEA]: [ - '0x7d43aabc515c356145049227cee54b608342c0ad', // BUSD - ], - [ChainId.SOLANA]: [ - 'EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o', // Dai - 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // usdc - 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', // usdt - '9mWRABuz2x6koTPCWiCPM49WUbcrNqGTHBV9T9k7y1o7', // MAI - 'HRQke5DKdDo3jV7wnomyiM8AA3EzkVnxMDdo2FQ5XUe1', // MIM - ], - [ChainId.GĂ–RLI]: [], - [ChainId.MUMBAI]: [], - [ChainId.BSCTESTNET]: [], - [ChainId.AVAXTESTNET]: [], - [ChainId.SOLANA_DEVNET]: [], -} - // This list is intentionally different from the list above // Was requested from product team, to implement Swap fee config export const STABLE_COIN_ADDRESSES_TO_TAKE_FEE: Record = { diff --git a/src/hooks/Tokens.ts b/src/hooks/Tokens.ts index 00a1a361cb..e4b2146c7e 100644 --- a/src/hooks/Tokens.ts +++ b/src/hooks/Tokens.ts @@ -4,6 +4,7 @@ import axios from 'axios' import { arrayify } from 'ethers/lib/utils' import { useCallback, useMemo } from 'react' import { useSelector } from 'react-redux' +import { useGetTokenListQuery } from 'services/ksSetting' import useSWR from 'swr' import ERC20_INTERFACE, { ERC20_BYTES32_INTERFACE } from 'constants/abis/erc20' @@ -383,3 +384,20 @@ export function useCurrencyV2(currencyId: string | undefined, customChainId?: Ch return tokenInWhitelist || token }, [chainId, isETH, token, currencyId, tokenInWhitelist]) } + +export const useStableCoins = (chainId: ChainId | undefined) => { + const { data } = useGetTokenListQuery({ chainId: chainId as ChainId, isStable: true }, { skip: !chainId }) + + const stableCoins = useMemo(() => { + return data?.data?.tokens || [] + }, [data]) + + const isStableCoin = useCallback( + (address: string | undefined) => { + if (!address) return false + return stableCoins.some(token => token.address.toLowerCase() === address?.toLowerCase()) + }, + [stableCoins], + ) + return { isStableCoin, stableCoins } +} diff --git a/src/pages/ProAmmPools/index.tsx b/src/pages/ProAmmPools/index.tsx index 0a418a1068..0284ba8d9c 100644 --- a/src/pages/ProAmmPools/index.tsx +++ b/src/pages/ProAmmPools/index.tsx @@ -12,8 +12,8 @@ import LocalLoader from 'components/LocalLoader' import Pagination from 'components/Pagination' import { Input as PaginationInput } from 'components/Pagination/PaginationInputOnMobile' import ShareModal from 'components/ShareModal' -import { STABLE_COINS_ADDRESS } from 'constants/tokens' import { useActiveWeb3React } from 'hooks' +import { useStableCoins } from 'hooks/Tokens' import { SelectPairInstructionWrapper } from 'pages/Pools/styleds' import { ApplicationModal } from 'state/application/actions' import { useModalOpen, useOpenModal } from 'state/application/hooks' @@ -184,6 +184,7 @@ export default function ProAmmPoolList({ const cbId = currencies[Field.CURRENCY_B]?.wrapped.address.toLowerCase() const { chainId, account, isEVM, networkInfo } = useActiveWeb3React() + const { stableCoins } = useStableCoins(chainId) const userLiquidityPositionsQueryResult = useUserProMMPositions(tokenPriceMap) const loadingUserPositions = !account ? false : userLiquidityPositionsQueryResult.loading const userPositions = useMemo( @@ -260,7 +261,7 @@ export default function ProAmmPoolList({ } if (onlyShowStable) { - const stableList = chainId ? STABLE_COINS_ADDRESS[chainId]?.map(item => item.toLowerCase()) || [] : [] + const stableList = chainId ? stableCoins?.map(item => item.address.toLowerCase()) || [] : [] filteredPools = filteredPools.filter(poolData => { return ( stableList.includes(poolData.token0.address.toLowerCase()) && @@ -285,6 +286,7 @@ export default function ProAmmPoolList({ onlyShowStable, searchValue, farms, + stableCoins, chainId, listComparator, ]) diff --git a/src/pages/SwapV2/index.tsx b/src/pages/SwapV2/index.tsx index c6aa1d779b..45abdfb5b9 100644 --- a/src/pages/SwapV2/index.tsx +++ b/src/pages/SwapV2/index.tsx @@ -52,9 +52,8 @@ import { Wrapper, } from 'components/swapv2/styleds' import { AGGREGATOR_WAITING_TIME, APP_PATHS, TIME_TO_REFRESH_SWAP_RATE } from 'constants/index' -import { STABLE_COINS_ADDRESS } from 'constants/tokens' import { useActiveWeb3React } from 'hooks' -import { useAllTokens, useIsLoadedTokenDefault } from 'hooks/Tokens' +import { useAllTokens, useIsLoadedTokenDefault, useStableCoins } from 'hooks/Tokens' import { ApprovalState, useApproveCallbackFromTradeV2 } from 'hooks/useApproveCallback' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import useParsedQueryString from 'hooks/useParsedQueryString' @@ -408,16 +407,11 @@ export default function Swap() { useSyncTokenSymbolToUrl(currencyIn, currencyOut, onSelectSuggestedPair, isSelectCurrencyManually) const isLoadedTokenDefault = useIsLoadedTokenDefault() + const { isStableCoin } = useStableCoins(chainId) const [rawSlippage] = useUserSlippageTolerance() - const isStableCoinSwap = Boolean( - INPUT?.currencyId && - OUTPUT?.currencyId && - chainId && - STABLE_COINS_ADDRESS[chainId].includes(INPUT?.currencyId) && - STABLE_COINS_ADDRESS[chainId].includes(OUTPUT?.currencyId), - ) + const isStableCoinSwap = isStableCoin(INPUT?.currencyId) && isStableCoin(OUTPUT?.currencyId) useUpdateSlippageInStableCoinSwap() diff --git a/src/pages/SwapV3/useUpdateSlippageInStableCoinSwap.tsx b/src/pages/SwapV3/useUpdateSlippageInStableCoinSwap.tsx index 7deb4f1994..32552c8bef 100644 --- a/src/pages/SwapV3/useUpdateSlippageInStableCoinSwap.tsx +++ b/src/pages/SwapV3/useUpdateSlippageInStableCoinSwap.tsx @@ -3,14 +3,15 @@ import { useSelector } from 'react-redux' import { usePrevious } from 'react-use' import { DEFAULT_SLIPPAGE, DEFAULT_SLIPPAGE_STABLE_PAIR_SWAP } from 'constants/index' -import { STABLE_COINS_ADDRESS } from 'constants/tokens' import { useActiveWeb3React } from 'hooks' +import { useStableCoins } from 'hooks/Tokens' import { AppState } from 'state' import { Field } from 'state/swap/actions' import { useUserSlippageTolerance } from 'state/user/hooks' const useUpdateSlippageInStableCoinSwap = () => { const { chainId } = useActiveWeb3React() + const { isStableCoin } = useStableCoins(chainId) const inputCurrencyId = useSelector((state: AppState) => state.swap[Field.INPUT].currencyId) const previousInputCurrencyId = usePrevious(inputCurrencyId) const outputCurrencyId = useSelector((state: AppState) => state.swap[Field.OUTPUT].currencyId) @@ -21,21 +22,8 @@ const useUpdateSlippageInStableCoinSwap = () => { rawSlippageRef.current = slippage useEffect(() => { - const isStableCoinPreviousSwap = Boolean( - chainId && - previousInputCurrencyId && - previousOutputCurrencyId && - STABLE_COINS_ADDRESS[chainId].includes(previousInputCurrencyId) && - STABLE_COINS_ADDRESS[chainId].includes(previousOutputCurrencyId), - ) - - const isStableCoinSwap = Boolean( - chainId && - inputCurrencyId && - outputCurrencyId && - STABLE_COINS_ADDRESS[chainId].includes(inputCurrencyId) && - STABLE_COINS_ADDRESS[chainId].includes(outputCurrencyId), - ) + const isStableCoinPreviousSwap = isStableCoin(previousInputCurrencyId) && isStableCoin(previousOutputCurrencyId) + const isStableCoinSwap = isStableCoin(inputCurrencyId) && isStableCoin(outputCurrencyId) if (isStableCoinPreviousSwap === isStableCoinSwap) { return @@ -49,7 +37,15 @@ const useUpdateSlippageInStableCoinSwap = () => { if (!isStableCoinSwap && rawSlippageRef.current > DEFAULT_SLIPPAGE) { setSlippage(DEFAULT_SLIPPAGE) } - }, [chainId, inputCurrencyId, outputCurrencyId, previousInputCurrencyId, previousOutputCurrencyId, setSlippage]) + }, [ + isStableCoin, + chainId, + inputCurrencyId, + outputCurrencyId, + previousInputCurrencyId, + previousOutputCurrencyId, + setSlippage, + ]) } export default useUpdateSlippageInStableCoinSwap diff --git a/src/services/ksSetting.ts b/src/services/ksSetting.ts index ee7ca0648a..e756d4afb4 100644 --- a/src/services/ksSetting.ts +++ b/src/services/ksSetting.ts @@ -79,16 +79,11 @@ const ksSettingApi = createApi({ getTokenList: builder.query< TokenListResponse, - { chainId: number; page: number; pageSize: number; isWhitelisted: boolean } + { chainId: number; page?: number; pageSize?: number; isWhitelisted?: boolean; isStable?: boolean } >({ - query: ({ chainId, page, pageSize, isWhitelisted }) => ({ + query: ({ chainId, ...params }) => ({ url: `/tokens`, - params: { - chainIds: chainId, - page, - pageSize, - isWhitelisted, - }, + params: { ...params, chainIds: chainId }, }), }), }), @@ -99,6 +94,7 @@ export const { useLazyGetKyberswapConfigurationQuery, useGetKyberswapGlobalConfigurationQuery, useLazyGetTokenListQuery, + useGetTokenListQuery, } = ksSettingApi export default ksSettingApi diff --git a/src/state/swap/hooks.ts b/src/state/swap/hooks.ts index 8b0667cdfb..447c71f656 100644 --- a/src/state/swap/hooks.ts +++ b/src/state/swap/hooks.ts @@ -9,9 +9,9 @@ import { useDispatch, useSelector } from 'react-redux' import { useLocation } from 'react-router-dom' import { APP_PATHS, BAD_RECIPIENT_ADDRESSES } from 'constants/index' -import { DEFAULT_OUTPUT_TOKEN_BY_CHAIN, NativeCurrencies, STABLE_COINS_ADDRESS } from 'constants/tokens' +import { DEFAULT_OUTPUT_TOKEN_BY_CHAIN, NativeCurrencies } from 'constants/tokens' import { useActiveWeb3React } from 'hooks' -import { useCurrencyV2 } from 'hooks/Tokens' +import { useCurrencyV2, useStableCoins } from 'hooks/Tokens' import { useTradeExactIn } from 'hooks/Trades' import useENS from 'hooks/useENS' import useParsedQueryString from 'hooks/useParsedQueryString' @@ -435,16 +435,11 @@ export const useOutputCurrency = () => { export const useCheckStablePairSwap = () => { const { chainId } = useActiveWeb3React() + const { isStableCoin } = useStableCoins(chainId) const inputCurrencyId = useSelector((state: AppState) => state.swap[Field.INPUT].currencyId) const outputCurrencyId = useSelector((state: AppState) => state.swap[Field.OUTPUT].currencyId) - const isStablePairSwap = Boolean( - chainId && - inputCurrencyId && - outputCurrencyId && - STABLE_COINS_ADDRESS[chainId].includes(inputCurrencyId) && - STABLE_COINS_ADDRESS[chainId].includes(outputCurrencyId), - ) + const isStablePairSwap = isStableCoin(inputCurrencyId) && isStableCoin(outputCurrencyId) return isStablePairSwap } diff --git a/src/state/topTokens/hooks.ts b/src/state/topTokens/hooks.ts index 7cb0a3b55a..08b4d6c2b7 100644 --- a/src/state/topTokens/hooks.ts +++ b/src/state/topTokens/hooks.ts @@ -3,8 +3,9 @@ import { useEffect, useMemo } from 'react' import { KS_SETTING_API } from 'constants/env' import { SUPPORTED_NETWORKS } from 'constants/networks' -import { CORRELATED_COINS_ADDRESS, STABLE_COINS_ADDRESS, SUPER_STABLE_COINS_ADDRESS } from 'constants/tokens' +import { CORRELATED_COINS_ADDRESS, SUPER_STABLE_COINS_ADDRESS } from 'constants/tokens' import { useActiveWeb3React } from 'hooks' +import { useStableCoins } from 'hooks/Tokens' import { useAppDispatch, useAppSelector } from 'state/hooks' import { updateTopTokens } from '.' @@ -73,6 +74,7 @@ export const usePairFactor = (tokens: [Token | undefined | null, Token | undefin // - non-whitelisted / non-whitelisted const { chainId } = useActiveWeb3React() const topTokens = useTopTokens() + const { isStableCoin } = useStableCoins(chainId) const token0 = tokens[0] const token1 = tokens[1] @@ -83,8 +85,7 @@ export const usePairFactor = (tokens: [Token | undefined | null, Token | undefin SUPER_STABLE_COINS_ADDRESS[chainId].includes(token1.address) if (isBothSuperStable) return PairFactor.SUPER_STABLE - const isBothStable = - STABLE_COINS_ADDRESS[chainId].includes(token0.address) && STABLE_COINS_ADDRESS[chainId].includes(token1.address) + const isBothStable = isStableCoin(token0.address) && isStableCoin(token1.address) const isCorrelated = CORRELATED_COINS_ADDRESS[chainId].some( coinsGroup => coinsGroup.includes(token0.address) && coinsGroup.includes(token1.address), ) @@ -93,8 +94,8 @@ export const usePairFactor = (tokens: [Token | undefined | null, Token | undefin const isBothTop = topTokens[token0.address] && topTokens[token1.address] && - !STABLE_COINS_ADDRESS[chainId].includes(token0.address) && - !STABLE_COINS_ADDRESS[chainId].includes(token1.address) + !isStableCoin(token0.address) && + !isStableCoin(token1.address) if (isBothTop) return PairFactor.NOMAL return PairFactor.EXOTIC From e9fe02c1ff9a95849c1c5d99fbca420718f00ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Ho=C3=A0i=20Danh?= <33005392+nguyenhoaidanh@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:16:38 +0700 Subject: [PATCH 05/10] kyberAI api: remove wallet address (#2143) --- src/pages/CrossChain/SwapForm/index.tsx | 12 ++++++------ .../TrueSightV2/components/KyberAIWidget.tsx | 3 +-- src/pages/TrueSightV2/components/table/index.tsx | 8 ++------ .../hooks/useIsReachMaxLimitWatchedToken.tsx | 4 ---- src/pages/TrueSightV2/hooks/useKyberAIData.tsx | 16 ++++++---------- .../hooks/useKyberAITokenOverview.tsx | 4 +--- src/pages/TrueSightV2/pages/SingleToken.tsx | 3 +-- .../TrueSightV2/pages/TokenAnalysisList.tsx | 11 ++--------- src/state/user/hooks.tsx | 7 ++----- 9 files changed, 21 insertions(+), 47 deletions(-) diff --git a/src/pages/CrossChain/SwapForm/index.tsx b/src/pages/CrossChain/SwapForm/index.tsx index 226c18ff9e..7bc0ff3724 100644 --- a/src/pages/CrossChain/SwapForm/index.tsx +++ b/src/pages/CrossChain/SwapForm/index.tsx @@ -222,6 +222,12 @@ export default function SwapForm() { dstAmount: tokenAmountOut, } + saveTxsToDb(payload) + .unwrap() + .catch(e => { + captureExceptionCrossChain(payload, e, 'CrossChain') + }) + // trigger for partner const params = { transactionId: tx.hash, @@ -237,12 +243,6 @@ export default function SwapForm() { }) }, 3000) }) - - saveTxsToDb(payload) - .unwrap() - .catch(e => { - captureExceptionCrossChain(payload, e, 'CrossChain') - }) } catch (error) { console.error(error) setSwapState(state => ({ ...state, attemptingTxn: false, errorMessage: error?.message || error })) diff --git a/src/pages/TrueSightV2/components/KyberAIWidget.tsx b/src/pages/TrueSightV2/components/KyberAIWidget.tsx index 0ada939827..262fa79fc6 100644 --- a/src/pages/TrueSightV2/components/KyberAIWidget.tsx +++ b/src/pages/TrueSightV2/components/KyberAIWidget.tsx @@ -203,7 +203,7 @@ export default function Widget() { const { data, isFetching, isError } = useTokenListQuery( activeTab === WidgetTab.MyWatchlist - ? { type: KyberAIListType.ALL, page: 1, pageSize: 5, wallet: account, watchlist: true } + ? { type: KyberAIListType.ALL, page: 1, pageSize: 5, watchlist: true } : { type: { [WidgetTab.Bearish]: KyberAIListType.BEARISH, @@ -213,7 +213,6 @@ export default function Widget() { chain: 'all', page: 1, pageSize: 5, - wallet: account, }, { refetchOnMountOrArgChange: true, skip: !isWhiteList }, ) diff --git a/src/pages/TrueSightV2/components/table/index.tsx b/src/pages/TrueSightV2/components/table/index.tsx index 5964136d40..2b08cc48d0 100644 --- a/src/pages/TrueSightV2/components/table/index.tsx +++ b/src/pages/TrueSightV2/components/table/index.tsx @@ -649,9 +649,7 @@ const WidgetTokenRow = ({ ranking_order: index, option: 'remove', }) - Promise.all( - token.tokens.map(t => removeFromWatchlist({ wallet: account, tokenAddress: t.address, chain: t.chain })), - ).then(() => { + Promise.all(token.tokens.map(t => removeFromWatchlist({ tokenAddress: t.address, chain: t.chain }))).then(() => { setIsWatched(false) setLoadingStar(false) }) @@ -663,9 +661,7 @@ const WidgetTokenRow = ({ ranking_order: index, option: 'add', }) - Promise.all( - token.tokens.map(t => addToWatchlist({ wallet: account, tokenAddress: t.address, chain: t.chain })), - ).then(() => { + Promise.all(token.tokens.map(t => addToWatchlist({ tokenAddress: t.address, chain: t.chain }))).then(() => { setIsWatched(true) setLoadingStar(false) }) diff --git a/src/pages/TrueSightV2/hooks/useIsReachMaxLimitWatchedToken.tsx b/src/pages/TrueSightV2/hooks/useIsReachMaxLimitWatchedToken.tsx index c0374e209f..9a06730b1f 100644 --- a/src/pages/TrueSightV2/hooks/useIsReachMaxLimitWatchedToken.tsx +++ b/src/pages/TrueSightV2/hooks/useIsReachMaxLimitWatchedToken.tsx @@ -1,19 +1,15 @@ import { useMemo } from 'react' -import { useActiveWeb3React } from 'hooks' - import { ITokenList, KyberAIListType } from '../types' import { useTokenListQuery } from './useKyberAIData' const MAX_LIMIT_WATCHED_TOKEN = 30 export default function useIsReachMaxLimitWatchedToken(tokenCount?: number) { - const { account } = useActiveWeb3React() const { data } = useTokenListQuery({ type: KyberAIListType.ALL, chain: 'all', page: 1, pageSize: 30, - wallet: account, watchlist: true, }) diff --git a/src/pages/TrueSightV2/hooks/useKyberAIData.tsx b/src/pages/TrueSightV2/hooks/useKyberAIData.tsx index 089a2692fe..b3a1a74b62 100644 --- a/src/pages/TrueSightV2/hooks/useKyberAIData.tsx +++ b/src/pages/TrueSightV2/hooks/useKyberAIData.tsx @@ -34,19 +34,17 @@ const kyberAIApi = createApi({ chain?: string page?: number pageSize?: number - wallet?: string watchlist?: boolean keywords?: string } >({ - query: ({ type, chain, page, pageSize, wallet, watchlist, keywords }) => ({ + query: ({ type, chain, page, pageSize, watchlist, keywords }) => ({ url: '/tokens', params: { type: type || 'all', chain: chain || 'all', page: page || 1, size: pageSize || 10, - wallet, watchlist: watchlist ? 'true' : undefined, keywords, }, @@ -57,12 +55,11 @@ const kyberAIApi = createApi({ } throw new Error(res.msg) }, - providesTags: (result, error, arg) => - arg.watchlist === true && !!arg.wallet ? ['myWatchList', 'tokenList'] : ['tokenList'], + providesTags: (result, error, arg) => (arg.watchlist === true ? ['myWatchList', 'tokenList'] : ['tokenList']), }), //2. addToWatchlist: builder.mutation({ - query: (params: { wallet: string; tokenAddress: string; chain: string }) => ({ + query: (params: { tokenAddress: string; chain: string }) => ({ url: `/watchlist`, method: 'POST', params, @@ -71,7 +68,7 @@ const kyberAIApi = createApi({ }), //3. removeFromWatchlist: builder.mutation({ - query: (params: { wallet: string; tokenAddress: string; chain: string }) => ({ + query: (params: { tokenAddress: string; chain: string }) => ({ url: `/watchlist`, method: 'DELETE', params, @@ -84,10 +81,9 @@ const kyberAIApi = createApi({ }), //4. - tokenDetail: builder.query({ - query: ({ chain, address, account }: { chain?: string; address?: string; account?: string }) => ({ + tokenDetail: builder.query({ + query: ({ chain, address }: { chain?: string; address?: string }) => ({ url: `/overview/${chain}/${address}`, - params: { wallet: account }, }), transformResponse: (res: any) => { // If token is stablecoin remove its kyberscore value diff --git a/src/pages/TrueSightV2/hooks/useKyberAITokenOverview.tsx b/src/pages/TrueSightV2/hooks/useKyberAITokenOverview.tsx index c84ff25b49..5548573a65 100644 --- a/src/pages/TrueSightV2/hooks/useKyberAITokenOverview.tsx +++ b/src/pages/TrueSightV2/hooks/useKyberAITokenOverview.tsx @@ -1,15 +1,13 @@ import { useParams } from 'react-router' -import { useActiveWeb3React } from 'hooks' import { useIsWhiteListKyberAI } from 'state/user/hooks' import { useTokenDetailQuery } from './useKyberAIData' export default function useKyberAITokenOverview() { - const { account } = useActiveWeb3React() const { isWhiteList } = useIsWhiteListKyberAI() const { chain, address } = useParams() - const result = useTokenDetailQuery({ chain, address, account }, { skip: !chain || !address || !isWhiteList }) + const result = useTokenDetailQuery({ chain, address }, { skip: !chain || !address || !isWhiteList }) return result } diff --git a/src/pages/TrueSightV2/pages/SingleToken.tsx b/src/pages/TrueSightV2/pages/SingleToken.tsx index 29de65834e..fb7c97b525 100644 --- a/src/pages/TrueSightV2/pages/SingleToken.tsx +++ b/src/pages/TrueSightV2/pages/SingleToken.tsx @@ -255,7 +255,6 @@ const TokenNameGroup = ({ token, isLoading }: { token?: ITokenOverview; isLoadin }) removeFromWatchlist({ - wallet: account, tokenAddress: token?.address, chain, }).then(() => setIsWatched(false)) @@ -266,7 +265,7 @@ const TokenNameGroup = ({ token, isLoading }: { token?: ITokenOverview; isLoadin source: 'explore', option: 'add', }) - addToWatchlist({ wallet: account, tokenAddress: token?.address, chain }).then(() => setIsWatched(true)) + addToWatchlist({ tokenAddress: token?.address, chain }).then(() => setIsWatched(true)) } } } diff --git a/src/pages/TrueSightV2/pages/TokenAnalysisList.tsx b/src/pages/TrueSightV2/pages/TokenAnalysisList.tsx index 6b370d9b12..e73fb5bbd4 100644 --- a/src/pages/TrueSightV2/pages/TokenAnalysisList.tsx +++ b/src/pages/TrueSightV2/pages/TokenAnalysisList.tsx @@ -523,9 +523,7 @@ const TokenRow = ({ ranking_order: index, option: 'remove', }) - Promise.all( - token.tokens.map(t => removeFromWatchlist({ wallet: account, tokenAddress: t.address, chain: t.chain })), - ).then(() => { + Promise.all(token.tokens.map(t => removeFromWatchlist({ tokenAddress: t.address, chain: t.chain }))).then(() => { setIsWatched(false) setLoadingStar(false) }) @@ -537,9 +535,7 @@ const TokenRow = ({ ranking_order: index, option: 'add', }) - Promise.all( - token.tokens.map(t => addToWatchlist({ wallet: account, tokenAddress: t.address, chain: t.chain })), - ).then(() => { + Promise.all(token.tokens.map(t => addToWatchlist({ tokenAddress: t.address, chain: t.chain }))).then(() => { setIsWatched(true) setLoadingStar(false) }) @@ -773,7 +769,6 @@ const LoadingRowSkeleton = ({ hasExtraCol }: { hasExtraCol?: boolean }) => { } export default function TokenAnalysisList() { const theme = useTheme() - const { account } = useActiveWeb3React() const { mixpanelHandler } = useMixpanel() const [showShare, setShowShare] = useState(false) const [isScrolling, setIsScrolling] = useState(false) @@ -795,7 +790,6 @@ export default function TokenAnalysisList() { chain: (chain && SUPPORTED_NETWORK_KYBERAI[Number(chain) as ChainId]) || 'all', page, pageSize, - wallet: account, watchlist: true, } : { @@ -803,7 +797,6 @@ export default function TokenAnalysisList() { chain: (chain && SUPPORTED_NETWORK_KYBERAI[Number(chain) as ChainId]) || 'all', page, pageSize, - wallet: account, }, ) const listData = data?.data || [] diff --git a/src/state/user/hooks.tsx b/src/state/user/hooks.tsx index 74fa4ac391..6e3d3e1abd 100644 --- a/src/state/user/hooks.tsx +++ b/src/state/user/hooks.tsx @@ -539,14 +539,11 @@ export const useIsWhiteListKyberAI = () => { const loadingDebounced = useDebounce(isLoading, 500) || connectingWallet const participantInfo = isError || loadingDebounced || !account ? participantDefault : rawData - // isWhitelist, isWaitList check account is hotfix for now, will remove utils backend fixed bug return { loading: loadingDebounced, isWhiteList: - !!account && - isLogin && - (participantInfo?.status === ParticipantStatus.WHITELISTED || userInfo?.data?.hasAccessToKyberAI), - isWaitList: !!account && isLogin && participantInfo?.status === ParticipantStatus.WAITLISTED, + isLogin && (participantInfo?.status === ParticipantStatus.WHITELISTED || userInfo?.data?.hasAccessToKyberAI), + isWaitList: isLogin && participantInfo?.status === ParticipantStatus.WAITLISTED, refetch, } } From f8aef3be77eee1d9836293383bf9d4e7bf1e73d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Ho=C3=A0i=20Danh?= <33005392+nguyenhoaidanh@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:43:27 +0700 Subject: [PATCH 06/10] feat: token secutiry info (#2142) --- src/assets/svg/logo_goplus.svg | 9 + src/assets/svg/security_contract.svg | 6 + src/assets/svg/security_info.svg | 3 + src/assets/svg/security_trading.svg | 5 + src/assets/svg/ziczac.svg | 2 +- src/components/Collapse.tsx | 40 +++- src/components/CurrencyLogo/index.tsx | 1 + .../swapv2/SwapSettingsPanel/index.tsx | 23 +- .../swapv2/TokenInfo/MarketInfo.tsx | 90 +++++++ .../swapv2/TokenInfo/SecurityInfo/Content.tsx | 128 ++++++++++ .../swapv2/TokenInfo/SecurityInfo/Header.tsx | 54 +++++ .../swapv2/TokenInfo/SecurityInfo/index.tsx | 86 +++++++ src/components/swapv2/TokenInfo/index.tsx | 167 +++++++++++++ src/components/swapv2/TokenInfo/utils.ts | 189 +++++++++++++++ src/components/swapv2/TokenInfoIcon.tsx | 49 +--- src/components/swapv2/TokenInfoTab.tsx | 225 ------------------ src/hooks/useBasicChartData.ts | 25 +- src/hooks/useTokenInfo.ts | 52 +--- src/pages/Pools/InstructionAndGlobalData.tsx | 2 +- src/pages/SwapV2/index.tsx | 2 +- src/pages/SwapV3/index.tsx | 8 +- src/services/coingecko.ts | 53 +++++ src/state/application/actions.ts | 1 - src/state/index.ts | 4 + 24 files changed, 867 insertions(+), 357 deletions(-) create mode 100644 src/assets/svg/logo_goplus.svg create mode 100644 src/assets/svg/security_contract.svg create mode 100644 src/assets/svg/security_info.svg create mode 100644 src/assets/svg/security_trading.svg create mode 100644 src/components/swapv2/TokenInfo/MarketInfo.tsx create mode 100644 src/components/swapv2/TokenInfo/SecurityInfo/Content.tsx create mode 100644 src/components/swapv2/TokenInfo/SecurityInfo/Header.tsx create mode 100644 src/components/swapv2/TokenInfo/SecurityInfo/index.tsx create mode 100644 src/components/swapv2/TokenInfo/index.tsx create mode 100644 src/components/swapv2/TokenInfo/utils.ts delete mode 100644 src/components/swapv2/TokenInfoTab.tsx create mode 100644 src/services/coingecko.ts diff --git a/src/assets/svg/logo_goplus.svg b/src/assets/svg/logo_goplus.svg new file mode 100644 index 0000000000..d860bb27bf --- /dev/null +++ b/src/assets/svg/logo_goplus.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/svg/security_contract.svg b/src/assets/svg/security_contract.svg new file mode 100644 index 0000000000..7cf660b2d0 --- /dev/null +++ b/src/assets/svg/security_contract.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/svg/security_info.svg b/src/assets/svg/security_info.svg new file mode 100644 index 0000000000..e30b0c67d2 --- /dev/null +++ b/src/assets/svg/security_info.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/security_trading.svg b/src/assets/svg/security_trading.svg new file mode 100644 index 0000000000..eda73aa126 --- /dev/null +++ b/src/assets/svg/security_trading.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/ziczac.svg b/src/assets/svg/ziczac.svg index 77bd580f57..0673c66e4a 100644 --- a/src/assets/svg/ziczac.svg +++ b/src/assets/svg/ziczac.svg @@ -1,3 +1,3 @@ - + diff --git a/src/components/Collapse.tsx b/src/components/Collapse.tsx index ac3b6e1b92..2233676185 100644 --- a/src/components/Collapse.tsx +++ b/src/components/Collapse.tsx @@ -1,6 +1,6 @@ import React, { CSSProperties, ReactNode, useState } from 'react' import { ChevronDown } from 'react-feather' -import styled from 'styled-components' +import styled, { css } from 'styled-components' const ItemWrapper = styled.div` position: relative; @@ -14,7 +14,7 @@ const Header = styled.div` display: flex; align-items: center; justify-content: space-between; - + user-select: none; cursor: pointer; ` @@ -39,11 +39,17 @@ const ArrowWrapper = styled.div` } ` -const ContentWrapper = styled.div` +const ContentWrapper = styled.div<{ $hasAnim?: boolean; $maxHeight?: string }>` width: 100%; - + overflow: hidden; + ${({ $hasAnim, $maxHeight }) => + $hasAnim && + css` + transition: max-height 500ms ease; + max-height: ${$maxHeight}; + `}; &[data-expanded='false'] { - display: none; + max-height: 0; } ` @@ -56,6 +62,11 @@ type Props = { onExpand?: () => void className?: string arrowComponent?: ReactNode + headerStyle?: CSSProperties + headerBorderRadius?: string + arrowStyle?: CSSProperties + animation?: boolean + maxHeight?: string } export const CollapseItem: React.FC = ({ @@ -65,20 +76,35 @@ export const CollapseItem: React.FC = ({ expandedOnMount = false, style = {}, className, + headerStyle, + headerBorderRadius, + arrowStyle, + animation = false, + maxHeight, }) => { const [isExpanded, setExpanded] = useState(expandedOnMount) return (
{ setExpanded(e => !e) }} > {header} - {arrowComponent || } + + {arrowComponent || } +
- {children} + + {children} +
) } diff --git a/src/components/CurrencyLogo/index.tsx b/src/components/CurrencyLogo/index.tsx index d74b8906c9..40baf7655b 100644 --- a/src/components/CurrencyLogo/index.tsx +++ b/src/components/CurrencyLogo/index.tsx @@ -14,6 +14,7 @@ import { getProxyTokenLogo } from 'utils/tokenInfo' const StyledNativeCurrencyLogo = styled.img<{ size: string }>` width: ${({ size }) => size}; height: ${({ size }) => size}; + min-width: ${({ size }) => size}; box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075); border-radius: 24px; ` diff --git a/src/components/swapv2/SwapSettingsPanel/index.tsx b/src/components/swapv2/SwapSettingsPanel/index.tsx index 1c0ae7975d..d4d7370579 100644 --- a/src/components/swapv2/SwapSettingsPanel/index.tsx +++ b/src/components/swapv2/SwapSettingsPanel/index.tsx @@ -1,7 +1,7 @@ import { Trans, t } from '@lingui/macro' import { rgba } from 'polished' import React, { useRef, useState } from 'react' -import { ArrowLeft } from 'react-feather' +import { ChevronLeft } from 'react-feather' import { Box, Flex, Text } from 'rebass' import styled from 'styled-components' @@ -39,18 +39,9 @@ type Props = { isSwapPage?: boolean isCrossChainPage?: boolean } -const BackIconWrapper = styled(ArrowLeft)` - height: 20px; - width: 20px; - margin-right: 10px; - cursor: pointer; - path { - stroke: ${({ theme }) => theme.text} !important; - } -` const BackText = styled.span` - font-size: 18px; + font-size: 20px; font-weight: 500; color: ${({ theme }) => theme.text}; ` @@ -101,14 +92,8 @@ const SettingsPanel: React.FC = ({ return ( - - + + {t`Settings`} diff --git a/src/components/swapv2/TokenInfo/MarketInfo.tsx b/src/components/swapv2/TokenInfo/MarketInfo.tsx new file mode 100644 index 0000000000..7258a5abba --- /dev/null +++ b/src/components/swapv2/TokenInfo/MarketInfo.tsx @@ -0,0 +1,90 @@ +import { Token } from '@kyberswap/ks-sdk-core' +import { Trans } from '@lingui/macro' +import { useMemo, useState } from 'react' +import { Flex } from 'rebass' +import styled from 'styled-components' + +import AddTokenToMetaMask from 'components/AddToMetamask' +import { DropdownArrowIcon } from 'components/ArrowRotate' +import CopyHelper from 'components/Copy' +import CurrencyLogo from 'components/CurrencyLogo' +import Loader from 'components/Loader' +import { RowBetween, RowFit } from 'components/Row' +import { getMarketTokenInfo } from 'components/swapv2/TokenInfo/utils' +import { useActiveWeb3React } from 'hooks' +import useTheme from 'hooks/useTheme' +import useTokenInfo from 'hooks/useTokenInfo' +import { shortenAddress } from 'utils' + +const Wrapper = styled.div` + border-radius: 4px; + width: 100%; + padding: 0 26px; + display: flex; + flex-direction: column; + gap: 12px; +` + +const InfoRowLabel = styled.div` + color: ${({ theme }) => theme.subText}; + font-size: 12px; +` + +const InfoRowValue = styled.div` + color: ${({ theme }) => theme.text}; + font-size: 12px; + font-weight: 500; +` +export default function MarketInfo({ token }: { token: Token | undefined }) { + const { data: tokenInfo, loading } = useTokenInfo(token) + const [expand, setExpand] = useState(false) + const { chainId } = useActiveWeb3React() + const theme = useTheme() + const listData = useMemo(() => getMarketTokenInfo(tokenInfo), [tokenInfo]) + + return ( + + {(expand ? listData : listData.slice(0, 3)).map(item => ( + + {item.label} + {loading ? : item.value} + + ))} + + + + Contract Address + + + + {token ? ( + <> + + {shortenAddress(chainId, token.address, 3)} + + + + ) : ( + + )} + + + + setExpand(!expand)} + > + {!expand ? View more : View less} + + + ) +} diff --git a/src/components/swapv2/TokenInfo/SecurityInfo/Content.tsx b/src/components/swapv2/TokenInfo/SecurityInfo/Content.tsx new file mode 100644 index 0000000000..b0ef67db15 --- /dev/null +++ b/src/components/swapv2/TokenInfo/SecurityInfo/Content.tsx @@ -0,0 +1,128 @@ +import { Trans, t } from '@lingui/macro' +import { AlertOctagon } from 'react-feather' +import { Flex } from 'rebass' +import styled from 'styled-components' + +import Loader from 'components/Loader' +import { RISKY_THRESHOLD, isItemRisky } from 'components/swapv2/TokenInfo/utils' +import useTheme from 'hooks/useTheme' + +export enum WarningType { + RISKY, + WARNING, +} + +export type ItemData = { + label: string + value: string | undefined + type: WarningType + isNumber?: boolean + riskyReverse?: boolean +} + +const Label = styled.span<{ color?: string }>` + color: ${({ theme, color }) => color || theme.subText}; + font-size: 12px; + font-weight: 400; +` + +const ItemWrapper = styled.div` + display: flex; + gap: 6px; + justify-content: space-between; + align-items: center; + flex-basis: 45%; +` + +const ContentWrapper = styled.div` + display: flex; + gap: 12px; + padding: 16px 20px; + justify-content: space-between; + flex-wrap: wrap; + ${({ theme }) => theme.mediaWidth.upToMedium` + flex-direction: column; + `}; +` + +const NO_DATA = '--' +const InfoItem = ({ data, loading }: { data: ItemData; loading: boolean }) => { + const { label, value, type, isNumber } = data + const theme = useTheme() + const displayValue = loading ? ( + + ) : isNumber && value ? ( + `${+value * 100}%` + ) : value === '0' ? ( + t`No` + ) : value === '1' ? ( + t`Yes` + ) : isNumber ? ( + t`Unknown` + ) : ( + NO_DATA + ) + const colorRiskyByType = type === WarningType.RISKY ? theme.red : theme.warning + const colorRiskyByAmount = Number(value) > RISKY_THRESHOLD.RISKY ? theme.red : theme.warning + return ( + + + + + ) +} + +const Content = ({ + data, + totalRisk, + totalWarning, + loading, +}: { + data: ItemData[] + totalRisk: number + totalWarning: number + loading: boolean +}) => { + const theme = useTheme() + return ( + + + + + + + + + + + + + + + + + + {data.map(item => ( + + ))} + + ) +} +export default Content diff --git a/src/components/swapv2/TokenInfo/SecurityInfo/Header.tsx b/src/components/swapv2/TokenInfo/SecurityInfo/Header.tsx new file mode 100644 index 0000000000..296fabe48a --- /dev/null +++ b/src/components/swapv2/TokenInfo/SecurityInfo/Header.tsx @@ -0,0 +1,54 @@ +import { ReactNode } from 'react' +import { AlertOctagon } from 'react-feather' +import { Flex } from 'rebass' +import styled from 'styled-components' + +import useTheme from 'hooks/useTheme' + +const Label = styled.span` + color: ${({ theme }) => theme.subText}; + font-size: 14px; + font-weight: 400; +` + +const BadgeWarning = ({ warning, danger }: { warning: number; danger: number }) => { + const theme = useTheme() + return ( + + {danger > 0 && ( + + {danger} + + )} + {warning > 0 && ( + + {warning} + + )} + + ) +} + +const Header = ({ + warning, + danger, + title, + icon, +}: { + warning: number + danger: number + title: string + icon: ReactNode +}) => { + return ( + + + {icon} + + + + + ) +} + +export default Header diff --git a/src/components/swapv2/TokenInfo/SecurityInfo/index.tsx b/src/components/swapv2/TokenInfo/SecurityInfo/index.tsx new file mode 100644 index 0000000000..67447e3e75 --- /dev/null +++ b/src/components/swapv2/TokenInfo/SecurityInfo/index.tsx @@ -0,0 +1,86 @@ +import { ChainId, Token } from '@kyberswap/ks-sdk-core' +import { t } from '@lingui/macro' +import { rgba } from 'polished' +import { useMemo } from 'react' +import { useGetSecurityTokenInfoQuery } from 'services/coingecko' +import { CSSProperties } from 'styled-components' + +import { ReactComponent as DropdownSVG } from 'assets/svg/down.svg' +import { ReactComponent as ContractSecurity } from 'assets/svg/security_contract.svg' +import { ReactComponent as TreadingSecurity } from 'assets/svg/security_trading.svg' +import { CollapseItem } from 'components/Collapse' +import { getSecurityTokenInfo } from 'components/swapv2/TokenInfo/utils' +import useTheme from 'hooks/useTheme' + +import { Container } from '../index' +import Content from './Content' +import Header from './Header' + +export default function SecurityInfo({ token }: { token: Token | undefined }) { + const theme = useTheme() + const style: CSSProperties = { background: rgba(theme.black, 0.2), borderRadius: '16px', padding: '0' } + const headerStyle: CSSProperties = { background: rgba(theme.black, 0.48) } + const arrowStyle: CSSProperties = { marginRight: '6px', color: theme.subText } + const { data, isLoading } = useGetSecurityTokenInfoQuery( + { chainId: token?.chainId as ChainId, address: token?.address ?? '' }, + { skip: !token?.address }, + ) + + const { contractData, tradingData, totalWarningContract, totalWarningTrading, totalRiskContract, totalRiskTrading } = + useMemo(() => getSecurityTokenInfo(data), [data]) + + return ( + + } + title={t`Trading Security`} + warning={totalWarningTrading} + danger={totalRiskTrading} + /> + } + arrowComponent={} + > + + + + } + title={t`Contract Security`} + warning={totalWarningContract} + danger={totalRiskContract} + /> + } + arrowComponent={} + > + + + + ) +} diff --git a/src/components/swapv2/TokenInfo/index.tsx b/src/components/swapv2/TokenInfo/index.tsx new file mode 100644 index 0000000000..8c08037a03 --- /dev/null +++ b/src/components/swapv2/TokenInfo/index.tsx @@ -0,0 +1,167 @@ +import { Currency } from '@kyberswap/ks-sdk-core' +import { Trans, t } from '@lingui/macro' +import { rgba } from 'polished' +import { useEffect, useState } from 'react' +import { ChevronLeft } from 'react-feather' +import { Flex } from 'rebass' +import styled from 'styled-components' + +import { ReactComponent as Coingecko } from 'assets/svg/coingecko_color.svg' +import { ReactComponent as GoplusLogo } from 'assets/svg/logo_goplus.svg' +import { ReactComponent as SecurityInfoIcon } from 'assets/svg/security_info.svg' +import { ReactComponent as ZiczacIcon } from 'assets/svg/ziczac.svg' +import { ButtonEmpty } from 'components/Button' +import CurrencyLogo from 'components/CurrencyLogo' +import MarketInfo from 'components/swapv2/TokenInfo/MarketInfo' +import SecurityInfo from 'components/swapv2/TokenInfo/SecurityInfo' +import { useActiveWeb3React } from 'hooks' +import useTheme from 'hooks/useTheme' +import { Field } from 'state/swap/actions' +import { useCurrencyConvertedToNative } from 'utils/dmm' + +const TabContainer = styled.div` + display: flex; + border-radius: 999px; + background-color: ${({ theme }) => theme.tabBackground}; + padding: 2px; + min-width: 160px; +` + +const Tab = styled(ButtonEmpty)<{ isActive?: boolean; isLeft?: boolean }>` + display: flex; + justify-content: center; + align-items: center; + min-width: 80px; + width: fit-content; + background-color: ${({ theme, isActive }) => (isActive ? theme.tabActive : theme.tabBackground)}; + padding: 6px 8px; + gap: 4px; + font-size: 12px; + font-weight: 500; + border-radius: 999px; + + &:hover { + text-decoration: none; + } +` + +const TabText = styled.div<{ isActive: boolean }>` + color: ${({ theme, isActive }) => (isActive ? theme.text : theme.subText)}; + white-space: nowrap; +` + +const PoweredByWrapper = styled.div` + display: flex; + gap: 4px; + justify-content: flex-end; + align-items: center; +` + +const PoweredByText = styled.span` + font-size: 10px; + font-weight: 400; + color: ${({ theme }) => theme.subText}; +` + +const BackText = styled.span` + font-size: 20px; + font-weight: 500; + line-height: 22px; + color: ${({ theme }) => theme.text}; +` + +const HeaderPanel = styled.div` + background-color: ${({ theme }) => rgba(theme.subText, 0.2)}; + display: flex; + align-items: center; + justify-content: space-between; + height: 40px; + padding: 0 16px; +` + +const LabelHeaderPanel = styled.div` + display: flex; + gap: 8px; + align-items: center; +` + +export const Container = styled.div` + display: flex; + flex-direction: column; + gap: 14px; + padding: 0 14px; +` + +enum TAB { + TOKEN_IN, + TOKEN_OUT, +} +const TokenInfoTab = ({ currencies, onBack }: { currencies: { [field in Field]?: Currency }; onBack?: () => void }) => { + const { chainId } = useActiveWeb3React() + const inputNativeCurrency = useCurrencyConvertedToNative(currencies[Field.INPUT]) + const outputNativeCurrency = useCurrencyConvertedToNative(currencies[Field.OUTPUT]) + const inputToken = inputNativeCurrency?.wrapped + const outputToken = outputNativeCurrency?.wrapped + const [activeTab, setActiveTab] = useState(TAB.TOKEN_IN) + const selectedToken = activeTab === TAB.TOKEN_OUT ? outputToken : inputToken + + // Handle switch network case + useEffect(() => { + inputToken?.address && setActiveTab(TAB.TOKEN_IN) + }, [chainId, inputToken]) + + const isActiveTokenIn = activeTab === TAB.TOKEN_IN + const isActiveTokenOut = activeTab === TAB.TOKEN_OUT + const theme = useTheme() + + return ( + + + {onBack && ( + + + {t`Token Info`} + + )} + + setActiveTab(TAB.TOKEN_IN)}> + + {inputNativeCurrency?.symbol} + + setActiveTab(TAB.TOKEN_OUT)}> + + {outputNativeCurrency?.symbol} + + + + + + + Market Info + + + + Powered by + {' '} + + + + + + + + Security Info + + + + Powered by + {' '} + + + + + + ) +} + +export default TokenInfoTab diff --git a/src/components/swapv2/TokenInfo/utils.ts b/src/components/swapv2/TokenInfo/utils.ts new file mode 100644 index 0000000000..5d396ef4e4 --- /dev/null +++ b/src/components/swapv2/TokenInfo/utils.ts @@ -0,0 +1,189 @@ +import { t } from '@lingui/macro' +import { SecurityInfo } from 'services/coingecko' + +import { ItemData, WarningType } from 'components/swapv2/TokenInfo/SecurityInfo/Content' +import { TokenInfo } from 'hooks/useTokenInfo' +import { formattedNum } from 'utils' +import { formatLongNumber } from 'utils/formatBalance' + +export const RISKY_THRESHOLD = { + RISKY: 0.05, + WARNING: 0.01, +} + +export const isItemRisky = ({ value, isNumber, riskyReverse }: ItemData) => { + const isRisky = + (!isNumber && value === '0') || (isNumber && (Number(value) >= RISKY_THRESHOLD.WARNING || value === '')) + return value !== undefined && (riskyReverse ? !isRisky : isRisky) +} + +const reverseValue = (value: string | undefined) => (!value ? undefined : value === '0' ? '1' : '0') + +const calcTotalRiskFn = (total: { totalRisk: number; totalWarning: number }, item: ItemData) => { + if (isItemRisky(item)) { + if (item.type === WarningType.RISKY) total.totalRisk++ + else total.totalWarning++ + } + return total +} + +const calcTotalRisk = (data: ItemData[]) => { + return data.reduce(calcTotalRiskFn, { + totalRisk: 0, + totalWarning: 0, + }) +} + +export const getSecurityTokenInfo = (data: SecurityInfo | undefined) => { + const contractData: ItemData[] = [ + { + label: t`Open Source`, + value: data?.is_open_source, + type: WarningType.RISKY, + }, + { + label: t`Proxy Contract`, + value: data?.is_proxy, + type: WarningType.WARNING, + riskyReverse: true, + }, + { + label: t`Mint Function`, + value: data?.is_mintable, + type: WarningType.WARNING, + riskyReverse: true, + }, + { + label: t`Take Back Ownership`, + value: data?.can_take_back_ownership, + type: WarningType.RISKY, + riskyReverse: true, + }, + { + label: t`Can Change Balance`, + value: data?.owner_change_balance, + type: WarningType.RISKY, + riskyReverse: true, + }, + { + label: t`Self-destruct`, + value: data?.selfdestruct, + type: WarningType.RISKY, + riskyReverse: true, + }, + { + label: t`External Call`, + value: data?.external_call, + type: WarningType.RISKY, + riskyReverse: true, + }, + { + label: t`Gas Abuser`, + value: data?.gas_abuse, + type: WarningType.WARNING, + riskyReverse: true, + }, + ].filter(el => el.value !== undefined) + + const tradingData: ItemData[] = [ + { + label: t`Buy Tax`, + value: data?.buy_tax, + type: WarningType.WARNING, + isNumber: true, + }, + { + label: t`Sell Tax`, + value: data?.sell_tax, + type: WarningType.WARNING, + isNumber: true, + }, + { + label: t`Modifiable Tax`, + value: data?.slippage_modifiable, + type: WarningType.WARNING, + riskyReverse: true, + }, + { + label: t`Honeypot`, + value: data?.is_honeypot, + type: WarningType.RISKY, + riskyReverse: true, + }, + { + label: t`Can be bought`, + value: reverseValue(data?.cannot_buy), + type: WarningType.RISKY, + }, + { + label: t`Can sell all`, + value: reverseValue(data?.cannot_sell_all), + type: WarningType.RISKY, + }, + { + label: t`Blacklisted Function`, + value: data?.is_blacklisted, + type: WarningType.WARNING, + riskyReverse: true, + }, + { + label: t`Whitelisted Function`, + value: data?.is_whitelisted, + type: WarningType.WARNING, + riskyReverse: true, + }, + { + label: t`Anti Whale`, + value: data?.is_anti_whale, + type: WarningType.WARNING, + riskyReverse: true, + }, + { + label: t`Modifiable Anti Whale`, + value: data?.anti_whale_modifiable, + type: WarningType.WARNING, + riskyReverse: true, + }, + ].filter(el => el.value !== undefined) + + const { totalRisk: totalRiskContract, totalWarning: totalWarningContract } = calcTotalRisk(contractData) + const { totalRisk: totalRiskTrading, totalWarning: totalWarningTrading } = calcTotalRisk(tradingData) + + return { contractData, tradingData, totalRiskContract, totalWarningContract, totalWarningTrading, totalRiskTrading } +} + +const NOT_AVAILABLE = '--' +export const getMarketTokenInfo = (tokenInfo: TokenInfo) => { + const listData = [ + { label: t`Price`, value: tokenInfo.price ? formattedNum(tokenInfo.price.toString(), true) : NOT_AVAILABLE }, + { + label: t`Market Cap Rank`, + value: tokenInfo.marketCapRank ? `#${formattedNum(tokenInfo.marketCapRank.toString())}` : NOT_AVAILABLE, + }, + { + label: t`Trading Volume (24H)`, + value: tokenInfo.tradingVolume ? formatLongNumber(tokenInfo.tradingVolume.toString(), true) : NOT_AVAILABLE, + }, + { + label: t`Market Cap`, + value: tokenInfo.marketCap ? formatLongNumber(tokenInfo.marketCap.toString(), true) : NOT_AVAILABLE, + }, + { + label: t`All-Time High`, + value: tokenInfo.allTimeHigh ? formattedNum(tokenInfo.allTimeHigh.toString(), true) : NOT_AVAILABLE, + }, + { + label: t`All-Time Low`, + value: tokenInfo.allTimeLow ? formattedNum(tokenInfo.allTimeLow.toString(), true) : NOT_AVAILABLE, + }, + { + label: t`Circulating Supply`, + value: tokenInfo.circulatingSupply ? formatLongNumber(tokenInfo.circulatingSupply.toString()) : NOT_AVAILABLE, + }, + { + label: t`Total Supply`, + value: tokenInfo.totalSupply ? formatLongNumber(tokenInfo.totalSupply.toString()) : NOT_AVAILABLE, + }, + ] + return listData +} diff --git a/src/components/swapv2/TokenInfoIcon.tsx b/src/components/swapv2/TokenInfoIcon.tsx index 14412c3301..a55d10cf5a 100644 --- a/src/components/swapv2/TokenInfoIcon.tsx +++ b/src/components/swapv2/TokenInfoIcon.tsx @@ -1,51 +1,22 @@ import { Currency } from '@kyberswap/ks-sdk-core' -import { Trans, t } from '@lingui/macro' -import { MobileView, isMobile } from 'react-device-detect' -import { Info, X } from 'react-feather' -import { Flex, Text } from 'rebass' +import { t } from '@lingui/macro' +import { isMobile } from 'react-device-detect' +import { Info } from 'react-feather' -import { RowBetween } from 'components/Row' import { MouseoverTooltip } from 'components/Tooltip' -import TokenInfoTab from 'components/swapv2/TokenInfoTab' -import { MobileModalWrapper, StyledActionButtonSwapForm } from 'components/swapv2/styleds' +import { StyledActionButtonSwapForm } from 'components/swapv2/styleds' import useTheme from 'hooks/useTheme' -import { ApplicationModal } from 'state/application/actions' -import { useModalOpen, useToggleModal } from 'state/application/hooks' import { Field } from 'state/swap/actions' -import { ButtonText } from 'theme/components' -function TokenInfoIcon({ currencies, onClick }: { currencies: { [field in Field]?: Currency }; onClick?: () => void }) { +function TokenInfoIcon({ onClick }: { currencies: { [field in Field]?: Currency }; onClick?: () => void }) { const theme = useTheme() - const isOpen = useModalOpen(ApplicationModal.MOBILE_TOKEN_INFO) - const toggle = useToggleModal(ApplicationModal.MOBILE_TOKEN_INFO) return ( - <> - - - - - - - Info - - - - - - - - - - - - - - - - - - + + + + + ) } diff --git a/src/components/swapv2/TokenInfoTab.tsx b/src/components/swapv2/TokenInfoTab.tsx deleted file mode 100644 index 785f1d9f09..0000000000 --- a/src/components/swapv2/TokenInfoTab.tsx +++ /dev/null @@ -1,225 +0,0 @@ -import { Currency } from '@kyberswap/ks-sdk-core' -import { Trans, t } from '@lingui/macro' -import { useEffect, useState } from 'react' -import { ArrowLeft } from 'react-feather' -import { Flex } from 'rebass' -import styled from 'styled-components' - -import { ReactComponent as Coingecko } from 'assets/svg/coingecko_color.svg' -import { ReactComponent as CoingeckoLight } from 'assets/svg/coingecko_color_light.svg' -import AddTokenToMetaMask from 'components/AddToMetamask' -import { ButtonEmpty } from 'components/Button' -import Copy from 'components/Copy' -import CurrencyLogo from 'components/CurrencyLogo' -import Loader from 'components/Loader' -import { AutoRow, RowBetween } from 'components/Row' -import { useActiveWeb3React } from 'hooks' -import useTokenInfo from 'hooks/useTokenInfo' -import { Field } from 'state/swap/actions' -import { useIsDarkMode } from 'state/user/hooks' -import { formattedNum, shortenAddress } from 'utils' -import { useCurrencyConvertedToNative } from 'utils/dmm' -import { formatLongNumber } from 'utils/formatBalance' - -const NOT_AVAIALBLE = '--' - -const Wrapper = styled.div` - border-radius: 4px; - width: 100%; -` - -const TabContainer = styled.div` - display: flex; - flex: 1; - border-radius: 999px; - background-color: ${({ theme }) => theme.tabBackground}; - padding: 2px; -` - -const Tab = styled(ButtonEmpty)<{ isActive?: boolean; isLeft?: boolean }>` - display: flex; - justify-content: center; - align-items: center; - flex: 1; - background-color: ${({ theme, isActive }) => (isActive ? theme.tabActive : theme.tabBackground)}; - padding: 4px; - font-size: 12px; - font-weight: 500; - border-radius: 999px; - - &:hover { - text-decoration: none; - } -` - -const TabText = styled.div<{ isActive: boolean }>` - display: flex; - align-items: center; - gap: 2px; - color: ${({ theme, isActive }) => (isActive ? theme.text : theme.subText)}; - margin-left: 4px; -` - -const InfoRow = styled(RowBetween)` - padding: 14px 0; - border-bottom: 1px solid ${({ theme }) => theme.border}; - - ${({ theme }) => theme.mediaWidth.upToSmall` - padding: 12px 0 - `} -` - -const InfoRowLabel = styled.div` - color: ${({ theme }) => theme.subText}; - font-size: 12px; -` - -const InfoRowValue = styled.div` - color: ${({ theme }) => theme.text}; - font-size: 12px; - font-weight: 500; -` - -const PoweredByWrapper = styled.div` - display: flex; - gap: 4px; - justify-content: flex-end; - align-items: center; - margin-top: 14px; -` - -const PoweredByText = styled.span` - font-size: 12px; - font-weight: 400; - color: ${({ theme }) => theme.subText}; -` - -const BackText = styled.span` - font-size: 18px; - font-weight: 500; - color: ${({ theme }) => theme.text}; -` - -const BackIconWrapper = styled(ArrowLeft)` - height: 20px; - width: 20px; - margin-right: 10px; - cursor: pointer; - path { - stroke: ${({ theme }) => theme.text} !important; - } -` -enum TAB { - TOKEN_IN, - TOKEN_OUT, -} -const TokenInfoTab = ({ currencies, onBack }: { currencies: { [field in Field]?: Currency }; onBack?: () => void }) => { - const { chainId } = useActiveWeb3React() - const inputNativeCurrency = useCurrencyConvertedToNative(currencies[Field.INPUT]) - const outputNativeCurrency = useCurrencyConvertedToNative(currencies[Field.OUTPUT]) - const inputToken = inputNativeCurrency?.wrapped - const outputToken = outputNativeCurrency?.wrapped - const [activeTab, setActiveTab] = useState(TAB.TOKEN_IN) - const selectedToken = activeTab === TAB.TOKEN_OUT ? outputToken : inputToken - const { data: tokenInfo, loading } = useTokenInfo(selectedToken) - const isDarkMode = useIsDarkMode() - - // Handle switch network case - useEffect(() => { - inputToken?.address && setActiveTab(TAB.TOKEN_IN) - }, [chainId, inputToken]) - - const listData = [ - { label: t`Price`, value: tokenInfo.price ? formattedNum(tokenInfo.price.toString(), true) : NOT_AVAIALBLE }, - { - label: t`Trading Volume (24H)`, - value: tokenInfo.tradingVolume ? formatLongNumber(tokenInfo.tradingVolume.toString(), true) : NOT_AVAIALBLE, - }, - { - label: t`Market Cap Rank`, - value: tokenInfo.marketCapRank ? `#${formattedNum(tokenInfo.marketCapRank.toString())}` : NOT_AVAIALBLE, - }, - { - label: t`Market Cap`, - value: tokenInfo.marketCap ? formatLongNumber(tokenInfo.marketCap.toString(), true) : NOT_AVAIALBLE, - }, - { - label: t`All-Time High`, - value: tokenInfo.allTimeHigh ? formattedNum(tokenInfo.allTimeHigh.toString(), true) : NOT_AVAIALBLE, - }, - { - label: t`All-Time Low`, - value: tokenInfo.allTimeLow ? formattedNum(tokenInfo.allTimeLow.toString(), true) : NOT_AVAIALBLE, - }, - { - label: t`Circulating Supply`, - value: tokenInfo.circulatingSupply ? formatLongNumber(tokenInfo.circulatingSupply.toString()) : NOT_AVAIALBLE, - }, - { - label: t`Total Supply`, - value: tokenInfo.totalSupply ? formatLongNumber(tokenInfo.totalSupply.toString()) : NOT_AVAIALBLE, - }, - ] - - const isActiveTokenIn = activeTab === TAB.TOKEN_IN - const isActiveTokenOut = activeTab === TAB.TOKEN_OUT - - return ( - <> - - - {onBack && ( - - - {t`Info`} - - )} - - setActiveTab(TAB.TOKEN_IN)}> - - {inputNativeCurrency?.symbol} - - setActiveTab(TAB.TOKEN_OUT)}> - - {outputNativeCurrency?.symbol} - - - - - {listData.map(item => ( - - {item.label} - {loading ? : item.value} - - ))} - - - - Contract Address - - - - {selectedToken ? ( - <> - - {shortenAddress(chainId, selectedToken.address, 3)} - - - - ) : ( - - )} - - - - - - Powered by - {' '} - {isDarkMode ? : } - - - ) -} - -export default TokenInfoTab diff --git a/src/hooks/useBasicChartData.ts b/src/hooks/useBasicChartData.ts index a67865cd6c..3998cad91a 100644 --- a/src/hooks/useBasicChartData.ts +++ b/src/hooks/useBasicChartData.ts @@ -1,6 +1,6 @@ import { KyberOauth2Api } from '@kybernetwork/oauth2' import { ChainId, Token, WETH } from '@kyberswap/ks-sdk-core' -import axios from 'axios' +import axios, { AxiosResponse } from 'axios' import { getUnixTime, subHours } from 'date-fns' import { useMemo } from 'react' import useSWR from 'swr' @@ -60,15 +60,18 @@ const getClosestPrice = (prices: any[], time: number) => { }) return prices[closestIndex][0] - time > 10000000 ? 0 : prices[closestIndex][1] } - -const fetchKyberDataSWR = async (url: string) => { - const res = await axios.get(url, { timeout: 5000 }) +const formatData = (res: AxiosResponse) => { if (res.status === 204) { throw new Error('No content') } return res.data } +const fetchKyberDataSWR = async (url: string) => { + const res = await axios.get(url, { timeout: 5000 }) + return formatData(res) +} + const fetchKyberDataSWRWithHeader = async (url: string) => { const res = await KyberOauth2Api.get(url, undefined, { timeout: 5000, @@ -77,10 +80,7 @@ const fetchKyberDataSWRWithHeader = async (url: string) => { }, }) - if (res.status === 204) { - throw new Error('No content') - } - return res.data + return formatData(res) } const fetchCoingeckoDataSWR = async ([tokenAddresses, chainIds, timeFrame, coingeckoAPI]: [ @@ -90,15 +90,10 @@ const fetchCoingeckoDataSWR = async ([tokenAddresses, chainIds, timeFrame, coing coingeckoAPI: string, ]): Promise => { return await Promise.all( - [tokenAddresses[0], tokenAddresses[1]].map((address, i) => + tokenAddresses.map((address, i) => KyberOauth2Api.get(generateCoingeckoUrl(coingeckoAPI, chainIds[i], address, timeFrame), undefined, { timeout: 5000, - }).then(res => { - if (res.status === 204) { - throw new Error('No content') - } - return res.data - }), + }).then(formatData), ), ) } diff --git a/src/hooks/useTokenInfo.ts b/src/hooks/useTokenInfo.ts index 0c5cbce575..bbe0090ecc 100644 --- a/src/hooks/useTokenInfo.ts +++ b/src/hooks/useTokenInfo.ts @@ -1,8 +1,6 @@ -import { KyberOauth2Api } from '@kybernetwork/oauth2' -import { Token, WETH } from '@kyberswap/ks-sdk-core' -import useSWR from 'swr' +import { Token } from '@kyberswap/ks-sdk-core' +import { useGetMarketTokenInfoQuery } from 'services/coingecko' -import { NETWORKS_INFO } from 'constants/networks' import { useActiveWeb3React } from 'hooks' import useCoingeckoAPI from './useCoingeckoAPI' @@ -24,50 +22,12 @@ export default function useTokenInfo(token: Token | undefined): { data: TokenInf const { isSolana, chainId: currentChain } = useActiveWeb3React() const chainId = token?.chainId || currentChain const coingeckoAPI = useCoingeckoAPI() - const fetcher = (url: string) => - url - ? KyberOauth2Api.get(url).then(res => { - if (res.status === 204) { - throw new Error('No content') - } - return res.data - }) - : Promise.reject({ data: {}, error: '' }) const tokenAddress = isSolana ? token?.address || '' : (token?.address || '').toLowerCase() - - let url = '' - - if (tokenAddress.toLowerCase() === WETH[chainId].address.toLowerCase()) { - // If the token is native token, we have to use different endpoint - url = `${coingeckoAPI}/coins/${NETWORKS_INFO[chainId].coingeckoNativeTokenId}` - } else if (tokenAddress) { - url = `${coingeckoAPI}/coins/${NETWORKS_INFO[chainId].coingeckoNetworkId}/contract/${tokenAddress}` - } - - const { data, error } = useSWR(url, fetcher, { - refreshInterval: 60000, - onErrorRetry: (error, key, config, revalidate, { retryCount }) => { - // Never retry on 404. - if (error.status === 404) return - - // Only retry up to 10 times. - if (retryCount >= 10) return - - if (error.status === 403) { - // If API return 403, retry after 30 seconds. - setTimeout(() => revalidate({ retryCount }), 30000) - return - } - - // Retry after 20 seconds. - setTimeout(() => revalidate({ retryCount }), 20000) - }, - }) - - if (error && import.meta.env.DEV) { - console.error(error) - } + const { data, error } = useGetMarketTokenInfoQuery( + { chainId, address: tokenAddress, coingeckoAPI }, + { skip: !tokenAddress, pollingInterval: 60_000 }, + ) const loading = !data diff --git a/src/pages/Pools/InstructionAndGlobalData.tsx b/src/pages/Pools/InstructionAndGlobalData.tsx index a9e1e34d3c..eacca52fc3 100644 --- a/src/pages/Pools/InstructionAndGlobalData.tsx +++ b/src/pages/Pools/InstructionAndGlobalData.tsx @@ -171,7 +171,7 @@ export const Instruction = () => { - + You can create pools by setting your own static fees or by using dynamic fees. With dynamic fees, trading fees are adjusted on-the-fly based on market conditions diff --git a/src/pages/SwapV2/index.tsx b/src/pages/SwapV2/index.tsx index 45abdfb5b9..c63a965911 100644 --- a/src/pages/SwapV2/index.tsx +++ b/src/pages/SwapV2/index.tsx @@ -36,7 +36,7 @@ import GasPriceTrackerPanel from 'components/swapv2/GasPriceTrackerPanel' import LiquiditySourcesPanel from 'components/swapv2/LiquiditySourcesPanel' import RefreshButton from 'components/swapv2/RefreshButton' import SettingsPanel from 'components/swapv2/SwapSettingsPanel' -import TokenInfoTab from 'components/swapv2/TokenInfoTab' +import TokenInfoTab from 'components/swapv2/TokenInfo' import TokenInfoV2 from 'components/swapv2/TokenInfoV2' import TradePrice from 'components/swapv2/TradePrice' import TradeTypeSelection from 'components/swapv2/TradeTypeSelection' diff --git a/src/pages/SwapV3/index.tsx b/src/pages/SwapV3/index.tsx index af26823214..db61cf1521 100644 --- a/src/pages/SwapV3/index.tsx +++ b/src/pages/SwapV3/index.tsx @@ -20,7 +20,7 @@ import { ListOrderHandle } from 'components/swapv2/LimitOrder/type' import LiquiditySourcesPanel from 'components/swapv2/LiquiditySourcesPanel' import PairSuggestion, { PairSuggestionHandle } from 'components/swapv2/PairSuggestion' import SettingsPanel from 'components/swapv2/SwapSettingsPanel' -import TokenInfoTab from 'components/swapv2/TokenInfoTab' +import TokenInfoTab from 'components/swapv2/TokenInfo' import TokenInfoV2 from 'components/swapv2/TokenInfoV2' import { Container, @@ -246,7 +246,11 @@ export default function Swap() { /> )} - + {isSwapPage && ( ({ + getMarketTokenInfo: builder.query({ + query: ({ chainId, address, coingeckoAPI }) => ({ + url: + address.toLowerCase() === WETH[chainId].address.toLowerCase() + ? `${coingeckoAPI}/coins/${NETWORKS_INFO[chainId].coingeckoNativeTokenId}` + : `${coingeckoAPI}/coins/${NETWORKS_INFO[chainId].coingeckoNetworkId}/contract/${address}`, + authentication: true, + }), + }), + getSecurityTokenInfo: builder.query({ + query: ({ chainId, address }) => ({ + url: `https://api.gopluslabs.io/api/v1/token_security/${chainId}?contract_addresses=${address}`, + }), + transformResponse: (data: any, _, arg) => data?.result?.[arg.address.toLowerCase()], + }), + }), +}) + +// todo danh (not for now) move basic chart api to this file +export const { useGetMarketTokenInfoQuery, useGetSecurityTokenInfoQuery } = coingeckoApi + +export default coingeckoApi diff --git a/src/state/application/actions.ts b/src/state/application/actions.ts index 8d09d128ce..4f8d5420d9 100644 --- a/src/state/application/actions.ts +++ b/src/state/application/actions.ts @@ -20,7 +20,6 @@ export enum ApplicationModal { MOBILE_LIVE_CHART, MOBILE_TRADE_ROUTES, - MOBILE_TOKEN_INFO, SHARE, TRENDING_SOON_SORTING, diff --git a/src/state/index.ts b/src/state/index.ts index 8a0605a3a3..c4ad291a28 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -1,5 +1,6 @@ import { configureStore } from '@reduxjs/toolkit' import { load, save } from 'redux-localstorage-simple' +import coingeckoApi from 'services/coingecko' import kyberAISubscriptionApi from 'services/kyberAISubscription' import priceAlertApi from 'services/priceAlert' import routeApi from 'services/route' @@ -90,6 +91,8 @@ const store = configureStore({ [announcementApi.reducerPath]: announcementApi.reducer, [publicAnnouncementApi.reducerPath]: publicAnnouncementApi.reducer, [geckoTerminalApi.reducerPath]: geckoTerminalApi.reducer, + [coingeckoApi.reducerPath]: coingeckoApi.reducer, + [kyberAIApi.reducerPath]: kyberAIApi.reducer, [kyberAISubscriptionApi.reducerPath]: kyberAISubscriptionApi.reducer, [kyberDAO.reducerPath]: kyberDAO.reducer, @@ -116,6 +119,7 @@ const store = configureStore({ getDefaultMiddleware({ thunk: true, immutableCheck: false, serializableCheck: false }) .concat(save({ states: PERSISTED_KEYS, debounce: 100 })) .concat(geckoTerminalApi.middleware) + .concat(coingeckoApi.middleware) .concat(kyberAIApi.middleware) .concat(kyberAISubscriptionApi.middleware) .concat(announcementApi.middleware) From eae44c64e72c2511d7a0fad6b006c873c9c58076 Mon Sep 17 00:00:00 2001 From: Nguyen Van Viet Date: Thu, 10 Aug 2023 13:42:50 +0700 Subject: [PATCH 07/10] fix: aggregator routing (#2156) --- src/components/TradeRouting/index.tsx | 26 +++++++++++++++++--------- src/utils/aggregationRouting.ts | 8 +++++++- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/components/TradeRouting/index.tsx b/src/components/TradeRouting/index.tsx index 7598b3617c..904e18abf1 100644 --- a/src/components/TradeRouting/index.tsx +++ b/src/components/TradeRouting/index.tsx @@ -1,4 +1,4 @@ -import { ChainId, Currency, CurrencyAmount } from '@kyberswap/ks-sdk-core' +import { ChainId, Currency, CurrencyAmount, Token } from '@kyberswap/ks-sdk-core' import React, { memo, useCallback, useEffect, useRef } from 'react' import ScrollContainer from 'react-indiana-drag-scroll' @@ -25,6 +25,7 @@ import { StyledWrapToken, } from 'components/TradeRouting/styled' import { useActiveWeb3React } from 'hooks' +import { useCurrencyV2 } from 'hooks/Tokens' import { useAllDexes } from 'state/customizeDexes/hooks' import { getEtherscanLink, isAddress } from 'utils' import { SwapRouteV2 } from 'utils/aggregationRouting' @@ -67,14 +68,7 @@ const RouteRow = ({ route, chainId, backgroundColor }: RouteRowProps) => { return ( - - - {token?.symbol} - + {Array.isArray(subRoute) ? subRoute.map(pool => { const dex = getDexInfoByPool(pool, allDexes) @@ -202,4 +196,18 @@ const Routing = ({ tradeComposition, maxHeight, inputAmount, outputAmount, curre ) } +const TokenRoute = ({ token }: { token: Token }) => { + const currency = useCurrencyV2(token.address) + return ( + + + {currency?.symbol} + + ) +} + export default memo(Routing) diff --git a/src/utils/aggregationRouting.ts b/src/utils/aggregationRouting.ts index c1e91a44f3..9a4d87e7e8 100644 --- a/src/utils/aggregationRouting.ts +++ b/src/utils/aggregationRouting.ts @@ -149,7 +149,13 @@ export function getTradeComposition( return NativeCurrencies[chainId] } - return allTokens?.[isAddressString(chainId, address)] || tokens[address] || defaultToken + return ( + allTokens?.[isAddressString(chainId, address)] || + tokens[address] || + (isAddressString(chainId, address) + ? new Token(chainId, isAddressString(chainId, address), 0, '--', '--') + : defaultToken) + ) } // Convert all Swaps to ChartSwaps From 00e2eeac1725a37500c8d75ace9dcd81fb49c634 Mon Sep 17 00:00:00 2001 From: Nguyen Van Viet Date: Thu, 10 Aug 2023 18:13:22 +0700 Subject: [PATCH 08/10] LP earning classic page (#2133) * Add classic page (#2053) * wip: add MyEarning to header * wip: implement earning dashboard * wip: remove transition in pie chart * wip: card background and button variant * null check * wip: multicall work * wip: work get tick data from another chainId * wip: add liquidity button * wip: update style for stats values * wip: check with arbitrum & ethereum * fix backdrop of liquidity range chart by using unique ids * view more positions button * minor style * wip: responsive * wip: display token logo with chain logo in breakdown chart * adapt to new UI design * farm available tooltip * show only active positions * adapt to new UI design * today change diff * wip * handle empty state * fill data for missing days * show/hide closed positions * show number of active/inactive/closed positions * implement search function * show in range/out range position status * Responsive UI for extra small screens * implement Expand/Collapse all pools * implement refresh button * wip: implement Share modal * done share ui * update env * use social utils c c c * cache share url * update api call fix update ch * wip: aggregate data in case data from data is missing * refactor: insert legacy elastic pool to tab list * refactor: use pool address to get pool data * wip: turn off aggregating data from frontend * wip: use prod env * refactor: use pool address to get price range chart data * Revert "refactor: use pool address to get pool data" This reverts commit 3e41dba37ad4886fac3a6e7a5bc8149761991dc5. * Revert "refactor: use pool address to get price range chart data" This reverts commit b71b2e6b61dfa36bc5f0d3e27d59b1e8d5477bbf. * fix: pass poolAddress to usePoolv2 This enables expand button to see positions of a pool in LP page * fix: show loading when re-fetching LP data * Revert "wip: turn off aggregating data from frontend" This reverts commit f7614fc3a3afaa21875c6b0ce4740a2a19dd8813. * fix: set default share mode according to the device * refactor: modify ClassicElasticPools tab * fix: fix hanging when switching tabs * env dev oaatuh * feat: aggregate account earnings across multiple chains * refactor: use data of the last day for other empty days * refactor: fill data for unavailable days * fix: calculate earning breakdown from earning tick * fix: crash due to null field * fix: fix build failed * refactor: select all chains by default * style: take more width and adjust width of single position * small refactor * refactor: fill empty days for position earnings * refactor: fill empty days for ticks * fix: calculate wrong fees due to modifying objects instead of deep-cloning it * chore: use dev env * use proxy token logo * feat: prepare queries for elastic earning * feat: make 2 separate requests for elastic and elastic legacy * refactor: turn off Liquidity chart of legacy positions by default * refactor: display at most 5 tokens in the tooltip of earning overtime panel * refactor: use compact notation for shared earning value * refactor: sort pools by TVL * refactor: group elastic and classic pools * fix: refresh button * wip: prepare for classic positions * fix crash * fix: merge earning tick * refactor: aggregate pool data BE now doesn't return `pools` in the response * feat: pending fees (#1988) * feat: pending fees * chore: fix prettier issue --------- Co-authored-by: Doan Sy Hung * try fix token logo when download * rm ununsed * chore: try debug share img * fix: not show logo when download * loading for btn share * test build * feat: calc apr, staked balance in position view (#1989) * refactor: make zoom out modal smaller * add scroll share modal if overflow * fix: rotate layout * feat: add Kyber logo to earning chart and add label * fix: use hard-coded token symbols * feat: view earnings button * fix: https://team-kyber.atlassian.net/browse/KGP-1259 c * refactor: not show labels if chart is in small container * refactor: use pointer for buttons * fix: not show labels if container width is < 400 * refactor: reduce height of zoom out earning modal * fix: fix wrong layout for zoom out modal * fix: remove border radius from my earnings overtime panel * feat: collect fees * feat: save the time period when viewing in modal * feat: add Analytic button for elastic positions * refactor: update style for Sharing modal * refactor: update url to Add Liquidity * refactor: add url to Remove Liquidity and Increase Liquidity * chore: suppress warnings/errors * style: update Expand All button * refactor: update style for View Earning/Position button * feat: implement classic pools * feat: display classic pool earnings * feat: implement position detail for classic pool * feat: click on each pool to expand/collapse * chore: update classic response * show some properties of Classic pools/positions * update urls for Remove/Add liquidity classic pools * feat: add up classic earnings * fix build failed * use prod env * update pool currency logos * make equal piece for pie chart with values of 0 * use env dev * display LP Token balance * display share of pool * style Share image * show copy address button for elastic pools * update view earnings or positions button * use pointer cursor for Share button * add banner * fix build failed * update add/increase liquidity links using native symbol * show native tokens instead of wrapped ones * try add proxy to logos * update currency logos in Share modal * fix build failed * fix NaN in earning pie chart * hide ConnectWalletFooter in MyEarnings page * sort data top to bottom in pie chart * update style for elastic/classic buttons * display Subscribe button in full * show icon only for Subscribe button in mobile * update placeholder for MyEarnings on mobile * update placeholder * disable remove/increase buttons if liquidity = 0 * update layout of positions * temp disable classic pools button * disable increase liquidity button for legacy positions * update layout for legends of LP pie chart * update link for MyEarnings in the banner * update style for elastic/classic tab * update color for Analytics button * exclude classic data * disable Add Liquidity button for Legacy pools * remove console.log * update position layout * adapt to medium-sized screens * shrink Legacy tag * adapt pie chart to medium screens * update format for fee numbers * move ActionButtons to a separate component * remove liquidity from elastic legacy position * show fees for legacy positions * revert api social env stg env prod * update texts * fix build failed * hardcode stg ks-setting c * refactor: update View Earnings/Positions * refactor: use calculated breakpoints * filter out empty token earnings * add tooltip for percent change * add token logo to token in earning area chart * show relative numbers if too small * handle change network glitch while collecting fees * avoid scientific notation * disable hover dropdown if no value * display 0 for $0 earning * auto refetch earning data after 5 minutes * disable hover dropdown if there's no liquidity * add mixpanel events * test * latest sdk * retain state while refetching data * update number formatters * stop propagation for click buttons in pool * try fix size logo when download * try fix size logo when download * update value formatter * refactor common codes of single pool * comment unused codes * usd value format * display my liquidity balance for each pool * display total APR * fix refetching LP with wrong chains * calculate total of token earnings * fix minor UI issues * get user farm data from custom chainId * adjust decimals for balance * sort positions by TVL * fix calling wrong farm contract to collect fees * revert token logo share modal c * no wrap small balances * remove video tutorial * update texts * update pending texts in modal * fix issue network changed before collecting fees * fix comments * sort closed positions by nft id * use proxy only in share earnings modal * refactor today * remove done todo * fix refreshing issue * sdk version * fix minor comments * remove classic page * Add classic page This reverts commit b87457e7d249eab389cdfee13cfce1cc60b8184c. --------- Co-authored-by: Danh Co-authored-by: Nguyen Van Viet * feat: handle classic * add no liquidity found * update missing info * fix: missing token * amp liq tooltip * disable some network * swithc to prod env * tooltip placement top * fix: select chain * revert env --------- Co-authored-by: Hung Doan <19758667+hungdoansy@users.noreply.github.com> Co-authored-by: Danh --- src/components/EarningAreaChart/index.tsx | 17 +- src/components/SubscribeButton/index.tsx | 10 +- src/constants/networks.ts | 14 +- src/hooks/Tokens.ts | 3 + src/pages/MyEarnings/ChainSelect.tsx | 23 +- src/pages/MyEarnings/ClassicElasticTab.tsx | 63 +-- .../ClassicPools/SinglePool/Position.tsx | 212 ++++++++ .../ClassicPools/SinglePool/index.tsx | 503 ++++++++++++++++++ src/pages/MyEarnings/ClassicPools/index.tsx | 139 +++++ .../SinglePool/SharePoolEarningsButton.tsx | 7 +- .../ElasticPools/SinglePool/index.tsx | 77 +-- src/pages/MyEarnings/ElasticPools/index.tsx | 2 +- .../MultipleChainSelect/PopoverBody.tsx | 25 +- src/pages/MyEarnings/MyEarningStats/index.tsx | 25 +- src/pages/MyEarnings/PoolEarningsSection.tsx | 69 ++- .../ClosedPositionsToggle.tsx | 4 +- src/pages/MyEarnings/Pools/index.tsx | 4 +- src/pages/MyEarnings/ShareModal.tsx | 9 +- .../TotalEarningsAndChainSelect.tsx | 38 +- src/pages/MyEarnings/hooks.ts | 12 +- src/pages/MyEarnings/styled.tsx | 80 +++ src/services/earning/types.ts | 8 +- 22 files changed, 1154 insertions(+), 190 deletions(-) create mode 100644 src/pages/MyEarnings/ClassicPools/SinglePool/Position.tsx create mode 100644 src/pages/MyEarnings/ClassicPools/SinglePool/index.tsx create mode 100644 src/pages/MyEarnings/ClassicPools/index.tsx create mode 100644 src/pages/MyEarnings/styled.tsx diff --git a/src/components/EarningAreaChart/index.tsx b/src/components/EarningAreaChart/index.tsx index 20181a8274..abcaee3b7a 100644 --- a/src/components/EarningAreaChart/index.tsx +++ b/src/components/EarningAreaChart/index.tsx @@ -57,16 +57,17 @@ const subscriptMap: { [key: string]: string } = { } const formatter = (value: string) => { - const num = Number(value) + const num = parseFloat(value) const numberOfZero = -Math.floor(Math.log10(num) + 1) if (num > 0 && num < 1 && numberOfZero > 2) { - const temp = Number(toFixed(num).split('.')[1]) + const temp = Number(toFixed(num).split('.')[1]).toString() + return `$0.0${numberOfZero .toString() .split('') .map(item => subscriptMap[item]) - .join('')}${temp > 10 ? (temp / 10).toFixed(0) : temp}` + .join('')}${temp.substring(0, 2)}` } const formatter = Intl.NumberFormat('en-US', { @@ -108,7 +109,15 @@ const EarningAreaChart: React.FC = ({ data, setHoverValue = EMPTY_FUNCTIO - + bgColor}; } ${({ iconOnly, bgColor }) => iconOnly && cssSubscribeBtnSmall(bgColor)}; - ${({ theme, bgColor }) => theme.mediaWidth.upToExtraSmall` + ${({ theme, bgColor, iconOnly }) => + iconOnly !== false && + theme.mediaWidth.upToExtraSmall` ${cssSubscribeBtnSmall(bgColor)} `} ` @@ -48,13 +50,15 @@ const ButtonText = styled(Text)<{ iconOnly?: boolean }>` font-weight: 500; margin-left: 6px !important; ${({ iconOnly }) => iconOnly && `display: none`}; - ${({ theme }) => theme.mediaWidth.upToExtraSmall` + ${({ theme, iconOnly }) => + iconOnly !== false && + theme.mediaWidth.upToExtraSmall` display: none; `} ` export default function SubscribeNotificationButton({ subscribeTooltip, - iconOnly = false, + iconOnly, trackingEvent, onClick, topicId, diff --git a/src/constants/networks.ts b/src/constants/networks.ts index 2cad0713f3..43ef0bd59f 100644 --- a/src/constants/networks.ts +++ b/src/constants/networks.ts @@ -212,7 +212,19 @@ export const SUPPORTED_NETWORKS_FOR_MY_EARNINGS = [ ChainId.OASIS, ] export const COMING_SOON_NETWORKS_FOR_MY_EARNINGS: ChainId[] = [] -export const COMING_SOON_NETWORKS_FOR_MY_EARNINGS_LEGACY: ChainId[] = [ChainId.MATIC] +export const COMING_SOON_NETWORKS_FOR_MY_EARNINGS_LEGACY: ChainId[] = [] +export const COMING_SOON_NETWORKS_FOR_MY_EARNINGS_CLASSIC: ChainId[] = [ + ChainId.CRONOS, + ChainId.OASIS, + ChainId.MATIC, + ChainId.BSCMAINNET, + ChainId.AVAXMAINNET, + ChainId.CRONOS, + ChainId.BTTC, + ChainId.VELAS, + ChainId.AURORA, + ChainId.OASIS, +] // by pass invalid price impact/unable to calculate price impact/price impact too large export const CHAINS_BYPASS_PRICE_IMPACT = [ChainId.LINEA_TESTNET] diff --git a/src/hooks/Tokens.ts b/src/hooks/Tokens.ts index e4b2146c7e..0b3a22b6f4 100644 --- a/src/hooks/Tokens.ts +++ b/src/hooks/Tokens.ts @@ -304,6 +304,9 @@ export const fetchTokenByAddress = async (address: string, chainId: ChainId, sig } export const fetchListTokenByAddresses = async (address: string[], chainId: ChainId) => { + const cached = filterTruthy(address.map(addr => findCacheToken(addr))) + if (cached.length === address.length) return cached + const response = await axios.get(`${KS_SETTING_API}/v1/tokens?addresses=${address}&chainIds=${chainId}`) const tokens = response?.data?.data?.tokens ?? [] return filterTruthy(tokens.map(formatAndCacheToken)) as WrappedTokenInfo[] diff --git a/src/pages/MyEarnings/ChainSelect.tsx b/src/pages/MyEarnings/ChainSelect.tsx index e06845bea9..6d3bf822a3 100644 --- a/src/pages/MyEarnings/ChainSelect.tsx +++ b/src/pages/MyEarnings/ChainSelect.tsx @@ -5,11 +5,18 @@ import { Flex, Text } from 'rebass' import styled from 'styled-components' import { ButtonOutlined } from 'components/Button' -import { SUPPORTED_NETWORKS_FOR_MY_EARNINGS } from 'constants/networks' +import { + COMING_SOON_NETWORKS_FOR_MY_EARNINGS, + COMING_SOON_NETWORKS_FOR_MY_EARNINGS_CLASSIC, + COMING_SOON_NETWORKS_FOR_MY_EARNINGS_LEGACY, + SUPPORTED_NETWORKS_FOR_MY_EARNINGS, +} from 'constants/networks' +import { VERSION } from 'constants/v2' import { useActiveWeb3React } from 'hooks' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import useTheme from 'hooks/useTheme' import MultipleChainSelect from 'pages/MyEarnings/MultipleChainSelect' +import { useAppSelector } from 'state/hooks' import { selectChains } from 'state/myEarnings/actions' import { useShowMyEarningChart } from 'state/user/hooks' import { MEDIA_WIDTHS } from 'theme' @@ -49,7 +56,19 @@ const ChainSelect = () => { const dispatch = useDispatch() const { mixpanelHandler } = useMixpanel() const { chainId } = useActiveWeb3React() - const isValidNetwork = SUPPORTED_NETWORKS_FOR_MY_EARNINGS.includes(chainId) + + const isLegacy = useAppSelector(state => state.myEarnings.activeTab === VERSION.ELASTIC_LEGACY) + const isClassic = useAppSelector(state => state.myEarnings.activeTab === VERSION.CLASSIC) + + const comingSoonList = isLegacy + ? COMING_SOON_NETWORKS_FOR_MY_EARNINGS_LEGACY + : isClassic + ? COMING_SOON_NETWORKS_FOR_MY_EARNINGS_CLASSIC + : COMING_SOON_NETWORKS_FOR_MY_EARNINGS + + const networkList = SUPPORTED_NETWORKS_FOR_MY_EARNINGS.filter(item => !comingSoonList.includes(item)) + + const isValidNetwork = networkList.includes(chainId) const handleClickCurrentChain = () => { if (!isValidNetwork) { diff --git a/src/pages/MyEarnings/ClassicElasticTab.tsx b/src/pages/MyEarnings/ClassicElasticTab.tsx index 21212c526b..d113720a22 100644 --- a/src/pages/MyEarnings/ClassicElasticTab.tsx +++ b/src/pages/MyEarnings/ClassicElasticTab.tsx @@ -3,7 +3,7 @@ import { rgba } from 'polished' import { stringify } from 'querystring' import { useEffect } from 'react' import { useDispatch } from 'react-redux' -import { Link, useNavigate } from 'react-router-dom' +import { useNavigate } from 'react-router-dom' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' @@ -11,7 +11,7 @@ import { ReactComponent as DropdownSVG } from 'assets/svg/down.svg' import { PoolClassicIcon, PoolElasticIcon } from 'components/Icons' import Wallet from 'components/Icons/Wallet' import { MouseoverTooltip } from 'components/Tooltip' -import { APP_PATHS, PROMM_ANALYTICS_URL } from 'constants/index' +import { PROMM_ANALYTICS_URL } from 'constants/index' import { VERSION } from 'constants/v2' import { useActiveWeb3React } from 'hooks' import useParsedQueryString from 'hooks/useParsedQueryString' @@ -78,12 +78,10 @@ function ClassicElasticTab() { dispatch(setActiveTab(tab)) }, [dispatch, tab]) - useEffect(() => { - if (tab !== VERSION.ELASTIC && tab !== VERSION.ELASTIC_LEGACY) { - const newQs = { ...qs, tab: VERSION.ELASTIC } - navigate({ search: stringify(newQs) }, { replace: true }) - } - }, [navigate, qs, tab]) + const handleClickClassic = () => { + const newQs = { ...qs, tab: VERSION.CLASSIC } + navigate({ search: stringify(newQs) }, { replace: true }) + } const renderComboPoolButtonsForMobile = () => { return ( @@ -124,8 +122,8 @@ function ClassicElasticTab() { - + Classic Pools @@ -210,38 +208,29 @@ function ClassicElasticTab() { } const renderClassicPoolsButton = () => { - const color = tab === VERSION.CLASSIC ? theme.primary : theme.disableText - const here = here + const color = tab === VERSION.CLASSIC ? theme.primary : theme.subText return ( - - Coming soon. In the meantime, you can still manage your Classic liquidity positions {here} - - } - placement="top" + - - - - - Classic Pools - + - + + Classic Pools + + ) } diff --git a/src/pages/MyEarnings/ClassicPools/SinglePool/Position.tsx b/src/pages/MyEarnings/ClassicPools/SinglePool/Position.tsx new file mode 100644 index 0000000000..52e791e88c --- /dev/null +++ b/src/pages/MyEarnings/ClassicPools/SinglePool/Position.tsx @@ -0,0 +1,212 @@ +import { ChainId, Token } from '@kyberswap/ks-sdk-core' +import { Trans, t } from '@lingui/macro' +import { useMemo } from 'react' +import { useMedia } from 'react-use' +import { Box, Flex, Text } from 'rebass' +import { ClassicPositionEarningWithDetails } from 'services/earning/types' + +import CurrencyLogo from 'components/CurrencyLogo' +import useTheme from 'hooks/useTheme' +import HoverDropdown from 'pages/MyEarnings/HoverDropdown' +import PoolEarningsSection from 'pages/MyEarnings/PoolEarningsSection' +import { WIDTHS } from 'pages/MyEarnings/constants' +import { useShowMyEarningChart } from 'state/user/hooks' +import { formattedNum } from 'utils' +import { formatDollarAmount } from 'utils/numbers' +import { unwrappedToken } from 'utils/wrappedCurrency' + +type ColumnProps = { + label: string + value: React.ReactNode +} +const Column: React.FC = ({ label, value }) => { + const theme = useTheme() + return ( + + + {label} + + + {value} + + ) +} + +type Props = { + chainId: ChainId + poolEarning: ClassicPositionEarningWithDetails +} +const Position: React.FC = ({ poolEarning, chainId }) => { + const theme = useTheme() + const [showEarningChart] = useShowMyEarningChart() + const mobileView = useMedia(`(max-width: ${WIDTHS[2]}px)`) + + const myLiquidityBalance = + poolEarning.liquidityTokenBalanceIncludingStake !== '0' && poolEarning.pool.totalSupply !== '0' + ? formatDollarAmount( + (+poolEarning.liquidityTokenBalanceIncludingStake * +poolEarning.pool.reserveUSD) / + +poolEarning.pool.totalSupply, + ) + : '--' + + const myShareOfPool = +poolEarning.liquidityTokenBalanceIncludingStake / +poolEarning.pool.totalSupply + + const pooledToken0 = +poolEarning.pool.reserve0 * myShareOfPool + const pooledToken1 = +poolEarning.pool.reserve1 * myShareOfPool + + const token0 = useMemo( + () => + new Token( + chainId, + poolEarning.pool.token0.id, + +poolEarning.pool.token0.decimals, + poolEarning.pool.token0.symbol, + poolEarning.pool.token0.name, + ), + [chainId, poolEarning], + ) + + const token1 = useMemo( + () => + new Token( + chainId, + poolEarning.pool.token1.id, + +poolEarning.pool.token1.decimals, + poolEarning.pool.token1.symbol, + poolEarning.pool.token1.name, + ), + [chainId, poolEarning], + ) + + const liquidityStaked = +poolEarning.liquidityTokenBalanceIncludingStake - +poolEarning.liquidityTokenBalance + const myStakedBalance = + liquidityStaked !== 0 + ? formatDollarAmount((liquidityStaked * +poolEarning.pool.reserveUSD) / +poolEarning.pool.totalSupply) + : '--' + + const stakedShare = liquidityStaked / +poolEarning.pool.totalSupply + + const stakedToken0 = +poolEarning.pool.reserve0 * stakedShare + const stakedToken1 = +poolEarning.pool.reserve1 * stakedShare + + return ( + + + My Liquidity Positions + + + {showEarningChart && } + + *:nth-child(2n)': { + alignItems: 'flex-end', + }, + }), + }} + > + + {myLiquidityBalance}} + disabled={poolEarning.liquidityTokenBalance === '0'} + text={ +
+ + + + {formattedNum(pooledToken0)} {unwrappedToken(token0).symbol} + + + + + + {formattedNum(pooledToken1)} {unwrappedToken(token1).symbol} + + +
+ } + /> + + } + /> + + + {myStakedBalance}} + disabled={liquidityStaked === 0} + text={ +
+ + + + {formattedNum(stakedToken0)} {unwrappedToken(token0).symbol} + + + + + + {formattedNum(stakedToken1)} {unwrappedToken(token1).symbol} + + +
+ } + /> + + } + /> + + + + + + +
+
+ ) +} + +export default Position diff --git a/src/pages/MyEarnings/ClassicPools/SinglePool/index.tsx b/src/pages/MyEarnings/ClassicPools/SinglePool/index.tsx new file mode 100644 index 0000000000..f6050cec5c --- /dev/null +++ b/src/pages/MyEarnings/ClassicPools/SinglePool/index.tsx @@ -0,0 +1,503 @@ +import { ChainId, Fraction, Token } from '@kyberswap/ks-sdk-core' +import { Trans, t } from '@lingui/macro' +import JSBI from 'jsbi' +import { rgba } from 'polished' +import { useEffect, useMemo, useState } from 'react' +import { BarChart2, Info, Minus, Plus } from 'react-feather' +import { Link } from 'react-router-dom' +import { useMedia } from 'react-use' +import { Box, Flex, Text } from 'rebass' +import { ClassicPositionEarningWithDetails } from 'services/earning/types' +import styled from 'styled-components' + +import { ReactComponent as DropdownSVG } from 'assets/svg/down.svg' +import CopyHelper from 'components/Copy' +import Divider from 'components/Divider' +import DoubleCurrencyLogo from 'components/DoubleLogo' +import { MoneyBag } from 'components/Icons' +import { MouseoverTooltip, TextDashed } from 'components/Tooltip' +import { APRTooltipContent } from 'components/YieldPools/FarmingPoolAPRCell' +import { APP_PATHS, DMM_ANALYTICS_URL, SUBGRAPH_AMP_MULTIPLIER } from 'constants/index' +import { NETWORKS_INFO } from 'constants/networks' +import useTheme from 'hooks/useTheme' +import Position from 'pages/MyEarnings/ClassicPools/SinglePool/Position' +import { StatItem } from 'pages/MyEarnings/ElasticPools/SinglePool' +import SharePoolEarningsButton from 'pages/MyEarnings/ElasticPools/SinglePool/SharePoolEarningsButton' +import { WIDTHS } from 'pages/MyEarnings/constants' +import { ClassicRow, DownIcon, MobileStat, MobileStatWrapper, Wrapper } from 'pages/MyEarnings/styled' +import { ButtonIcon } from 'pages/Pools/styleds' +import { useAppSelector } from 'state/hooks' +import { TokenAddressMap } from 'state/lists/reducer' +import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo' +import { isAddress, shortenAddress } from 'utils' +import { currencyId } from 'utils/currencyId' +import { formatDollarAmount } from 'utils/numbers' +import { getTokenSymbolWithHardcode } from 'utils/tokenInfo' +import { unwrappedToken } from 'utils/wrappedCurrency' + +const calculateAmpLiquidity = (rawAmp: string, reserveUSD: string) => { + const amp = new Fraction(rawAmp).divide(JSBI.BigInt(SUBGRAPH_AMP_MULTIPLIER)) + const ampLiquidity = parseFloat(amp.toSignificant(5)) * parseFloat(reserveUSD) + return ampLiquidity +} + +const Badge = styled.div<{ $color?: string }>` + display: flex; + align-items: center; + gap: 4px; + + padding: 2px 8px; + font-weight: 500; + font-size: 12px; + line-height: 16px; + border-radius: 16px; + + user-select: none; + + color: ${({ $color, theme }) => $color || theme.subText}; + background: ${({ $color, theme }) => rgba($color || theme.subText, 0.3)}; + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + height: 16px; + padding: 0 4px; + `} +` + +export type Props = { + chainId: ChainId + poolEarning: ClassicPositionEarningWithDetails +} + +const getCurrencyFromTokenAddress = ( + tokensByChainId: TokenAddressMap, + chainId: ChainId, + address: string, +): WrappedTokenInfo | undefined => { + const tokenAddress = isAddress(chainId, address) + if (!tokenAddress) { + return undefined + } + + const currency = tokensByChainId[chainId][tokenAddress] + return currency +} + +const SinglePool: React.FC = ({ poolEarning, chainId }) => { + const theme = useTheme() + const networkInfo = NETWORKS_INFO[chainId] + const [isExpanded, setExpanded] = useState(false) + const tabletView = useMedia(`(max-width: ${WIDTHS[3]}px)`) + const mobileView = useMedia(`(max-width: ${WIDTHS[2]}px)`) + const tokensByChainId = useAppSelector(state => state.lists.mapWhitelistTokens) + const shouldExpandAllPools = useAppSelector(state => state.myEarnings.shouldExpandAllPools) + const currency0 = + getCurrencyFromTokenAddress(tokensByChainId, chainId, poolEarning.pool.token0.id) || + new Token( + chainId, + poolEarning.pool.token0.id, + +poolEarning.pool.token0.decimals, + poolEarning.pool.token0.symbol, + poolEarning.pool.token0.name, + ) + const currency1 = + getCurrencyFromTokenAddress(tokensByChainId, chainId, poolEarning.pool.token1.id) || + new Token( + chainId, + poolEarning.pool.token1.id, + +poolEarning.pool.token1.decimals, + poolEarning.pool.token1.symbol, + poolEarning.pool.token1.name, + ) + + // Need these because we'll display native tokens instead of wrapped tokens + const visibleCurrency0 = currency0 ? unwrappedToken(currency0) : undefined + const visibleCurrency1 = currency1 ? unwrappedToken(currency1) : undefined + + /* Some tokens have different symbols in our system */ + const visibleCurrency0Symbol = getTokenSymbolWithHardcode( + chainId, + poolEarning.pool.token0.id, + visibleCurrency0?.symbol || poolEarning.pool.token0.symbol, + ) + const visibleCurrency1Symbol = getTokenSymbolWithHardcode( + chainId, + poolEarning.pool.token1.id, + visibleCurrency1?.symbol || poolEarning.pool.token1.symbol, + ) + const myLiquidityBalance = + poolEarning.liquidityTokenBalance !== '0' && poolEarning.pool.totalSupply !== '0' + ? formatDollarAmount( + (+poolEarning.liquidityTokenBalance * +poolEarning.pool.reserveUSD) / +poolEarning.pool.totalSupply, + ) + : '--' + + const here = ( + + here + + ) + + const isFarmingPool = poolEarning.pool.farmApr !== '0' + + const poolEarningToday = useMemo(() => { + const earning = poolEarning.historicalEarning[0]?.total?.reduce( + (acc, tokenEarning) => acc + Number(tokenEarning.amountUSD), + 0, + ) + + return earning || 0 + }, [poolEarning.historicalEarning]) + + useEffect(() => { + setExpanded(shouldExpandAllPools) + }, [shouldExpandAllPools]) + + const amp = +poolEarning.pool.amp / 10_000 + + const ampLiquidity = calculateAmpLiquidity(poolEarning.pool.amp, poolEarning.pool.reserveUSD) + + const renderShareButton = () => { + return ( + + ) + } + + const share = ( + { + e.stopPropagation() + }} + > + + {renderShareButton()} + + ) + + if (tabletView) { + return ( + + + + + + + + + {visibleCurrency0Symbol} - {visibleCurrency1Symbol} + + + + AMP {amp} + + {isFarmingPool && ( + + Available for yield farming. Click {here} to go to the farm. +
+ } + > + + + + + )} +
+ + {share} +
+ + + + + + APR + + } + value={ + + + {(+poolEarning.pool.apr).toFixed(2)}% + + + + } + > + + + + } + /> + + + + + + + + { + e.stopPropagation() + }} + > + + + + {poolEarning.liquidityTokenBalance !== '0' && ( + e.stopPropagation()} + > + + + )} + + e.stopPropagation()} + > + + + + setExpanded(e => !e)} + > + + + + + + {isExpanded && mobileView && } + {isExpanded && } +
+ ) + } + + return ( + <> + + setExpanded(e => !e)}> + + + + + {NETWORKS_INFO[chainId].name} + + + + + {visibleCurrency0Symbol} - {visibleCurrency1Symbol} + + AMP {+poolEarning.pool.amp / 10000} + + {isFarmingPool && ( + + Available for yield farming. Click {here} to go to the farm. + + } + > + + + + + )} + + + {share} + + + +
+ {formatDollarAmount(ampLiquidity)} + + {formatDollarAmount(+poolEarning.pool.reserveUSD)} + +
+ + + + {(+poolEarning.pool.apr + +poolEarning.pool.farmApr).toFixed(2)}% + + + + } + > + + + + + + {formatDollarAmount(Number(poolEarning.pool.volumeUsd) - Number(poolEarning.pool.volumeUsdOneDayAgo))} + + {formatDollarAmount(Number(poolEarning.pool.feeUSD) - Number(poolEarning.pool.feesUsdOneDayAgo))} + {myLiquidityBalance} + {formatDollarAmount(poolEarningToday)} + + + e.stopPropagation()} + > + + + + {poolEarning.liquidityTokenBalance !== '0' && ( + e.stopPropagation()} + > + + + )} + + e.stopPropagation()} + > + + + +
+ {isExpanded && } +
+ + ) +} + +export default SinglePool diff --git a/src/pages/MyEarnings/ClassicPools/index.tsx b/src/pages/MyEarnings/ClassicPools/index.tsx new file mode 100644 index 0000000000..0ba78db0eb --- /dev/null +++ b/src/pages/MyEarnings/ClassicPools/index.tsx @@ -0,0 +1,139 @@ +import { Trans, t } from '@lingui/macro' +import { useMedia } from 'react-use' +import { Flex, Text } from 'rebass' +import { useGetClassicEarningQuery } from 'services/earning' +import { ClassicPositionEarningWithDetails } from 'services/earning/types' +import styled from 'styled-components' + +import InfoHelper from 'components/InfoHelper' +import { AMP_LIQUIDITY_HINT } from 'constants/index' +import { COMING_SOON_NETWORKS_FOR_MY_EARNINGS_CLASSIC } from 'constants/networks' +import { useActiveWeb3React } from 'hooks' +import useDebounce from 'hooks/useDebounce' +import useTheme from 'hooks/useTheme' +import { chainIdByRoute } from 'pages/MyEarnings/utils' +import { useAppSelector } from 'state/hooks' + +import { WIDTHS } from '../constants' +import SinglePool from './SinglePool' + +const Header = styled.div` + background: ${({ theme }) => theme.tableHeader}; + color: ${({ theme }) => theme.subText}; + text-transform: uppercase; + padding: 16px 12px; + font-size: 12px; + font-weight: 500; + border-top-left-radius: 16px; + border-top-right-radius: 16px; + display: grid; + grid-template-columns: 3fr 2fr repeat(6, 1.3fr); +` + +const ClassicPools = () => { + const { account = '' } = useActiveWeb3React() + const selectedChainIds = useAppSelector(state => state.myEarnings.selectedChains) + + const classicEarningQueryResponse = useGetClassicEarningQuery({ + account, + chainIds: selectedChainIds.filter(item => !COMING_SOON_NETWORKS_FOR_MY_EARNINGS_CLASSIC.includes(item)), + }) + + const originalSearchText = useAppSelector(state => state.myEarnings.searchText) + const searchText = useDebounce(originalSearchText, 300).toLowerCase().trim() + const shouldShowClosedPositions = useAppSelector(state => state.myEarnings.shouldShowClosedPositions) + + const data = classicEarningQueryResponse.data + + const tabletView = useMedia(`(max-width: ${WIDTHS[3]}px)`) + const mobileView = useMedia(`(max-width: ${WIDTHS[2]}px)`) + const theme = useTheme() + + const renderPools = () => { + const filterFn = (item: ClassicPositionEarningWithDetails) => { + const removedFilter = shouldShowClosedPositions + ? true + : item.liquidityTokenBalance !== '0' || item.liquidityTokenBalanceIncludingStake !== '0' + const poolId = item.pool.id.toLowerCase() + const searchFilter = + poolId === searchText || + item.pool.token0.id.toLowerCase() === searchText || + item.pool.token0.symbol.toLowerCase().includes(searchText) || + item.pool.token0.name.toLowerCase().includes(searchText) || + item.pool.token1.id.toLowerCase() === searchText || + item.pool.token1.symbol.toLowerCase().includes(searchText) || + item.pool.token1.name.toLowerCase().includes(searchText) + return searchFilter && removedFilter + } + + if (!data || Object.keys(data).every(key => !data[key]?.positions?.filter(filterFn).length)) { + return ( + + No liquidity found + + ) + } + + return Object.keys(data).map(chainRoute => { + const chainId = chainIdByRoute[chainRoute] + const poolEarnings = data[chainRoute].positions.filter(filterFn) + + return ( + <> + {poolEarnings.map(poolEarning => { + return + })} + + ) + }) + } + + return ( + + {!tabletView && ( +
+ + Pool | AMP + + + AMP Liquidity | TVL + + + + APR + + + + Volume (24h) + + + Fees (24h) + + + My Liquidity + + + My Earnings + + + Actions + +
+ )} + + {renderPools()} +
+ ) +} + +export default ClassicPools diff --git a/src/pages/MyEarnings/ElasticPools/SinglePool/SharePoolEarningsButton.tsx b/src/pages/MyEarnings/ElasticPools/SinglePool/SharePoolEarningsButton.tsx index 292e84cd25..43113cbb2e 100644 --- a/src/pages/MyEarnings/ElasticPools/SinglePool/SharePoolEarningsButton.tsx +++ b/src/pages/MyEarnings/ElasticPools/SinglePool/SharePoolEarningsButton.tsx @@ -12,7 +12,8 @@ type Props = { currency1: Currency | undefined currency0Symbol: string currency1Symbol: string - feePercent: string + feePercent?: string + amp?: string } const SharePoolEarningsButton: React.FC = ({ totalValue, @@ -21,6 +22,7 @@ const SharePoolEarningsButton: React.FC = ({ currency0Symbol, currency1Symbol, feePercent, + amp, }) => { const [isOpen, setOpen] = useState(false) @@ -47,13 +49,14 @@ const SharePoolEarningsButton: React.FC = ({ isOpen={isOpen} setIsOpen={setOpen} poolInfo={ - currency0 && currency1 && feePercent + currency0 && currency1 && (feePercent || amp) ? { currency0, currency1, currency0Symbol, currency1Symbol, feePercent, + amp, } : undefined } diff --git a/src/pages/MyEarnings/ElasticPools/SinglePool/index.tsx b/src/pages/MyEarnings/ElasticPools/SinglePool/index.tsx index 09956acb03..4870eea577 100644 --- a/src/pages/MyEarnings/ElasticPools/SinglePool/index.tsx +++ b/src/pages/MyEarnings/ElasticPools/SinglePool/index.tsx @@ -1,14 +1,12 @@ import { ChainId, Token } from '@kyberswap/ks-sdk-core' import { FeeAmount, Pool, Position } from '@kyberswap/ks-sdk-elastic' import { Trans, t } from '@lingui/macro' -import { rgba } from 'polished' import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react' import { BarChart2, Info, Plus } from 'react-feather' import { Link } from 'react-router-dom' import { useMedia } from 'react-use' import { Box, Flex, Text } from 'rebass' import { ElasticPoolEarningWithDetails, ElasticPositionEarningWithDetails } from 'services/earning/types' -import styled, { css } from 'styled-components' import { ReactComponent as DropdownSVG } from 'assets/svg/down.svg' import { ButtonLight } from 'components/Button' @@ -27,6 +25,7 @@ import useTheme from 'hooks/useTheme' import SharePoolEarningsButton from 'pages/MyEarnings/ElasticPools/SinglePool/SharePoolEarningsButton' import Positions from 'pages/MyEarnings/Positions' import { WIDTHS } from 'pages/MyEarnings/constants' +import { Badge, DownIcon, MobileStat, MobileStatWrapper, Row, Wrapper } from 'pages/MyEarnings/styled' import { ButtonIcon } from 'pages/Pools/styleds' import { useAppSelector } from 'state/hooks' import { isAddress, shortenAddress } from 'utils' @@ -34,77 +33,6 @@ import { formatDollarAmount } from 'utils/numbers' import { getTokenSymbolWithHardcode } from 'utils/tokenInfo' import { unwrappedToken } from 'utils/wrappedCurrency' -const DownIcon = styled(DropdownSVG)<{ isOpen: boolean }>` - transform: rotate(${({ isOpen }) => (!isOpen ? '-90deg' : '0')}); - transition: transform 0.3s; -` - -const Wrapper = styled.div` - border-bottom: 1px solid ${({ theme }) => theme.border}; - - :last-child { - border-bottom: none; - } -` - -const MobileStatWrapper = styled(Flex)` - flex-direction: column; - gap: 1rem; -` - -const MobileStat = styled.div<{ mobileView: boolean }>` - display: flex; - justify-content: space-between; - - ${({ mobileView }) => - mobileView && - css` - display: grid; - grid-template-columns: 1fr 1fr; - gap: 0.75rem; - - > *:nth-child(2n) { - align-items: flex-end; - } - `} -` - -const Row = styled.div` - align-items: center; - padding: 12px; - font-size: 14px; - font-weight: 500; - display: grid; - grid-template-columns: 3fr repeat(7, 1fr); - - :hover { - cursor: pointer; - background: ${({ theme }) => rgba(theme.primary, 0.15)}; - } -` - -const Badge = styled.div<{ $color?: string }>` - display: flex; - align-items: center; - gap: 4px; - - padding: 2px 8px; - font-weight: 500; - font-size: 12px; - line-height: 16px; - border-radius: 16px; - - user-select: none; - - color: ${({ $color, theme }) => $color || theme.subText}; - background: ${({ $color, theme }) => rgba($color || theme.subText, 0.3)}; - - ${({ theme }) => theme.mediaWidth.upToExtraSmall` - height: 16px; - padding: 0 4px; - `} -` - export type Props = { chainId: ChainId poolEarning: ElasticPoolEarningWithDetails @@ -113,7 +41,7 @@ export type Props = { tokenPrices: { [id: string]: number } } -const StatItem = ({ label, value }: { label: ReactNode | string; value: ReactNode | string }) => { +export const StatItem = ({ label, value }: { label: ReactNode | string; value: ReactNode | string }) => { const theme = useTheme() return ( @@ -126,6 +54,7 @@ const StatItem = ({ label, value }: { label: ReactNode | string; value: ReactNod ) } + const SinglePool: React.FC = ({ poolEarning, chainId, positionEarnings, pendingFees, tokenPrices }) => { const theme = useTheme() const { mixpanelHandler } = useMixpanel() diff --git a/src/pages/MyEarnings/ElasticPools/index.tsx b/src/pages/MyEarnings/ElasticPools/index.tsx index 5c93ce23ea..98711f2e31 100644 --- a/src/pages/MyEarnings/ElasticPools/index.tsx +++ b/src/pages/MyEarnings/ElasticPools/index.tsx @@ -123,7 +123,7 @@ const ElasticPools = () => { return ( pool.id.toLowerCase() === searchText || - pool.token0.id.toLowerCase().includes(searchText) || + pool.token0.id.toLowerCase() === searchText || pool.token0.symbol.toLowerCase().includes(searchText) || pool.token0.name.toLowerCase().includes(searchText) || pool.token1.id.toLowerCase() === searchText || diff --git a/src/pages/MyEarnings/MultipleChainSelect/PopoverBody.tsx b/src/pages/MyEarnings/MultipleChainSelect/PopoverBody.tsx index 81706f63dc..288ce73c20 100644 --- a/src/pages/MyEarnings/MultipleChainSelect/PopoverBody.tsx +++ b/src/pages/MyEarnings/MultipleChainSelect/PopoverBody.tsx @@ -11,6 +11,7 @@ import Checkbox from 'components/CheckBox' import { MouseoverTooltip } from 'components/Tooltip' import { COMING_SOON_NETWORKS_FOR_MY_EARNINGS, + COMING_SOON_NETWORKS_FOR_MY_EARNINGS_CLASSIC, COMING_SOON_NETWORKS_FOR_MY_EARNINGS_LEGACY, NETWORKS_INFO, SUPPORTED_NETWORKS_FOR_MY_EARNINGS, @@ -103,16 +104,22 @@ const PopoverBody: React.FC = ({ onClose }) => { const theme = useTheme() const { mixpanelHandler } = useMixpanel() const selectAllRef = useRef(null) - const selectedChains = useSelector((state: AppState) => state.myEarnings.selectedChains) - const dispatch = useDispatch() const isLegacy = useAppSelector(state => state.myEarnings.activeTab === VERSION.ELASTIC_LEGACY) + const isClassic = useAppSelector(state => state.myEarnings.activeTab === VERSION.CLASSIC) - const comingSoonList = isLegacy ? COMING_SOON_NETWORKS_FOR_MY_EARNINGS_LEGACY : COMING_SOON_NETWORKS_FOR_MY_EARNINGS + const comingSoonList = isLegacy + ? COMING_SOON_NETWORKS_FOR_MY_EARNINGS_LEGACY + : isClassic + ? COMING_SOON_NETWORKS_FOR_MY_EARNINGS_CLASSIC + : COMING_SOON_NETWORKS_FOR_MY_EARNINGS - const [localSelectedChains, setLocalSelectedChains] = useState(() => - selectedChains.filter(item => !comingSoonList.includes(item)), + const selectedChains = useSelector((state: AppState) => + state.myEarnings.selectedChains.filter(item => !comingSoonList.includes(item)), ) + const dispatch = useDispatch() + + const [localSelectedChains, setLocalSelectedChains] = useState(() => selectedChains) const networkList = SUPPORTED_NETWORKS_FOR_MY_EARNINGS.filter(item => !comingSoonList.includes(item)) @@ -123,17 +130,17 @@ const PopoverBody: React.FC = ({ onClose }) => { useEffect(() => { setLocalSelectedChains(selectedChains) - }, [selectedChains]) + // eslint-disable-next-line + }, [selectedChains.length]) useEffect(() => { if (!selectAllRef.current) { return } - const indeterminate = - 0 < localSelectedChains.length && localSelectedChains.length < SUPPORTED_NETWORKS_FOR_MY_EARNINGS.length + const indeterminate = 0 < localSelectedChains.length && localSelectedChains.length < networkList.length selectAllRef.current.indeterminate = indeterminate - }, [localSelectedChains]) + }, [localSelectedChains, networkList.length]) const allNetworks = [...networkList, ...comingSoonList] diff --git a/src/pages/MyEarnings/MyEarningStats/index.tsx b/src/pages/MyEarnings/MyEarningStats/index.tsx index 6193881964..6b9f37005d 100644 --- a/src/pages/MyEarnings/MyEarningStats/index.tsx +++ b/src/pages/MyEarnings/MyEarningStats/index.tsx @@ -2,10 +2,13 @@ import { Trans } from '@lingui/macro' import { useMemo } from 'react' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' -import { useGetElasticEarningQuery, useGetElasticLegacyEarningQuery } from 'services/earning' +import { useGetClassicEarningQuery, useGetElasticEarningQuery, useGetElasticLegacyEarningQuery } from 'services/earning' import styled from 'styled-components' -import { COMING_SOON_NETWORKS_FOR_MY_EARNINGS_LEGACY } from 'constants/networks' +import { + COMING_SOON_NETWORKS_FOR_MY_EARNINGS_CLASSIC, + COMING_SOON_NETWORKS_FOR_MY_EARNINGS_LEGACY, +} from 'constants/networks' import { useActiveWeb3React } from 'hooks' import useTheme from 'hooks/useTheme' import ChainSelect from 'pages/MyEarnings/ChainSelect' @@ -84,23 +87,25 @@ const MyEarningStats = () => { account, chainIds: selectedChainIds.filter(item => !COMING_SOON_NETWORKS_FOR_MY_EARNINGS_LEGACY.includes(item)), }) - // const classicEarningQueryResponse = useGetClassicEarningQuery({ account, chainIds: selectedChainIds }) + const classicEarningQueryResponse = useGetClassicEarningQuery({ + account, + chainIds: selectedChainIds.filter(item => !COMING_SOON_NETWORKS_FOR_MY_EARNINGS_CLASSIC.includes(item)), + }) - const isLoading = elasticEarningQueryResponse.isFetching || elasticLegacyEarningQueryResponse.isFetching + const isLoading = + elasticEarningQueryResponse.isLoading || + elasticLegacyEarningQueryResponse.isLoading || + classicEarningQueryResponse.isLoading // chop the data into the right duration // format pool value // multiple chains const ticks: EarningStatsTick[] | undefined = useMemo(() => { return calculateTicksOfAccountEarningsInMultipleChains( - [ - elasticEarningQueryResponse.data, - elasticLegacyEarningQueryResponse.data, - // classicEarningQueryResponse.data - ], + [elasticEarningQueryResponse.data, elasticLegacyEarningQueryResponse.data, classicEarningQueryResponse.data], tokensByChainId, ) - }, [elasticEarningQueryResponse, elasticLegacyEarningQueryResponse, , tokensByChainId]) + }, [elasticEarningQueryResponse, elasticLegacyEarningQueryResponse, classicEarningQueryResponse, tokensByChainId]) const earningBreakdown: EarningsBreakdown | undefined = useMemo(() => { return calculateEarningBreakdowns(ticks?.[0]) diff --git a/src/pages/MyEarnings/PoolEarningsSection.tsx b/src/pages/MyEarnings/PoolEarningsSection.tsx index b1f7f7d68b..7d936728ed 100644 --- a/src/pages/MyEarnings/PoolEarningsSection.tsx +++ b/src/pages/MyEarnings/PoolEarningsSection.tsx @@ -1,6 +1,6 @@ import { ChainId, WETH } from '@kyberswap/ks-sdk-core' import { t } from '@lingui/macro' -import { useMemo } from 'react' +import { useEffect, useMemo, useState } from 'react' import { useMedia } from 'react-use' import { Box, Flex } from 'rebass' import { HistoricalSingleData } from 'services/earning/types' @@ -8,12 +8,14 @@ import styled from 'styled-components' import { useGetNativeTokenLogo } from 'components/CurrencyLogo' import { NativeCurrencies } from 'constants/tokens' +import { fetchListTokenByAddresses } from 'hooks/Tokens' import useTheme from 'hooks/useTheme' import { calculateEarningStatsTick, getToday } from 'pages/MyEarnings/utils' import { useAppSelector } from 'state/hooks' +import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo' import { MEDIA_WIDTHS } from 'theme' import { EarningStatsTick, EarningsBreakdown } from 'types/myEarnings' -import { isAddress } from 'utils' +import { isAddressString } from 'utils' import OriginalEarningsBreakdownPanel from './EarningsBreakdownPanel' import OriginalMyEarningsOverTimePanel from './MyEarningsOverTimePanel' @@ -88,37 +90,48 @@ const PoolEarningsSection: React.FC = ({ historicalEarning, chainId }) => const tokensByChainId = useAppSelector(state => state.lists.mapWhitelistTokens) const nativeLogo = useGetNativeTokenLogo(chainId) + const [tokens, setTokens] = useState<{ [address: string]: WrappedTokenInfo }>({}) + + const missingTokens = useMemo(() => { + const today = getToday() + + return ( + historicalEarning?.[0]?.day === today + ? historicalEarning?.[0].total?.filter(tokenData => { + return !tokensByChainId[chainId][isAddressString(chainId, tokenData.token)] + }) || [] + : [] + ).map(item => item.token) + }, [historicalEarning, tokensByChainId, chainId]) + + useEffect(() => { + fetchListTokenByAddresses(missingTokens, chainId).then(res => + setTokens(res.reduce((acc, cur) => ({ ...acc, [cur.address]: cur }), {})), + ) + }, [missingTokens, chainId]) + const earningBreakdown: EarningsBreakdown | undefined = useMemo(() => { const data = historicalEarning const today = getToday() const latestData = data?.[0]?.day === today - ? data?.[0].total - ?.filter(tokenData => { - const tokenAddress = isAddress(chainId, tokenData.token) - if (!tokenAddress) { - return false - } - - const currency = tokensByChainId[chainId][tokenAddress] - return !!currency - }) - .map(tokenData => { - const tokenAddress = isAddress(chainId, tokenData.token) - const currency = tokensByChainId[chainId][String(tokenAddress)] - const isNative = currency.isNative || tokenAddress === WETH[chainId].address - const symbol = (isNative ? NativeCurrencies[chainId].symbol : currency.symbol) || 'NO SYMBOL' - const logoUrl = (isNative ? nativeLogo : currency.logoURI) || '' - - return { - address: tokenAddress, - logoUrl, - symbol, - amountUSD: Number(tokenData.amountUSD), - chainId, - } - }) || [] + ? data?.[0].total?.map(tokenData => { + const tokenAddress = isAddressString(chainId, tokenData.token) + const currency = tokensByChainId[chainId][String(tokenAddress)] || tokens[tokenAddress] + + const isNative = currency?.isNative || tokenAddress === WETH[chainId].address + const symbol = (isNative ? NativeCurrencies[chainId].symbol : currency?.symbol) || 'NO SYMBOL' + const logoUrl = (isNative ? nativeLogo : currency?.logoURI) || '' + + return { + address: tokenAddress, + logoUrl, + symbol, + amountUSD: Number(tokenData.amountUSD), + chainId, + } + }) || [] : [] latestData.sort((data1, data2) => data2.amountUSD - data1.amountUSD) @@ -158,7 +171,7 @@ const PoolEarningsSection: React.FC = ({ historicalEarning, chainId }) => totalValue, breakdowns, } - }, [chainId, historicalEarning, tokensByChainId, nativeLogo]) + }, [chainId, historicalEarning, tokensByChainId, nativeLogo, tokens]) // format pool value const ticks: EarningStatsTick[] | undefined = useMemo(() => { diff --git a/src/pages/MyEarnings/PoolFilteringBar/ClosedPositionsToggle.tsx b/src/pages/MyEarnings/PoolFilteringBar/ClosedPositionsToggle.tsx index e89158b485..a1ae8a74d5 100644 --- a/src/pages/MyEarnings/PoolFilteringBar/ClosedPositionsToggle.tsx +++ b/src/pages/MyEarnings/PoolFilteringBar/ClosedPositionsToggle.tsx @@ -3,6 +3,7 @@ import { useDispatch } from 'react-redux' import { Flex, Text } from 'rebass' import Toggle from 'components/Toggle' +import { VERSION } from 'constants/v2' import useTheme from 'hooks/useTheme' import { useAppSelector } from 'state/hooks' import { toggleShowClosedPositions } from 'state/myEarnings/actions' @@ -11,6 +12,7 @@ const ClosedPositionsToggle = () => { const dispatch = useDispatch() const theme = useTheme() const isActive = useAppSelector(state => state.myEarnings.shouldShowClosedPositions) + const activeTab = useAppSelector(state => state.myEarnings.activeTab) const toggle = () => { dispatch(toggleShowClosedPositions()) @@ -33,7 +35,7 @@ const ClosedPositionsToggle = () => { whiteSpace: 'nowrap', }} > - Closed Positions + {activeTab === VERSION.CLASSIC ? Removed Liquidity : Closed Positions} diff --git a/src/pages/MyEarnings/Pools/index.tsx b/src/pages/MyEarnings/Pools/index.tsx index dec87d73e2..99cd13d939 100644 --- a/src/pages/MyEarnings/Pools/index.tsx +++ b/src/pages/MyEarnings/Pools/index.tsx @@ -6,6 +6,8 @@ import ElasticPools from 'pages/MyEarnings/ElasticPools' import PoolFilteringBar from 'pages/MyEarnings/PoolFilteringBar' import { useAppSelector } from 'state/hooks' +import ClassicPools from '../ClassicPools' + const Pools = () => { const activeTab = useAppSelector(state => state.myEarnings.activeTab) @@ -14,7 +16,7 @@ const Pools = () => { return } - return null + return } return ( diff --git a/src/pages/MyEarnings/ShareModal.tsx b/src/pages/MyEarnings/ShareModal.tsx index 0960643634..bbd2b02b1c 100644 --- a/src/pages/MyEarnings/ShareModal.tsx +++ b/src/pages/MyEarnings/ShareModal.tsx @@ -138,7 +138,8 @@ type Props = { currency1: Currency currency0Symbol: string currency1Symbol: string - feePercent: string + feePercent?: string + amp?: string } } @@ -248,7 +249,11 @@ export default function ShareModal({ isOpen, setIsOpen, title, value, poolInfo } {poolInfo.currency0Symbol} - {poolInfo.currency1Symbol} - Fee {poolInfo.feePercent} + {poolInfo.feePercent ? ( + Fee {poolInfo.feePercent} + ) : ( + AMP {poolInfo.amp} + )} ) } diff --git a/src/pages/MyEarnings/TotalEarningsAndChainSelect.tsx b/src/pages/MyEarnings/TotalEarningsAndChainSelect.tsx index 7e09b95510..5aeb85ed0b 100644 --- a/src/pages/MyEarnings/TotalEarningsAndChainSelect.tsx +++ b/src/pages/MyEarnings/TotalEarningsAndChainSelect.tsx @@ -3,14 +3,21 @@ import { rgba } from 'polished' import { useEffect, useRef } from 'react' import { useDispatch } from 'react-redux' import { Button, Flex } from 'rebass' -import earningApi, { useLazyGetElasticEarningQuery, useLazyGetElasticLegacyEarningQuery } from 'services/earning' -import styled from 'styled-components' +import earningApi, { + useLazyGetClassicEarningQuery, + useLazyGetElasticEarningQuery, + useLazyGetElasticLegacyEarningQuery, +} from 'services/earning' +import styled, { keyframes } from 'styled-components' import { ReactComponent as RefreshIcon } from 'assets/svg/refresh.svg' import { formatUSDValue } from 'components/EarningAreaChart/utils' import { MouseoverTooltip } from 'components/Tooltip' import { EMPTY_FUNCTION } from 'constants/index' -import { COMING_SOON_NETWORKS_FOR_MY_EARNINGS_LEGACY } from 'constants/networks' +import { + COMING_SOON_NETWORKS_FOR_MY_EARNINGS_CLASSIC, + COMING_SOON_NETWORKS_FOR_MY_EARNINGS_LEGACY, +} from 'constants/networks' import { useActiveWeb3React } from 'hooks' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import useTheme from 'hooks/useTheme' @@ -40,6 +47,20 @@ const Value = styled.span` overflow: hidden; text-overflow: ellipsis; ` +const rotate = keyframes` + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +` +const StyledRefreshIcon = styled(RefreshIcon)` + width: 1rem; + height: 1rem; + animation: ${rotate} 1.5s linear infinite; +` const RefreshButton = () => { const theme = useTheme() @@ -51,9 +72,9 @@ const RefreshButton = () => { const selectedChainIds = useAppSelector(state => state.myEarnings.selectedChains) const [elasticTrigger, elasticData] = useLazyGetElasticEarningQuery() const [elasticLegacyTrigger, elasticLegacyData] = useLazyGetElasticLegacyEarningQuery() - // const [classicTrigger, classicData] = useLazyGetClassicEarningQuery() + const [classicTrigger, classicData] = useLazyGetClassicEarningQuery() - const isFetching = elasticData.isFetching || elasticLegacyData.isFetching + const isFetching = elasticData.isFetching || elasticLegacyData.isFetching || classicData.isFetching refetchRef.current = () => { if (isFetching || !account) { @@ -66,7 +87,10 @@ const RefreshButton = () => { account, chainIds: selectedChainIds.filter(item => !COMING_SOON_NETWORKS_FOR_MY_EARNINGS_LEGACY.includes(item)), }) - // classicTrigger({ account, chainIds: selectedChainIds }) + classicTrigger({ + account, + chainIds: selectedChainIds.filter(item => !COMING_SOON_NETWORKS_FOR_MY_EARNINGS_CLASSIC.includes(item)), + }) } useEffect(() => { @@ -108,7 +132,7 @@ const RefreshButton = () => { refetchRef.current() }} > - + {isFetching ? : } ) } diff --git a/src/pages/MyEarnings/hooks.ts b/src/pages/MyEarnings/hooks.ts index 134d485cdc..88a0e8007a 100644 --- a/src/pages/MyEarnings/hooks.ts +++ b/src/pages/MyEarnings/hooks.ts @@ -34,6 +34,8 @@ import { unwrappedToken } from 'utils/wrappedCurrency' export type ClassicPoolData = { id: string amp: string + apr: string + farmApr: string fee: number reserve0: string reserve1: string @@ -41,12 +43,12 @@ export type ClassicPoolData = { vReserve1: string totalSupply: string reserveUSD: string - volumeUSD: string + volumeUsd: string + volumeUsdOneDayAgo: string + volumeUsdTwoDaysAgo: string feeUSD: string - oneDayVolumeUSD: string - oneDayVolumeUntracked: string - oneDayFeeUSD: string - oneDayFeeUntracked: string + feesUsdOneDayAgo: string + feesUsdTwoDaysAgo: string token0: { id: string symbol: string diff --git a/src/pages/MyEarnings/styled.tsx b/src/pages/MyEarnings/styled.tsx new file mode 100644 index 0000000000..2aba73d535 --- /dev/null +++ b/src/pages/MyEarnings/styled.tsx @@ -0,0 +1,80 @@ +import { rgba } from 'polished' +import { Flex } from 'rebass' +import styled, { css } from 'styled-components' + +import { ReactComponent as DropdownSVG } from 'assets/svg/down.svg' + +export const DownIcon = styled(DropdownSVG)<{ isOpen: boolean }>` + transform: rotate(${({ isOpen }) => (!isOpen ? '-90deg' : '0')}); + transition: transform 0.3s; +` + +export const Wrapper = styled.div` + border-bottom: 1px solid ${({ theme }) => theme.border}; + + :last-child { + border-bottom: none; + } +` + +export const MobileStatWrapper = styled(Flex)` + flex-direction: column; + gap: 1rem; +` + +export const MobileStat = styled.div<{ mobileView: boolean }>` + display: flex; + justify-content: space-between; + + ${({ mobileView }) => + mobileView && + css` + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.75rem; + + > *:nth-child(2n) { + align-items: flex-end; + } + `} +` + +export const Row = styled.div` + align-items: center; + padding: 12px; + font-size: 14px; + font-weight: 500; + display: grid; + grid-template-columns: 3fr repeat(7, 1fr); + + :hover { + cursor: pointer; + background: ${({ theme }) => rgba(theme.primary, 0.15)}; + } +` + +export const Badge = styled.div<{ $color?: string }>` + display: flex; + align-items: center; + gap: 4px; + + padding: 2px 8px; + font-weight: 500; + font-size: 12px; + line-height: 16px; + border-radius: 16px; + + user-select: none; + + color: ${({ $color, theme }) => $color || theme.subText}; + background: ${({ $color, theme }) => rgba($color || theme.subText, 0.3)}; + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + height: 16px; + padding: 0 4px; + `} +` + +export const ClassicRow = styled(Row)` + grid-template-columns: 3fr 2fr repeat(6, 1.3fr); +` diff --git a/src/services/earning/types.ts b/src/services/earning/types.ts index 1d3f25eec4..3813e85228 100644 --- a/src/services/earning/types.ts +++ b/src/services/earning/types.ts @@ -1,5 +1,7 @@ import { ChainId } from '@kyberswap/ks-sdk-core' +import { ClassicPoolData } from 'pages/MyEarnings/hooks' + export type TokenEarning = { token: string amount: string @@ -88,9 +90,9 @@ export type ElasticPositionEarningWithDetails = { export type ClassicPositionEarningWithDetails = { id: string ownerOriginal: string - pool: { - id: string - } + liquidityTokenBalance: string + liquidityTokenBalanceIncludingStake: string + pool: ClassicPoolData } & HistoricalEarning export type ClassicPoolEarningWithDetails = ClassicPositionEarningWithDetails From 05d74c42d787e8d543dcfa7440f4bd874d67a1db Mon Sep 17 00:00:00 2001 From: Nguyen Van Viet Date: Fri, 11 Aug 2023 10:00:14 +0700 Subject: [PATCH 09/10] refactor: using rtk query for block service (#2158) --- src/data/poolRate.ts | 11 +------- src/pages/MyEarnings/hooks.ts | 4 +-- src/services/blockService.ts | 26 +++++++++++++++++++ src/state/application/hooks.ts | 10 +++---- src/state/farms/classic/updater.ts | 1 - src/state/index.ts | 2 ++ src/state/mint/proamm/hooks.tsx | 5 +--- src/state/pools/hooks.ts | 18 ++----------- src/state/prommPools/hooks.ts | 10 +------ .../useGetElasticPoolsV1.ts | 10 +------ src/utils/index.ts | 23 ++++++---------- 11 files changed, 47 insertions(+), 73 deletions(-) create mode 100644 src/services/blockService.ts diff --git a/src/data/poolRate.ts b/src/data/poolRate.ts index 796eee0ffd..afbf65335c 100644 --- a/src/data/poolRate.ts +++ b/src/data/poolRate.ts @@ -20,7 +20,6 @@ export const getHourlyRateData = async ( networkInfo: EVMNetworkInfo, elasticClient: ApolloClient, blockClient: ApolloClient, - signal: AbortSignal, ): Promise<[PoolRatesEntry[], PoolRatesEntry[]] | undefined> => { try { const utcEndTime = dayjs.utc() @@ -39,14 +38,7 @@ export const getHourlyRateData = async ( } // once you have all the timestamps, get the blocks for each timestamp in a bulk query - let blocks = await getBlocksFromTimestamps( - isEnableBlockService, - blockClient, - timestamps, - networkInfo.chainId, - signal, - ) - if (signal.aborted) return + let blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, timestamps, networkInfo.chainId) // catch failing case if (!blocks || blocks?.length === 0) { return @@ -60,7 +52,6 @@ export const getHourlyRateData = async ( [poolAddress], 100, ) - if (signal.aborted) return // format token ETH price results const values: { diff --git a/src/pages/MyEarnings/hooks.ts b/src/pages/MyEarnings/hooks.ts index 88a0e8007a..33656dbbbf 100644 --- a/src/pages/MyEarnings/hooks.ts +++ b/src/pages/MyEarnings/hooks.ts @@ -108,11 +108,10 @@ async function getBulkPoolDataWithPagination( blockClient: ApolloClient, ethPrice: string, chainId: ChainId, - signal: AbortSignal, ): Promise { try { const [t1] = getTimestampsForChanges() - const blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [t1], chainId, signal) + const blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [t1], chainId) // In case we can't get the block one day ago then we set it to 0 which is fine // because our subgraph never syncs from block 0 => response is empty @@ -227,7 +226,6 @@ export function useAllPoolsData(chainId: ChainId): { blockClient, String(ethPrice), chainId, - controller.signal, ), ) } diff --git a/src/services/blockService.ts b/src/services/blockService.ts new file mode 100644 index 0000000000..88fe437b0c --- /dev/null +++ b/src/services/blockService.ts @@ -0,0 +1,26 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +import { BLOCK_SERVICE_API } from 'constants/env' +import { NETWORKS_INFO } from 'constants/networks' + +const blockServiceApi = createApi({ + reducerPath: 'blockServiceApi', + baseQuery: fetchBaseQuery({ + baseUrl: BLOCK_SERVICE_API, + }), + endpoints: builder => ({ + getBlocks: builder.query< + { data: Array<{ number: number; timestamp: number }> }, + { chainId: ChainId; timestamps: number[] } + >({ + query: ({ chainId, timestamps }) => ({ + url: `/${NETWORKS_INFO[chainId].aggregatorRoute}/api/v1/block?timestamps=${timestamps.join(',')}`, + }), + }), + }), +}) + +export const { useLazyGetBlocksQuery } = blockServiceApi + +export default blockServiceApi diff --git a/src/state/application/hooks.ts b/src/state/application/hooks.ts index ab39abe3cd..4a525bb2af 100644 --- a/src/state/application/hooks.ts +++ b/src/state/application/hooks.ts @@ -241,7 +241,6 @@ export const getEthPrice = async ( chainId: ChainId, apolloClient: ApolloClient, blockClient: ApolloClient, - signal: AbortSignal, ) => { const utcCurrentTime = dayjs() const utcOneDayBack = utcCurrentTime.subtract(1, 'day').startOf('minute').unix() @@ -252,7 +251,7 @@ export const getEthPrice = async ( try { const oneDayBlock = ( - await getBlocksFromTimestamps(isEnableBlockService, blockClient, [utcOneDayBack], chainId, signal) + await getBlocksFromTimestamps(isEnableBlockService, blockClient, [utcOneDayBack], chainId) )?.[0]?.number const result = await apolloClient.query({ query: ETH_PRICE(), @@ -281,7 +280,6 @@ const getPrommEthPrice = async ( chainId: ChainId, apolloClient: ApolloClient, blockClient: ApolloClient, - signal: AbortSignal, ) => { const utcCurrentTime = dayjs() const utcOneDayBack = utcCurrentTime.subtract(1, 'day').startOf('minute').unix() @@ -292,7 +290,7 @@ const getPrommEthPrice = async ( try { const oneDayBlock = ( - await getBlocksFromTimestamps(isEnableBlockService, blockClient, [utcOneDayBack], chainId, signal) + await getBlocksFromTimestamps(isEnableBlockService, blockClient, [utcOneDayBack], chainId) )?.[0]?.number const result = await apolloClient.query({ query: PROMM_ETH_PRICE(), @@ -332,8 +330,8 @@ export function useETHPrice(version: string = VERSION.CLASSIC): AppState['applic async function checkForEthPrice() { try { const [newPrice, oneDayBackPrice, pricePercentChange] = await (version === VERSION.ELASTIC - ? getPrommEthPrice(isEnableBlockService, chainId, elasticClient, blockClient, controller.signal) - : getEthPrice(isEnableBlockService, chainId, classicClient, blockClient, controller.signal)) + ? getPrommEthPrice(isEnableBlockService, chainId, elasticClient, blockClient) + : getEthPrice(isEnableBlockService, chainId, classicClient, blockClient)) dispatch( version === VERSION.ELASTIC diff --git a/src/state/farms/classic/updater.ts b/src/state/farms/classic/updater.ts index 6dbbab3cde..9c64385b45 100644 --- a/src/state/farms/classic/updater.ts +++ b/src/state/farms/classic/updater.ts @@ -124,7 +124,6 @@ export default function Updater({ isInterval = true }: { isInterval?: boolean }) blockClient, chainId, ethPriceRef.current, - abortController.signal, ) if (abortController.signal.aborted) throw new AbortedError() diff --git a/src/state/index.ts b/src/state/index.ts index c4ad291a28..d3aab11231 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -1,5 +1,6 @@ import { configureStore } from '@reduxjs/toolkit' import { load, save } from 'redux-localstorage-simple' +import blockServiceApi from 'services/blockService' import coingeckoApi from 'services/coingecko' import kyberAISubscriptionApi from 'services/kyberAISubscription' import priceAlertApi from 'services/priceAlert' @@ -114,6 +115,7 @@ const store = configureStore({ [earningApi.reducerPath]: earningApi.reducer, [tokenApi.reducerPath]: tokenApi.reducer, [socialApi.reducerPath]: socialApi.reducer, + [blockServiceApi.reducerPath]: blockServiceApi.reducer, }, middleware: getDefaultMiddleware => getDefaultMiddleware({ thunk: true, immutableCheck: false, serializableCheck: false }) diff --git a/src/state/mint/proamm/hooks.tsx b/src/state/mint/proamm/hooks.tsx index 4b2855d573..ba592f1629 100644 --- a/src/state/mint/proamm/hooks.tsx +++ b/src/state/mint/proamm/hooks.tsx @@ -1414,7 +1414,6 @@ export function useHourlyRateData( const { elasticClient, blockClient, isEnableBlockService } = useKyberSwapConfig() useEffect(() => { - const controller = new AbortController() const currentTime = dayjs.utc() let startTime: number @@ -1459,13 +1458,11 @@ export function useHourlyRateData( NETWORKS_INFO[chainId], elasticClient, blockClient, - controller.signal, ) - !controller.signal.aborted && ratesData && setRatesData(ratesData) + ratesData && setRatesData(ratesData) } } fetch() - return () => controller.abort() }, [timeWindow, poolAddress, dispatch, chainId, elasticClient, blockClient, isEnableBlockService]) return ratesData diff --git a/src/state/pools/hooks.ts b/src/state/pools/hooks.ts index d17f58657f..8f664b602b 100644 --- a/src/state/pools/hooks.ts +++ b/src/state/pools/hooks.ts @@ -138,7 +138,6 @@ export async function getBulkPoolDataFromPoolList( blockClient: ApolloClient, chainId: ChainId, ethPrice: string | undefined, - signal: AbortSignal, ): Promise { try { const current = await apolloClient.query({ @@ -147,7 +146,7 @@ export async function getBulkPoolDataFromPoolList( }) let poolData const [t1] = getTimestampsForChanges() - const blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [t1], chainId, signal) + const blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [t1], chainId) if (!blocks.length) { return current.data.pools } else { @@ -206,11 +205,10 @@ export async function getBulkPoolDataWithPagination( blockClient: ApolloClient, ethPrice: string, chainId: ChainId, - signal: AbortSignal, ): Promise { try { const [t1] = getTimestampsForChanges() - const blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [t1], chainId, signal) + const blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [t1], chainId) // In case we can't get the block one day ago then we set it to 0 which is fine // because our subgraph never syncs from block 0 => response is empty @@ -324,7 +322,6 @@ export function useAllPoolsData(): { const poolCountSubgraph = usePoolCountInSubgraph() useEffect(() => { if (!isEVM) return - const controller = new AbortController() const getPoolsData = async () => { try { @@ -342,25 +339,20 @@ export function useAllPoolsData(): { blockClient, ethPrice, chainId, - controller.signal, ), ) } const pools = (await Promise.all(promises.map(callback => callback()))).flat() - if (controller.signal.aborted) return dispatch(updatePools({ pools })) dispatch(setLoading(false)) } } catch (error) { - if (controller.signal.aborted) return dispatch(setError(error as Error)) dispatch(setLoading(false)) } } getPoolsData() - - return () => controller.abort() }, [ chainId, dispatch, @@ -399,7 +391,6 @@ export function useSinglePoolData( useEffect(() => { if (!isEVM) return - const controller = new AbortController() async function checkForPools() { setLoading(true) @@ -413,15 +404,12 @@ export function useSinglePoolData( blockClient, chainId, ethPrice, - controller.signal, ) - if (controller.signal.aborted) return if (pools.length > 0) { setPoolData(pools[0]) } } } catch (error) { - if (controller.signal.aborted) return setError(error as Error) } @@ -429,8 +417,6 @@ export function useSinglePoolData( } checkForPools() - - return () => controller.abort() }, [ethPrice, error, poolAddress, chainId, isEVM, networkInfo, classicClient, blockClient, isEnableBlockService]) return { loading, error, data: poolData } diff --git a/src/state/prommPools/hooks.ts b/src/state/prommPools/hooks.ts index a2ab3a8943..6d03596b2e 100644 --- a/src/state/prommPools/hooks.ts +++ b/src/state/prommPools/hooks.ts @@ -209,20 +209,12 @@ export const usePoolBlocks = () => { const [block, setBlock] = useState(undefined) useEffect(() => { - const controller = new AbortController() const getBlocks = async () => { - const [block] = await getBlocksFromTimestamps( - isEnableBlockService, - blockClient, - [last24h], - chainId, - controller.signal, - ) + const [block] = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [last24h], chainId) setBlock(block?.number) } getBlocks() - return () => controller.abort() }, [chainId, last24h, blockClient, isEnableBlockService]) return { blockLast24h: block } diff --git a/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV1.ts b/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV1.ts index a4941e9fc5..790ca9f6bc 100644 --- a/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV1.ts +++ b/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV1.ts @@ -27,20 +27,12 @@ const usePoolBlocks = () => { const [blocks, setBlocks] = useState<{ number: number }[]>([]) useEffect(() => { - const controller = new AbortController() const getBlocks = async () => { - const blocks = await getBlocksFromTimestamps( - isEnableBlockService, - blockClient, - [last24h], - chainId, - controller.signal, - ) + const blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [last24h], chainId) setBlocks(blocks) } getBlocks() - return () => controller.abort() }, [chainId, last24h, blockClient, isEnableBlockService]) const [blockLast24h] = blocks ?? [] diff --git a/src/utils/index.ts b/src/utils/index.ts index c2c02c48b7..478da2b351 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -5,9 +5,10 @@ import { PublicKey } from '@solana/web3.js' import dayjs from 'dayjs' import JSBI from 'jsbi' import Numeral from 'numeral' +import blockServiceApi from 'services/blockService' import { GET_BLOCKS } from 'apollo/queries' -import { BLOCK_SERVICE_API, ENV_KEY } from 'constants/env' +import { ENV_KEY } from 'constants/env' import { DEFAULT_GAS_LIMIT_MARGIN, ZERO_ADDRESS } from 'constants/index' import { NETWORKS_INFO, SUPPORTED_NETWORKS, isEVM } from 'constants/networks' import { KNC, KNCL_ADDRESS } from 'constants/tokens' @@ -294,7 +295,7 @@ export async function splitQuery( * @dev timestamps are returns as they were provided; not the block time. * @param {Array} timestamps */ -export async function getBlocksFromTimestampsSubgraph( +async function getBlocksFromTimestampsSubgraph( blockClient: ApolloClient, timestamps: number[], chainId: ChainId, @@ -320,31 +321,24 @@ export async function getBlocksFromTimestampsSubgraph( return blocks } -export async function getBlocksFromTimestampsBlockService( +async function getBlocksFromTimestampsBlockService( timestamps: number[], chainId: ChainId, - signal: AbortSignal, ): Promise<{ timestamp: number; number: number }[]> { if (!isEVM(chainId)) return [] if (timestamps?.length === 0) { return [] } + const allChunkResult = ( await Promise.all( chunk(timestamps, 50).map( async timestampsChunk => - ( - await fetch( - `${BLOCK_SERVICE_API}/${ - NETWORKS_INFO[chainId].aggregatorRoute - }/api/v1/block?timestamps=${timestampsChunk.join(',')}`, - { signal }, - ) - ).json() as Promise<{ data: { timestamp: number; number: number }[] }>, + await store.dispatch(blockServiceApi.endpoints.getBlocks.initiate({ chainId, timestamps: timestampsChunk })), ), ) ) - .map(chunk => chunk.data) + .map(chunk => chunk.data?.data || []) .flat() .sort((a, b) => a.number - b.number) @@ -356,9 +350,8 @@ export async function getBlocksFromTimestamps( blockClient: ApolloClient, timestamps: number[], chainId: ChainId, - signal: AbortSignal, ): Promise<{ timestamp: number; number: number }[]> { - if (isEnableBlockService) return getBlocksFromTimestampsBlockService(timestamps, chainId, signal) + if (isEnableBlockService) return getBlocksFromTimestampsBlockService(timestamps, chainId) return getBlocksFromTimestampsSubgraph(blockClient, timestamps, chainId) } From 77f8cc1e1737f19786a4b0650688c9fad9e41ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Ho=C3=A0i=20Danh?= <33005392+nguyenhoaidanh@users.noreply.github.com> Date: Fri, 11 Aug 2023 14:21:03 +0700 Subject: [PATCH 10/10] LO: remove warning gas fee lo ethereum (#2159) --- .../swapv2/LimitOrder/DeltaRate.tsx | 6 ++-- .../swapv2/LimitOrder/LimitOrderForm.tsx | 22 +++---------- .../LimitOrder/Modals/CancelOrderModal.tsx | 4 +-- .../LimitOrder/Modals/ConfirmOrderModal.tsx | 4 +-- .../swapv2/LimitOrder/Modals/styled.tsx | 4 +-- src/components/swapv2/LimitOrder/const.ts | 2 -- src/hooks/useBaseTradeInfo.ts | 33 ++----------------- 7 files changed, 16 insertions(+), 59 deletions(-) diff --git a/src/components/swapv2/LimitOrder/DeltaRate.tsx b/src/components/swapv2/LimitOrder/DeltaRate.tsx index b5e408dc9e..c5cd0479f9 100644 --- a/src/components/swapv2/LimitOrder/DeltaRate.tsx +++ b/src/components/swapv2/LimitOrder/DeltaRate.tsx @@ -4,7 +4,7 @@ import { Text } from 'rebass' import InfoHelper from 'components/InfoHelper' import { Label } from 'components/swapv2/LimitOrder/LimitOrderForm' -import { BaseTradeInfoLO } from 'hooks/useBaseTradeInfo' +import { BaseTradeInfo } from 'hooks/useBaseTradeInfo' import useTheme from 'hooks/useTheme' import { RateInfo } from './type' @@ -13,7 +13,7 @@ export function useGetDeltaRateLimitOrder({ marketPrice, rateInfo, }: { - marketPrice: BaseTradeInfoLO | undefined + marketPrice: BaseTradeInfo | undefined rateInfo: RateInfo }) { const { deltaText, percent } = useMemo(() => { @@ -48,7 +48,7 @@ const DeltaRate = ({ rateInfo, symbolIn, }: { - marketPrice: BaseTradeInfoLO | undefined + marketPrice: BaseTradeInfo | undefined rateInfo: RateInfo symbolIn: string }) => { diff --git a/src/components/swapv2/LimitOrder/LimitOrderForm.tsx b/src/components/swapv2/LimitOrder/LimitOrderForm.tsx index 7e08da9da1..e8d7989645 100644 --- a/src/components/swapv2/LimitOrder/LimitOrderForm.tsx +++ b/src/components/swapv2/LimitOrder/LimitOrderForm.tsx @@ -1,4 +1,4 @@ -import { ChainId, Currency, CurrencyAmount, Token, TokenAmount, WETH } from '@kyberswap/ks-sdk-core' +import { Currency, CurrencyAmount, Token, TokenAmount, WETH } from '@kyberswap/ks-sdk-core' import { Trans, t } from '@lingui/macro' import dayjs from 'dayjs' import { ethers } from 'ethers' @@ -40,7 +40,7 @@ import { useLimitActionHandlers, useLimitState } from 'state/limit/hooks' import { tryParseAmount } from 'state/swap/hooks' import { useCurrencyBalance } from 'state/wallet/hooks' import { TransactionFlowState } from 'types/TransactionFlowState' -import { formattedNum, getLimitOrderContract } from 'utils' +import { getLimitOrderContract } from 'utils' import { subscribeNotificationOrderCancelled, subscribeNotificationOrderExpired } from 'utils/firebase' import { maxAmountSpend } from 'utils/maxAmountSpend' @@ -646,21 +646,10 @@ const LimitOrderForm = function LimitOrderForm({ ) } - const isMainNet = chainId === ChainId.MAINNET const threshold = USD_THRESHOLD[chainId] const showWarningThresHold = outputAmount && estimateUSD.rawInput && estimateUSD.rawInput < threshold - if (isMainNet && showWarningThresHold && tradeInfo?.gasFee) { - messages.push( - - - Your order may only be filled when market price of {currencyIn?.symbol} to {currencyOut?.symbol} is <{' '} - {formattedNum(String(tradeInfo?.marketRate), true)}, as estimated gas fee to fill - your order is ~${removeTrailingZero(tradeInfo?.gasFee?.toPrecision(6) ?? '0')}. - - , - ) - } - if (!isMainNet && showWarningThresHold) { + + if (showWarningThresHold) { messages.push( @@ -675,15 +664,12 @@ const LimitOrderForm = function LimitOrderForm({ }, [ chainId, currencyIn, - currencyOut?.symbol, deltaRate.percent, deltaRate.profit, deltaRate.rawPercent, displayRate, estimateUSD.rawInput, outputAmount, - tradeInfo?.gasFee, - tradeInfo?.marketRate, ]) return ( diff --git a/src/components/swapv2/LimitOrder/Modals/CancelOrderModal.tsx b/src/components/swapv2/LimitOrder/Modals/CancelOrderModal.tsx index af2ab83c7e..b84a5dc5cd 100644 --- a/src/components/swapv2/LimitOrder/Modals/CancelOrderModal.tsx +++ b/src/components/swapv2/LimitOrder/Modals/CancelOrderModal.tsx @@ -9,7 +9,7 @@ import { useCurrencyV2 } from 'hooks/Tokens' import useTheme from 'hooks/useTheme' import { TransactionFlowState } from 'types/TransactionFlowState' -import { BaseTradeInfoLO, useBaseTradeInfoLimitOrder } from '../../../../hooks/useBaseTradeInfo' +import { BaseTradeInfo, useBaseTradeInfoLimitOrder } from '../../../../hooks/useBaseTradeInfo' import { calcPercentFilledOrder, formatAmountOrder } from '../helpers' import { LimitOrder, LimitOrderStatus } from '../type' import { Container, Header, Label, ListInfo, MarketInfo, Note, Rate, Value } from './styled' @@ -24,7 +24,7 @@ function ContentCancel({ }: { isCancelAll: boolean order: LimitOrder | undefined - marketPrice: BaseTradeInfoLO | undefined + marketPrice: BaseTradeInfo | undefined onSubmit: () => void onDismiss: () => void }) { diff --git a/src/components/swapv2/LimitOrder/Modals/ConfirmOrderModal.tsx b/src/components/swapv2/LimitOrder/Modals/ConfirmOrderModal.tsx index 356bf8f694..a98104fc97 100644 --- a/src/components/swapv2/LimitOrder/Modals/ConfirmOrderModal.tsx +++ b/src/components/swapv2/LimitOrder/Modals/ConfirmOrderModal.tsx @@ -10,7 +10,7 @@ import CurrencyLogo from 'components/CurrencyLogo' import TransactionConfirmationModal, { TransactionErrorContent } from 'components/TransactionConfirmationModal' import { WORSE_PRICE_DIFF_THRESHOLD } from 'components/swapv2/LimitOrder/const' import { useActiveWeb3React } from 'hooks' -import { BaseTradeInfoLO } from 'hooks/useBaseTradeInfo' +import { BaseTradeInfo } from 'hooks/useBaseTradeInfo' import ErrorWarningPanel from 'pages/Bridge/ErrorWarning' import { TransactionFlowState } from 'types/TransactionFlowState' @@ -43,7 +43,7 @@ export default memo(function ConfirmOrderModal({ inputAmount: string outputAmount: string expireAt: number - marketPrice: BaseTradeInfoLO | undefined + marketPrice: BaseTradeInfo | undefined rateInfo: RateInfo note?: string warningMessage: ReactNode[] diff --git a/src/components/swapv2/LimitOrder/Modals/styled.tsx b/src/components/swapv2/LimitOrder/Modals/styled.tsx index eb6dcb8be0..9f41f93851 100644 --- a/src/components/swapv2/LimitOrder/Modals/styled.tsx +++ b/src/components/swapv2/LimitOrder/Modals/styled.tsx @@ -7,7 +7,7 @@ import styled from 'styled-components' import { Swap as SwapIcon } from 'components/Icons' import TradePrice from 'components/swapv2/LimitOrder/TradePrice' -import { BaseTradeInfoLO } from 'hooks/useBaseTradeInfo' +import { BaseTradeInfo } from 'hooks/useBaseTradeInfo' import useTheme from 'hooks/useTheme' import { formatAmountOrder, formatRateLimitOrder } from '../helpers' @@ -87,7 +87,7 @@ export const MarketInfo = ({ symbolIn, symbolOut, }: { - marketPrice: BaseTradeInfoLO | undefined + marketPrice: BaseTradeInfo | undefined symbolIn: string | undefined symbolOut: string | undefined }) => { diff --git a/src/components/swapv2/LimitOrder/const.ts b/src/components/swapv2/LimitOrder/const.ts index d91e2ed33d..dc6cf69fcd 100644 --- a/src/components/swapv2/LimitOrder/const.ts +++ b/src/components/swapv2/LimitOrder/const.ts @@ -53,8 +53,6 @@ export const CLOSE_ORDER_OPTIONS = [ }, ] -export const GAS_AMOUNT_ETHEREUM = 1_200_000 - const _USD_THRESHOLD: { [chainId: number]: number } = { [ChainId.MAINNET]: 300, } diff --git a/src/hooks/useBaseTradeInfo.ts b/src/hooks/useBaseTradeInfo.ts index 4a99f7d360..6ee20e3f91 100644 --- a/src/hooks/useBaseTradeInfo.ts +++ b/src/hooks/useBaseTradeInfo.ts @@ -1,13 +1,10 @@ import { ChainId, Currency, WETH } from '@kyberswap/ks-sdk-core' -import { ethers } from 'ethers' -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useEffect, useMemo } from 'react' import { parseGetRouteResponse } from 'services/route/utils' import useGetRoute, { ArgsGetRoute, useGetRouteSolana } from 'components/SwapForm/hooks/useGetRoute' -import { GAS_AMOUNT_ETHEREUM } from 'components/swapv2/LimitOrder/const' -import { useActiveWeb3React, useWeb3React } from 'hooks' +import { useActiveWeb3React } from 'hooks' import useDebounce from 'hooks/useDebounce' -import useInterval from 'hooks/useInterval' import { useTokenPricesWithLoading } from 'state/tokenPrices/hooks' export type BaseTradeInfo = { @@ -51,34 +48,10 @@ function useBaseTradeInfo(currencyIn: Currency | undefined, currencyOut: Currenc return { loading, tradeInfo, refetch } } -export type BaseTradeInfoLO = BaseTradeInfo & { - gasFee: number -} - export function useBaseTradeInfoLimitOrder(currencyIn: Currency | undefined, currencyOut: Currency | undefined) { - const { library } = useWeb3React() - - const [gasFee, setGasFee] = useState(0) const { loading, tradeInfo } = useBaseTradeInfo(currencyIn, currencyOut) - const nativePriceUsd = tradeInfo?.nativePriceUsd - - const fetchGasFee = useCallback(() => { - if (!library || !nativePriceUsd) return - library - .getSigner() - .getGasPrice() - .then(data => { - const gasPrice = Number(ethers.utils.formatEther(data)) - if (gasPrice) setGasFee(gasPrice * nativePriceUsd * GAS_AMOUNT_ETHEREUM) - }) - .catch(e => { - console.error('fetchGasFee', e) - }) - }, [library, nativePriceUsd]) - - useInterval(fetchGasFee, nativePriceUsd ? 15_000 : 2000) const debouncedLoading = useDebounce(loading, 100) // prevent flip flop UI when loading from true to false - return { loading: loading || debouncedLoading, tradeInfo: { ...tradeInfo, gasFee } as BaseTradeInfoLO } + return { loading: loading || debouncedLoading, tradeInfo } } export const useBaseTradeInfoWithAggregator = (args: ArgsGetRoute) => {