diff --git a/src/components/HoverInlineText/index.tsx b/src/components/HoverInlineText/index.tsx index d126164692..2a27593689 100644 --- a/src/components/HoverInlineText/index.tsx +++ b/src/components/HoverInlineText/index.tsx @@ -45,7 +45,7 @@ const HoverInlineText = ({ if (text.length > maxCharacters) { return ( - + setShowHover(true)} onMouseLeave={() => setShowHover(false)} diff --git a/src/components/YieldPools/ElasticFarmGroup/PostionDetail.tsx b/src/components/YieldPools/ElasticFarmGroup/PostionDetail.tsx index 3e60b705f9..158b82b7dd 100644 --- a/src/components/YieldPools/ElasticFarmGroup/PostionDetail.tsx +++ b/src/components/YieldPools/ElasticFarmGroup/PostionDetail.tsx @@ -16,6 +16,7 @@ import { FarmingPool, NFTPosition } from 'state/farms/elastic/types' import { Bound } from 'state/mint/proamm/type' import { formatTickPrice } from 'utils/formatTickPrice' import { formatDollarAmount } from 'utils/numbers' +import { unwrappedToken } from 'utils/wrappedCurrency' import FeeTarget from './FeeTarget' import { ButtonColorScheme, MinimalActionButton } from './buttons' @@ -144,16 +145,16 @@ const PositionDetail = ({ dropdownContent={ <> - + - {item.amount0.toSignificant(8)} {item.amount0.currency.symbol} + {item.amount0.toSignificant(8)} {unwrappedToken(item.amount0.currency).symbol} - + - {item.amount1.toSignificant(8)} {item.amount1.currency.symbol} + {item.amount1.toSignificant(8)} {unwrappedToken(item.amount1.currency).symbol} @@ -176,7 +177,7 @@ const PositionDetail = ({ - {rw.toSignificant(8)} {rw.currency.wrapped.symbol} + {rw.toSignificant(8)} {rw.currency.symbol} ))} diff --git a/src/components/YieldPools/ElasticFarmGroup/Row.tsx b/src/components/YieldPools/ElasticFarmGroup/Row.tsx index 5d21afa44e..9d6e8c9826 100644 --- a/src/components/YieldPools/ElasticFarmGroup/Row.tsx +++ b/src/components/YieldPools/ElasticFarmGroup/Row.tsx @@ -23,9 +23,10 @@ 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 } from 'state/farms/elastic/hooks' +import { useElasticFarms, usePositionFilter } from 'state/farms/elastic/hooks' import { FarmingPool, NFTPosition } from 'state/farms/elastic/types' import { useViewMode } from 'state/user/hooks' import { VIEW_MODE } from 'state/user/reducer' @@ -65,7 +66,7 @@ const Row = ({ onHarvest: () => void tokenPrices: { [key: string]: number } }) => { - const { chainId, networkInfo } = useActiveWeb3React() + const { chainId, networkInfo, account } = useActiveWeb3React() const theme = useTheme() const currentTimestamp = Math.floor(Date.now() / 1000) const [viewMode] = useViewMode() @@ -74,17 +75,19 @@ const Row = ({ const [isRevertPrice, setIsRevertPrice] = useState(false) const { userFarmInfo } = useElasticFarms() const joinedPositions = userFarmInfo?.[fairlaunchAddress]?.joinedPositions[farmingPool.pid] || [] + const depositedPositions = userFarmInfo?.[fairlaunchAddress]?.depositedPositions.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() + 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() ) }) || [] @@ -142,15 +145,17 @@ const Row = ({ getFeeTargetInfo() }, [contract, farmingPool.feeTarget, fairlaunchAddress, farmingPool.pid, userFarmInfo]) - const canStake = - farmingPool.endTime > currentTimestamp && - depositedPositions.some(pos => { - const stakedPos = joinedPositions.find(j => j.nftId.toString() === pos.nftId.toString()) - return !stakedPos - ? true - : BigNumber.from(pos.liquidity.toString()).gt(BigNumber.from(stakedPos.liquidity.toString())) - }) + const { positions } = useProAmmPositions(account) + + const { eligiblePositions } = usePositionFilter(positions || [], [farmingPool.poolAddress.toLowerCase()]) + const canUpdateLiquidity = depositedPositions.some(pos => { + const stakedPos = joinedPositions.find(j => j.nftId.toString() === pos.nftId.toString()) + return !stakedPos + ? true + : BigNumber.from(pos.liquidity.toString()).gt(BigNumber.from(stakedPos.liquidity.toString())) + }) + const canStake = farmingPool.endTime > currentTimestamp && (eligiblePositions.length > 0 || canUpdateLiquidity) const canHarvest = rewardPendings.some(amount => amount.greaterThan(0)) const canUnstake = !!joinedPositions.length diff --git a/src/components/YieldPools/ElasticFarmGroup/buttons.tsx b/src/components/YieldPools/ElasticFarmGroup/buttons.tsx index 7cf19cfe44..4f7716422d 100644 --- a/src/components/YieldPools/ElasticFarmGroup/buttons.tsx +++ b/src/components/YieldPools/ElasticFarmGroup/buttons.tsx @@ -114,8 +114,6 @@ export const WithdrawButton: React.FC> style, ...others }) => { - const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) - const renderButton = () => { return ( > onClick={onClick} disabled={disabled} style={{ - width: upToExtraSmall ? '100%' : 'max-content', + width: 'max-content', height: '38px', padding: '12px', ...style, @@ -158,8 +156,6 @@ export const HarvestAllButton: React.FC style, ...others }) => { - const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) - return ( style={{ boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.16)', whiteSpace: 'nowrap', - width: upToExtraSmall ? '100%' : 'max-content', + width: 'max-content', height: '38px', ...style, }} diff --git a/src/components/YieldPools/ElasticFarmGroup/index.tsx b/src/components/YieldPools/ElasticFarmGroup/index.tsx index 8c1e07f075..dca0303ce1 100644 --- a/src/components/YieldPools/ElasticFarmGroup/index.tsx +++ b/src/components/YieldPools/ElasticFarmGroup/index.tsx @@ -13,10 +13,8 @@ import { ButtonPrimary } from 'components/Button' import { AutoColumn } from 'components/Column' import CurrencyLogo from 'components/CurrencyLogo' import HoverDropdown from 'components/HoverDropdown' -import Deposit from 'components/Icons/Deposit' -import Harvest from 'components/Icons/Harvest' import InfoHelper from 'components/InfoHelper' -import { MouseoverTooltipDesktopOnly } from 'components/Tooltip' +import { MouseoverTooltip, MouseoverTooltipDesktopOnly, TextDashed } from 'components/Tooltip' import { FARM_TAB, ZERO_ADDRESS } from 'constants/index' import { NETWORKS_INFO, isEVM } from 'constants/networks' import { useActiveWeb3React } from 'hooks' @@ -36,19 +34,11 @@ import { formatDollarAmount } from 'utils/numbers' import { ClickableText, ProMMFarmTableHeader } from '../styleds' import Row, { Pool } from './Row' import { - ConnectWalletButton, - DepositButton, //ForceWithdrawButton, + ConnectWalletButton, //ForceWithdrawButton, HarvestAllButton, WithdrawButton, } from './buttons' -import { - DepositedContainer, - FarmList, - RewardAndDepositInfo, - RewardContainer, - RewardDetail, - RewardDetailContainer, -} from './styleds' +import { FarmList } from './styleds' const FarmContent = styled.div<{ borderBottom: boolean; borderTop: boolean }>` background: ${({ theme }) => theme.background}; @@ -278,6 +268,7 @@ const ProMMFarmGroup: React.FC = ({ const tab = searchParams.get('type') || 'active' const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) + const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) const [viewMode] = useViewMode() @@ -298,6 +289,7 @@ const ProMMFarmGroup: React.FC = ({ whiteSpace: 'nowrap', height: '38px', padding: '0 12px', + width: 'fit-content', }} onClick={handleApprove} disabled @@ -356,7 +348,14 @@ const ProMMFarmGroup: React.FC = ({ const renderFarmGroupHeader = () => { return ( - + Dynamic Farms @@ -374,16 +373,15 @@ const ProMMFarmGroup: React.FC = ({ {!isApprovedForAll && res?.loading ? ( - ) : ( - - {!account ? : renderApproveButton()} - {/* - account && canWithdraw && isApprovedForAll && ( - onOpenModal('forcedWithdraw')} /> - ) - */} + ) : !account ? ( + + + ) : ( + renderApproveButton() )} + + {isApprovedForAll && !!account && summaryRewardAndDepositInfo()} ) } @@ -541,115 +539,118 @@ const ProMMFarmGroup: React.FC = ({ const summaryRewardAndDepositInfo = () => { return ( - - - - {!upToExtraSmall && } - - - + + + + + Deposited Liquidity - - - - - {formatDollarAmount(depositedUsd)} - - ) : ( - '--' - ) - } - hideIcon={!account || !depositedUsd} - dropdownContent={ - Object.values(userDepositedTokenAmounts).some(amount => amount.greaterThan(0)) ? ( - - {Object.values(userDepositedTokenAmounts).map( - amount => - amount.greaterThan(0) && ( - - - - {amount.toSignificant(8)} {amount.currency.symbol} - - - ), - )} - - ) : ( - '' - ) - } - /> - - - - - onOpenModal('deposit')} - /> - onOpenModal('withdraw')} + + + + + {formatDollarAmount(depositedUsd)} + + ) : ( + '--' + ) + } + hideIcon={!account || !depositedUsd} + dropdownContent={ + Object.values(userDepositedTokenAmounts).some(amount => amount.greaterThan(0)) ? ( + + {Object.values(userDepositedTokenAmounts).map( + amount => + amount.greaterThan(0) && ( + + + + {amount.toSignificant(8)} {amount.currency.symbol} + + + ), + )} + + ) : ( + '' + ) + } /> - - - - - {!upToExtraSmall && } - - - Available Rewards - - - {formatDollarAmount(rewardUSD)} - - ) : ( - '--' - ) - } - hideIcon={!account || !rewardUSD} - dropdownContent={ - Object.values(rewardAmounts).length ? ( - - {Object.values(rewardAmounts).map( - amount => - amount.greaterThan(0) && ( - - - - {amount.toSignificant(8)} {amount.currency.symbol} - - - ), - )} - - ) : ( - '' - ) - } - /> - - + onOpenModal('withdraw')} + /> + + + + {' '} + + Rewards + + + {account && !!rewardUSD ? formatDollarAmount(rewardUSD) : '$0.00'} + + } + hideIcon={!account || !rewardUSD} + dropdownContent={ + Object.values(rewardAmounts).length ? ( + + {Object.values(rewardAmounts).map( + amount => + amount.greaterThan(0) && ( + + + + {amount.toSignificant(8)} {amount.currency.symbol} + + + ), + )} + + ) : ( + '' + ) + } + /> + onOpenModal('harvest')} disabled={!account || !canHarvest} /> - - + + ) } @@ -681,7 +682,6 @@ const ProMMFarmGroup: React.FC = ({ return ( {renderFarmGroupHeader()} - {summaryRewardAndDepositInfo()} {tab === FARM_TAB.MY_FARMS ? ( <> diff --git a/src/components/YieldPools/ElasticFarmGroup/styleds.tsx b/src/components/YieldPools/ElasticFarmGroup/styleds.tsx index 08b7cda346..265977795e 100644 --- a/src/components/YieldPools/ElasticFarmGroup/styleds.tsx +++ b/src/components/YieldPools/ElasticFarmGroup/styleds.tsx @@ -4,77 +4,6 @@ import styled, { css } from 'styled-components' import bgimg from 'assets/images/card-background.png' import { ButtonLight } from 'components/Button' -export const RewardAndDepositInfo = styled.div` - display: flex; - margin: 0 1.5rem; - gap: 24px; - - ${({ theme }) => theme.mediaWidth.upToMedium` - flex-direction: column; - `}; - - ${({ theme }) => theme.mediaWidth.upToExtraSmall` - margin: 0 1rem; - `} -` -export const RewardContainer = styled.div` - border-radius: 20px; - width: calc(100% / 3 - 16px); - padding: 1.25rem 1rem; - display: flex; - align-items: center; - justify-content: space-between; - background: ${({ theme }) => theme.radialGradient}; - - ${({ theme }) => theme.mediaWidth.upToLarge` - width: 100%; - flex: 1 - `}; - - ${({ theme }) => theme.mediaWidth.upToExtraSmall` - flex-direction: column; - gap: 16px; - `} -` - -export const DepositedContainer = styled.div` - flex: 1; - border-radius: 1.25rem; - padding: 1.25rem 1rem; - background: ${({ theme }) => theme.buttonBlack}; - display: flex; - align-items: center; - justify-content: space-between; - - ${({ theme }) => theme.mediaWidth.upToExtraSmall` - flex-direction: column; - gap: 16px; - `}; -` - -export const RewardDetailContainer = styled.div` - display: flex; - align-items: center; - gap: 12px; - - ${({ theme }) => theme.mediaWidth.upToExtraSmall` - width: 100%; - `}; -` - -export const RewardDetail = styled.div` - display: flex; - flex-direction: column; - gap: 8px; - - ${({ theme }) => theme.mediaWidth.upToExtraSmall` - flex-direction: row; - justify-content: space-between; - align-items: center; - width: 100%; - `}; -` - export const FarmList = styled.div<{ gridMode: boolean }>` margin: 1.5rem; border-radius: 20px; diff --git a/src/components/YieldPools/ElasticFarmModals/StakeModal.tsx b/src/components/YieldPools/ElasticFarmModals/StakeModal.tsx index 3e16313f7e..3b7e609463 100644 --- a/src/components/YieldPools/ElasticFarmModals/StakeModal.tsx +++ b/src/components/YieldPools/ElasticFarmModals/StakeModal.tsx @@ -1,8 +1,8 @@ -import { ChainId } from '@kyberswap/ks-sdk-core' import { computePoolAddress } from '@kyberswap/ks-sdk-elastic' import { Trans } from '@lingui/macro' import { BigNumber } from 'ethers' import { useEffect, useMemo, useRef, useState } from 'react' +import { isMobile } from 'react-device-detect' import { Info, X } from 'react-feather' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' @@ -14,20 +14,24 @@ import { ButtonEmpty, ButtonPrimary } from 'components/Button' import Checkbox from 'components/CheckBox' import CurrencyLogo from 'components/CurrencyLogo' import DoubleCurrencyLogo from 'components/DoubleLogo' +import LocalLoader from 'components/LocalLoader' import Modal from 'components/Modal' import { MouseoverTooltip } from 'components/Tooltip' import { APP_PATHS } from 'constants/index' import { NETWORKS_INFO, isEVM } from 'constants/networks' import { useActiveWeb3React } from 'hooks' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' +import { useProAmmPositions } from 'hooks/useProAmmPositions' import useTheme from 'hooks/useTheme' -import { StakeParam, useElasticFarms, useFarmAction } from 'state/farms/elastic/hooks' +import { Tab } from 'pages/Pools/styleds' +import { StakeParam, useElasticFarms, useFarmAction, usePositionFilter } from 'state/farms/elastic/hooks' import { NFTPosition } from 'state/farms/elastic/types' import { useTokenPrices } from 'state/tokenPrices/hooks' import { StyledInternalLink } from 'theme' import { formatDollarAmount } from 'utils/numbers' import { unwrappedToken } from 'utils/wrappedCurrency' +import { TabGroup } from '../styleds' import { ModalContentWrapper, TableHeader, TableRow, Title } from './styled' const generateCommonCSS = (isUnstake: boolean) => { @@ -226,18 +230,22 @@ function StakeModal({ }) { const theme = useTheme() const checkboxGroupRef = useRef() - const { chainId, networkInfo } = useActiveWeb3React() + const { account, chainId, networkInfo } = useActiveWeb3React() + + const { positions, loading: positionsLoading } = useProAmmPositions(account) + + const { eligiblePositions } = usePositionFilter(positions || [], [poolAddress]) const { farms, userFarmInfo } = useElasticFarms() const selectedFarm = farms?.find(farm => farm.id.toLowerCase() === selectedFarmAddress.toLowerCase()) - const { stake, unstake, emergencyWithdraw } = useFarmAction(selectedFarmAddress) + const { stake, unstake, depositAndJoin } = useFarmAction(selectedFarmAddress) const selectedPool = selectedFarm?.pools.find(pool => Number(pool.pid) === Number(poolId)) const { token0, token1 } = selectedPool || {} - const eligibleNfts: ExplicitNFT[] = useMemo(() => { + const depositedNfts: ExplicitNFT[] = useMemo(() => { if (!isEVM(chainId)) return [] const joinedPositions = userFarmInfo?.[selectedFarmAddress]?.joinedPositions?.[poolId] || [] const depositedPositions = @@ -254,7 +262,7 @@ function StakeModal({ ) }) || [] - return depositedPositions + const depositedNfts = depositedPositions .map(item => { const stakedLiquidity = BigNumber.from( joinedPositions.find(pos => pos.nftId.toString() === item.nftId.toString())?.liquidity.toString() || 0, @@ -284,8 +292,50 @@ function StakeModal({ } return BigNumber.from(item.staked.liquidity.toString()).gt(BigNumber.from(0)) }) + + return depositedNfts }, [type, selectedPool, chainId, poolId, poolAddress, selectedFarmAddress, userFarmInfo]) + const depositAndJoinNfts = useMemo( + () => + selectedPool + ? eligiblePositions.map(item => ({ + available: new NFTPosition({ + nftId: item.tokenId, + pool: selectedPool.pool, + liquidity: item.liquidity.toString(), + tickLower: item.tickLower, + tickUpper: item.tickUpper, + }), + poolAddress, + staked: new NFTPosition({ + nftId: item.tokenId, + pool: selectedPool.pool, + liquidity: '0', + tickLower: item.tickLower, + tickUpper: item.tickUpper, + }), + })) + : [], + [selectedPool, eligiblePositions, poolAddress], + ) + + const [tab, setTab] = useState<'stake' | 'deposit'>(() => (type === 'stake' ? 'deposit' : 'stake')) + + useEffect(() => { + if (eligiblePositions.length && !depositedNfts.length) { + setTab('deposit') + } else if (!eligiblePositions.length && depositedNfts.length) { + setTab('stake') + } + }, [eligiblePositions.length, depositedNfts.length]) + + const eligibleNfts = tab === 'stake' ? depositedNfts : depositAndJoinNfts + + useEffect(() => { + setSeletedNFTs([]) + }, [tab]) + const [selectedNFTs, setSeletedNFTs] = useState([]) const { mixpanelHandler } = useMixpanel() useEffect(() => { @@ -310,7 +360,10 @@ function StakeModal({ stakedLiquidity: e.staked.liquidity.toString(), })) if (type === 'stake') { - const txhash = await stake(BigNumber.from(poolId), params) + let txhash = '' + if (tab === 'deposit') txhash = await depositAndJoin(BigNumber.from(poolId), params) + else txhash = await stake(BigNumber.from(poolId), params) + if (txhash) { mixpanelHandler(MIXPANEL_TYPE.ELASTIC_STAKE_LIQUIDITY_COMPLETED, { token_1: token0?.symbol, @@ -318,16 +371,12 @@ function StakeModal({ }) } } else { - if (chainId === ChainId.AVAXMAINNET && Number(poolId) === 125) { - await emergencyWithdraw(params.map(item => item.nftId)) - } else { - const txhash = await unstake(BigNumber.from(poolId), params) - if (txhash) { - mixpanelHandler(MIXPANEL_TYPE.ELASTIC_UNSTAKE_LIQUIDITY_COMPLETED, { - token_1: token0?.symbol, - token_2: token1?.symbol, - }) - } + const txhash = await unstake(BigNumber.from(poolId), params) + if (txhash) { + mixpanelHandler(MIXPANEL_TYPE.ELASTIC_UNSTAKE_LIQUIDITY_COMPLETED, { + token_1: token0?.symbol, + token_2: token1?.symbol, + }) } } onDismiss() @@ -365,7 +414,9 @@ function StakeModal({ )} - {!eligibleNfts.length ? ( + {positionsLoading && type === 'stake' ? ( + + ) : !eligibleNfts.length ? ( type === 'stake' ? ( - You haven't deposited any liquidity positions (NFT tokens) for this farming pair yet. + You don't have any liquidity positions (NFT tokens) for this farming pair yet.

- Add liquidity to this pool first in our{' '} - Pools page. If - you've done that, deposit your liquidity position (NFT tokens) before you stake + Add liquidity to this pool first on our{' '} + Pools page, + then stake your liquidity positions (NFT tokens) to start earning rewards
@@ -393,6 +444,16 @@ function StakeModal({ ) ) : ( <> + {!!eligiblePositions.length && !!depositedNfts.length && type === 'stake' && ( + + setTab('deposit')}> + New Positions + + setTab('stake')}> + Deposited Positions + + + )} - -
+ +
- Farm V1 APR:{' '} + Dynamic Farm APR:{' '} {farmAPR.toFixed(2)}% @@ -123,7 +123,7 @@ export const APRTooltipContent = ({ }} > - Farm V2 APR:{' '} + Static Farm APR:{' '} {farmV2APR.toFixed(2)}% diff --git a/src/constants/abis/v2/farm.json b/src/constants/abis/v2/farm.json index a2b8132662..e311a032d5 100644 --- a/src/constants/abis/v2/farm.json +++ b/src/constants/abis/v2/farm.json @@ -5,6 +5,11 @@ "internalType": "contract IERC721", "name": "_nft", "type": "address" + }, + { + "internalType": "contract IKSElasticLMHelper", + "name": "_helper", + "type": "address" } ], "stateMutability": "nonpayable", @@ -298,6 +303,19 @@ "name": "UpdateOperator", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "enableOrDisable", + "type": "bool" + } + ], + "name": "UpdateSpecialFeatureEnabled", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -419,6 +437,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "pId", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "nftIds", + "type": "uint256[]" + } + ], + "name": "depositAndJoin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "emergencyEnable", @@ -953,6 +989,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "specialFeatureEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -1031,6 +1080,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "enableOrDisable", + "type": "bool" + } + ], + "name": "updateSpecialFeatureEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "weth", diff --git a/src/pages/Farm/ElasticFarmv2/components/FarmCard.tsx b/src/pages/Farm/ElasticFarmv2/components/FarmCard.tsx index 6be79bba6e..bf6955b093 100644 --- a/src/pages/Farm/ElasticFarmv2/components/FarmCard.tsx +++ b/src/pages/Farm/ElasticFarmv2/components/FarmCard.tsx @@ -108,7 +108,7 @@ function FarmCard({ }) { const theme = useTheme() const { account, networkInfo } = useActiveWeb3React() - const [activeRangeIndex, setActiveRangeIndex] = useState(0) + const [activeRangeIndex, setActiveRangeIndex] = useState(farm.ranges[0].index) const [, setSharePoolAddress] = useShareFarmAddress() @@ -133,6 +133,7 @@ function FarmCard({ }) const myDepositUSD = stakedPos.reduce((total, item) => item.stakedUsdValue + total, 0) + const rewardUsd = stakedPos.reduce((total, item) => item.unclaimedRewardsUsd + total, 0) const isEnded = currentTimestamp > farm.endTime const isAllRangesInactive = !farm.ranges.some(item => !item.isRemoved) @@ -197,6 +198,8 @@ function FarmCard({ const mixpanelPayload = { farm_pool_address: farm.poolAddress, farm_id: farm.id, farm_fid: farm.fId } + const range = farm.ranges.find(range => range.index === activeRangeIndex) + return ( <> @@ -329,13 +332,7 @@ function FarmCard({ - } + text={} placement="top" > This indicates that range is idle. Staked positions in this range is still earning small amount of rewards. @@ -361,15 +358,13 @@ function FarmCard({ > - {farm.ranges[activeRangeIndex].isRemoved ? ( + {range?.isRemoved ? ( Idle Range ) : ( @@ -380,12 +375,8 @@ function FarmCard({ - - {(poolAPR + (farm.ranges[activeRangeIndex].apr || 0)).toFixed(2)}% + + {(poolAPR + (range?.apr || 0)).toFixed(2)}% ), @@ -395,9 +386,17 @@ function FarmCard({ - - {hasRewards ? My Rewards : Rewards} - + + + {hasRewards ? My Rewards : Rewards} + + + {rewardUsd > 0 && ( + + {formatDollarAmount(rewardUsd)} + + )} + {farm.totalRewards.map((rw, index: number) => ( <> diff --git a/src/pages/Farm/ElasticFarmv2/components/NewRangesNotiModal.tsx b/src/pages/Farm/ElasticFarmv2/components/NewRangesNotiModal.tsx index b35659687c..d73e137ebb 100644 --- a/src/pages/Farm/ElasticFarmv2/components/NewRangesNotiModal.tsx +++ b/src/pages/Farm/ElasticFarmv2/components/NewRangesNotiModal.tsx @@ -80,9 +80,9 @@ export default function NewRangesNotiModal({ updatedFarms }: { updatedFarms: Ela {hasIdleRange && hasNewRange ? ( - One or more of the Elastic v2 farm ranges you were participating in have become idle and have new farming - ranges. You are still earning farming rewards from this idle farm range. However, to continue earning more - rewards, please stake your liquidity into the other ranges instead + One or more of the Elastic static farm ranges you were participating in have become idle and have new + farming ranges. You are still earning farming rewards from this idle farm range. However, to continue + earning more rewards, please stake your liquidity into the other ranges instead ) : hasNewRange ? ( @@ -91,7 +91,7 @@ export default function NewRangesNotiModal({ updatedFarms }: { updatedFarms: Ela ) : ( - One or more of the Elastic v2 farm ranges you were participating in have become idle. You are still + One or more of the Elastic static farm ranges you were participating in have become idle. You are still earning farming rewards from this idle farm range. However, to continue earning more rewards, please stake your liquidity into the other active ranges instead diff --git a/src/pages/Farm/ElasticFarmv2/index.tsx b/src/pages/Farm/ElasticFarmv2/index.tsx index d04f37c3b4..78daf7114a 100644 --- a/src/pages/Farm/ElasticFarmv2/index.tsx +++ b/src/pages/Farm/ElasticFarmv2/index.tsx @@ -1,3 +1,4 @@ +import { Currency, CurrencyAmount } from '@kyberswap/ks-sdk-core' import { Trans, t } from '@lingui/macro' import mixpanel from 'mixpanel-browser' import { useState } from 'react' @@ -9,10 +10,13 @@ import styled from 'styled-components' import { ReactComponent as QuestionSquareIcon } from 'assets/svg/question_icon_square.svg' import { ButtonPrimary } from 'components/Button' +import { AutoColumn } from 'components/Column' +import CurrencyLogo from 'components/CurrencyLogo' import Divider from 'components/Divider' +import HoverDropdown from 'components/HoverDropdown' import InfoHelper from 'components/InfoHelper' -import { RowBetween, RowFit } from 'components/Row' -import { MouseoverTooltipDesktopOnly } from 'components/Tooltip' +import { RowFit } from 'components/Row' +import { MouseoverTooltip, MouseoverTooltipDesktopOnly, TextDashed } from 'components/Tooltip' import { ConnectWalletButton } from 'components/YieldPools/ElasticFarmGroup/buttons' import { FarmList } from 'components/YieldPools/ElasticFarmGroup/styleds' import { ClickableText, ElasticFarmV2TableHeader } from 'components/YieldPools/styleds' @@ -30,6 +34,8 @@ import useGetElasticPools from 'state/prommPools/useGetElasticPools' import { useIsTransactionPending } from 'state/transactions/hooks' import { useViewMode } from 'state/user/hooks' import { VIEW_MODE } from 'state/user/reducer' +import { MEDIA_WIDTHS } from 'theme' +import { formatDollarAmount } from 'utils/numbers' import FarmCard from './components/FarmCard' import { ListView } from './components/ListView' @@ -70,13 +76,26 @@ export default function ElasticFarmv2({ const { chainId, account } = useActiveWeb3React() const farmAddress = (NETWORKS_INFO[chainId] as EVMNetworkInfo).elastic?.farmV2Contract const above1000 = useMedia('(min-width: 1000px)') + const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) const [searchParams, setSearchParams] = useSearchParams() const sortField = searchParams.get('orderBy') || SORT_FIELD.MY_DEPOSIT const sortDirection = searchParams.get('orderDirection') || SORT_DIRECTION.DESC - const { filteredFarms, farms, updatedFarms } = useFilteredFarmsV2() + const { filteredFarms, farms, updatedFarms, userInfo } = useFilteredFarmsV2() + const depositedUsd = userInfo?.reduce((acc, cur) => acc + cur.positionUsdValue, 0) || 0 + + const depositedTokenAmounts: { [address: string]: CurrencyAmount } = {} + userInfo?.map(item => { + const address0 = item.position.amount0.currency.wrapped.address + const address1 = item.position.amount1.currency.wrapped.address + if (!depositedTokenAmounts[address0]) depositedTokenAmounts[address0] = item.position.amount0 + else depositedTokenAmounts[address0] = depositedTokenAmounts[address0].add(item.position.amount0) + + if (!depositedTokenAmounts[address1]) depositedTokenAmounts[address1] = item.position.amount1 + else depositedTokenAmounts[address1] = depositedTokenAmounts[address1].add(item.position.amount1) + }) const { approve } = useFarmV2Action() const posManager = useProAmmNFTPositionManagerContract() @@ -103,7 +122,50 @@ export default function ElasticFarmv2({ if (res?.loading) return if (isApprovedForAll) { - return null + return ( + + + + Deposited Liquidity + + + + + {formatDollarAmount(depositedUsd)} + + ) : ( + '--' + ) + } + hideIcon={!account || !depositedUsd} + dropdownContent={ + Object.values(depositedTokenAmounts).some(amount => amount.greaterThan(0)) ? ( + + {Object.values(depositedTokenAmounts).map( + amount => + amount.greaterThan(0) && ( + + + + {amount.toSignificant(8)} {amount.currency.symbol} + + + ), + )} + + ) : ( + '' + ) + } + /> + + ) } if (approvalTx && isApprovalTxPending) { @@ -288,10 +350,15 @@ export default function ElasticFarmv2({ if (!filteredFarms?.length) return null const listMode = above1000 && viewMode === VIEW_MODE.LIST + return ( {!!updatedFarms?.length && } - + Static Farms @@ -307,7 +374,7 @@ export default function ElasticFarmv2({ {renderApproveButton()} - + { [addTransactionWithType, contract, address], ) + const depositAndJoin = useCallback( + async (pid: BigNumber, selectedNFTs: StakeParam[]) => { + if (!contract) { + throw new Error(CONTRACT_NOT_FOUND_MSG) + } + + const nftIds = selectedNFTs.map(item => item.nftId) + + const estimateGas = await contract.estimateGas.depositAndJoin(pid, nftIds) + const tx = await contract.depositAndJoin(pid, nftIds, { + gasLimit: calculateGasMargin(estimateGas), + }) + addTransactionWithType({ + hash: tx.hash, + type: TRANSACTION_TYPE.STAKE, + extraInfo: getTransactionExtraInfo( + selectedNFTs.map(e => e.position), + selectedNFTs.map(e => e.poolAddress), + nftIds.map(e => e.toString()), + ), + }) + + return tx.hash + }, + [addTransactionWithType, contract], + ) + const stake = useCallback( async (pid: BigNumber, selectedNFTs: StakeParam[]) => { if (!contract) { @@ -392,7 +419,7 @@ export const useFarmAction = (address: string) => { [addTransactionWithType, contract], ) - return { deposit, withdraw, approve, stake, unstake, harvest, emergencyWithdraw } + return { deposit, withdraw, approve, stake, unstake, harvest, emergencyWithdraw, depositAndJoin } } const filterOptions = [ diff --git a/src/state/farms/elasticv2/updater.tsx b/src/state/farms/elasticv2/updater.tsx index a78a5b7f84..cdbf6f81b9 100644 --- a/src/state/farms/elasticv2/updater.tsx +++ b/src/state/farms/elasticv2/updater.tsx @@ -88,7 +88,7 @@ const queryFarms = gql` amount index } - ranges { + ranges(orderBy: isRemoved) { id index isRemoved