From c3cba5b5859fde0a758a261827ab9abf44b098b3 Mon Sep 17 00:00:00 2001 From: Nguyen Van Viet Date: Wed, 20 Sep 2023 13:44:06 +0700 Subject: [PATCH 1/9] Integrate kn-protocol FarmV2 (#2221) * feat(my-earnings): handle collect fee + harvest in case position in farm v2 * integrate kn protocol farm v2 * fix: can not claim reward * update env to prod * fix: minor * fix: mixpanel * fix: data is not refresh when switch change * fix: farms is reset * enable base + linea * fix: minor issue * enable zksync * revert env --- .../YieldPools/FarmingPoolAPRCell.tsx | 2 +- src/constants/networks.ts | 18 +--- src/hooks/useKyberSwapConfig.ts | 2 +- src/hooks/useMixpanel.ts | 1 + src/pages/App.tsx | 24 +++--- .../ElasticFarmv2/components/FarmCard.tsx | 31 +------ .../SinglePosition/CollectFeesPanel.tsx | 25 ++++-- .../SinglePosition/PositionView.tsx | 62 +++++++++++--- .../SinglePosition/PriceRangeChart.tsx | 5 ++ src/pages/Pools/index.tsx | 2 + src/pages/ProAmmPools/ListItem.tsx | 13 +-- src/pages/ProAmmPools/index.tsx | 2 - src/services/earning/index.ts | 1 + src/services/earning/types.ts | 12 +++ src/services/knprotocol.ts | 24 ++++++ src/services/ksSetting.ts | 4 +- src/state/application/hooks.ts | 6 +- src/state/application/reducer.ts | 3 +- src/state/farms/elastic/updaters/index.tsx | 11 +-- src/state/farms/elasticv2/types.ts | 5 +- src/state/farms/elasticv2/updater.tsx | 83 +++++++++++++------ src/state/index.ts | 5 +- .../prommPools/useGetElasticPools/index.ts | 10 +-- .../useGetElasticPoolsV1.ts | 8 +- .../useGetElasticPoolsV2.ts | 11 +-- src/utils/tokenInfo.ts | 2 + 26 files changed, 225 insertions(+), 147 deletions(-) create mode 100644 src/services/knprotocol.ts diff --git a/src/components/YieldPools/FarmingPoolAPRCell.tsx b/src/components/YieldPools/FarmingPoolAPRCell.tsx index 83c1d376a6..c2406faf64 100644 --- a/src/components/YieldPools/FarmingPoolAPRCell.tsx +++ b/src/components/YieldPools/FarmingPoolAPRCell.tsx @@ -189,7 +189,7 @@ const FarmingPoolAPRCell: React.FC = ({ } + text={} > {formatDisplayNumber((poolAPR + maxFarmAPR) / 100, { style: 'percent', fractionDigits: 2 })} diff --git a/src/constants/networks.ts b/src/constants/networks.ts index 1d2f7068c3..51149291d8 100644 --- a/src/constants/networks.ts +++ b/src/constants/networks.ts @@ -130,21 +130,6 @@ export function isSupportedChainId(chainId?: number): chainId is ChainId { } export const FAUCET_NETWORKS = [ChainId.BTTC] -export const CHAINS_SUPPORT_NEW_POOL_FARM_API: readonly ChainId[] = [ - ChainId.MAINNET, - // ChainId.MUMBAI, - // ChainId.MATIC, - // ChainId.BSCTESTNET, - ChainId.BSCMAINNET, - // ChainId.AVAXTESTNET, - ChainId.AVAXMAINNET, - ChainId.FANTOM, - ChainId.CRONOS, - ChainId.BTTC, - ChainId.ARBITRUM, - ChainId.AURORA, - ChainId.OPTIMISM, -] // Fee options instead of dynamic fee export const STATIC_FEE_OPTIONS: { [chainId: number]: number[] | undefined } = { @@ -197,6 +182,9 @@ export const SUPPORTED_NETWORKS_FOR_MY_EARNINGS = [ ChainId.ARBITRUM, ChainId.OPTIMISM, ChainId.MATIC, + ChainId.LINEA, + ChainId.BASE, + ChainId.ZKSYNC, ChainId.BSCMAINNET, ChainId.AVAXMAINNET, ChainId.FANTOM, diff --git a/src/hooks/useKyberSwapConfig.ts b/src/hooks/useKyberSwapConfig.ts index 24501dfb8a..7aaa9dfae9 100644 --- a/src/hooks/useKyberSwapConfig.ts +++ b/src/hooks/useKyberSwapConfig.ts @@ -37,8 +37,8 @@ const parseResponse = ( return { rpc, - prochart: data?.prochart || false, isEnableBlockService: data?.isEnableBlockService || false, + isEnableKNProtocol: data?.isEnableKNProtocol || false, blockClient: isEVM(defaultChainId) ? createClient(data?.blockSubgraph || NETWORKS_INFO[defaultChainId].defaultBlockSubgraph) : createClient(ethereumInfo.defaultBlockSubgraph), diff --git a/src/hooks/useMixpanel.ts b/src/hooks/useMixpanel.ts index d7e6ac6e7e..cda967d80e 100644 --- a/src/hooks/useMixpanel.ts +++ b/src/hooks/useMixpanel.ts @@ -1739,6 +1739,7 @@ export const useGlobalMixpanelEvents = () => { 'notification-center': 'Notification', [APP_PATHS.KYBERAI_ABOUT]: 'KyberAI About', [APP_PATHS.KYBERDAO_KNC_UTILITY]: 'Gas refund - KNC Utility', + [APP_PATHS.MY_EARNINGS]: 'Earning Dashboard', } const protectedPaths: { [key: string]: string } = { [APP_PATHS.KYBERAI_RANKINGS]: 'KyberAI Rankings', diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 0d636745c7..06a7ec8e40 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -24,7 +24,7 @@ import Snowfall from 'components/Snowflake/Snowfall' import Web3ReactManager from 'components/Web3ReactManager' import { ENV_LEVEL } from 'constants/env' import { APP_PATHS, BLACKLIST_WALLETS, CHAINS_SUPPORT_CROSS_CHAIN } from 'constants/index' -import { CLASSIC_NOT_SUPPORTED, NETWORKS_INFO, SUPPORTED_NETWORKS } from 'constants/networks' +import { CLASSIC_NOT_SUPPORTED, ELASTIC_NOT_SUPPORTED, NETWORKS_INFO, SUPPORTED_NETWORKS } from 'constants/networks' import { ENV_TYPE } from 'constants/type' import { useActiveWeb3React } from 'hooks' import { useAutoLogin } from 'hooks/useLogin' @@ -191,15 +191,19 @@ const RoutesWithNetworkPrefix = () => { )} - } - /> - } - /> - } /> + {!ELASTIC_NOT_SUPPORTED[chainId] && ( + <> + } + /> + } + /> + } /> + + )} } /> diff --git a/src/pages/Farm/ElasticFarmv2/components/FarmCard.tsx b/src/pages/Farm/ElasticFarmv2/components/FarmCard.tsx index 0e6452da34..c9dd8006fa 100644 --- a/src/pages/Farm/ElasticFarmv2/components/FarmCard.tsx +++ b/src/pages/Farm/ElasticFarmv2/components/FarmCard.tsx @@ -262,34 +262,9 @@ function FarmCard({ - - - - {farm.tvlToken0.toSignificant(6)} {farm.token0.symbol} - - - - - {farm.tvlToken1.toSignificant(6)} {farm.token1.symbol} - - - ) : ( - '' - ) - } - > - - {farm.tvl ? formatDollarAmount(farm.tvl) : '--'} - - - {!!farm.tvl && } - - + + {farm.tvl ? formatDollarAmount(farm.tvl) : '--'} + {isEnded || farm.isSettled ? ( isEnded ? ( diff --git a/src/pages/MyEarnings/ElasticPools/SinglePosition/CollectFeesPanel.tsx b/src/pages/MyEarnings/ElasticPools/SinglePosition/CollectFeesPanel.tsx index f5d1d35766..417ba4dc86 100644 --- a/src/pages/MyEarnings/ElasticPools/SinglePosition/CollectFeesPanel.tsx +++ b/src/pages/MyEarnings/ElasticPools/SinglePosition/CollectFeesPanel.tsx @@ -13,6 +13,7 @@ import { ButtonOutlined } from 'components/Button' import CurrencyLogo from 'components/CurrencyLogo' import { MouseoverTooltip } from 'components/Tooltip' import PROMM_FARM_ABI from 'constants/abis/v2/farm.json' +import FarmV2ABI from 'constants/abis/v2/farmv2.json' import { useActiveWeb3React, useWeb3React } from 'hooks' import { useProAmmNFTPositionManagerContract } from 'hooks/useContract' import { config } from 'hooks/useElasticLegacy' @@ -31,6 +32,7 @@ import { formatDollarAmount } from 'utils/numbers' type Props = { nftId: string + fId: string feeValue0: CurrencyAmount feeValue1: CurrencyAmount feeUsd: number @@ -43,9 +45,11 @@ type Props = { } const FarmInterface = new Interface(PROMM_FARM_ABI) +const FarmV2Interface = new Interface(FarmV2ABI) const CollectFeesPanel: React.FC = ({ nftId, + fId, chainId, feeUsd, feeValue0, @@ -140,14 +144,19 @@ const CollectFeesPanel: React.FC = ({ const amount0Min = feeValue0.subtract(feeValue0.multiply(basisPointsToPercent(allowedSlippage))) const amount1Min = feeValue1.subtract(feeValue1.multiply(basisPointsToPercent(allowedSlippage))) try { - const encoded = FarmInterface.encodeFunctionData('claimFee', [ - [nftId], - amount0Min.quotient.toString(), - amount1Min.quotient.toString(), - poolAddress, - true, - deadline?.toString(), - ]) + const encoded = (fId ? FarmV2Interface : FarmInterface).encodeFunctionData( + 'claimFee', + fId + ? [fId, [nftId], amount0Min.quotient.toString(), amount1Min.quotient.toString(), deadline?.toString(), true] + : [ + [nftId], + amount0Min.quotient.toString(), + amount1Min.quotient.toString(), + poolAddress, + true, + deadline?.toString(), + ], + ) sendTransaction({ to: farmAddress, diff --git a/src/pages/MyEarnings/ElasticPools/SinglePosition/PositionView.tsx b/src/pages/MyEarnings/ElasticPools/SinglePosition/PositionView.tsx index b51b52c3e7..77daf9abe8 100644 --- a/src/pages/MyEarnings/ElasticPools/SinglePosition/PositionView.tsx +++ b/src/pages/MyEarnings/ElasticPools/SinglePosition/PositionView.tsx @@ -15,6 +15,7 @@ import Divider from 'components/Divider' import FormattedCurrencyAmount from 'components/FormattedCurrencyAmount' import { MouseoverTooltip } from 'components/Tooltip' import PROMM_FARM_ABI from 'constants/abis/v2/farm.json' +import FarmV2ABI from 'constants/abis/v2/farmv2.json' import { VERSION } from 'constants/v2' import { useActiveWeb3React, useWeb3React } from 'hooks' import { useAllTokens } from 'hooks/Tokens' @@ -39,6 +40,7 @@ import { formatDisplayNumber } from 'utils/numbers' import ActionButtons from './ActionButtons' const FarmInterface = new Interface(PROMM_FARM_ABI) +const FarmV2Interface = new Interface(FarmV2ABI) const PositionView: React.FC = props => { const { positionEarning, position, pendingFee, tokenPrices: prices, chainId, currency0, currency1 } = props @@ -65,9 +67,14 @@ const PositionView: React.FC = props => { parseFloat(position.amount0.toExact() || '0') * prices[currency0.wrapped.address || ''] + parseFloat(position.amount1.toExact() || '0') * prices[currency1.wrapped.address || ''] + const stakedLiqFarmv2 = positionEarning.farmV2DepositedPositions?.[0]?.liquidity || '0' + const weight = positionEarning.farmV2DepositedPositions?.[0]?.range?.weight || '1' + const stakedPosition = new Position({ pool: position.pool, - liquidity: positionEarning.joinedPositions?.[0]?.liquidity || '0', + liquidity: + positionEarning.joinedPositions?.[0]?.liquidity || + BigNumber.from(stakedLiqFarmv2).div(BigNumber.from(weight)).toString(), tickLower: position.tickLower, tickUpper: position.tickUpper, }) @@ -114,35 +121,61 @@ const PositionView: React.FC = props => { const theme = useTheme() const tokens = useAllTokens(true, chainId) - const farmRewards = positionEarning.joinedPositions?.[0]?.farmingPool?.rewardTokensIds?.map((rwId, index) => { - const token = tokens[rwId] || new Token(chainId, rwId, 18, '', '') + let farmRewards = + positionEarning.joinedPositions?.[0]?.farmingPool?.rewardTokensIds?.map((rwId, index) => { + const token = tokens[rwId] || new Token(chainId, rwId, 18, '', '') - return CurrencyAmount.fromRawAmount(token, positionEarning.joinedPositions?.[0]?.pendingRewards?.[index] || '0') - }) + return CurrencyAmount.fromRawAmount(token, positionEarning.joinedPositions?.[0]?.pendingRewards?.[index] || '0') + }) || [] + + const farmV2Rewards = + positionEarning.farmV2DepositedPositions?.[0].pendingRewards.map((amount, index) => { + const tokenId = positionEarning.farmV2DepositedPositions?.[0].farmV2.rewards[index].tokenID || '' + const token = tokens[tokenId] || new Token(chainId, tokenId, 18, '', '') + + return CurrencyAmount.fromRawAmount(token, amount) + }) || [] + + farmRewards = farmRewards.concat(farmV2Rewards) + + const disabledHarvest = + !positionEarning.joinedPositions?.[0]?.pendingRewards?.some(item => item !== '0') && + !positionEarning.farmV2DepositedPositions?.[0].pendingRewards?.some(item => item !== '0') const addTransactionWithType = useTransactionAdder() const handleHarvest = () => { - const farmContract = positionEarning.joinedPositions?.[0]?.farmId + const farmContract = + positionEarning.joinedPositions?.[0]?.farmId || + positionEarning.farmV2DepositedPositions?.[0].farmV2.id.split('_')[0] + const isInFarmV2 = !!positionEarning.farmV2DepositedPositions?.[0] const pId = positionEarning.joinedPositions?.[0]?.pid + const fId = positionEarning?.farmV2DepositedPositions?.[0].farmV2.id.split('_')[1] + const library = libraryRef.current dispatch(setShowPendingModal(MODAL_PENDING_TEXTS.HARVEST)) dispatch(setAttemptingTxn(true)) - if (!library || !pId || !farmContract) { + if (!library || (isInFarmV2 ? !fId : !pId) || !farmContract) { dispatch(setAttemptingTxn(false)) dispatch(setTxError('Something went wrong!')) return } - const encodedPid = defaultAbiCoder.encode(['tupple(uint256[] pIds)'], [{ pIds: [pId] }]) - const encodedData = FarmInterface.encodeFunctionData('harvestMultiplePools', [[positionEarning.id], [encodedPid]]) + let encodedData = '' + if (isInFarmV2) { + encodedData = FarmV2Interface.encodeFunctionData('claimReward', [fId, [positionEarning.id]]) + } else { + const encodedPid = defaultAbiCoder.encode(['tupple(uint256[] pIds)'], [{ pIds: [pId] }]) + encodedData = FarmInterface.encodeFunctionData('harvestMultiplePools', [[positionEarning.id], [encodedPid]]) + } const txn = { to: farmContract, data: encodedData, } + library .getSigner() .estimateGas(txn) @@ -253,12 +286,17 @@ const PositionView: React.FC = props => { = props => { - + Farm Rewards @@ -303,7 +341,7 @@ const PositionView: React.FC = props => { item !== '0')} + disabled={disabledHarvest} style={{ height: '36px', width: 'fit-content', diff --git a/src/pages/MyEarnings/ElasticPools/SinglePosition/PriceRangeChart.tsx b/src/pages/MyEarnings/ElasticPools/SinglePosition/PriceRangeChart.tsx index 0edaf23c05..b4c334da86 100644 --- a/src/pages/MyEarnings/ElasticPools/SinglePosition/PriceRangeChart.tsx +++ b/src/pages/MyEarnings/ElasticPools/SinglePosition/PriceRangeChart.tsx @@ -75,6 +75,11 @@ const PriceRangeChart: React.FC = ({ position, disabled }) => { overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', + ':hover': { + overflow: 'visible', + whiteSpace: 'normal', + height: 'auto', + }, }} > Current Price: diff --git a/src/pages/Pools/index.tsx b/src/pages/Pools/index.tsx index 579f84aaf4..5d41df8e1c 100644 --- a/src/pages/Pools/index.tsx +++ b/src/pages/Pools/index.tsx @@ -33,6 +33,7 @@ import { Instruction } from 'pages/Pools/InstructionAndGlobalData' import ProAmmPoolList from 'pages/ProAmmPools' import { ApplicationModal } from 'state/application/actions' import { useOpenModal } from 'state/application/hooks' +import ElasticFarmV2Updater from 'state/farms/elasticv2/updater' import { Field } from 'state/pair/actions' import { MEDIA_WIDTHS } from 'theme' import { currencyId } from 'utils/currencyId' @@ -370,6 +371,7 @@ const Pools = () => { )} + ) } diff --git a/src/pages/ProAmmPools/ListItem.tsx b/src/pages/ProAmmPools/ListItem.tsx index 0c8b990b02..cd65570d58 100644 --- a/src/pages/ProAmmPools/ListItem.tsx +++ b/src/pages/ProAmmPools/ListItem.tsx @@ -128,7 +128,7 @@ export default function ProAmmPoolListItem({ pool, onShared, userPositions }: Li .find(farm => farm.poolAddress.toLowerCase() === pool.address.toLowerCase()) const isFarmV2 = !!farmV2 - const isFarmingPool = isFarmV1 || isFarmV2 + const isFarmingPool = isFarmV1 || isFarmV2 || !!pool.farmAPR const maxFarmV2Apr = Math.max(...(farmV2?.ranges.map(item => item.apr || 0) || []), 0) @@ -145,14 +145,7 @@ export default function ProAmmPoolListItem({ pool, onShared, userPositions }: Li ) } - return ( - - {pool.apr.toFixed(2)}% - - ) + return {pool.apr.toFixed(2)}% } return ( @@ -190,7 +183,7 @@ export default function ProAmmPoolListItem({ pool, onShared, userPositions }: Li Fee {(pool.feeTier * 100) / ELASTIC_BASE_FEE_UNIT}% - {isFarmingPool && } + {isFarmingPool && } diff --git a/src/pages/ProAmmPools/index.tsx b/src/pages/ProAmmPools/index.tsx index 0284ba8d9c..236af8b042 100644 --- a/src/pages/ProAmmPools/index.tsx +++ b/src/pages/ProAmmPools/index.tsx @@ -19,7 +19,6 @@ import { ApplicationModal } from 'state/application/actions' import { useModalOpen, useOpenModal } from 'state/application/hooks' import { FarmUpdater, useElasticFarms } from 'state/farms/elastic/hooks' import { useElasticFarmsV2 } from 'state/farms/elasticv2/hooks' -import ElasticFarmV2Updater from 'state/farms/elasticv2/updater' import { Field } from 'state/mint/proamm/type' import { useTopPoolAddresses, useUserProMMPositions } from 'state/prommPools/hooks' import useGetElasticPools from 'state/prommPools/useGetElasticPools' @@ -472,7 +471,6 @@ export default function ProAmmPoolList({ title={sharedPoolId ? t`Share this pool with your friends!` : t`Share this list of pools with your friends`} /> - ) } diff --git a/src/services/earning/index.ts b/src/services/earning/index.ts index d8e77c9d4b..d7fd8ffc0e 100644 --- a/src/services/earning/index.ts +++ b/src/services/earning/index.ts @@ -55,6 +55,7 @@ const earningApi = createApi({ const positionData: GetElasticEarningResponse = (positionsRes?.data as any).data as GetElasticEarningResponse const aggregateData = Object.keys(positionData).reduce((acc, chainName) => { + if (!positionData?.[chainName]) return acc return { ...acc, [chainName]: { diff --git a/src/services/earning/types.ts b/src/services/earning/types.ts index 3813e85228..93b6e775cb 100644 --- a/src/services/earning/types.ts +++ b/src/services/earning/types.ts @@ -85,6 +85,18 @@ export type ElasticPositionEarningWithDetails = { pendingRewardUSD: string[] liquidity: string }[] + + farmV2DepositedPositions: { + farmV2: { + id: string + rewards: Array<{ amount: string; index: number; tokenID: string }> + } + pendingRewards: Array + liquidity: string + range: { + weight: string + } + }[] } & HistoricalEarning export type ClassicPositionEarningWithDetails = { diff --git a/src/services/knprotocol.ts b/src/services/knprotocol.ts new file mode 100644 index 0000000000..fe1d7c0ee7 --- /dev/null +++ b/src/services/knprotocol.ts @@ -0,0 +1,24 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +import { POOL_FARM_BASE_URL } from 'constants/env' +import { NETWORKS_INFO } from 'constants/networks' +import { EVMNetworkInfo } from 'constants/networks/type' +import { SubgraphFarmV2 } from 'state/farms/elasticv2/types' + +const knProtocolApi = createApi({ + reducerPath: 'knProtocol', + baseQuery: fetchBaseQuery({ baseUrl: POOL_FARM_BASE_URL }), + endpoints: builder => ({ + getFarmV2: builder.query<{ data: { data: SubgraphFarmV2[] } }, ChainId>({ + query: (chainId: ChainId) => ({ + url: `/${ + (NETWORKS_INFO[chainId] as EVMNetworkInfo).poolFarmRoute + }/api/v1/elastic-new/farm-v2?perPage=1000&page=1`, + }), + }), + }), +}) + +export default knProtocolApi +export const { useLazyGetFarmV2Query } = knProtocolApi diff --git a/src/services/ksSetting.ts b/src/services/ksSetting.ts index 9a20c38b0d..4e93177ee4 100644 --- a/src/services/ksSetting.ts +++ b/src/services/ksSetting.ts @@ -11,8 +11,8 @@ import { TopToken } from 'state/topTokens/type' export type KyberSwapConfig = { rpc: string - prochart: boolean isEnableBlockService: boolean + isEnableKNProtocol: boolean blockClient: ApolloClient classicClient: ApolloClient elasticClient: ApolloClient @@ -23,8 +23,8 @@ export type KyberSwapConfig = { export type KyberSwapConfigResponse = { rpc: string - prochart: boolean isEnableBlockService: boolean + isEnableKNProtocol: boolean blockSubgraph: string classicSubgraph: string elasticSubgraph: string diff --git a/src/state/application/hooks.ts b/src/state/application/hooks.ts index 62a76749e0..1572fc41fe 100644 --- a/src/state/application/hooks.ts +++ b/src/state/application/hooks.ts @@ -414,7 +414,7 @@ function getDefaultConfig(chainId: ChainId): KyberSwapConfigResponse { const evm = isEVM(chainId) return { rpc: NETWORKS_INFO[chainId].defaultRpcUrl, - prochart: false, + isEnableKNProtocol: false, isEnableBlockService: false, blockSubgraph: (evm ? NETWORKS_INFO[chainId] : ethereumInfo).defaultBlockSubgraph, elasticSubgraph: (evm ? NETWORKS_INFO[chainId] : ethereumInfo).elastic.defaultSubgraph, @@ -449,8 +449,8 @@ export const useKyberSwapConfig = (customChainId?: ChainId): KyberSwapConfig => return { rpc: config.rpc, isEnableBlockService: config.isEnableBlockService, + isEnableKNProtocol: config.isEnableKNProtocol, readProvider, - prochart: config.prochart, blockClient, elasticClient, classicClient, @@ -460,7 +460,7 @@ export const useKyberSwapConfig = (customChainId?: ChainId): KyberSwapConfig => }, [ config.rpc, config.isEnableBlockService, - config.prochart, + config.isEnableKNProtocol, config.commonTokens, readProvider, blockClient, diff --git a/src/state/application/reducer.ts b/src/state/application/reducer.ts index 2aa47c8bb3..fc7534ad81 100644 --- a/src/state/application/reducer.ts +++ b/src/state/application/reducer.ts @@ -172,6 +172,7 @@ export default createReducer(initialState, builder => const data = action.payload.data.config const rpc = data?.rpc || NETWORKS_INFO[chainId].defaultRpcUrl const isEnableBlockService = data?.isEnableBlockService ?? false + const isEnableKNProtocol = data?.isEnableKNProtocol ?? false const blockSubgraph = evm ? data?.blockSubgraph || NETWORKS_INFO[chainId].defaultBlockSubgraph @@ -191,7 +192,7 @@ export default createReducer(initialState, builder => [chainId]: { rpc, isEnableBlockService, - prochart: data?.prochart || false, + isEnableKNProtocol, blockSubgraph, elasticSubgraph, classicSubgraph, diff --git a/src/state/farms/elastic/updaters/index.tsx b/src/state/farms/elastic/updaters/index.tsx index 2dfa4e23a0..426e4dda50 100644 --- a/src/state/farms/elastic/updaters/index.tsx +++ b/src/state/farms/elastic/updaters/index.tsx @@ -1,5 +1,4 @@ -import { CHAINS_SUPPORT_NEW_POOL_FARM_API } from 'constants/networks' -import { useActiveWeb3React } from 'hooks' +import { useKyberSwapConfig } from 'state/application/hooks' import useGetUserFarmingInfo from './useGetUserElasticFarmInfo' import FarmUpdaterV1 from './v1' @@ -10,15 +9,11 @@ export type CommonProps = { } const FarmUpdater: React.FC = ({ interval = true }) => { - const { chainId } = useActiveWeb3React() + const { isEnableKNProtocol } = useKyberSwapConfig() useGetUserFarmingInfo(interval) - if (!chainId) { - return null - } - - if (CHAINS_SUPPORT_NEW_POOL_FARM_API.includes(chainId)) { + if (isEnableKNProtocol) { return } else { return diff --git a/src/state/farms/elasticv2/types.ts b/src/state/farms/elasticv2/types.ts index 7ee3b293ea..658062c1b1 100644 --- a/src/state/farms/elasticv2/types.ts +++ b/src/state/farms/elasticv2/types.ts @@ -27,8 +27,6 @@ export interface ElasticFarmV2 { token0: Currency token1: Currency tvl: number - tvlToken0: CurrencyAmount - tvlToken1: CurrencyAmount totalRewards: Array> ranges: Array } @@ -62,7 +60,8 @@ export interface SubgraphFarmV2 { endTime: string isSettled: boolean liquidity: string - depositedPositions: Array<{ + stakedTvl?: string + depositedPositions?: Array<{ id: string position: { id: string diff --git a/src/state/farms/elasticv2/updater.tsx b/src/state/farms/elasticv2/updater.tsx index b6aaa41b27..3327c123b2 100644 --- a/src/state/farms/elasticv2/updater.tsx +++ b/src/state/farms/elasticv2/updater.tsx @@ -6,7 +6,8 @@ import { CurrencyAmount, Token, WETH } from '@kyberswap/ks-sdk-core' import { FeeAmount, Pool, Position } from '@kyberswap/ks-sdk-elastic' import { BigNumber } from 'ethers' import { Interface } from 'ethers/lib/utils' -import { useEffect } from 'react' +import { useEffect, useMemo, useRef } from 'react' +import { useLazyGetFarmV2Query } from 'services/knprotocol' import FarmV2QuoterABI from 'constants/abis/farmv2Quoter.json' import NFTPositionManagerABI from 'constants/abis/v2/ProAmmNFTPositionManager.json' @@ -107,6 +108,7 @@ export default function ElasticFarmV2Updater({ interval = true }: { interval?: b const { networkInfo, isEVM, chainId, account } = useActiveWeb3React() const elasticFarm = useAppSelector(state => state.elasticFarmV2[chainId] || defaultChainData) const { elasticClient } = useKyberSwapConfig() + const isEnableKNProtocol = true const multicallContract = useMulticallContract() const farmv2QuoterContract = useContract( @@ -114,30 +116,53 @@ export default function ElasticFarmV2Updater({ interval = true }: { interval?: b FarmV2QuoterABI, ) - const [getElasticFarmV2, { data, error }] = useLazyQuery(queryFarms, { + const [getElasticFarmV2, { data: subgraphData, error: subgraphError }] = useLazyQuery(queryFarms, { client: elasticClient, fetchPolicy: 'network-only', }) + const [getElasticFarmV2FromKnProtocol, { data: knProtocolData, error: knProtocolError }] = useLazyGetFarmV2Query() + + const latestKnProtocolData = useRef(knProtocolData) + + const data = useMemo(() => { + if (isEnableKNProtocol) { + return { + farmV2S: knProtocolData?.data?.data || latestKnProtocolData.current?.data?.data || [], + } + } else return subgraphData + }, [isEnableKNProtocol, knProtocolData, subgraphData]) + + const error = useMemo(() => { + if (isEnableKNProtocol) return knProtocolError + return subgraphError + }, [isEnableKNProtocol, subgraphError, knProtocolError]) + useEffect(() => { if (isEVM && !elasticFarm?.farms && !elasticFarm?.loading) { dispatch(setLoading({ chainId, loading: true })) - getElasticFarmV2().finally(() => { - dispatch(setLoading({ chainId, loading: false })) - }) + if (isEnableKNProtocol) { + getElasticFarmV2FromKnProtocol(chainId).finally(() => { + dispatch(setLoading({ chainId, loading: false })) + }) + } else + getElasticFarmV2().finally(() => { + dispatch(setLoading({ chainId, loading: false })) + }) } - }, [isEVM, chainId, dispatch, getElasticFarmV2, elasticFarm]) + }, [isEVM, chainId, dispatch, getElasticFarmV2, elasticFarm, getElasticFarmV2FromKnProtocol, isEnableKNProtocol]) useEffect(() => { const i = interval ? setInterval(() => { - getElasticFarmV2() + if (isEnableKNProtocol) getElasticFarmV2FromKnProtocol(chainId) + else getElasticFarmV2() }, 10_000) : undefined return () => { i && clearInterval(i) } - }, [interval, dispatch, getElasticFarmV2]) + }, [interval, chainId, dispatch, getElasticFarmV2, getElasticFarmV2FromKnProtocol, isEnableKNProtocol]) useEffect(() => { if (error && chainId) { @@ -150,7 +175,7 @@ export default function ElasticFarmV2Updater({ interval = true }: { interval?: b useEffect(() => { const getData = async () => { - if (data?.farmV2S && chainId) { + if (data?.farmV2S.length && chainId) { const tokens = [ ...new Set( data.farmV2S @@ -193,23 +218,29 @@ export default function ElasticFarmV2Updater({ interval = true }: { interval?: b farm.pool.reinvestL, Number(farm.pool.tick), ) - let tvlToken0 = CurrencyAmount.fromRawAmount(token0.wrapped, 0) - let tvlToken1 = CurrencyAmount.fromRawAmount(token1.wrapped, 0) - - farm.depositedPositions.forEach(pos => { - const position = new Position({ - pool: p, - liquidity: pos.position.liquidity, - tickLower: Number(pos.position.tickLower.tickIdx), - tickUpper: Number(pos.position.tickUpper.tickIdx), - }) - tvlToken0 = tvlToken0.add(position.amount0) - tvlToken1 = tvlToken1.add(position.amount1) - }) - const tvl = - Number(tvlToken0.toExact() || '0') * (prices[farm.pool.token0.id] || 0) + - Number(tvlToken1.toExact() || '0') * (prices[farm.pool.token1.id] || 0) + let tvl = 0 + if (farm.stakedTvl) { + tvl = +farm.stakedTvl + } else { + let tvlToken0 = CurrencyAmount.fromRawAmount(token0.wrapped, 0) + let tvlToken1 = CurrencyAmount.fromRawAmount(token1.wrapped, 0) + + farm.depositedPositions?.forEach(pos => { + const position = new Position({ + pool: p, + liquidity: pos.position.liquidity, + tickLower: Number(pos.position.tickLower.tickIdx), + tickUpper: Number(pos.position.tickUpper.tickIdx), + }) + + tvlToken0 = tvlToken0.add(position.amount0) + tvlToken1 = tvlToken1.add(position.amount1) + }) + tvl = + Number(tvlToken0.toExact() || '0') * (prices[farm.pool.token0.id] || 0) + + Number(tvlToken1.toExact() || '0') * (prices[farm.pool.token1.id] || 0) + } const totalRewards = farm.rewards.map(item => CurrencyAmount.fromRawAmount(getToken(item.token, true), item.amount), @@ -227,8 +258,6 @@ export default function ElasticFarmV2Updater({ interval = true }: { interval?: b token0, token1, totalRewards, - tvlToken0, - tvlToken1, tvl, ranges: farm.ranges.map(r => { // https://www.notion.so/kybernetwork/LM-v2-APR-Formula-15b8606e820745b59a5a3aded8bf46e0 diff --git a/src/state/index.ts b/src/state/index.ts index 2d505c263e..caffdcdc0c 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -16,6 +16,7 @@ import crosschainApi from '../services/crossChain' import earningApi from '../services/earning' import geckoTerminalApi from '../services/geckoTermial' import identifyApi from '../services/identity' +import knProtocolApi from '../services/knprotocol' import ksSettingApi from '../services/ksSetting' import kyberDAO from '../services/kyberDAO' import limitOrderApi from '../services/limitOrder' @@ -118,6 +119,7 @@ const store = configureStore({ [tokenApi.reducerPath]: tokenApi.reducer, [socialApi.reducerPath]: socialApi.reducer, [blockServiceApi.reducerPath]: blockServiceApi.reducer, + [knProtocolApi.reducerPath]: knProtocolApi.reducer, }, middleware: getDefaultMiddleware => getDefaultMiddleware({ thunk: true, immutableCheck: false, serializableCheck: false }) @@ -138,7 +140,8 @@ const store = configureStore({ .concat(earningApi.middleware) .concat(socialApi.middleware) .concat(tokenApi.middleware) - .concat(blockServiceApi.middleware), + .concat(blockServiceApi.middleware) + .concat(knProtocolApi.middleware), preloadedState, }) diff --git a/src/state/prommPools/useGetElasticPools/index.ts b/src/state/prommPools/useGetElasticPools/index.ts index f78f657c1f..127cda4f38 100644 --- a/src/state/prommPools/useGetElasticPools/index.ts +++ b/src/state/prommPools/useGetElasticPools/index.ts @@ -1,5 +1,4 @@ -import { CHAINS_SUPPORT_NEW_POOL_FARM_API } from 'constants/networks' -import { useActiveWeb3React } from 'hooks' +import { useKyberSwapConfig } from 'state/application/hooks' import { ElasticPoolDetail } from 'types/pool' import useGetElasticPoolsV1 from './useGetElasticPoolsV1' @@ -14,13 +13,12 @@ export type CommonReturn = { } const useGetElasticPools = (poolAddresses: string[]): CommonReturn => { - const { chainId } = useActiveWeb3React() + const { isEnableKNProtocol } = useKyberSwapConfig() - const shouldRunV2 = CHAINS_SUPPORT_NEW_POOL_FARM_API.includes(chainId) - const responseV1 = useGetElasticPoolsV1(poolAddresses, shouldRunV2) + const responseV1 = useGetElasticPoolsV1(poolAddresses) const responseV2 = useGetElasticPoolsV2() - if (shouldRunV2) { + if (isEnableKNProtocol) { return responseV2 } diff --git a/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV1.ts b/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV1.ts index 790ca9f6bc..512f9120f9 100644 --- a/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV1.ts +++ b/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV1.ts @@ -124,15 +124,15 @@ const parsedPoolData = ( return formatted } -const useGetElasticPoolsV1 = (poolAddresses: string[], skip?: boolean): CommonReturn => { - const { elasticClient } = useKyberSwapConfig() +const useGetElasticPoolsV1 = (poolAddresses: string[]): CommonReturn => { + const { elasticClient, isEnableKNProtocol } = useKyberSwapConfig() const { blockLast24h } = usePoolBlocks() const { loading, error, data } = useQuery(PROMM_POOLS_BULK(undefined, poolAddresses), { client: elasticClient, fetchPolicy: 'no-cache', - skip, + skip: isEnableKNProtocol, }) const { @@ -142,7 +142,7 @@ const useGetElasticPoolsV1 = (poolAddresses: string[], skip?: boolean): CommonRe } = useQuery(PROMM_POOLS_BULK(blockLast24h, poolAddresses), { client: elasticClient, fetchPolicy: 'no-cache', - skip, + skip: isEnableKNProtocol, }) const anyError = error24?.message.includes('Failed to decode `block.number`') diff --git a/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV2.ts b/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV2.ts index cdd34eeede..f071b96f8a 100644 --- a/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV2.ts +++ b/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV2.ts @@ -1,8 +1,9 @@ import useSWRImmutable from 'swr/immutable' import { POOL_FARM_BASE_URL } from 'constants/env' -import { CHAINS_SUPPORT_NEW_POOL_FARM_API, NETWORKS_INFO, isEVM } from 'constants/networks' +import { NETWORKS_INFO, isEVM } from 'constants/networks' import { useActiveWeb3React } from 'hooks' +import { useKyberSwapConfig } from 'state/application/hooks' import { ElasticPoolDetail } from 'types/pool' import { CommonReturn } from '.' @@ -50,20 +51,20 @@ type PoolAccumulator = { [address: string]: ElasticPoolDetail } const useGetElasticPoolsV2 = (): CommonReturn => { const { chainId } = useActiveWeb3React() + const { isEnableKNProtocol } = useKyberSwapConfig() - const shouldSkip = !isEVM(chainId) || !CHAINS_SUPPORT_NEW_POOL_FARM_API.includes(chainId) const chainRoute = !isEVM(chainId) || NETWORKS_INFO[chainId].poolFarmRoute const { isValidating, error, data } = useSWRImmutable( - `${POOL_FARM_BASE_URL}/${chainRoute}/api/v1/elastic-new/pools?includeLowTvl=true&page=1&perPage=10000`, + `${POOL_FARM_BASE_URL}/${chainRoute}/api/v1/elastic-new/pools?includeLowTvl=true&page=1&perPage=10000&thisParamToForceRefresh=${isEnableKNProtocol}`, async (url: string) => { - if (shouldSkip) { + if (!isEnableKNProtocol) { return Promise.resolve({}) } return fetch(url).then(resp => resp.json()) }, { - refreshInterval: shouldSkip ? 0 : 60_000, + refreshInterval: isEnableKNProtocol ? 0 : 60_000, }, ) diff --git a/src/utils/tokenInfo.ts b/src/utils/tokenInfo.ts index bfeb5844cd..53328f997e 100644 --- a/src/utils/tokenInfo.ts +++ b/src/utils/tokenInfo.ts @@ -59,6 +59,8 @@ export const getTokenSymbolWithHardcode = ( return 'mKNC' } if (chainId === ChainId.ARBITRUM && formatAddress === '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8') return 'USDC.e' + if (chainId === ChainId.ARBITRUM && formatAddress === '0x9cfb13e6c11054ac9fcb92ba89644f30775436e4') + return 'axl.wstETH' return defaultSymbol ?? '' } From 4a225452399018ed4464f7d390ff596e2856660c Mon Sep 17 00:00:00 2001 From: Nguyen Van Viet Date: Wed, 20 Sep 2023 13:48:49 +0700 Subject: [PATCH 2/9] refactor(dynamic-farm): using multicall from redux hooks (#2183) * refactor: reduce rpc of dynamic farm * fix: re-render filtered farm v1 * revert env --- package.json | 3 +- src/components/HorizontalScroll.tsx | 6 +- .../ElasticFarmGroup/PostionDetail.tsx | 8 +- .../YieldPools/ElasticFarmGroup/Row.tsx | 35 +- .../YieldPools/ElasticFarmGroup/index.tsx | 24 +- .../ElasticFarmModals/DepositModal.tsx | 2 +- .../ElasticFarmModals/HarvestModal.tsx | 5 +- .../ElasticFarmModals/StakeModal.tsx | 17 +- .../ElasticFarmModals/WithdrawModal.tsx | 19 +- .../YieldPools/ElasticFarmModals/styled.tsx | 6 +- src/components/YieldPools/ElasticFarms.tsx | 10 +- src/hooks/useProAmmPositions.ts | 102 +++--- src/index.tsx | 1 + src/pages/Farm/ElasticFarmCombination.tsx | 13 +- src/state/farms/elastic/hooks.ts | 190 ++++++++++- src/state/farms/elastic/index.ts | 20 +- src/state/farms/elastic/types.ts | 1 - src/state/farms/elastic/updaters/index.tsx | 2 +- .../updaters/useGetUserElasticFarmInfo.ts | 242 +------------- src/state/farms/elastic/updaters/v1.tsx | 16 +- src/state/farms/elastic/updaters/v2.tsx | 4 +- src/state/farms/elasticv2/updater.tsx | 10 +- src/state/mint/proamm/utils.ts | 18 + src/state/multicall/hooks.ts | 103 +++--- src/wdyr.ts | 14 + tsconfig.json | 5 +- vite.config.ts | 5 + yarn.lock | 316 ++++++++++-------- 28 files changed, 609 insertions(+), 588 deletions(-) create mode 100644 src/wdyr.ts diff --git a/package.json b/package.json index b89d25bbf3..bb9bfc0cfd 100644 --- a/package.json +++ b/package.json @@ -178,6 +178,7 @@ "@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/parser": "^5.58.0", "@vitejs/plugin-react": "^3.1.0", + "@welldone-software/why-did-you-render": "^7.0.1", "babel-plugin-lodash": "^3.3.4", "babel-plugin-macros": "^3.1.0", "env-cmd": "^10.1.0", @@ -207,4 +208,4 @@ "@lingui/core": "3.14.0", "@lingui/conf": "3.16.0" } -} \ No newline at end of file +} diff --git a/src/components/HorizontalScroll.tsx b/src/components/HorizontalScroll.tsx index 25faa23795..839ceb6f77 100644 --- a/src/components/HorizontalScroll.tsx +++ b/src/components/HorizontalScroll.tsx @@ -1,4 +1,4 @@ -import { CSSProperties, ReactNode, useEffect, useRef } from 'react' +import { CSSProperties, Fragment, ReactNode, useEffect, useRef } from 'react' import ScrollContainer from 'react-indiana-drag-scroll' import { Flex } from 'rebass' import styled from 'styled-components' @@ -61,7 +61,9 @@ const HorizontalScroll = ({ > - {(items ?? []).map(renderItem)} + {(items ?? []).map(i => ( + {renderItem(i)} + ))} diff --git a/src/components/YieldPools/ElasticFarmGroup/PostionDetail.tsx b/src/components/YieldPools/ElasticFarmGroup/PostionDetail.tsx index 158b82b7dd..97c4a17256 100644 --- a/src/components/YieldPools/ElasticFarmGroup/PostionDetail.tsx +++ b/src/components/YieldPools/ElasticFarmGroup/PostionDetail.tsx @@ -11,7 +11,7 @@ import PriceVisualize from 'components/ProAmm/PriceVisualize' import { MouseoverTooltipDesktopOnly } from 'components/Tooltip' import useIsTickAtLimit from 'hooks/useIsTickAtLimit' import useTheme from 'hooks/useTheme' -import { useElasticFarms, useFarmAction } from 'state/farms/elastic/hooks' +import { useFarmAction, useUserInfoByFarm } from 'state/farms/elastic/hooks' import { FarmingPool, NFTPosition } from 'state/farms/elastic/types' import { Bound } from 'state/mint/proamm/type' import { formatTickPrice } from 'utils/formatTickPrice' @@ -42,8 +42,8 @@ const PositionDetail = ({ }: Props) => { const theme = useTheme() - const { userFarmInfo } = useElasticFarms() - const joinedPositions = userFarmInfo?.[farmAddress]?.joinedPositions[pool.pid] || [] + const userInfo = useUserInfoByFarm(farmAddress) + const joinedPositions = userInfo?.joinedPositions[pool.pid] || [] const { unstake } = useFarmAction(farmAddress) const joinedInfo = joinedPositions.find(jp => jp.nftId.toString() === item.nftId.toString()) @@ -61,7 +61,7 @@ const PositionDetail = ({ const priceLower = !isRevertPrice ? item.token0PriceLower : item.token0PriceUpper.invert() const priceUpper = !isRevertPrice ? item.token0PriceUpper : item.token0PriceLower.invert() - const rewardByNft = userFarmInfo?.[farmAddress]?.rewardByNft + const rewardByNft = userInfo?.rewardByNft const rewards = rewardByNft?.[pool.pid + '_' + item.nftId.toString()] || [] const rewardValue = rewards.reduce( diff --git a/src/components/YieldPools/ElasticFarmGroup/Row.tsx b/src/components/YieldPools/ElasticFarmGroup/Row.tsx index 9d6e8c9826..832addf5dc 100644 --- a/src/components/YieldPools/ElasticFarmGroup/Row.tsx +++ b/src/components/YieldPools/ElasticFarmGroup/Row.tsx @@ -1,9 +1,8 @@ -import { ChainId, CurrencyAmount, Fraction } from '@kyberswap/ks-sdk-core' -import { computePoolAddress } from '@kyberswap/ks-sdk-elastic' +import { CurrencyAmount, Fraction } from '@kyberswap/ks-sdk-core' import { Trans, t } from '@lingui/macro' import dayjs from 'dayjs' import { BigNumber } from 'ethers' -import { useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react' import { Info, Minus, Plus, Share2 } from 'react-feather' import { Link } from 'react-router-dom' import { useMedia } from 'react-use' @@ -19,15 +18,15 @@ import Harvest from 'components/Icons/Harvest' import InfoHelper from 'components/InfoHelper' import { MouseoverTooltip, MouseoverTooltipDesktopOnly } from 'components/Tooltip' import { APP_PATHS, ELASTIC_BASE_FEE_UNIT } from 'constants/index' -import { NETWORKS_INFO, isEVM } from 'constants/networks' import { TOBE_EXTENDED_FARMING_POOLS } from 'constants/v2' import { useActiveWeb3React } from 'hooks' import { useProMMFarmContract } from 'hooks/useContract' import { useProAmmPositions } from 'hooks/useProAmmPositions' import useTheme from 'hooks/useTheme' import { useShareFarmAddress } from 'state/farms/classic/hooks' -import { useElasticFarms, usePositionFilter } from 'state/farms/elastic/hooks' +import { useDepositedNftsByFarm, usePositionFilter, useUserInfoByFarm } from 'state/farms/elastic/hooks' import { FarmingPool, NFTPosition } from 'state/farms/elastic/types' +import { getPoolAddress } from 'state/mint/proamm/utils' import { useViewMode } from 'state/user/hooks' import { VIEW_MODE } from 'state/user/reducer' import { shortenAddress } from 'utils' @@ -73,26 +72,22 @@ const Row = ({ const above1000 = useMedia('(min-width: 1000px)') const [isRevertPrice, setIsRevertPrice] = useState(false) - const { userFarmInfo } = useElasticFarms() - const joinedPositions = userFarmInfo?.[fairlaunchAddress]?.joinedPositions[farmingPool.pid] || [] + const userInfo = useUserInfoByFarm(fairlaunchAddress) + + const joinedPositions = userInfo?.joinedPositions[farmingPool.pid] || [] + + const depositedPositionsByFarm = useDepositedNftsByFarm(fairlaunchAddress) const depositedPositions = - userFarmInfo?.[fairlaunchAddress]?.depositedPositions.filter(pos => { + depositedPositionsByFarm.filter(pos => { return ( pos.liquidity.toString() !== '0' && - farmingPool.poolAddress.toLowerCase() === - computePoolAddress({ - factoryAddress: NETWORKS_INFO[isEVM(chainId) ? chainId : ChainId.MAINNET].elastic.coreFactory, - tokenA: pos.pool.token0, - tokenB: pos.pool.token1, - fee: pos.pool.fee, - initCodeHashManualOverride: NETWORKS_INFO[isEVM(chainId) ? chainId : ChainId.MAINNET].elastic.initCodeHash, - }).toLowerCase() + farmingPool.poolAddress.toLowerCase() === getPoolAddress(pos.pool).toLowerCase() ) }) || [] const rewardPendings = - userFarmInfo?.[fairlaunchAddress]?.rewardPendings[farmingPool.pid] || + userInfo?.rewardPendings[farmingPool.pid] || farmingPool.rewardTokens.map(token => CurrencyAmount.fromRawAmount(token, 0)) const rewardValue = rewardPendings.reduce( @@ -108,7 +103,7 @@ const Row = ({ useEffect(() => { const getFeeTargetInfo = async () => { if (!contract || farmingPool.feeTarget === '0') return - const userJoinedPos = userFarmInfo?.[fairlaunchAddress].joinedPositions[farmingPool.pid] || [] + const userJoinedPos = userInfo.joinedPositions[farmingPool.pid] || [] if (!userJoinedPos.length) { setTargetPercent('') @@ -143,7 +138,7 @@ const Row = ({ } getFeeTargetInfo() - }, [contract, farmingPool.feeTarget, fairlaunchAddress, farmingPool.pid, userFarmInfo]) + }, [contract, farmingPool.feeTarget, fairlaunchAddress, farmingPool.pid, userInfo]) const { positions } = useProAmmPositions(account) @@ -537,4 +532,4 @@ const Row = ({ ) } -export default Row +export default React.memo(Row) diff --git a/src/components/YieldPools/ElasticFarmGroup/index.tsx b/src/components/YieldPools/ElasticFarmGroup/index.tsx index 83c8d40f1d..6afe935553 100644 --- a/src/components/YieldPools/ElasticFarmGroup/index.tsx +++ b/src/components/YieldPools/ElasticFarmGroup/index.tsx @@ -22,8 +22,8 @@ import { useProAmmNFTPositionManagerContract } from 'hooks/useContract' import useTheme from 'hooks/useTheme' import { Dots } from 'pages/Pool/styleds' import { useWalletModalToggle } from 'state/application/hooks' -import { useElasticFarms, useFarmAction } from 'state/farms/elastic/hooks' -import { FarmingPool, UserInfo } from 'state/farms/elastic/types' +import { useDepositedNftsByFarm, useElasticFarms, useFarmAction, useUserInfoByFarm } from 'state/farms/elastic/hooks' +import { FarmingPool } from 'state/farms/elastic/types' import { useSingleCallResult } from 'state/multicall/hooks' import { useIsTransactionPending } from 'state/transactions/hooks' import { useViewMode } from 'state/user/hooks' @@ -61,7 +61,6 @@ type Props = { pool?: FarmingPool, ) => void pools: FarmingPool[] - userInfo?: UserInfo onShowStepGuide: () => void tokenPrices: { [key: string]: number } } @@ -80,11 +79,14 @@ enum SORT_DIRECTION { DESC = 'desc', } -const ProMMFarmGroup: React.FC = ({ address, onOpenModal, pools, userInfo, onShowStepGuide, tokenPrices }) => { +const ElasticFarmGroup: React.FC = ({ address, onOpenModal, pools, onShowStepGuide, tokenPrices }) => { const theme = useTheme() const { account, chainId } = useActiveWeb3React() const above1000 = useMedia('(min-width: 1000px)') + const depositedPositions = useDepositedNftsByFarm(address) + const userInfo = useUserInfoByFarm(address) + const [searchParams, setSearchParams] = useSearchParams() const sortField = searchParams.get('orderBy') || SORT_FIELD.MY_DEPOSIT const sortDirection = searchParams.get('orderDirection') || SORT_DIRECTION.DESC @@ -92,7 +94,7 @@ const ProMMFarmGroup: React.FC = ({ address, onOpenModal, pools, userInfo const { poolFeeLast24h } = useElasticFarms() const depositedUsd = - userInfo?.depositedPositions.reduce( + depositedPositions.reduce( (acc, cur) => acc + Number(cur.amount0.toExact()) * (tokenPrices[cur.amount0.currency.wrapped.address] || 0) + @@ -101,7 +103,7 @@ const ProMMFarmGroup: React.FC = ({ address, onOpenModal, pools, userInfo ) || 0 const userDepositedTokenAmounts = - userInfo?.depositedPositions.reduce<{ + depositedPositions.reduce<{ [address: string]: CurrencyAmount }>((result, pos) => { const address0 = pos.amount0.currency.address @@ -156,8 +158,8 @@ const ProMMFarmGroup: React.FC = ({ address, onOpenModal, pools, userInfo } const joinedPositions = userInfo?.joinedPositions[pool.pid] || [] - const depositedPositions = - userInfo?.depositedPositions.filter(pos => { + const poolDepositedPositions = + depositedPositions.filter(pos => { return ( pool.poolAddress.toLowerCase() === computePoolAddress({ @@ -171,7 +173,7 @@ const ProMMFarmGroup: React.FC = ({ address, onOpenModal, pools, userInfo ) }) || [] - const depositedUsd = depositedPositions.reduce( + const depositedUsd = poolDepositedPositions.reduce( (usd, pos) => usd + Number(pos.amount1.toExact()) * (tokenPrices[pos.pool.token1.address.toLowerCase()] || 0) + @@ -258,7 +260,7 @@ const ProMMFarmGroup: React.FC = ({ address, onOpenModal, pools, userInfo if (!pools) return null const canHarvest = Object.values(userInfo?.rewardPendings || {}).some(rw => rw.some(item => item.greaterThan('0'))) - const canWithdraw = !!userInfo?.depositedPositions.length + const canWithdraw = !!depositedPositions.length const renderApproveButton = () => { if (isApprovedForAll || tab === 'ended') { @@ -747,4 +749,4 @@ const ProMMFarmGroup: React.FC = ({ address, onOpenModal, pools, userInfo ) } -export default ProMMFarmGroup +export default ElasticFarmGroup diff --git a/src/components/YieldPools/ElasticFarmModals/DepositModal.tsx b/src/components/YieldPools/ElasticFarmModals/DepositModal.tsx index 052d3ebfab..cf18fc4a2a 100644 --- a/src/components/YieldPools/ElasticFarmModals/DepositModal.tsx +++ b/src/components/YieldPools/ElasticFarmModals/DepositModal.tsx @@ -221,7 +221,7 @@ function ProMMDepositNFTModal({ setShowMenu(prev => !prev)}> {filterOptions.find(item => item.code === activeFilter)?.value} - + {showMenu && ( diff --git a/src/components/YieldPools/ElasticFarmModals/styled.tsx b/src/components/YieldPools/ElasticFarmModals/styled.tsx index a57b2c3431..9ea12eca6f 100644 --- a/src/components/YieldPools/ElasticFarmModals/styled.tsx +++ b/src/components/YieldPools/ElasticFarmModals/styled.tsx @@ -88,13 +88,13 @@ export const SelectOption = styled.div` color: ${({ theme }) => theme.subText}; ` -export const DropdownIcon = styled.div<{ rotate?: boolean }>` - transform: rotate(${({ rotate }) => (rotate ? '-180deg' : '0')}); +export const DropdownIcon = styled.div<{ isRotate?: boolean }>` + transform: rotate(${({ isRotate: rotate }) => (rotate ? '-180deg' : '0')}); height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 4px solid ${({ theme }) => theme.text}; transition: transform 300ms; - transform: rotate(${({ rotate }) => (rotate ? '-180deg' : '0')}); + transform: rotate(${({ isRotate: rotate }) => (rotate ? '-180deg' : '0')}); ` diff --git a/src/components/YieldPools/ElasticFarms.tsx b/src/components/YieldPools/ElasticFarms.tsx index 7b93807166..5ef78a234f 100644 --- a/src/components/YieldPools/ElasticFarms.tsx +++ b/src/components/YieldPools/ElasticFarms.tsx @@ -1,7 +1,6 @@ -import { useRef, useState } from 'react' +import { useState } from 'react' import { Flex } from 'rebass' -import { useOnClickOutside } from 'hooks/useOnClickOutside' import { useFilteredFarms } from 'state/farms/elastic/hooks' import { FarmingPool } from 'state/farms/elastic/types' import { useTokenPrices } from 'state/tokenPrices/hooks' @@ -14,11 +13,7 @@ import WithdrawModal from './ElasticFarmModals/WithdrawModal' type ModalType = 'deposit' | 'withdraw' | 'stake' | 'unstake' | 'harvest' | 'forcedWithdraw' function ElasticFarms({ onShowStepGuide }: { onShowStepGuide: () => void }) { - const ref = useRef() - const [open, setOpen] = useState(false) - useOnClickOutside(ref, open ? () => setOpen(prev => !prev) : undefined) - - const { filteredFarms, farms, userFarmInfo } = useFilteredFarms() + const { filteredFarms, farms } = useFilteredFarms() const [selectedFarm, setSeletedFarm] = useState(null) const [selectedModal, setSeletedModal] = useState(null) @@ -85,7 +80,6 @@ function ElasticFarms({ onShowStepGuide }: { onShowStepGuide: () => void }) { setSeletedPool(pool) }} pools={farm.pools} - userInfo={userFarmInfo?.[farm.id]} tokenPrices={tokenPrices} /> ) diff --git a/src/hooks/useProAmmPositions.ts b/src/hooks/useProAmmPositions.ts index 8054ee1501..8202db984d 100644 --- a/src/hooks/useProAmmPositions.ts +++ b/src/hooks/useProAmmPositions.ts @@ -7,7 +7,7 @@ import { useMemo } from 'react' import { EVMNetworkInfo } from 'constants/networks/type' import { useActiveWeb3React } from 'hooks' -import { useElasticFarms } from 'state/farms/elastic/hooks' +import { useDepositedNfts, useElasticFarms, useJoinedPositions } from 'state/farms/elastic/hooks' import { Result, useSingleCallResult, useSingleContractMultipleData } from 'state/multicall/hooks' import { PositionDetails } from 'types/position' @@ -18,7 +18,7 @@ interface UseProAmmPositionsResults { positions: PositionDetails[] | undefined } -function useProAmmPositionsFromTokenIds(tokenIds: BigNumber[] | undefined): UseProAmmPositionsResults { +export function useProAmmPositionsFromTokenIds(tokenIds: BigNumber[] | undefined): UseProAmmPositionsResults { const positionManager = useProAmmNFTPositionManagerContract() const { isEVM, networkInfo } = useActiveWeb3React() @@ -132,68 +132,64 @@ export function useProAmmPositions(account: string | null | undefined): UseProAm export const useFarmPositions = () => { const { isEVM, networkInfo } = useActiveWeb3React() - const { farms, loading, userFarmInfo, loadingUserInfo } = useElasticFarms() + const { farms, loading } = useElasticFarms() + const userFarmInfo = useJoinedPositions() + const depositedPositions = useDepositedNfts() const farmingPools = useMemo(() => farms?.map(farm => farm.pools).flat() || [], [farms]) - const farmPositions = useMemo(() => { + const farmPositions: PositionDetails[] = useMemo(() => { if (!isEVM) return [] - return Object.values(userFarmInfo || {}) - .map(info => { - return info.depositedPositions - .map(pos => { - const poolAddress = computePoolAddress({ - factoryAddress: (networkInfo as EVMNetworkInfo).elastic.coreFactory, - tokenA: pos.pool.token0, - tokenB: pos.pool.token1, - fee: pos.pool.fee, - initCodeHashManualOverride: (networkInfo as EVMNetworkInfo).elastic.initCodeHash, - }) - const pool = farmingPools.filter(pool => pool.poolAddress.toLowerCase() === poolAddress.toLowerCase()) - - const joinedLiquidity = - // I'm sure we can always find pool - // eslint-disable-next-line - Object.values(info.joinedPositions) - .flat() - .filter(joinedPos => joinedPos.nftId.toString() === pos.nftId.toString()) - .reduce( - (acc, cur) => - acc.gt(BigNumber.from(cur.liquidity.toString())) ? acc : BigNumber.from(cur.liquidity.toString()), - BigNumber.from(0), - ) || BigNumber.from(0) - - return { - nonce: BigNumber.from('1'), - tokenId: pos.nftId, - operator: '0x0000000000000000000000000000000000000000', - poolId: poolAddress, - tickLower: pos.tickLower, - tickUpper: pos.tickUpper, - liquidity: BigNumber.from(pos.liquidity.toString()), - // not used - feeGrowthInsideLast: BigNumber.from(0), - stakedLiquidity: joinedLiquidity, - // not used - rTokenOwed: BigNumber.from(0), - token0: pos.pool.token0.address, - token1: pos.pool.token1.address, - fee: pos.pool.fee, - endTime: pool?.[0]?.endTime, - rewardPendings: [], - } - }) + return Object.values(depositedPositions) + .flat() + .map(pos => { + const poolAddress = computePoolAddress({ + factoryAddress: (networkInfo as EVMNetworkInfo).elastic.coreFactory, + tokenA: pos.pool.token0, + tokenB: pos.pool.token1, + fee: pos.pool.fee, + initCodeHashManualOverride: (networkInfo as EVMNetworkInfo).elastic.initCodeHash, + }) + const pool = farmingPools.filter(pool => pool.poolAddress.toLowerCase() === poolAddress.toLowerCase()) + + const joinedLiquidity = Object.values(userFarmInfo) + .map(item => Object.values(item.joinedPositions).flat()) .flat() + .filter(joinedPos => joinedPos.nftId.toString() === pos.nftId.toString()) + .reduce( + (acc, cur) => + acc.gt(BigNumber.from(cur.liquidity.toString())) ? acc : BigNumber.from(cur.liquidity.toString()), + BigNumber.from(0), + ) + + return { + nonce: BigNumber.from('1'), + tokenId: pos.nftId, + operator: '0x0000000000000000000000000000000000000000', + poolId: poolAddress, + tickLower: pos.tickLower, + tickUpper: pos.tickUpper, + liquidity: BigNumber.from(pos.liquidity.toString()), + // not used + feeGrowthInsideLast: BigNumber.from(0), + stakedLiquidity: joinedLiquidity, + // not used + rTokenOwed: BigNumber.from(0), + token0: pos.pool.token0.address, + token1: pos.pool.token1.address, + fee: pos.pool.fee, + endTime: pool?.[0]?.endTime, + rewardPendings: [], + } }) - .flat() - }, [farmingPools, userFarmInfo, isEVM, networkInfo]) + }, [farmingPools, isEVM, networkInfo, userFarmInfo, depositedPositions]) return useMemo(() => { return { farms, userFarmInfo, farmPositions, - loading: loadingUserInfo || loading, + loading: loading, } - }, [loading, loadingUserInfo, farmPositions, farms, userFarmInfo]) + }, [loading, farmPositions, farms, userFarmInfo]) } diff --git a/src/index.tsx b/src/index.tsx index 5339ccd99e..418efc1a28 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,3 +1,4 @@ +import '@/wdyr' import * as Sentry from '@sentry/react' import { BrowserTracing } from '@sentry/tracing' import { Web3ReactHooks, Web3ReactProvider } from '@web3-react/core' diff --git a/src/pages/Farm/ElasticFarmCombination.tsx b/src/pages/Farm/ElasticFarmCombination.tsx index 695f687ff9..fdec9e1c5b 100644 --- a/src/pages/Farm/ElasticFarmCombination.tsx +++ b/src/pages/Farm/ElasticFarmCombination.tsx @@ -1,5 +1,5 @@ import { Trans } from '@lingui/macro' -import { FC, useEffect, useMemo, useState } from 'react' +import { FC, Fragment, useCallback, useEffect, useMemo, useState } from 'react' import { useSearchParams } from 'react-router-dom' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' @@ -70,6 +70,9 @@ export const ElasticFarmCombination: FC = () => { const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) + const showFarmV2 = useCallback(() => setShowFarmStepGuide('v2'), []) + const showFarmV1 = useCallback(() => setShowFarmStepGuide('v1'), []) + if (loading && noFarms) { return ( { overflow: upToExtraSmall ? undefined : 'hidden', }} > - setShowFarmStepGuide('v1')} /> + {!!filteredFarmsV1.length && !!filteredFarmsV2.length && } {Object.keys(farmByContract).map((contract, index) => ( - <> - setShowFarmStepGuide('v2')} farmAddress={contract} key={contract} /> + + {index !== Object.keys(farmByContract).length - 1 && } - + ))} diff --git a/src/state/farms/elastic/hooks.ts b/src/state/farms/elastic/hooks.ts index 62303b369c..9d2b137203 100644 --- a/src/state/farms/elastic/hooks.ts +++ b/src/state/farms/elastic/hooks.ts @@ -3,9 +3,11 @@ import { Currency, CurrencyAmount, Token } from '@kyberswap/ks-sdk-core' import { FeeAmount, Position, computePoolAddress } from '@kyberswap/ks-sdk-elastic' import { t } from '@lingui/macro' import { BigNumber } from 'ethers' +import { Interface } from 'ethers/lib/utils' import { useCallback, useMemo, useState } from 'react' import { useSearchParams } from 'react-router-dom' +import ELASTIC_FARM_ABI from 'constants/abis/v2/farm.json' import { ELASTIC_FARM_TYPE, FARM_TAB } from 'constants/index' import { CONTRACT_NOT_FOUND_MSG } from 'constants/messages' import { isEVM as isEVMNetwork } from 'constants/networks' @@ -14,8 +16,11 @@ import { useActiveWeb3React } from 'hooks' import { useTokens } from 'hooks/Tokens' import { useProAmmNFTPositionManagerContract, useProMMFarmContract } from 'hooks/useContract' import { usePools } from 'hooks/usePools' -import { FarmingPool, NFTPosition } from 'state/farms/elastic/types' +import { useProAmmPositionsFromTokenIds } from 'hooks/useProAmmPositions' +import { FarmingPool, NFTPosition, UserFarmInfo, UserInfo } from 'state/farms/elastic/types' import { useAppSelector } from 'state/hooks' +import { getPoolAddress } from 'state/mint/proamm/utils' +import { toCallState, useCallsData, useMultipleContractSingleData } from 'state/multicall/hooks' import { useTransactionAdder } from 'state/transactions/hooks' import { TRANSACTION_TYPE, @@ -38,12 +43,13 @@ export const useElasticFarms = () => { export const useFilteredFarms = () => { const { isEVM, networkInfo, chainId } = useActiveWeb3React() + const depositedPositions = useDepositedNfts() const [searchParams] = useSearchParams() const filteredToken0Id = searchParams.get('token0') || undefined const filteredToken1Id = searchParams.get('token1') || undefined - const { farms, loading, userFarmInfo } = useElasticFarms() + const { farms, loading } = useElasticFarms() const type = searchParams.get('type') const activeTab: string = type || FARM_TAB.ACTIVE @@ -130,10 +136,10 @@ export const useFilteredFarms = () => { if (activeTab === FARM_TAB.MY_FARMS && isEVM) { result = result?.map(item => { - if (!userFarmInfo?.[item.id].depositedPositions.length) { + if (!depositedPositions[item.id]?.length) { return { ...item, pools: [] } } - const stakedPools = userFarmInfo?.[item.id].depositedPositions.map(pos => + const stakedPools = depositedPositions[item.id]?.map(pos => computePoolAddress({ factoryAddress: (networkInfo as EVMNetworkInfo).elastic.coreFactory, tokenA: pos.pool.token0, @@ -154,7 +160,7 @@ export const useFilteredFarms = () => { search, activeTab, chainId, - userFarmInfo, + depositedPositions, isEVM, networkInfo, filteredToken0Id, @@ -165,7 +171,6 @@ export const useFilteredFarms = () => { return { farms, loading, - userFarmInfo, filteredFarms, } } @@ -498,3 +503,176 @@ export const usePositionFilter = (positions: PositionDetails[], validPools: stri filterOptions, } } + +const farmInterface = new Interface(ELASTIC_FARM_ABI) + +export function useDepositedNfts(): { [address: string]: NFTPosition[] } { + const { farms } = useElasticFarms() + const farmAddresses = useMemo(() => { + return farms?.map(item => item.id) || [] + }, [farms]) + + const { account } = useActiveWeb3React() + + const inputs = useMemo(() => [account], [account]) + const options = useMemo( + () => ({ + blocksPerFetch: 40, + }), + [], + ) + const result = useMultipleContractSingleData(farmAddresses, farmInterface, 'getDepositedNFTs', inputs, options) + + const nftByFarmAddress = useMemo(() => { + const res: { [key: string]: BigNumber[] } = {} + result.forEach((item, idx) => { + res[farmAddresses[idx]] = item.result?.listNFTs || [] + }) + return res + }, [result, farmAddresses]) + + const tokenIds = useMemo(() => { + return Object.values(nftByFarmAddress).flat() + }, [nftByFarmAddress]) + const { positions = [] } = useProAmmPositionsFromTokenIds(tokenIds) + + return useMemo(() => { + const positionByFarmAddess: { [key: string]: Array } = {} + + farmAddresses.forEach(address => { + const { pools } = farms?.find(item => item.id === address) || {} + + positionByFarmAddess[address] = (nftByFarmAddress[address] || []) + .map((id: BigNumber) => { + const position = positions.find(item => item.tokenId.toString() === id.toString()) + const pool = pools?.find(pool => pool.poolAddress.toLowerCase() === position?.poolId.toLowerCase())?.pool + if (!pool || !position) return null + return new NFTPosition({ + nftId: id, + pool, + liquidity: position.liquidity.toString(), + tickLower: position.tickLower, + tickUpper: position.tickUpper, + }) + }) + .filter(item => item !== null) as NFTPosition[] + }) + + return positionByFarmAddess + }, [farms, positions, nftByFarmAddress, farmAddresses]) +} + +export function useDepositedNftsByFarm(farmAddress: string): NFTPosition[] { + const positionByFarm = useDepositedNfts() + return useMemo(() => positionByFarm[farmAddress] || [], [positionByFarm, farmAddress]) +} + +const getUserInfoFragment = farmInterface.getFunction('getUserInfo') + +export function useJoinedPositions() { + const positions = useDepositedNfts() + const { farms } = useElasticFarms() + + const params = useMemo(() => { + return (farms || []).map(farm => { + const calls: { address: string; callData: string; pos: NFTPosition; pid: string }[] = [] + + const deposited = positions[farm.id] || [] + deposited.forEach(pos => { + const poolAddress = getPoolAddress(pos.pool) + const matchedPools = farm.pools.filter(p => p.poolAddress.toLowerCase() === poolAddress.toLowerCase()) + + matchedPools.forEach(pool => { + if (pos.liquidity.toString() !== '0') + calls.push({ + address: farm.id, + callData: farmInterface.encodeFunctionData(getUserInfoFragment, [pos.nftId, pool.pid]), + pid: pool.pid.toString(), + pos: pos, + }) + }) + }) + return calls + }) + }, [farms, positions]) + + const options = useMemo( + () => ({ + blocksPerFetch: 30, + }), + [], + ) + const callInputs = useMemo(() => params.flat(), [params]) + const rawRes = useCallsData(callInputs, options) + + const result = useMemo(() => rawRes.map(item => toCallState(item, farmInterface, getUserInfoFragment)), [rawRes]) + + return useMemo(() => { + const userInfo: UserFarmInfo = {} + farms?.forEach((farm, idx) => { + const joinedPositions: { [pid: string]: NFTPosition[] } = {} + const rewardPendings: { [pid: string]: CurrencyAmount[] } = {} + const rewardByNft: { [pid_nftId: string]: CurrencyAmount[] } = {} + + const startIdx = idx === 0 ? 0 : params.slice(0, idx + 1).reduce((acc, cur) => acc + cur.length, 0) + const endIdx = startIdx + params[idx].length + + const res = result.slice(startIdx, endIdx) + + params[idx].forEach((param, index) => { + const pid = param.pid.toString() + const nftId = param.pos.nftId + + if (res[index].result) { + if (!joinedPositions[pid]) { + joinedPositions[pid] = [] + } + + const depositedPos = positions[farm.id].find(pos => pos.nftId.eq(nftId)) + const farmingPool = farm.pools.find(p => p.pid === pid) + + if (depositedPos && farmingPool) { + const pos = new NFTPosition({ + nftId, + liquidity: res[index].result?.liquidity, + tickLower: depositedPos.tickLower, + tickUpper: depositedPos.tickUpper, + pool: depositedPos.pool, + }) + joinedPositions[pid].push(pos) + + const id = `${pid}_${nftId.toString()}` + if (!rewardByNft[id]) { + rewardByNft[id] = [] + } + if (!rewardPendings[pid]) { + rewardPendings[pid] = [] + } + farmingPool.rewardTokens.forEach((currency, i) => { + const amount = CurrencyAmount.fromRawAmount(currency, res[index].result?.rewardPending[i]) + rewardByNft[id][i] = amount + + if (!rewardPendings[pid][i]) { + rewardPendings[pid][i] = amount + } else { + rewardPendings[pid][i] = rewardPendings[pid][i].add(amount) + } + }) + } + } + }) + + userInfo[farm.id] = { + joinedPositions, + rewardPendings, + rewardByNft, + } + }) + return userInfo + }, [result, params, farms, positions]) +} + +export function useUserInfoByFarm(farmAddress: string): UserInfo { + const userFarmsInfo = useJoinedPositions() + return useMemo(() => userFarmsInfo[farmAddress] || {}, [userFarmsInfo, farmAddress]) +} diff --git a/src/state/farms/elastic/index.ts b/src/state/farms/elastic/index.ts index 0662cd98e5..9788ef976f 100644 --- a/src/state/farms/elastic/index.ts +++ b/src/state/farms/elastic/index.ts @@ -1,13 +1,11 @@ import { createSlice } from '@reduxjs/toolkit' -import { ElasticFarm, UserFarmInfo } from './types' +import { ElasticFarm } from './types' interface ElasticFarmState { [chainId: number]: { loading: boolean farms: ElasticFarm[] | null - userFarmInfo?: UserFarmInfo - loadingUserInfo: boolean poolFeeLast24h: { [poolId: string]: number } @@ -18,7 +16,6 @@ export const defaultChainData = { loading: false, farms: [], poolFeeLast24h: {}, - loadingUserInfo: false, } as ElasticFarmState[number] const initialState: ElasticFarmState = {} @@ -38,25 +35,12 @@ const slice = createSlice({ } else state[chainId] = { ...state[chainId], loading } }, - setUserFarmInfo( - state, - { payload: { userInfo, chainId } }: { payload: { userInfo: UserFarmInfo; chainId: number } }, - ) { - state[chainId].userFarmInfo = userInfo - }, - setPoolFeeData(state, { payload: { chainId, data } }) { state[chainId].poolFeeLast24h = data }, - - setLoadingUserInfo(state, { payload: { loading, chainId } }) { - if (!state[chainId]) { - state[chainId] = { ...defaultChainData, loadingUserInfo: loading } - } else state[chainId] = { ...state[chainId], loadingUserInfo: loading } - }, }, }) -export const { setFarms, setLoading, setUserFarmInfo, setPoolFeeData, setLoadingUserInfo } = slice.actions +export const { setFarms, setLoading, setPoolFeeData } = slice.actions export default slice.reducer diff --git a/src/state/farms/elastic/types.ts b/src/state/farms/elastic/types.ts index e3c9f0c60a..cdeff655aa 100644 --- a/src/state/farms/elastic/types.ts +++ b/src/state/farms/elastic/types.ts @@ -50,7 +50,6 @@ export class NFTPosition extends Position { } export interface UserInfo { - depositedPositions: NFTPosition[] joinedPositions: { [pid: string]: NFTPosition[] } diff --git a/src/state/farms/elastic/updaters/index.tsx b/src/state/farms/elastic/updaters/index.tsx index 426e4dda50..7dc46ae736 100644 --- a/src/state/farms/elastic/updaters/index.tsx +++ b/src/state/farms/elastic/updaters/index.tsx @@ -11,7 +11,7 @@ export type CommonProps = { const FarmUpdater: React.FC = ({ interval = true }) => { const { isEnableKNProtocol } = useKyberSwapConfig() - useGetUserFarmingInfo(interval) + useGetUserFarmingInfo() if (isEnableKNProtocol) { return diff --git a/src/state/farms/elastic/updaters/useGetUserElasticFarmInfo.ts b/src/state/farms/elastic/updaters/useGetUserElasticFarmInfo.ts index 40ae345941..e7937aa06d 100644 --- a/src/state/farms/elastic/updaters/useGetUserElasticFarmInfo.ts +++ b/src/state/farms/elastic/updaters/useGetUserElasticFarmInfo.ts @@ -1,27 +1,14 @@ import { gql, useLazyQuery } from '@apollo/client' -import { defaultAbiCoder } from '@ethersproject/abi' -import { getCreate2Address } from '@ethersproject/address' -import { keccak256 } from '@ethersproject/solidity' -import { Currency, CurrencyAmount } from '@kyberswap/ks-sdk-core' -import { BigNumber } from 'ethers' -import { Interface } from 'ethers/lib/utils' -import { useCallback, useEffect, useRef } from 'react' +import { useEffect } from 'react' import { useDispatch } from 'react-redux' -import NFTPositionManagerABI from 'constants/abis/v2/ProAmmNFTPositionManager.json' -import ELASTIC_FARM_ABI from 'constants/abis/v2/farm.json' -import { NETWORKS_INFO, isEVM } from 'constants/networks' +import { isEVM } from 'constants/networks' import { useActiveWeb3React } from 'hooks' -import { useMulticallContract } from 'hooks/useContract' import { useKyberSwapConfig } from 'state/application/hooks' import { useAppSelector } from 'state/hooks' import { usePoolBlocks } from 'state/prommPools/hooks' -import { setLoadingUserInfo, setPoolFeeData, setUserFarmInfo } from '..' -import { NFTPosition, UserFarmInfo } from '../types' - -const farmInterface = new Interface(ELASTIC_FARM_ABI) -const positionManagerInterface = new Interface(NFTPositionManagerABI.abi) +import { setPoolFeeData } from '..' const POOL_FEE_HISTORY = gql` query poolFees($block: Int!, $poolIds: [String]!) { @@ -32,225 +19,12 @@ const POOL_FEE_HISTORY = gql` } ` -const defaultChainData = { - loading: false, - farms: null, - poolFeeLast24h: {}, -} -const useGetUserFarmingInfo = (interval?: boolean) => { +const useGetUserFarmingInfo = () => { const dispatch = useDispatch() - const { chainId, account } = useActiveWeb3React() - const multicallContract = useMulticallContract() + const { chainId } = useActiveWeb3React() const { elasticClient } = useKyberSwapConfig() - const elasticFarm = useAppSelector(state => state.elasticFarm[chainId || 1]) || defaultChainData - - const getUserFarmInfo = useCallback(async () => { - const farmAddresses = elasticFarm.farms?.map(farm => farm.id) - - if (isEVM(chainId) && account && farmAddresses?.length && multicallContract) { - dispatch(setLoadingUserInfo({ loading: true, chainId })) - // get userDepositedNFTs - const userDepositedNFTsFragment = farmInterface.getFunction('getDepositedNFTs') - const callData = farmInterface.encodeFunctionData(userDepositedNFTsFragment, [account]) - - const chunks = farmAddresses.map(address => ({ - target: address, - callData, - })) - - const multicallRes = await multicallContract.callStatic.tryBlockAndAggregate(false, chunks) - const returnData = multicallRes.returnData - // listNFTs by contract - const nftResults: Array> = returnData.map((data: [boolean, string]) => { - try { - return data[0] ? farmInterface.decodeFunctionResult(userDepositedNFTsFragment, data[1]).listNFTs : [] - } catch { - return [] - } - }) - - /* - * GET DETAIL NFT - */ - const allNFTs = nftResults.flat() - const nftDetailFragment = positionManagerInterface.getFunction('positions') - const nftDetailChunks = allNFTs.map(id => ({ - target: NETWORKS_INFO[chainId].elastic.nonfungiblePositionManager, - callData: positionManagerInterface.encodeFunctionData(nftDetailFragment, [id]), - })) - - const detailNFTMultiCallData = (await multicallContract.callStatic.tryBlockAndAggregate(false, nftDetailChunks)) - .returnData - - const nftDetailResult = detailNFTMultiCallData.map((data: [boolean, string]) => - data[0] ? positionManagerInterface.decodeFunctionResult(nftDetailFragment, data[1]) : null, - ) - - type NFT_INFO = { - [id: string]: { - poolAddress: string - liquidity: BigNumber - tickLower: BigNumber - tickUpper: BigNumber - } - } - const nftInfos = nftDetailResult.reduce((acc: NFT_INFO, item: any, index: number) => { - if (!item) return acc - return { - ...acc, - [allNFTs[index].toString()]: { - poolAddress: getCreate2Address( - NETWORKS_INFO[chainId].elastic.coreFactory, - keccak256( - ['bytes'], - [ - defaultAbiCoder.encode( - ['address', 'address', 'uint24'], - [item.info.token0, item.info.token1, item.info.fee], - ), - ], - ), - NETWORKS_INFO[chainId].elastic.initCodeHash, - ), - liquidity: item.pos.liquidity, - tickLower: item.pos.tickLower, - tickUpper: item.pos.tickUpper, - }, - } - }, {} as NFT_INFO) - - const promises = - elasticFarm.farms?.map(async (farm, index) => { - const nfts = nftResults[index] - - const depositedPositions: NFTPosition[] = [] - const joinedPositions: { [pid: string]: NFTPosition[] } = {} - const rewardPendings: { [pid: string]: CurrencyAmount[] } = {} - const rewardByNft: { [pid_nftId: string]: CurrencyAmount[] } = {} - - const userInfoParams: Array<[BigNumber, string]> = [] - nfts.forEach(id => { - const matchedPools = farm.pools.filter( - p => p.poolAddress.toLowerCase() === nftInfos[id.toString()]?.poolAddress.toLowerCase(), - ) - - matchedPools.forEach(pool => { - userInfoParams.push([id, pool.pid]) - }) - - if (matchedPools?.[0]) { - const pos = new NFTPosition({ - nftId: id, - pool: matchedPools[0].pool, - liquidity: nftInfos[id.toString()].liquidity, - tickLower: nftInfos[id.toString()].tickLower, - tickUpper: nftInfos[id.toString()].tickUpper, - }) - depositedPositions.push(pos) - } - }) - - const getUserInfoFragment = farmInterface.getFunction('getUserInfo') - if (nfts.length) { - const returnData = ( - await multicallContract.callStatic.tryBlockAndAggregate( - false, - userInfoParams.map(params => { - return { - target: farm.id, - callData: farmInterface.encodeFunctionData(getUserInfoFragment, params), - } - }), - ) - ).returnData.map((item: [boolean, string]) => item[1]) - - const result = returnData.map((data: string) => { - try { - return farmInterface.decodeFunctionResult(getUserInfoFragment, data) - } catch (e) { - return e - } - }) - userInfoParams.forEach((param, index) => { - const pid = param[1].toString() - const nftId = param[0] - if (!(result[index] instanceof Error)) { - if (!joinedPositions[pid]) { - joinedPositions[pid] = [] - } - - const depositedPos = depositedPositions.find(pos => pos.nftId.eq(nftId)) - const farmingPool = farm.pools.find(p => p.pid === pid) - - if (depositedPos && farmingPool) { - const pos = new NFTPosition({ - nftId, - liquidity: result[index].liquidity, - tickLower: depositedPos.tickLower, - tickUpper: depositedPos.tickUpper, - pool: depositedPos.pool, - }) - joinedPositions[pid].push(pos) - - const id = `${pid}_${nftId.toString()}` - if (!rewardByNft[id]) { - rewardByNft[id] = [] - } - if (!rewardPendings[pid]) { - rewardPendings[pid] = [] - } - farmingPool.rewardTokens.forEach((currency, i) => { - const amount = CurrencyAmount.fromRawAmount(currency, result[index].rewardPending[i]) - rewardByNft[id][i] = amount - - if (!rewardPendings[pid][i]) { - rewardPendings[pid][i] = amount - } else { - rewardPendings[pid][i] = rewardPendings[pid][i].add(amount) - } - }) - } - } - }) - } - return { - depositedPositions, - joinedPositions, - rewardPendings, - rewardByNft, - } - }) || [] - - const res = await Promise.all(promises) - - const userInfo = elasticFarm.farms?.reduce((userInfo, farm, index) => { - return { - ...userInfo, - [farm.id]: res[index], - } - }, {} as UserFarmInfo) - - dispatch(setLoadingUserInfo({ chainId, loading: false })) - if (userInfo) dispatch(setUserFarmInfo({ chainId, userInfo })) - } - }, [elasticFarm.farms, chainId, account, multicallContract, dispatch]) - - const getUserFarmInfoRef = useRef(getUserFarmInfo) - getUserFarmInfoRef.current = getUserFarmInfo - - useEffect(() => { - getUserFarmInfoRef.current() - - const i = interval - ? setInterval(() => { - getUserFarmInfoRef.current() - }, 10_000) - : undefined - return () => { - i && clearInterval(i) - } - }, [interval, account, elasticFarm.farms?.length]) + const farms = useAppSelector(state => state.elasticFarm[chainId || 1]?.farms) const { blockLast24h } = usePoolBlocks() const [getPoolInfo, { data: poolFeeData }] = useLazyQuery(POOL_FEE_HISTORY, { @@ -276,7 +50,7 @@ const useGetUserFarmingInfo = (interval?: boolean) => { useEffect(() => { if (!isEVM(chainId)) return - const poolIds = elasticFarm.farms?.map(item => item.pools.map(p => p.poolAddress.toLowerCase())).flat() + const poolIds = farms?.map(item => item.pools.map(p => p.poolAddress.toLowerCase())).flat() if (blockLast24h && poolIds?.length) { getPoolInfo({ @@ -286,7 +60,7 @@ const useGetUserFarmingInfo = (interval?: boolean) => { }, }) } - }, [elasticFarm.farms, blockLast24h, getPoolInfo, chainId]) + }, [farms, blockLast24h, getPoolInfo, chainId]) } export default useGetUserFarmingInfo diff --git a/src/state/farms/elastic/updaters/v1.tsx b/src/state/farms/elastic/updaters/v1.tsx index 028ac26b2f..6d1958b937 100644 --- a/src/state/farms/elastic/updaters/v1.tsx +++ b/src/state/farms/elastic/updaters/v1.tsx @@ -1,5 +1,5 @@ import { gql, useLazyQuery } from '@apollo/client' -import { ChainId, CurrencyAmount, Token, TokenAmount, WETH } from '@kyberswap/ks-sdk-core' +import { CurrencyAmount, Token, TokenAmount, WETH } from '@kyberswap/ks-sdk-core' import { FeeAmount, Pool, Position } from '@kyberswap/ks-sdk-elastic' import { useEffect } from 'react' @@ -153,9 +153,11 @@ const FarmUpdaterV1: React.FC = ({ interval }) => { useEffect(() => { if (!elasticFarm.farms && !elasticFarm.loading) { dispatch(setLoading({ chainId, loading: true })) - getElasticFarms().finally(() => { + try { + getElasticFarms() + } finally { dispatch(setLoading({ chainId, loading: false })) - }) + } } }, [elasticFarm, getElasticFarms, dispatch, chainId]) @@ -163,7 +165,7 @@ const FarmUpdaterV1: React.FC = ({ interval }) => { const i = interval ? setInterval(() => { getElasticFarms() - }, 10_000) + }, 20_000) : undefined return () => { i && clearInterval(i) @@ -178,7 +180,7 @@ const FarmUpdaterV1: React.FC = ({ interval }) => { }, [error, dispatch, chainId]) useEffect(() => { - if (data?.farms && chainId) { + if (data?.farms && chainId && !elasticFarm?.farms?.length) { // transform farm data const formattedData: ElasticFarm[] = data.farms.map((farm: SubgraphFarm) => { return { @@ -236,7 +238,7 @@ const FarmUpdaterV1: React.FC = ({ interval }) => { return { startTime: Number(pool.startTime), - endTime: chainId === ChainId.AVAXMAINNET && pool.pid === '125' ? 1680104783 : Number(pool.endTime), + endTime: Number(pool.endTime), pid: pool.pid, id: pool.id, feeTarget: pool.feeTarget, @@ -266,7 +268,7 @@ const FarmUpdaterV1: React.FC = ({ interval }) => { }) dispatch(setFarms({ chainId, farms: formattedData })) } - }, [data, dispatch, chainId]) + }, [data, dispatch, chainId, elasticFarm]) return null } diff --git a/src/state/farms/elastic/updaters/v2.tsx b/src/state/farms/elastic/updaters/v2.tsx index 88c31bd99f..0494a5e3c5 100644 --- a/src/state/farms/elastic/updaters/v2.tsx +++ b/src/state/farms/elastic/updaters/v2.tsx @@ -48,7 +48,9 @@ const useGetElasticFarms = () => { : '' return useSWR(endpoint, (url: string) => fetch(url).then(resp => resp.json()), { - refreshInterval: 10_000, + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, }) } diff --git a/src/state/farms/elasticv2/updater.tsx b/src/state/farms/elasticv2/updater.tsx index 3327c123b2..ef521f06dd 100644 --- a/src/state/farms/elasticv2/updater.tsx +++ b/src/state/farms/elasticv2/updater.tsx @@ -173,6 +173,9 @@ export default function ElasticFarmV2Updater({ interval = true }: { interval?: b const { fetchPrices } = useTokenPricesWithLoading([]) + const tokensRef = useRef([]) + const pricesRef = useRef<{ [key: string]: number | undefined }>({}) + useEffect(() => { const getData = async () => { if (data?.farmV2S.length && chainId) { @@ -192,7 +195,12 @@ export default function ElasticFarmV2Updater({ interval = true }: { interval?: b ), ] as string[] - const prices = await fetchPrices(tokens) + let prices = pricesRef.current + if (tokensRef.current.sort().join() !== tokens.sort().join()) { + tokensRef.current = tokens + prices = await fetchPrices(tokens) + pricesRef.current = prices + } const formattedData: ElasticFarmV2[] = data.farmV2S.map((farm: SubgraphFarmV2) => { const getToken = (t: SubgraphToken, keepWrapped = false) => { diff --git a/src/state/mint/proamm/utils.ts b/src/state/mint/proamm/utils.ts index a69b7d9eaf..d8efdc6afa 100644 --- a/src/state/mint/proamm/utils.ts +++ b/src/state/mint/proamm/utils.ts @@ -1,14 +1,20 @@ +import { defaultAbiCoder } from '@ethersproject/abi' +import { keccak256 } from '@ethersproject/solidity' import { Price, Token } from '@kyberswap/ks-sdk-core' import { FeeAmount, + Pool, TICK_SPACINGS, TickMath, encodeSqrtRatioX96, nearestUsableTick, priceToClosestTick, } from '@kyberswap/ks-sdk-elastic' +import { getCreate2Address } from 'ethers/lib/utils' import JSBI from 'jsbi' +import { NETWORKS_INFO } from 'constants/networks' +import { EVMNetworkInfo } from 'constants/networks/type' import { rangeData } from 'pages/AddLiquidityV2/constants' import { PairFactor } from 'state/topTokens/type' @@ -88,3 +94,15 @@ export const getRangeTicks = ( return result } + +export function getPoolAddress(pool: Pool): string { + const networkInfo = NETWORKS_INFO[pool.token0.chainId] as EVMNetworkInfo + return getCreate2Address( + networkInfo.elastic.coreFactory, + keccak256( + ['bytes'], + [defaultAbiCoder.encode(['address', 'address', 'uint24'], [pool.token0.address, pool.token1.address, pool.fee])], + ), + networkInfo.elastic.initCodeHash, + ) +} diff --git a/src/state/multicall/hooks.ts b/src/state/multicall/hooks.ts index 3b5a4b09c2..8460ae845b 100644 --- a/src/state/multicall/hooks.ts +++ b/src/state/multicall/hooks.ts @@ -2,12 +2,11 @@ import { FunctionFragment, Interface } from '@ethersproject/abi' import { BigNumber } from '@ethersproject/bignumber' import { Contract } from '@ethersproject/contracts' import { ChainId } from '@kyberswap/ks-sdk-core' -import { useEffect, useMemo } from 'react' +import { useEffect, useMemo, useRef } from 'react' import { useDispatch, useSelector } from 'react-redux' import { EMPTY_ARRAY } from 'constants/index' import { useActiveWeb3React } from 'hooks' -import { useBlockNumber } from 'state/application/hooks' import { AppDispatch, AppState } from 'state/index' import { @@ -53,7 +52,7 @@ export const NEVER_RELOAD: ListenerOptions = { } // the lowest level call for subscribing to contract data -function useCallsData(calls: (Call | undefined)[], options?: ListenerOptions): CallResult[] { +export function useCallsData(calls: (Call | undefined)[], options?: ListenerOptions): CallResult[] { const { chainId, isEVM } = useActiveWeb3React() const callResults = useSelector( state => state.multicall.callResults?.[chainId], @@ -98,23 +97,40 @@ function useCallsData(calls: (Call | undefined)[], options?: ListenerOptions): C } }, [isEVM, dispatch, options, serializedCallKeys, chainId]) - return useMemo( - () => - isEVM && calls.length - ? calls.map(call => { - if (!call) return INVALID_RESULT - - const result = callResults?.[toCallKey(call)] - let data - if (result?.data && result?.data !== '0x') { - data = result.data - } + const lastResults = useRef([]) + return useMemo(() => { + let isChanged = lastResults.current.length !== calls.length + + // Construct results using a for-loop to handle sparse arrays. + // Array.prototype.map would skip empty entries. + const results: CallResult[] = [] + for (let i = 0; i < calls.length; ++i) { + const call = calls[i] + let result = INVALID_RESULT + if (call) { + const callResult = callResults?.[toCallKey(call)] + result = { + valid: true, + data: callResult?.data && callResult.data !== '0x' ? callResult.data : undefined, + blockNumber: callResult?.blockNumber, + } + } - return { valid: true, data, blockNumber: result?.blockNumber } - }) - : EMPTY_ARRAY, - [callResults, calls, isEVM], - ) + isChanged = isChanged || !areCallResultsEqual(result, lastResults.current[i]) + results.push(result) + } + + // Force the results to be referentially stable if they have not changed. + // This is necessary because *all* callResults are passed as deps when initially memoizing the results. + if (isChanged) { + lastResults.current = results + } + return lastResults.current + }, [callResults, calls]) +} + +function areCallResultsEqual(a: CallResult, b: CallResult) { + return a.valid === b.valid && a.data === b.data && a.blockNumber === b.blockNumber } interface CallState { @@ -124,27 +140,24 @@ interface CallState { // true if the result has never been fetched readonly loading: boolean // true if the result is not for the latest block - readonly syncing: boolean + // readonly syncing: boolean // true if the call was made and is synced, but the return data is invalid readonly error: boolean } -const INVALID_CALL_STATE: CallState = { valid: false, result: undefined, loading: false, syncing: false, error: false } -const LOADING_CALL_STATE: CallState = { valid: true, result: undefined, loading: true, syncing: true, error: false } +const INVALID_CALL_STATE: CallState = { valid: false, result: undefined, loading: false, error: false } +const LOADING_CALL_STATE: CallState = { valid: true, result: undefined, loading: true, error: false } -function toCallState( +export function toCallState( callResult: CallResult | undefined, contractInterface: Interface | undefined, fragment: FunctionFragment | undefined, - latestBlockNumber: number | undefined, ): CallState { - if (!callResult) return INVALID_CALL_STATE - const { valid, data, blockNumber } = callResult - if (!valid) return INVALID_CALL_STATE - if (valid && !blockNumber) return LOADING_CALL_STATE - if (!contractInterface || !fragment || !latestBlockNumber) return LOADING_CALL_STATE + if (!callResult || !callResult.valid) return INVALID_CALL_STATE + const { data, blockNumber } = callResult + if (!blockNumber || !contractInterface || !fragment) return LOADING_CALL_STATE const success = data && data.length > 2 - const syncing = (blockNumber ?? 0) < latestBlockNumber + // const syncing = blockNumber < latestBlockNumber let result: Result | undefined = undefined if (success && data) { try { @@ -155,7 +168,6 @@ function toCallState( valid: true, loading: false, error: true, - syncing, result, } } @@ -163,7 +175,6 @@ function toCallState( return { valid: true, loading: false, - syncing, result: result, error: !success, } @@ -197,11 +208,9 @@ export function useSingleContractMultipleData( const results = useCallsData(calls, options) - const latestBlockNumber = useBlockNumber() - return useMemo(() => { - return isEVM ? results.map(result => toCallState(result, contract?.interface, fragment, latestBlockNumber)) : [] - }, [isEVM, fragment, contract, results, latestBlockNumber]) + return isEVM ? results.map(result => toCallState(result, contract?.interface, fragment)) : [] + }, [isEVM, fragment, contract, results]) } export function useMultipleContractSingleData( @@ -243,12 +252,10 @@ export function useMultipleContractSingleData( const results = useCallsData(calls, options) - const latestBlockNumber = useBlockNumber() - return useMemo(() => { - if (isEVM) return results.map(result => toCallState(result, contractInterface, fragment, latestBlockNumber)) + if (isEVM) return results.map(result => toCallState(result, contractInterface, fragment)) return [] - }, [isEVM, results, contractInterface, fragment, latestBlockNumber]) + }, [isEVM, results, contractInterface, fragment]) } export function useSingleCallResult( @@ -276,11 +283,10 @@ export function useSingleCallResult( }, [isEVM, contract, fragment, inputs, gasRequired]) const { valid, data, blockNumber } = useCallsData(calls, options)[0] || {} - const latestBlockNumber = useBlockNumber() return useMemo(() => { - return toCallState({ valid, data, blockNumber }, contract?.interface, fragment, latestBlockNumber) - }, [valid, data, blockNumber, contract, fragment, latestBlockNumber]) + return toCallState({ valid, data, blockNumber }, contract?.interface, fragment) + }, [valid, data, blockNumber, contract, fragment]) } export function useSingleContractWithCallData( @@ -305,16 +311,9 @@ export function useSingleContractWithCallData( const results = useCallsData(calls, options) - const latestBlockNumber = useBlockNumber() - return useMemo(() => { return results.map((result, i) => - toCallState( - result, - contract?.interface, - contract?.interface?.getFunction(callDatas[i].substring(0, 10)), - latestBlockNumber, - ), + toCallState(result, contract?.interface, contract?.interface?.getFunction(callDatas[i].substring(0, 10))), ) - }, [contract, results, latestBlockNumber, callDatas]) + }, [contract, results, callDatas]) } diff --git a/src/wdyr.ts b/src/wdyr.ts new file mode 100644 index 0000000000..fb011c3ed6 --- /dev/null +++ b/src/wdyr.ts @@ -0,0 +1,14 @@ +// eslint-disable-next-line +/// +import whyDidYouRender from '@welldone-software/why-did-you-render' +import * as React from 'react' +import * as ReactRedux from 'react-redux' + +if (import.meta.env.DEV) { + whyDidYouRender(React, { + trackAllPureComponents: true, + trackExtraHooks: [[ReactRedux, 'useSelector']], + logOwnerReasons: true, + logOnDifferentValues: true, + }) +} diff --git a/tsconfig.json b/tsconfig.json index 26277df31c..8094043b36 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,10 @@ "types": ["vite/client", "vite-plugin-svgr/client"], "typeRoots": ["./types"], "baseUrl": "src", - "useUnknownInCatchVariables": false + "useUnknownInCatchVariables": false, + "paths": { + "@/*": ["./src/*"] + } }, "exclude": ["node_modules", "cypress"], "include": ["./src/**/*.ts", "./src/**/*.tsx"] diff --git a/vite.config.ts b/vite.config.ts index e58c3226cb..6f9cf965cf 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,7 @@ import GlobalPolyFill from '@esbuild-plugins/node-globals-polyfill' import lingui from '@lingui/vite-plugin' import react from '@vitejs/plugin-react' +import path from 'path' import { defineConfig } from 'vite' import checker from 'vite-plugin-checker' import svgrPlugin from 'vite-plugin-svgr' @@ -13,6 +14,7 @@ export default defineConfig({ }, plugins: [ react({ + jsxImportSource: '@welldone-software/why-did-you-render', babel: { // Use .babelrc files, necessary to use LinguiJS CLI babelrc: true, @@ -53,6 +55,9 @@ export default defineConfig({ stream: 'stream-browserify', zlib: 'browserify-zlib', util: 'util', + 'react-redux': 'react-redux/dist/react-redux.js', + '@': path.resolve(__dirname, './src/'), + "react-dom/client": "react-dom/profiling", }, }, server: { diff --git a/yarn.lock b/yarn.lock index 28c71e35de..7ddf7f4678 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,15 +22,7 @@ resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.4.tgz#aae21cb858bbb0411949d5b7b3051f4209043f62" integrity sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw== -"@ampproject/remapping@^2.1.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" - integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== - dependencies: - "@jridgewell/gen-mapping" "^0.1.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@ampproject/remapping@^2.2.0": +"@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== @@ -62,14 +54,14 @@ tslib "^2.3.0" zen-observable-ts "^1.2.5" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.21.4": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.21.4": version "7.21.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== dependencies: "@babel/highlight" "^7.18.6" -"@babel/code-frame@^7.22.13": +"@babel/code-frame@^7.16.7", "@babel/code-frame@^7.22.13": version "7.22.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== @@ -159,7 +151,7 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/generator@^7.11.6", "@babel/generator@^7.17.3", "@babel/generator@^7.17.7", "@babel/generator@^7.22.0": +"@babel/generator@^7.11.6", "@babel/generator@^7.22.0": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.3.tgz#0ff675d2edb93d7596c5f6728b52615cfc0df01e" integrity sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A== @@ -169,7 +161,7 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/generator@^7.22.15": +"@babel/generator@^7.17.3", "@babel/generator@^7.17.7", "@babel/generator@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.15.tgz#1564189c7ec94cb8f77b5e8a90c4d200d21b2339" integrity sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA== @@ -186,18 +178,7 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz#bfcd6b7321ffebe33290d68550e2c9d7eb7c7a58" - integrity sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ== - dependencies: - "@babel/compat-data" "^7.22.0" - "@babel/helper-validator-option" "^7.21.0" - browserslist "^4.21.3" - lru-cache "^5.1.1" - semver "^6.3.0" - -"@babel/helper-compilation-targets@^7.22.15": +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== @@ -208,6 +189,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.22.1": + version "7.22.1" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz#bfcd6b7321ffebe33290d68550e2c9d7eb7c7a58" + integrity sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ== + dependencies: + "@babel/compat-data" "^7.22.0" + "@babel/helper-validator-option" "^7.21.0" + browserslist "^4.21.3" + lru-cache "^5.1.1" + semver "^6.3.0" + "@babel/helper-create-class-features-plugin@^7.21.0": version "7.21.4" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz#3a017163dc3c2ba7deb9a7950849a9586ea24c18" @@ -222,17 +214,25 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/helper-split-export-declaration" "^7.18.6" -"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.1": +"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" + integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== + +"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.1": version "7.22.1" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz#ac3a56dbada59ed969d712cf527bd8271fe3eba8" integrity sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA== -"@babel/helper-environment-visitor@^7.22.5": +"@babel/helper-function-name@^7.16.7", "@babel/helper-function-name@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" - integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" + integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== + dependencies: + "@babel/template" "^7.22.5" + "@babel/types" "^7.22.5" -"@babel/helper-function-name@^7.16.7", "@babel/helper-function-name@^7.21.0": +"@babel/helper-function-name@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== @@ -240,28 +240,20 @@ "@babel/template" "^7.20.7" "@babel/types" "^7.21.0" -"@babel/helper-function-name@^7.22.5": +"@babel/helper-hoist-variables@^7.16.7", "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" - integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== dependencies: - "@babel/template" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/helper-hoist-variables@^7.16.7", "@babel/helper-hoist-variables@^7.18.6": +"@babel/helper-hoist-variables@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== dependencies: "@babel/types" "^7.18.6" -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - "@babel/helper-member-expression-to-functions@^7.20.7", "@babel/helper-member-expression-to-functions@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5" @@ -283,7 +275,18 @@ dependencies: "@babel/types" "^7.22.15" -"@babel/helper-module-transforms@^7.17.7", "@babel/helper-module-transforms@^7.21.2", "@babel/helper-module-transforms@^7.22.1": +"@babel/helper-module-transforms@^7.17.7": + version "7.22.17" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.17.tgz#7edf129097a51ccc12443adbc6320e90eab76693" + integrity sha512-XouDDhQESrLHTpnBtCKExJdyY4gJCdrvH2Pyv8r8kovX2U8G0dRUOT45T9XlbLtuu9CLXP15eusnkprhoPV5iQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.15" + +"@babel/helper-module-transforms@^7.21.2", "@babel/helper-module-transforms@^7.22.1": version "7.22.1" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.1.tgz#e0cad47fedcf3cae83c11021696376e2d5a50c63" integrity sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw== @@ -358,20 +361,20 @@ dependencies: "@babel/types" "^7.20.0" -"@babel/helper-split-export-declaration@^7.16.7", "@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-split-export-declaration@^7.22.6": +"@babel/helper-split-export-declaration@^7.16.7", "@babel/helper-split-export-declaration@^7.22.6": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== dependencies: "@babel/types" "^7.22.5" +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-string-parser@^7.19.4", "@babel/helper-string-parser@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" @@ -402,16 +405,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== -"@babel/helpers@^7.17.8", "@babel/helpers@^7.22.0": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.3.tgz#53b74351da9684ea2f694bf0877998da26dd830e" - integrity sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w== - dependencies: - "@babel/template" "^7.21.9" - "@babel/traverse" "^7.22.1" - "@babel/types" "^7.22.3" - -"@babel/helpers@^7.22.15": +"@babel/helpers@^7.17.8", "@babel/helpers@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.15.tgz#f09c3df31e86e3ea0b7ff7556d85cdebd47ea6f1" integrity sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw== @@ -420,6 +414,15 @@ "@babel/traverse" "^7.22.15" "@babel/types" "^7.22.15" +"@babel/helpers@^7.22.0": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.3.tgz#53b74351da9684ea2f694bf0877998da26dd830e" + integrity sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w== + dependencies: + "@babel/template" "^7.21.9" + "@babel/traverse" "^7.22.1" + "@babel/types" "^7.22.3" + "@babel/highlight@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" @@ -448,10 +451,10 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.3.tgz#838ae31893373222cd9062568e2192c670037e00" integrity sha512-vrukxyW/ep8UD1UDzOYpTKQ6abgjFoeG6L+4ar9+c5TN9QnlqiOi6QK7LSR5ewm/ERyGkT/Ai6VboNrxhbr9Uw== -"@babel/parser@^7.16.4", "@babel/parser@^7.17.3", "@babel/parser@^7.17.8": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8" - integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA== +"@babel/parser@^7.17.3", "@babel/parser@^7.17.8", "@babel/parser@^7.20.15", "@babel/parser@^7.21.3", "@babel/parser@^7.22.16": + version "7.22.16" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" + integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== "@babel/parser@^7.21.2": version "7.22.5" @@ -588,16 +591,7 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.16.7", "@babel/template@^7.20.7", "@babel/template@^7.21.9": - version "7.21.9" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.21.9.tgz#bf8dad2859130ae46088a99c1f265394877446fb" - integrity sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ== - dependencies: - "@babel/code-frame" "^7.21.4" - "@babel/parser" "^7.21.9" - "@babel/types" "^7.21.5" - -"@babel/template@^7.22.15", "@babel/template@^7.22.5": +"@babel/template@^7.16.7", "@babel/template@^7.22.15", "@babel/template@^7.22.5": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== @@ -606,6 +600,15 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" +"@babel/template@^7.20.7", "@babel/template@^7.21.9": + version "7.21.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.21.9.tgz#bf8dad2859130ae46088a99c1f265394877446fb" + integrity sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ== + dependencies: + "@babel/code-frame" "^7.21.4" + "@babel/parser" "^7.21.9" + "@babel/types" "^7.21.5" + "@babel/traverse@7.17.3": version "7.17.3" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57" @@ -622,7 +625,23 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.17.3", "@babel/traverse@^7.20.7", "@babel/traverse@^7.22.1", "@babel/traverse@^7.4.5": +"@babel/traverse@^7.17.3": + version "7.22.17" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.17.tgz#b23c203ab3707e3be816043081b4a994fcacec44" + integrity sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.22.15" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.22.16" + "@babel/types" "^7.22.17" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/traverse@^7.20.7", "@babel/traverse@^7.22.1", "@babel/traverse@^7.4.5": version "7.22.1" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.1.tgz#bd22c50b1439cfcfc2fa137b7fdf6c06787456e9" integrity sha512-lAWkdCoUFnmwLBhIRLciFntGYsIIoC6vIbN8zrLPqBnJmPu7Z6nzqnKd7FsxQUNAvZfVZ0x6KdNvNp8zWIOHSQ== @@ -671,7 +690,7 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@babel/types@^7.11.5", "@babel/types@^7.17.0", "@babel/types@^7.18.6", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3": +"@babel/types@^7.11.5", "@babel/types@^7.18.6", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.3.tgz#0cc6af178b91490acaeb4a2f70dcbf27cdf3d8f3" integrity sha512-P3na3xIQHTKY4L0YOG7pM8M8uoUIB910WQaSiiMCZUC2Cy8XFEQONGABFnHWBa2gpGKODTAJcNhi5Zk0sLRrzg== @@ -680,6 +699,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.17.0", "@babel/types@^7.22.17": + version "7.22.17" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.17.tgz#f753352c4610ffddf9c8bc6823f9ff03e2303eee" + integrity sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.15" + to-fast-properties "^2.0.0" + "@babel/types@^7.22.15", "@babel/types@^7.22.5": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.15.tgz#266cb21d2c5fd0b3931e7a91b6dd72d2f617d282" @@ -2102,14 +2130,6 @@ "@walletconnect/utils" "2.0.0-rc.3" bs58 "^5.0.0" -"@jridgewell/gen-mapping@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" - integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== - dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" @@ -2129,7 +2149,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== -"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": +"@jridgewell/set-array@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== @@ -2147,7 +2167,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.15": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -5257,63 +5277,63 @@ magic-string "^0.27.0" react-refresh "^0.14.0" -"@vue/compiler-core@3.2.45": - version "3.2.45" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.45.tgz#d9311207d96f6ebd5f4660be129fb99f01ddb41b" - integrity sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A== +"@vue/compiler-core@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.3.4.tgz#7fbf591c1c19e1acd28ffd284526e98b4f581128" + integrity sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g== dependencies: - "@babel/parser" "^7.16.4" - "@vue/shared" "3.2.45" + "@babel/parser" "^7.21.3" + "@vue/shared" "3.3.4" estree-walker "^2.0.2" - source-map "^0.6.1" + source-map-js "^1.0.2" -"@vue/compiler-dom@3.2.45": - version "3.2.45" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz#c43cc15e50da62ecc16a42f2622d25dc5fd97dce" - integrity sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw== +"@vue/compiler-dom@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz#f56e09b5f4d7dc350f981784de9713d823341151" + integrity sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w== dependencies: - "@vue/compiler-core" "3.2.45" - "@vue/shared" "3.2.45" + "@vue/compiler-core" "3.3.4" + "@vue/shared" "3.3.4" "@vue/compiler-sfc@^3.2.40": - version "3.2.45" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz#7f7989cc04ec9e7c55acd406827a2c4e96872c70" - integrity sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q== - dependencies: - "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.45" - "@vue/compiler-dom" "3.2.45" - "@vue/compiler-ssr" "3.2.45" - "@vue/reactivity-transform" "3.2.45" - "@vue/shared" "3.2.45" + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz#b19d942c71938893535b46226d602720593001df" + integrity sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ== + dependencies: + "@babel/parser" "^7.20.15" + "@vue/compiler-core" "3.3.4" + "@vue/compiler-dom" "3.3.4" + "@vue/compiler-ssr" "3.3.4" + "@vue/reactivity-transform" "3.3.4" + "@vue/shared" "3.3.4" estree-walker "^2.0.2" - magic-string "^0.25.7" + magic-string "^0.30.0" postcss "^8.1.10" - source-map "^0.6.1" + source-map-js "^1.0.2" -"@vue/compiler-ssr@3.2.45": - version "3.2.45" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz#bd20604b6e64ea15344d5b6278c4141191c983b2" - integrity sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ== +"@vue/compiler-ssr@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz#9d1379abffa4f2b0cd844174ceec4a9721138777" + integrity sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ== dependencies: - "@vue/compiler-dom" "3.2.45" - "@vue/shared" "3.2.45" + "@vue/compiler-dom" "3.3.4" + "@vue/shared" "3.3.4" -"@vue/reactivity-transform@3.2.45": - version "3.2.45" - resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz#07ac83b8138550c83dfb50db43cde1e0e5e8124d" - integrity sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ== +"@vue/reactivity-transform@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz#52908476e34d6a65c6c21cd2722d41ed8ae51929" + integrity sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw== dependencies: - "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.45" - "@vue/shared" "3.2.45" + "@babel/parser" "^7.20.15" + "@vue/compiler-core" "3.3.4" + "@vue/shared" "3.3.4" estree-walker "^2.0.2" - magic-string "^0.25.7" + magic-string "^0.30.0" -"@vue/shared@3.2.45": - version "3.2.45" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.45.tgz#a3fffa7489eafff38d984e23d0236e230c818bc2" - integrity sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg== +"@vue/shared@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.3.4.tgz#06e83c5027f464eef861c329be81454bc8b70780" + integrity sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ== "@walletconnect/browser-utils@^1.8.0": version "1.8.0" @@ -5843,6 +5863,13 @@ motion "10.16.2" qrcode "1.5.3" +"@welldone-software/why-did-you-render@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-7.0.1.tgz#09f487d84844bd8e66435843c2e0305702e61efb" + integrity sha512-Qe/8Xxa2G+LMdI6VoazescPzjjkHYduCDa8aHOJR50e9Bgs8ihkfMBY+ev7B4oc3N59Zm547Sgjf8h5y0FOyoA== + dependencies: + lodash "^4" + "@wojtekmaj/date-utils@^1.0.2", "@wojtekmaj/date-utils@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@wojtekmaj/date-utils/-/date-utils-1.0.3.tgz#2dcfd92881425c5923e429c2aec86fb3609032a1" @@ -12480,7 +12507,7 @@ lodash.upperfirst@4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== -lodash@4.17.21, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: +lodash@4.17.21, lodash@^4, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -12579,13 +12606,6 @@ macos-release@^3.1.0: resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-3.1.0.tgz#6165bb0736ae567ed6649e36ce6a24d87cbb7aca" integrity sha512-/M/R0gCDgM+Cv1IuBG1XGdfTFnMEG6PZeT+KGWHO/OG+imqmaD9CH5vHBTycEM3+Kc4uG2Il+tFAuUWLqQOeUA== -magic-string@^0.25.7: - version "0.25.9" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" - integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== - dependencies: - sourcemap-codec "^1.4.8" - magic-string@^0.27.0: version "0.27.0" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3" @@ -12593,6 +12613,13 @@ magic-string@^0.27.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.13" +magic-string@^0.30.0: + version "0.30.3" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.3.tgz#403755dfd9d6b398dfa40635d52e96c5ac095b85" + integrity sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + make-dir@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -12989,11 +13016,6 @@ nano-css@^5.2.1: stacktrace-js "^2.0.2" stylis "^4.0.6" -nanoid@^3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" - integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== - nanoid@^3.3.6: version "3.3.6" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" @@ -14110,11 +14132,11 @@ postcss@^7.0.2: source-map "^0.6.1" postcss@^8.1.10: - version "8.4.20" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.20.tgz#64c52f509644cecad8567e949f4081d98349dc56" - integrity sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g== + version "8.4.29" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.29.tgz#33bc121cf3b3688d4ddef50be869b2a54185a1dd" + integrity sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw== dependencies: - nanoid "^3.3.4" + nanoid "^3.3.6" picocolors "^1.0.0" source-map-js "^1.0.2" From 23a3f004f7a731739b580c2ba8ce2ce92fb0ff30 Mon Sep 17 00:00:00 2001 From: Nam Nguyen Date: Wed, 20 Sep 2023 15:11:47 +0700 Subject: [PATCH 3/9] feat: add FRAX disclaimer (#2234) --- src/assets/svg/partner-farm.svg | 16 ++++++++ .../YieldPools/ElasticFarmGroup/FarmCard.tsx | 2 + .../YieldPools/ElasticFarmGroup/Row.tsx | 7 +++- src/components/YieldPools/PartnerFarmTag.tsx | 39 +++++++++++++++++++ src/components/YieldPools/styleds.tsx | 4 +- src/constants/index.ts | 11 ++++++ .../ElasticFarmv2/components/FarmCard.tsx | 11 ++++-- .../ElasticFarmv2/components/ListView.tsx | 2 + 8 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 src/assets/svg/partner-farm.svg create mode 100644 src/components/YieldPools/PartnerFarmTag.tsx diff --git a/src/assets/svg/partner-farm.svg b/src/assets/svg/partner-farm.svg new file mode 100644 index 0000000000..e1425c1b0c --- /dev/null +++ b/src/assets/svg/partner-farm.svg @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/src/components/YieldPools/ElasticFarmGroup/FarmCard.tsx b/src/components/YieldPools/ElasticFarmGroup/FarmCard.tsx index 7950031d8f..aa17263fa2 100644 --- a/src/components/YieldPools/ElasticFarmGroup/FarmCard.tsx +++ b/src/components/YieldPools/ElasticFarmGroup/FarmCard.tsx @@ -17,6 +17,7 @@ import { Swap as SwapIcon } from 'components/Icons' import Harvest from 'components/Icons/Harvest' import InfoHelper from 'components/InfoHelper' import { MouseoverTooltip } from 'components/Tooltip' +import { PartnerFarmTag } from 'components/YieldPools/PartnerFarmTag' import { APP_PATHS, ELASTIC_BASE_FEE_UNIT } from 'constants/index' import { TOBE_EXTENDED_FARMING_POOLS } from 'constants/v2' import { useActiveWeb3React } from 'hooks' @@ -125,6 +126,7 @@ const FarmCard = ({ FEE {(pool.pool.fee * 100) / ELASTIC_BASE_FEE_UNIT}% + diff --git a/src/components/YieldPools/ElasticFarmGroup/Row.tsx b/src/components/YieldPools/ElasticFarmGroup/Row.tsx index 832addf5dc..e12f791dea 100644 --- a/src/components/YieldPools/ElasticFarmGroup/Row.tsx +++ b/src/components/YieldPools/ElasticFarmGroup/Row.tsx @@ -17,6 +17,7 @@ import { MoneyBag, Swap2 as SwapIcon } from 'components/Icons' import Harvest from 'components/Icons/Harvest' import InfoHelper from 'components/InfoHelper' import { MouseoverTooltip, MouseoverTooltipDesktopOnly } from 'components/Tooltip' +import { PartnerFarmTag } from 'components/YieldPools/PartnerFarmTag' import { APP_PATHS, ELASTIC_BASE_FEE_UNIT } from 'constants/index' import { TOBE_EXTENDED_FARMING_POOLS } from 'constants/v2' import { useActiveWeb3React } from 'hooks' @@ -337,8 +338,10 @@ const Row = ({ - - FEE {(farmingPool.pool.fee * 100) / ELASTIC_BASE_FEE_UNIT}% + + FEE {(farmingPool.pool.fee * 100) / ELASTIC_BASE_FEE_UNIT}% + + { + const { chainId } = useActiveWeb3React() + const isFraxFarm = FRAX_FARMS[chainId]?.map(address => address.toLowerCase()).includes(farmPoolAddress.toLowerCase()) + if (!isFraxFarm) return null + + return ( +
+ + KyberSwap Frax farms do not currently receive KNC incentives. They are continuously available for staking so + that users can participate in KyberSwap Frax gauges to earn FXS emissions. The amount of FXS emissions + depends on the results of each weekly Frax gauge voting cycle. More info:{' '} + https://app.frax.finance/gauge and{' '} + + https://docs.frax.finance/vefxs/gauge + + + } + placement="top" + width="300px" + > + + + Partner Farm + + +
+ ) +} diff --git a/src/components/YieldPools/styleds.tsx b/src/components/YieldPools/styleds.tsx index 12f8d24e51..03207dcc0e 100644 --- a/src/components/YieldPools/styleds.tsx +++ b/src/components/YieldPools/styleds.tsx @@ -206,7 +206,7 @@ export const TableHeader = styled.div<{ fade?: boolean; oddRow?: boolean }>` export const ProMMFarmTableHeader = styled(TableHeader)` padding: 16px; - grid-template-columns: 230px 0.5fr 0.5fr 1fr 1fr 0.75fr 120px; + grid-template-columns: 280px 0.5fr 0.5fr 1fr 1fr 0.75fr 120px; grid-template-areas: 'token_pairs staked_tvl apr ending_in my_deposit reward action'; grid-gap: 2rem; @@ -215,7 +215,7 @@ export const ProMMFarmTableHeader = styled(TableHeader)` background-color: ${({ theme }) => theme.buttonGray}; ${({ theme }) => theme.mediaWidth.upToLarge` - grid-template-columns: 170px 0.5fr 0.75fr 1fr 1fr 0.75fr 120px; + grid-template-columns: 250px 0.5fr 0.75fr 1fr 1fr 0.75fr 120px; grid-gap: 1rem; `}; ` diff --git a/src/constants/index.ts b/src/constants/index.ts index c71fa4e63a..741f6fa4fb 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -380,3 +380,14 @@ export const ICON_IDS = [ 'discord', ] as const export type ICON_ID = typeof ICON_IDS[number] + +export const FRAX_FARMS: { [chainId in ChainId]?: string[] } = { + [ChainId.MAINNET]: [ + '0xe5379f5ee90d70a0f9de0ed8b3cdde3b9427524a', + '0xfd7b111aa83b9b6f547e617c7601efd997f64703', + '0x36240069ff26cecbde04d9e49a2af8d39146263e', + ], + [ChainId.MATIC]: ['0xa5ebdde0f2e657d77bebeda085dd49f6decf8504'], + [ChainId.ARBITRUM]: ['0x6a7dccf168fba624a81b293c2538d31427b5b4bd'], + [ChainId.OPTIMISM]: ['0xa837d04a64acf66912d05cfd9b951e4e399ab680'], +} diff --git a/src/pages/Farm/ElasticFarmv2/components/FarmCard.tsx b/src/pages/Farm/ElasticFarmv2/components/FarmCard.tsx index c9dd8006fa..760f00cc17 100644 --- a/src/pages/Farm/ElasticFarmv2/components/FarmCard.tsx +++ b/src/pages/Farm/ElasticFarmv2/components/FarmCard.tsx @@ -26,6 +26,7 @@ import { MouseoverTooltip } from 'components/Tooltip' import TransactionConfirmationModal, { TransactionErrorContent } from 'components/TransactionConfirmationModal' import { FeeTag } from 'components/YieldPools/ElasticFarmGroup/styleds' import { APRTooltipContent } from 'components/YieldPools/FarmingPoolAPRCell' +import { PartnerFarmTag } from 'components/YieldPools/PartnerFarmTag' import { APP_PATHS, ELASTIC_BASE_FEE_UNIT } from 'constants/index' import { useActiveWeb3React } from 'hooks' import useTheme from 'hooks/useTheme' @@ -229,10 +230,12 @@ function FarmCard({ - - - FEE {farm?.pool?.fee ? (farm?.pool?.fee * 100) / ELASTIC_BASE_FEE_UNIT : 0.03}% - + + + FEE {farm?.pool?.fee ? (farm?.pool?.fee * 100) / ELASTIC_BASE_FEE_UNIT : 0.03}% + + +
diff --git a/src/pages/Farm/ElasticFarmv2/components/ListView.tsx b/src/pages/Farm/ElasticFarmv2/components/ListView.tsx index 2a424103e1..8335109fdb 100644 --- a/src/pages/Farm/ElasticFarmv2/components/ListView.tsx +++ b/src/pages/Farm/ElasticFarmv2/components/ListView.tsx @@ -23,6 +23,7 @@ import { MouseoverTooltip, MouseoverTooltipDesktopOnly } from 'components/Toolti import TransactionConfirmationModal, { TransactionErrorContent } from 'components/TransactionConfirmationModal' import { ButtonColorScheme, MinimalActionButton } from 'components/YieldPools/ElasticFarmGroup/buttons' import { FeeTag } from 'components/YieldPools/ElasticFarmGroup/styleds' +import { PartnerFarmTag } from 'components/YieldPools/PartnerFarmTag' import { ElasticFarmV2TableRow } from 'components/YieldPools/styleds' import { APP_PATHS, ELASTIC_BASE_FEE_UNIT } from 'constants/index' import { useActiveWeb3React } from 'hooks' @@ -191,6 +192,7 @@ export const ListView = ({ > + From 43c6747bef5e00cc10e69fb91408393457e58a05 Mon Sep 17 00:00:00 2001 From: ltthienn <132639843+ltthienn@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:33:27 +0700 Subject: [PATCH 4/9] [QA-55] | E2E - Update cron job schedule (#2253) --- .github/workflows/cypress.yml | 2 +- .github/workflows/schedule.yml | 6 +- cypress.config.ts | 188 ++++++++++++++++----------------- 3 files changed, 96 insertions(+), 100 deletions(-) diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 1f836d7d07..338df4027a 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -116,7 +116,7 @@ jobs: run: |+ #!/bin/bash yarn preview & - yarn test-e2e -e grepTags=smoke,NETWORK=Ethereum + yarn test-e2e -c baseUrl='http://127.0.0.1:4173/' -e grepTags=smoke,NETWORK=Ethereum env: DISPLAY: :0.0 diff --git a/.github/workflows/schedule.yml b/.github/workflows/schedule.yml index bd85fa40ca..ad37319e3d 100644 --- a/.github/workflows/schedule.yml +++ b/.github/workflows/schedule.yml @@ -2,7 +2,8 @@ name: 'E2E Testing Schedule' on: schedule: - - cron: '0 1,5 * * *' # run at 8AM and 12PM (GTM+7) + - cron: '*/20 1-12 * * *' + - cron: '0 13 * * *' workflow_dispatch: jobs: @@ -49,12 +50,11 @@ jobs: echo ENV=${ENV} > .env echo GITHUB_RUN_ID=${GITHUB_RUN_ID} > .env echo CORE_PUSH_GATEWAY_URL=${{ secrets.CORE_PUSH_GATEWAY_URL }} > .env - echo CYPRESS_BASE_URL='https://kyberswap.com/' > .env - name: Run Cypress Test run: |+ #!/bin/bash - yarn test-schedule -e grepTags=regression,NETWORK=${{ matrix.network }} + yarn test-schedule -c baseUrl='https://kyberswap.com/' -e grepTags=regression,NETWORK=${{ matrix.network }} env: DISPLAY: :0.0 diff --git a/cypress.config.ts b/cypress.config.ts index 6c74242f56..1085451248 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -24,129 +24,125 @@ export default defineConfig({ // eslint-disable-next-line @typescript-eslint/no-var-requires require('@cypress/grep/src/plugin')(config) synpressPlugins(on, config) - console.log('baseURL: ', process.env.CYPRESS_BASE_URL) - if (process.env.CYPRESS_BASE_URL === 'https://kyberswap.com/') { - on('after:run', async results => { - if (results) { - const register = new client.Registry() - const prefix = 'e2e_cypress' - const suite = new client.Counter({ - name: `${prefix}_suite`, - help: `${prefix}_suite`, - labelNames: ['buildId', 'result', 'baseName', 'duration', 'chain'] as const, - }) - suite.reset() - const { totalPassed, totalFailed, totalTests, totalDuration, runs } = results - runs.forEach(run => { - const { stats, spec } = run - const { tests, passes, pending, failures, duration } = stats - const { baseName } = spec - suite - .labels({ - buildId: `${process.env.GITHUB_RUN_ID}`, - result: 'failed', - baseName: baseName, - duration: duration, - chain: config.env.NETWORK, - }) - .inc(failures) - - suite - .labels({ - buildId: `${process.env.GITHUB_RUN_ID}`, - result: 'passed', - baseName: baseName, - duration: duration, - chain: config.env.NETWORK, - }) - .inc(passes) - - suite - .labels({ - buildId: `${process.env.GITHUB_RUN_ID}`, - result: 'pending', - baseName: baseName, - duration: duration, - chain: config.env.NETWORK, - }) - .inc(pending) - suite - .labels({ - buildId: `${process.env.GITHUB_RUN_ID}`, - result: 'tests', - baseName: baseName, - duration: duration, - chain: config.env.NETWORK, - }) - .inc(tests) - }) + on('after:run', async results => { + if (results) { + const register = new client.Registry() + const prefix = 'e2e_cypress' + const suite = new client.Counter({ + name: `${prefix}_suite`, + help: `${prefix}_suite`, + labelNames: ['buildId', 'result', 'baseName', 'duration', 'chain'] as const, + }) + suite.reset() + const { totalPassed, totalFailed, totalTests, totalDuration, runs } = results + runs.forEach(run => { + const { stats, spec } = run + const { tests, passes, pending, failures, duration } = stats + const { baseName } = spec + suite + .labels({ + buildId: `${process.env.GITHUB_RUN_ID}`, + result: 'failed', + baseName: baseName, + duration: duration, + chain: config.env.NETWORK, + }) + .inc(failures) suite .labels({ buildId: `${process.env.GITHUB_RUN_ID}`, result: 'passed', - baseName: 'All Specs', - duration: totalDuration, + baseName: baseName, + duration: duration, chain: config.env.NETWORK, }) - .inc(totalPassed) + .inc(passes) suite .labels({ buildId: `${process.env.GITHUB_RUN_ID}`, - result: 'failed', - baseName: 'All Specs', - duration: totalDuration, + result: 'pending', + baseName: baseName, + duration: duration, chain: config.env.NETWORK, }) - .inc(totalFailed) + .inc(pending) suite .labels({ buildId: `${process.env.GITHUB_RUN_ID}`, - result: 'total', - baseName: 'All Specs', - duration: totalDuration, + result: 'tests', + baseName: baseName, + duration: duration, chain: config.env.NETWORK, }) - .inc(totalTests) + .inc(tests) + }) - const testPass = new client.Counter({ - name: `${prefix}_test_passed`, - help: `${prefix}_pass`, - labelNames: ['buildId', 'chain'] as const, + suite + .labels({ + buildId: `${process.env.GITHUB_RUN_ID}`, + result: 'passed', + baseName: 'All Specs', + duration: totalDuration, + chain: config.env.NETWORK, }) + .inc(totalPassed) - const testFail = new client.Counter({ - name: `${prefix}_test_failed`, - help: `${prefix}_fail`, - labelNames: ['buildId', 'chain'] as const, + suite + .labels({ + buildId: `${process.env.GITHUB_RUN_ID}`, + result: 'failed', + baseName: 'All Specs', + duration: totalDuration, + chain: config.env.NETWORK, + }) + .inc(totalFailed) + suite + .labels({ + buildId: `${process.env.GITHUB_RUN_ID}`, + result: 'total', + baseName: 'All Specs', + duration: totalDuration, + chain: config.env.NETWORK, }) + .inc(totalTests) - testPass.reset() - testFail.reset() + const testPass = new client.Counter({ + name: `${prefix}_test_passed`, + help: `${prefix}_pass`, + labelNames: ['buildId', 'chain'] as const, + }) - testFail.labels({ buildId: `${process.env.GITHUB_RUN_ID}`, chain: config.env.NETWORK }).inc(totalFailed) - testPass.labels({ buildId: `${process.env.GITHUB_RUN_ID}`, chain: config.env.NETWORK }).inc(totalPassed) + const testFail = new client.Counter({ + name: `${prefix}_test_failed`, + help: `${prefix}_fail`, + labelNames: ['buildId', 'chain'] as const, + }) - register.registerMetric(testPass) - register.registerMetric(testFail) - register.registerMetric(suite) + testPass.reset() + testFail.reset() - const gateway = new client.Pushgateway(`${process.env.CORE_PUSH_GATEWAY_URL}`, [], register) - await gateway - .push({ jobName: 'ui-automation' }) - .then(({ resp, body }) => { - console.log(`Body: ${body}`) - console.log(`Response status: ${resp}`) - }) - .catch((err: any) => { - console.log('err: ', err) - }) - } - }) - } + testFail.labels({ buildId: `${process.env.GITHUB_RUN_ID}`, chain: config.env.NETWORK }).inc(totalFailed) + testPass.labels({ buildId: `${process.env.GITHUB_RUN_ID}`, chain: config.env.NETWORK }).inc(totalPassed) + + register.registerMetric(testPass) + register.registerMetric(testFail) + register.registerMetric(suite) + + const gateway = new client.Pushgateway(`${process.env.CORE_PUSH_GATEWAY_URL}`, [], register) + await gateway + .push({ jobName: 'ui-automation' }) + .then(({ resp, body }) => { + console.log(`Body: ${body}`) + console.log(`Response status: ${resp}`) + }) + .catch((err: any) => { + console.log('err: ', err) + }) + } + }) }, - baseUrl: process.env.CYPRESS_BASE_URL, specPattern: 'cypress/e2e/specs/*.e2e.cy.ts', }, }) From 9c69c7af214f9906f4ea22bc3e1db0c902ea71ce 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, 21 Sep 2023 09:24:27 +0700 Subject: [PATCH 5/9] update dexs: add kyber-pmm, limit-order-v2 to ks dexes (#2247) --- .../swapv2/LiquiditySourcesPanel/index.tsx | 10 +++++---- src/constants/dexes.ts | 22 +++++-------------- src/state/customizeDexes/updater.tsx | 18 +++++++++------ 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/components/swapv2/LiquiditySourcesPanel/index.tsx b/src/components/swapv2/LiquiditySourcesPanel/index.tsx index acfdddb44c..8925fadf6c 100644 --- a/src/components/swapv2/LiquiditySourcesPanel/index.tsx +++ b/src/components/swapv2/LiquiditySourcesPanel/index.tsx @@ -106,6 +106,8 @@ const LiquiditySourceHeader = styled.div` align-items: center; ` +export const isKyberSwapDex = (id: string) => id.toLowerCase().includes('kyber') + const LiquiditySourcesPanel: React.FC = ({ onBack }) => { const [searchText, setSearchText] = useState('') const debouncedSearchText = useDebounce(searchText.toLowerCase(), 200).trim() @@ -135,7 +137,7 @@ const LiquiditySourcesPanel: React.FC = ({ onBack }) => { }, [excludeDexes, dexes]) const ksDexes = useMemo( - () => dexes.filter(item => item.id.includes('kyberswap')).sort((a, b) => a.sortId - b.sortId), + () => dexes.filter(item => isKyberSwapDex(item.id)).sort((a, b) => a.sortId - b.sortId), [dexes], ) @@ -210,10 +212,10 @@ const LiquiditySourcesPanel: React.FC = ({ onBack }) => { checked={!ksDexes.map(i => i.id).every(item => excludeDexes.includes(item))} onChange={e => { if (e.target.checked) { - setExcludeDexes(excludeDexes.filter(item => !item.includes('kyberswap'))) + setExcludeDexes(excludeDexes.filter(item => !isKyberSwapDex(item))) } else { const newData = [ - ...excludeDexes.filter(item => !item.includes('kyberswap')), + ...excludeDexes.filter(item => !isKyberSwapDex(item)), ...ksDexes.map(item => item.id), ] setExcludeDexes(newData) @@ -244,7 +246,7 @@ const LiquiditySourcesPanel: React.FC = ({ onBack }) => { )} {dexes - ?.filter(item => !item.id.includes('kyberswap') && item.name.toLowerCase().includes(debouncedSearchText)) + ?.filter(item => !isKyberSwapDex(item.id) && item.name.toLowerCase().includes(debouncedSearchText)) .map(({ name, logoURL, id }) => ( handleToggleDex(id)} /> diff --git a/src/constants/dexes.ts b/src/constants/dexes.ts index 05b0301353..8feef13cf9 100644 --- a/src/constants/dexes.ts +++ b/src/constants/dexes.ts @@ -1,32 +1,22 @@ -// To combine kyberswap & kyberswap-static into one option on UI -// They are both kyberswap classic, one is dynamic fee, other is static fee +// To combine all kyber options 1 option on UI export const KYBERSWAP_KS_DEXES_TO_UI_DEXES: { [key: string]: string | undefined } = { - 'kyberswap-elastic': 'kyberswap-elastic', kyberswap: 'kyberswapv1', // kyberswap classic old contract 'kyberswap-static': 'kyberswapv1', // kyberswap classic new contract -> with static fee + 'kyberswap-elastic': 'kyberswap-elastic', 'kyberswap-limit-order': 'kyberswap-limit-order', + 'kyberswap-limit-order-v2': 'kyberswap-limit-order-v2', + 'kyber-pmm': 'kyber-pmm', } -export const KYBERSWAP_UI_DEXES: { +// only put dex need to be custom, otherwise get from admin +export const KYBERSWAP_UI_DEXES_CUSTOM: { [key: string]: { name: string id: string - logoURL: string } } = { - 'kyberswap-elastic': { - name: 'KyberSwap Elastic', - id: 'kyberswap-elastic', - logoURL: 'https://kyberswap.com/favicon.ico', - }, kyberswapv1: { name: 'KyberSwap Classic', id: 'kyberswapv1', - logoURL: 'https://kyberswap.com/favicon.ico', - }, - 'kyberswap-limit-order': { - name: 'KyberSwap Limit Order', - id: 'kyberswap-limit-order', - logoURL: 'https://kyberswap.com/favicon.ico', }, } diff --git a/src/state/customizeDexes/updater.tsx b/src/state/customizeDexes/updater.tsx index 139f264aa5..7c8b15863e 100644 --- a/src/state/customizeDexes/updater.tsx +++ b/src/state/customizeDexes/updater.tsx @@ -1,7 +1,8 @@ import { useEffect, useMemo } from 'react' import { useDispatch } from 'react-redux' -import { KYBERSWAP_KS_DEXES_TO_UI_DEXES, KYBERSWAP_UI_DEXES } from 'constants/dexes' +import { isKyberSwapDex } from 'components/swapv2/LiquiditySourcesPanel' +import { KYBERSWAP_KS_DEXES_TO_UI_DEXES, KYBERSWAP_UI_DEXES_CUSTOM } from 'constants/dexes' import { useActiveWeb3React } from 'hooks' import useLiquiditySources from 'hooks/useAggregatorStats' import { AppDispatch } from 'state/index' @@ -18,16 +19,19 @@ export default function Updater(): null { // filterout kyberswap dexes, will hardcode const normalizeDexes = useMemo(() => { const dexesFormatted: Dex[] = dexes?.map(item => ({ ...item, id: item.dexId, sortId: item.id })) || [] - const dexesOutsideKyberswap = dexesFormatted.filter(item => !item.id.includes('kyberswap')) + const dexesOutsideKyberswap = dexesFormatted.filter(item => !isKyberSwapDex(item.id)) const dexesKyberswap = uniqueArray( dexesFormatted.filter(dex => KYBERSWAP_KS_DEXES_TO_UI_DEXES[dex.id]), dex => KYBERSWAP_KS_DEXES_TO_UI_DEXES[dex.id], ) - const dexesUIKyberswap = dexesKyberswap.map(dex => ({ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - ...KYBERSWAP_UI_DEXES[KYBERSWAP_KS_DEXES_TO_UI_DEXES[dex.id]!], - sortId: dex.sortId, - })) + const dexesUIKyberswap = dexesKyberswap.map(dex => { + const custom = KYBERSWAP_UI_DEXES_CUSTOM[KYBERSWAP_KS_DEXES_TO_UI_DEXES[dex.id] || ''] || dex + return { + ...custom, + sortId: dex.sortId, + logoURL: 'https://kyberswap.com/favicon.ico', + } + }) return [...dexesOutsideKyberswap, ...dexesUIKyberswap] }, [dexes]) From abfc61e62e6c73d9e4c0baaf84a4c809a71c9046 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, 21 Sep 2023 09:42:12 +0700 Subject: [PATCH 6/9] add: alert ks setting api (#2252) --- src/services/baseQueryOauth.ts | 15 ++++---- src/services/ksSetting.ts | 6 ++-- src/utils/iamError.ts | 62 ++++++++++++++++++---------------- 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/services/baseQueryOauth.ts b/src/services/baseQueryOauth.ts index b39f83773c..8b6c1fce8a 100644 --- a/src/services/baseQueryOauth.ts +++ b/src/services/baseQueryOauth.ts @@ -1,19 +1,20 @@ import { KyberOauth2Api } from '@kybernetwork/oauth2' import { BaseQueryFn, fetchBaseQuery } from '@reduxjs/toolkit/query' +import axios from 'axios' -import { checkIamDown } from 'utils/iamError' +import { checkApiDown } from 'utils/iamError' -const queryWithToken = async (config: any, baseUrl: string) => { +const queryWithTokenAndTracking = async (config: any, baseUrl: string, withAccessToken = true) => { try { if (config.method?.toLowerCase() !== 'get') { // mapping rtk query vs axios config.data = config.data || config.body } config.url = baseUrl + config.url - const result = await KyberOauth2Api.call(config) + const result = await (withAccessToken ? KyberOauth2Api.call(config) : axios(config)) return { data: result.data } } catch (err) { - checkIamDown(err) + checkApiDown(err) return { error: { status: err.response?.status, @@ -25,9 +26,9 @@ const queryWithToken = async (config: any, baseUrl: string) => { // this query is use for private api call: this will attach access token in every request, auto refresh token if expired const baseQueryOauth = - ({ baseUrl = '' }: { baseUrl?: string }): BaseQueryFn => + ({ baseUrl = '', trackingOnly }: { baseUrl?: string; trackingOnly?: boolean }): BaseQueryFn => async config => { - return queryWithToken(config, baseUrl) + return queryWithTokenAndTracking(config, baseUrl, !trackingOnly) } // same as baseQueryOauth, but has flag to revert if meet incident @@ -39,7 +40,7 @@ export const baseQueryOauthDynamic = const rawBaseQuery = fetchBaseQuery({ baseUrl }) return rawBaseQuery(args, WebApi, extraOptions) } - return queryWithToken(args, baseUrl) + return queryWithTokenAndTracking(args, baseUrl) } export default baseQueryOauth diff --git a/src/services/ksSetting.ts b/src/services/ksSetting.ts index 4e93177ee4..a262d96812 100644 --- a/src/services/ksSetting.ts +++ b/src/services/ksSetting.ts @@ -1,7 +1,8 @@ import { ApolloClient, NormalizedCacheObject } from '@apollo/client' import { ChainId } from '@kyberswap/ks-sdk-core' -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +import { createApi } from '@reduxjs/toolkit/query/react' import { Connection } from '@solana/web3.js' +import baseQueryOauth from 'services/baseQueryOauth' import { KS_SETTING_API } from 'constants/env' import { AppJsonRpcProvider } from 'constants/providers' @@ -58,8 +59,9 @@ export interface TokenListResponse { const ksSettingApi = createApi({ reducerPath: 'ksSettingConfigurationApi', - baseQuery: fetchBaseQuery({ + baseQuery: baseQueryOauth({ baseUrl: `${KS_SETTING_API}/v1`, + trackingOnly: true, }), endpoints: builder => ({ getKyberswapConfiguration: builder.query({ diff --git a/src/utils/iamError.ts b/src/utils/iamError.ts index e75f3ec6c2..1c54307ae9 100644 --- a/src/utils/iamError.ts +++ b/src/utils/iamError.ts @@ -1,22 +1,29 @@ import { captureException } from '@sentry/react' import { AxiosError } from 'axios' -import { BFF_API, ENV_LEVEL } from 'constants/env' +import { BFF_API, ENV_LEVEL, KS_SETTING_API } from 'constants/env' import { AGGREGATOR_API_PATHS } from 'constants/index' import { ENV_TYPE } from 'constants/type' -const ErrorInfo = { - routeApiError: 0, - iamApoError: 0, - errorThreshold: 2, - sentAlertIamApi: false, - sentAlertRouteApi: false, +enum ErrorType { + ROUTE_ERROR = 'Route API', + IAM_ERROR = 'IAM API', + KS_SETTING_ERROR = 'KsSetting API', + NOT_TRACK = 'Not track', } +const ErrorInfo = Object.values(ErrorType).reduce>( + (rs, cur) => { + rs[cur] = { sentAlert: false, errorCount: 0 } + return rs + }, + {}, +) + const apiDowns: string[] = [] -const isIamApiDown = () => ErrorInfo.iamApoError >= ErrorInfo.errorThreshold -const isRouteApiDown = () => ErrorInfo.routeApiError >= ErrorInfo.errorThreshold +const ERROR_THRESHOLD = 2 +const isApiDown = (type: ErrorType) => ErrorInfo[type]?.errorCount >= ERROR_THRESHOLD const sendError = (name: string, apiUrl: string, trackData: any) => { if (ENV_LEVEL < ENV_TYPE.STG) return @@ -38,11 +45,20 @@ function onDisconnect() { window.addEventListener('online', onConnect, false) window.addEventListener('offline', onDisconnect, false) +const getErrorType = (apiUrl: string) => { + if (apiUrl.endsWith(AGGREGATOR_API_PATHS.GET_ROUTE) || apiUrl.endsWith(AGGREGATOR_API_PATHS.BUILD_ROUTE)) + return ErrorType.ROUTE_ERROR + + if (apiUrl.startsWith(BFF_API) && !blacklistPathBff.some(path => apiUrl.endsWith(path))) return ErrorType.IAM_ERROR + + if (apiUrl.startsWith(KS_SETTING_API)) return ErrorType.KS_SETTING_ERROR + return ErrorType.NOT_TRACK +} + /** * check error status: blocked, maybe cors issues or server down - * only check bff api + 2 route apis */ -export const checkIamDown = (axiosErr: AxiosError) => { +export const checkApiDown = (axiosErr: AxiosError) => { const statusCode = axiosErr?.response?.status const response = axiosErr?.response?.data @@ -73,26 +89,14 @@ export const checkIamDown = (axiosErr: AxiosError) => { apiDowns, } - const isRouteApiDie = - isDie && (apiUrl.endsWith(AGGREGATOR_API_PATHS.GET_ROUTE) || apiUrl.endsWith(AGGREGATOR_API_PATHS.BUILD_ROUTE)) + const errorType = getErrorType(apiUrl) - const isIamDie = isDie && apiUrl.startsWith(BFF_API) && !blacklistPathBff.some(path => apiUrl.endsWith(path)) - - if (isRouteApiDie) { - ErrorInfo.routeApiError++ - if (isRouteApiDown() && !ErrorInfo.sentAlertRouteApi) { - ErrorInfo.sentAlertRouteApi = true - sendError('Route API', apiUrl, trackData) - } - } - if (isIamDie) { - ErrorInfo.iamApoError++ - if (isIamApiDown() && !ErrorInfo.sentAlertIamApi) { - ErrorInfo.sentAlertIamApi = true - sendError('IAM API', apiUrl, trackData) + if (isDie && errorType !== ErrorType.NOT_TRACK) { + ErrorInfo[errorType].errorCount++ + if (isApiDown(errorType) && !ErrorInfo[errorType].sentAlert) { + ErrorInfo[errorType].sentAlert = true + sendError(errorType, apiUrl, trackData) } - } - if (isRouteApiDie || isIamDie) { console.error(`${apiUrl} was down`, trackData) } } From 23cc4de178c871640fab358f2f77e1a25eea1b61 Mon Sep 17 00:00:00 2001 From: Nguyen Van Viet Date: Thu, 21 Sep 2023 11:26:03 +0700 Subject: [PATCH 7/9] minor issues (#2259) * fix: CR-617 * fix: hide num of active pos if its zero --- package.json | 1 - .../YieldPools/FarmingPoolAPRCell.tsx | 30 ++----------------- src/index.tsx | 1 - src/pages/MyEarnings/Positions.tsx | 18 ++++++----- src/services/earning/index.ts | 1 + src/state/farms/elasticv2/updater.tsx | 3 +- src/utils/numbers.ts | 2 +- src/wdyr.ts | 14 --------- yarn.lock | 9 +----- 9 files changed, 16 insertions(+), 63 deletions(-) delete mode 100644 src/wdyr.ts diff --git a/package.json b/package.json index bb9bfc0cfd..89fedc6195 100644 --- a/package.json +++ b/package.json @@ -178,7 +178,6 @@ "@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/parser": "^5.58.0", "@vitejs/plugin-react": "^3.1.0", - "@welldone-software/why-did-you-render": "^7.0.1", "babel-plugin-lodash": "^3.3.4", "babel-plugin-macros": "^3.1.0", "env-cmd": "^10.1.0", diff --git a/src/components/YieldPools/FarmingPoolAPRCell.tsx b/src/components/YieldPools/FarmingPoolAPRCell.tsx index c2406faf64..d7830481d6 100644 --- a/src/components/YieldPools/FarmingPoolAPRCell.tsx +++ b/src/components/YieldPools/FarmingPoolAPRCell.tsx @@ -80,7 +80,7 @@ export const APRTooltipContent = ({ - {!!farmAPR && ( + {!!maxFarmAPR && ( Farm APR:{' '} - {formatDisplayNumber(farmAPR / 100, { style: 'percent', fractionDigits: 2 })} - - - - Estimated return from additional rewards if you also participate in the farm - - - )} - - {!!farmV2APR && Number.isFinite(farmV2APR) && ( - - - Farm APR:{' '} - - {formatDisplayNumber(farmV2APR / 100, { style: 'percent', fractionDigits: 2 })} + {formatDisplayNumber(maxFarmAPR / 100, { style: 'percent', fractionDigits: 2 })} - - - - - - {numOfActivePositions} Active - - + {!!numOfActivePositions && ( + + + + + + {numOfActivePositions} Active + + + )} {numOfInactivePositions ? ( diff --git a/src/services/earning/index.ts b/src/services/earning/index.ts index d7fd8ffc0e..18c775ecbf 100644 --- a/src/services/earning/index.ts +++ b/src/services/earning/index.ts @@ -111,6 +111,7 @@ const earningApi = createApi({ const positionData: GetElasticEarningResponse = (positionsRes?.data as any).data as GetElasticEarningResponse const aggregateData = Object.keys(positionData).reduce((acc, chainName) => { + if (!positionData?.[chainName]) return acc return { ...acc, [chainName]: { diff --git a/src/state/farms/elasticv2/updater.tsx b/src/state/farms/elasticv2/updater.tsx index ef521f06dd..643de26358 100644 --- a/src/state/farms/elasticv2/updater.tsx +++ b/src/state/farms/elasticv2/updater.tsx @@ -107,8 +107,7 @@ export default function ElasticFarmV2Updater({ interval = true }: { interval?: b const dispatch = useAppDispatch() const { networkInfo, isEVM, chainId, account } = useActiveWeb3React() const elasticFarm = useAppSelector(state => state.elasticFarmV2[chainId] || defaultChainData) - const { elasticClient } = useKyberSwapConfig() - const isEnableKNProtocol = true + const { elasticClient, isEnableKNProtocol } = useKyberSwapConfig() const multicallContract = useMulticallContract() const farmv2QuoterContract = useContract( diff --git a/src/utils/numbers.ts b/src/utils/numbers.ts index 69323955e8..8762f090be 100644 --- a/src/utils/numbers.ts +++ b/src/utils/numbers.ts @@ -92,7 +92,7 @@ const parseNum = (value: FormatValue): Fraction => { value instanceof Price ) { const valueStr = (() => { - if (typeof value === 'string') return value + if (typeof value === 'string') return toFixed(+value) if (typeof value === 'number') return toFixed(value) if (value instanceof CurrencyAmount) return value.toFixed(value.currency.decimals) if (value instanceof Price) return value.toFixed(18) diff --git a/src/wdyr.ts b/src/wdyr.ts deleted file mode 100644 index fb011c3ed6..0000000000 --- a/src/wdyr.ts +++ /dev/null @@ -1,14 +0,0 @@ -// eslint-disable-next-line -/// -import whyDidYouRender from '@welldone-software/why-did-you-render' -import * as React from 'react' -import * as ReactRedux from 'react-redux' - -if (import.meta.env.DEV) { - whyDidYouRender(React, { - trackAllPureComponents: true, - trackExtraHooks: [[ReactRedux, 'useSelector']], - logOwnerReasons: true, - logOnDifferentValues: true, - }) -} diff --git a/yarn.lock b/yarn.lock index 7ddf7f4678..223ea84028 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5863,13 +5863,6 @@ motion "10.16.2" qrcode "1.5.3" -"@welldone-software/why-did-you-render@^7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-7.0.1.tgz#09f487d84844bd8e66435843c2e0305702e61efb" - integrity sha512-Qe/8Xxa2G+LMdI6VoazescPzjjkHYduCDa8aHOJR50e9Bgs8ihkfMBY+ev7B4oc3N59Zm547Sgjf8h5y0FOyoA== - dependencies: - lodash "^4" - "@wojtekmaj/date-utils@^1.0.2", "@wojtekmaj/date-utils@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@wojtekmaj/date-utils/-/date-utils-1.0.3.tgz#2dcfd92881425c5923e429c2aec86fb3609032a1" @@ -12507,7 +12500,7 @@ lodash.upperfirst@4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== -lodash@4.17.21, lodash@^4, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: +lodash@4.17.21, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== From bfc13563211c637b725f7e6ee213d506de1ecb20 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, 21 Sep 2023 11:29:30 +0700 Subject: [PATCH 8/9] fix: modal pc can not scroll (#2258) --- src/components/Modal/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx index e873648bec..23609f8146 100644 --- a/src/components/Modal/index.tsx +++ b/src/components/Modal/index.tsx @@ -30,8 +30,6 @@ const StyledDialogContent = styled( ).attrs({ 'aria-label': 'dialog', })` - overflow-y: ${({ mobile }) => (mobile ? 'scroll' : 'hidden')}; - &[data-reach-dialog-content] { margin: ${({ margin }) => margin || '0 0 2rem 0'}; background-color: ${({ theme, bgColor }) => bgColor || theme.tableHeader}; @@ -39,7 +37,7 @@ const StyledDialogContent = styled( padding: 0; width: ${({ width }) => width || '50vw'}; height: ${({ height }) => height || 'auto'}; - overflow-y: ${({ mobile }) => (mobile ? 'scroll' : 'hidden')}; + overflow-y: scroll; overflow-x: hidden; align-self: ${({ mobile }) => (mobile ? 'flex-end' : 'center')}; max-width: ${({ maxWidth }) => (maxWidth && !isNaN(maxWidth) ? `${maxWidth}px` : maxWidth)}; From fe502baeb67c9f5f17cd2270f67313c921a2d0f1 Mon Sep 17 00:00:00 2001 From: Nam Nguyen Date: Fri, 22 Sep 2023 12:02:42 +0700 Subject: [PATCH 9/9] fix: farm spam API (#2262) --- src/state/farms/elasticv2/updater.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/state/farms/elasticv2/updater.tsx b/src/state/farms/elasticv2/updater.tsx index 643de26358..f8b893e5ca 100644 --- a/src/state/farms/elasticv2/updater.tsx +++ b/src/state/farms/elasticv2/updater.tsx @@ -136,9 +136,11 @@ export default function ElasticFarmV2Updater({ interval = true }: { interval?: b if (isEnableKNProtocol) return knProtocolError return subgraphError }, [isEnableKNProtocol, subgraphError, knProtocolError]) + const isLoadingElasticFarm = useRef(elasticFarm.loading) + isLoadingElasticFarm.current = elasticFarm.loading useEffect(() => { - if (isEVM && !elasticFarm?.farms && !elasticFarm?.loading) { + if (isEVM && !elasticFarm?.farms && !isLoadingElasticFarm.current) { dispatch(setLoading({ chainId, loading: true })) if (isEnableKNProtocol) { getElasticFarmV2FromKnProtocol(chainId).finally(() => { @@ -149,7 +151,15 @@ export default function ElasticFarmV2Updater({ interval = true }: { interval?: b dispatch(setLoading({ chainId, loading: false })) }) } - }, [isEVM, chainId, dispatch, getElasticFarmV2, elasticFarm, getElasticFarmV2FromKnProtocol, isEnableKNProtocol]) + }, [ + isEVM, + chainId, + dispatch, + getElasticFarmV2, + elasticFarm?.farms, + getElasticFarmV2FromKnProtocol, + isEnableKNProtocol, + ]) useEffect(() => { const i = interval