From cd9f2e18b36a280d25c8727f730414c95ea90979 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 21 Nov 2024 17:05:16 +0500 Subject: [PATCH] fix(yield): show green banner only when vampire attack is ready (#5064) * fix(yield): show green banner only when vampire attack is ready * fix(yield): make APR green only when another token is alternative * chore: fix displaying of apy * chore: fix cow amm banner displaying * chore: reset lp token balances * chore: reset balances loaded flag * chore: reset balances cache * chore: fix lpTokensWithBalancesAtom state * chore: fix lint --- .../CoWAmmBanner/useVampireAttack.ts | 101 ------------------ .../LpBalancesAndAllowancesUpdater.tsx | 4 + .../yield/containers/YieldWidget/elements.tsx | 2 +- .../yield/containers/YieldWidget/index.tsx | 21 ++-- .../yield/state/lpTokensWithBalancesAtom.ts | 4 +- .../LpTokensWithBalancesUpdater/index.ts | 29 ++--- .../yield/updaters/VampireAttackUpdater.tsx | 6 +- .../src/updaters/BalancesCacheUpdater.tsx | 18 ++-- 8 files changed, 55 insertions(+), 130 deletions(-) delete mode 100644 apps/cowswap-frontend/src/common/containers/CoWAmmBanner/useVampireAttack.ts diff --git a/apps/cowswap-frontend/src/common/containers/CoWAmmBanner/useVampireAttack.ts b/apps/cowswap-frontend/src/common/containers/CoWAmmBanner/useVampireAttack.ts deleted file mode 100644 index e6bec7d64c..0000000000 --- a/apps/cowswap-frontend/src/common/containers/CoWAmmBanner/useVampireAttack.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { useAtomValue } from 'jotai' -import { useMemo } from 'react' - -import { LP_TOKEN_LIST_COW_AMM_ONLY, useAllLpTokens } from '@cowprotocol/tokens' -import { LpTokenProvider } from '@cowprotocol/types' - -import { useLpTokensWithBalances, usePoolsInfo } from 'modules/yield/shared' -import { POOLS_AVERAGE_DATA_MOCK } from 'modules/yield/updaters/PoolsInfoUpdater/mockPoolInfo' - -import { TokenWithAlternative, TokenWithSuperiorAlternative, VampireAttackContext } from './types' - -import { useSafeMemoObject } from '../../hooks/useSafeMemo' -import { areLpBalancesLoadedAtom } from '../../updaters/LpBalancesAndAllowancesUpdater' - -export function useVampireAttack(): VampireAttackContext | null { - const { tokens: lpTokensWithBalances, count: lpTokensWithBalancesCount } = useLpTokensWithBalances() - const cowAmmLpTokens = useAllLpTokens(LP_TOKEN_LIST_COW_AMM_ONLY) - const poolsInfo = usePoolsInfo() - const areLpBalancesLoaded = useAtomValue(areLpBalancesLoadedAtom) - - const alternativesResult = useMemo(() => { - if (lpTokensWithBalancesCount === 0) return null - - const result = Object.keys(lpTokensWithBalances).reduce( - (acc, tokenAddress) => { - const { token: lpToken, balance: tokenBalance } = lpTokensWithBalances[tokenAddress] - const alternative = cowAmmLpTokens.find((cowAmmLpToken) => { - return cowAmmLpToken.tokens.every((token) => lpToken.tokens.includes(token)) - }) - - if (alternative) { - const tokenPoolInfo = poolsInfo?.[lpToken.address.toLowerCase()]?.info - const alternativePoolInfo = poolsInfo?.[alternative.address.toLowerCase()]?.info - - // When CoW AMM pool has better APY - if (alternativePoolInfo?.apy && tokenPoolInfo?.apy && alternativePoolInfo.apy > tokenPoolInfo.apy) { - acc.superiorAlternatives.push({ - token: lpToken, - alternative, - tokenPoolInfo, - alternativePoolInfo, - tokenBalance, - }) - } else { - acc.alternatives.push({ token: lpToken, alternative, tokenBalance }) - } - } - - return acc - }, - { superiorAlternatives: [] as TokenWithSuperiorAlternative[], alternatives: [] as TokenWithAlternative[] }, - ) - - return { - superiorAlternatives: result.superiorAlternatives.sort((a, b) => { - if (!b.tokenPoolInfo || !a.tokenPoolInfo) return 0 - - return b.tokenPoolInfo.apy - a.tokenPoolInfo.apy - }), - alternatives: result.alternatives.sort((a, b) => { - const aBalance = lpTokensWithBalances[a.token.address.toLowerCase()].balance - const bBalance = lpTokensWithBalances[b.token.address.toLowerCase()].balance - - return +bBalance.sub(aBalance).toString() - }), - } - }, [lpTokensWithBalancesCount, lpTokensWithBalances, cowAmmLpTokens, poolsInfo]) - - const averageApy = useMemo(() => { - const keys = Object.keys(POOLS_AVERAGE_DATA_MOCK) - let count = 0 - - return ( - keys.reduce((result, _key) => { - const key = _key as LpTokenProvider - - if (key === LpTokenProvider.COW_AMM) return result - - count++ - const pool = POOLS_AVERAGE_DATA_MOCK[key] - - return result + (pool?.apy || 0) - }, 0) / count - ) - }, []) - - const { [LpTokenProvider.COW_AMM]: cowAmmData, ...poolsAverageData } = POOLS_AVERAGE_DATA_MOCK - const averageApyDiff = cowAmmData ? +(cowAmmData.apy - averageApy).toFixed(2) : 0 - - const context = useSafeMemoObject({ - superiorAlternatives: alternativesResult?.superiorAlternatives || null, - alternatives: alternativesResult?.alternatives || null, - cowAmmLpTokensCount: cowAmmLpTokens.length, - poolsAverageData, - averageApyDiff, - }) - - if (cowAmmLpTokens.length === 0 || !areLpBalancesLoaded) return null - - return context -} diff --git a/apps/cowswap-frontend/src/common/updaters/LpBalancesAndAllowancesUpdater.tsx b/apps/cowswap-frontend/src/common/updaters/LpBalancesAndAllowancesUpdater.tsx index 1ce5c28e8e..939a7f5b81 100644 --- a/apps/cowswap-frontend/src/common/updaters/LpBalancesAndAllowancesUpdater.tsx +++ b/apps/cowswap-frontend/src/common/updaters/LpBalancesAndAllowancesUpdater.tsx @@ -50,5 +50,9 @@ export function LpBalancesAndAllowancesUpdater({ account, chainId, enablePolling return () => clearTimeout(timeout) }, []) + useEffect(() => { + setAreLpBalancesLoaded(false) + }, [account, setAreLpBalancesLoaded]) + return null } diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/elements.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/elements.tsx index cc03577e42..7b4411d9a0 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/elements.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/elements.tsx @@ -29,7 +29,7 @@ export const CoWAmmInlineBanner = ({ token, apyDiff }: { token: LpToken | undefi {token && apyDiff && apyDiff > 0 ? ( <> Convert your LP tokens into CoW AMM pools and earn up to{' '} - +{apyDiff}% more yield compared to . Or, swap + +{apyDiff.toFixed(1)}% more yield compared to . Or, swap ) : ( 'Swap' diff --git a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx index 1888b6d921..9ee5f5e376 100644 --- a/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/yield/containers/YieldWidget/index.tsx @@ -27,7 +27,7 @@ import { CurrencyInfo } from 'common/pure/CurrencyInputPanel/types' import { CoWAmmInlineBanner, SelectAPoolButton } from './elements' import { usePoolsInfo } from '../../hooks/usePoolsInfo' -import { useVampireAttackFirstTarget } from '../../hooks/useVampireAttack' +import { useVampireAttack, useVampireAttackFirstTarget } from '../../hooks/useVampireAttack' import { useYieldDerivedState } from '../../hooks/useYieldDerivedState' import { useYieldDeadlineState, @@ -58,7 +58,7 @@ const YIELD_UNLOCK_SCREEN = { } export function YieldWidget() { - const { chainId } = useWalletInfo() + const { chainId, account } = useWalletInfo() const { showRecipient } = useYieldSettings() const deadlineState = useYieldDeadlineState() const recipientToggleState = useYieldRecipientToggleState() @@ -69,6 +69,7 @@ export function YieldWidget() { const widgetActions = useYieldWidgetActions() const receiveAmountInfo = useReceiveAmountInfo() const poolsInfo = usePoolsInfo() + const vampireAttackContext = useVampireAttack() const vampireAttackTarget = useVampireAttackFirstTarget() const { @@ -100,6 +101,11 @@ export function YieldWidget() { const inputApy = inputPoolState?.info.apy const outputApy = outputPoolState?.info.apy + const isTradeContainAlternativePool = + inputCurrency instanceof LpToken && + outputCurrency instanceof LpToken && + inputCurrency.tokens.every((token) => outputCurrency.tokens.includes(token)) + const inputCurrencyInfo: CurrencyInfo = { field: Field.INPUT, currency: inputCurrency, @@ -113,8 +119,7 @@ export function YieldWidget() { outputApy : true), )} @@ -136,7 +141,7 @@ export function YieldWidget() { inputApy : true), )} @@ -161,7 +166,11 @@ export function YieldWidget() { const rateInfoParams = useRateInfoParams(inputCurrencyInfo.amount, outputCurrencyInfo.amount) const slots: TradeWidgetSlots = { - topContent: , + topContent: vampireAttackContext ? ( + + ) : !account ? ( + + ) : null, selectTokenWidget: , settingsWidget: , bottomContent: useCallback( diff --git a/apps/cowswap-frontend/src/modules/yield/state/lpTokensWithBalancesAtom.ts b/apps/cowswap-frontend/src/modules/yield/state/lpTokensWithBalancesAtom.ts index 8c8a9593ba..2098983618 100644 --- a/apps/cowswap-frontend/src/modules/yield/state/lpTokensWithBalancesAtom.ts +++ b/apps/cowswap-frontend/src/modules/yield/state/lpTokensWithBalancesAtom.ts @@ -10,6 +10,6 @@ export type LpTokenWithBalance = { type LpTokensWithBalancesState = { tokens: Record; count: 0 } -export const LP_TOKENS_WITH_BALANCES_DEFAULT_STATE: LpTokensWithBalancesState = { tokens: {}, count: 0 } +export const LP_TOKENS_WITH_BALANCES_DEFAULT_STATE: () => LpTokensWithBalancesState = () => ({ tokens: {}, count: 0 }) -export const lpTokensWithBalancesAtom = atom(LP_TOKENS_WITH_BALANCES_DEFAULT_STATE) +export const lpTokensWithBalancesAtom = atom(LP_TOKENS_WITH_BALANCES_DEFAULT_STATE()) diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/LpTokensWithBalancesUpdater/index.ts b/apps/cowswap-frontend/src/modules/yield/updaters/LpTokensWithBalancesUpdater/index.ts index 9be3864707..00236675b9 100644 --- a/apps/cowswap-frontend/src/modules/yield/updaters/LpTokensWithBalancesUpdater/index.ts +++ b/apps/cowswap-frontend/src/modules/yield/updaters/LpTokensWithBalancesUpdater/index.ts @@ -3,12 +3,14 @@ import { useEffect } from 'react' import { useTokensBalances } from '@cowprotocol/balances-and-allowances' import { TokenListCategory, useAllLpTokens } from '@cowprotocol/tokens' +import { useWalletInfo } from '@cowprotocol/wallet' import { LP_TOKENS_WITH_BALANCES_DEFAULT_STATE, lpTokensWithBalancesAtom } from '../../state/lpTokensWithBalancesAtom' const LP_CATEGORY = [TokenListCategory.LP] export function LpTokensWithBalancesUpdater() { + const { account } = useWalletInfo() const lpTokens = useAllLpTokens(LP_CATEGORY) const { values: balances } = useTokensBalances() const setState = useSetAtom(lpTokensWithBalancesAtom) @@ -16,23 +18,26 @@ export function LpTokensWithBalancesUpdater() { useEffect(() => { if (!lpTokens.length) return - const state = lpTokens.reduce( - (acc, token) => { - const addressLower = token.address.toLowerCase() - const balance = balances[addressLower] + const state = lpTokens.reduce((acc, token) => { + const addressLower = token.address.toLowerCase() + const balance = balances[addressLower] - if (balance && !balance.isZero()) { - acc.count++ - acc.tokens[addressLower] = { token, balance } - } + if (balance && !balance.isZero()) { + acc.count++ + acc.tokens[addressLower] = { token, balance } + } - return acc - }, - { ...LP_TOKENS_WITH_BALANCES_DEFAULT_STATE }, - ) + return acc + }, LP_TOKENS_WITH_BALANCES_DEFAULT_STATE()) setState(state) }, [setState, lpTokens, balances]) + useEffect(() => { + if (!account) { + setState(LP_TOKENS_WITH_BALANCES_DEFAULT_STATE()) + } + }, [account, setState]) + return null } diff --git a/apps/cowswap-frontend/src/modules/yield/updaters/VampireAttackUpdater.tsx b/apps/cowswap-frontend/src/modules/yield/updaters/VampireAttackUpdater.tsx index 3a64725f1e..06fcad7cff 100644 --- a/apps/cowswap-frontend/src/modules/yield/updaters/VampireAttackUpdater.tsx +++ b/apps/cowswap-frontend/src/modules/yield/updaters/VampireAttackUpdater.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo } from 'react' import { LP_TOKEN_LIST_COW_AMM_ONLY, useAllLpTokens } from '@cowprotocol/tokens' import { LpTokenProvider } from '@cowprotocol/types' +import { useWalletInfo } from '@cowprotocol/wallet' import { useLpTokensWithBalances, usePoolsInfo } from 'modules/yield/shared' import { POOLS_AVERAGE_DATA_MOCK } from 'modules/yield/updaters/PoolsInfoUpdater/mockPoolInfo' @@ -14,6 +15,7 @@ import { vampireAttackAtom } from '../state/vampireAttackAtom' import { TokenWithAlternative, TokenWithSuperiorAlternative } from '../types' export function VampireAttackUpdater(): null { + const { account } = useWalletInfo() const { tokens: lpTokensWithBalances, count: lpTokensWithBalancesCount } = useLpTokensWithBalances() const cowAmmLpTokens = useAllLpTokens(LP_TOKEN_LIST_COW_AMM_ONLY) const poolsInfo = usePoolsInfo() @@ -102,12 +104,12 @@ export function VampireAttackUpdater(): null { }) useEffect(() => { - if (cowAmmLpTokens.length === 0 || !areLpBalancesLoaded) { + if (!account || cowAmmLpTokens.length === 0 || !areLpBalancesLoaded) { setVampireAttack(null) } else { setVampireAttack(context) } - }, [context, cowAmmLpTokens.length, areLpBalancesLoaded, setVampireAttack]) + }, [account, context, cowAmmLpTokens.length, areLpBalancesLoaded, setVampireAttack]) return null } diff --git a/libs/balances-and-allowances/src/updaters/BalancesCacheUpdater.tsx b/libs/balances-and-allowances/src/updaters/BalancesCacheUpdater.tsx index 724b5f6c9c..5ad2d697df 100644 --- a/libs/balances-and-allowances/src/updaters/BalancesCacheUpdater.tsx +++ b/libs/balances-and-allowances/src/updaters/BalancesCacheUpdater.tsx @@ -1,18 +1,23 @@ import { useAtom } from 'jotai/index' -import { useEffect, useRef } from 'react' +import { useEffect, useLayoutEffect, useRef } from 'react' -import { SupportedChainId } from '@cowprotocol/cow-sdk' +import { mapSupportedNetworks, SupportedChainId } from '@cowprotocol/cow-sdk' import { BigNumber } from '@ethersproject/bignumber' import { balancesAtom, balancesCacheAtom } from '../state/balancesAtom' -export function BalancesCacheUpdater({ chainId }: { chainId: SupportedChainId }) { +export function BalancesCacheUpdater({ chainId, account }: { chainId: SupportedChainId; account?: string }) { const [balances, setBalances] = useAtom(balancesAtom) const [balancesCache, setBalancesCache] = useAtom(balancesCacheAtom) const areBalancesRestoredFromCacheRef = useRef(false) // Persist into localStorage only non-zero balances useEffect(() => { + if (!account) { + setBalancesCache(mapSupportedNetworks({})) + return + } + setBalancesCache((state) => { const balancesValues = balances.values @@ -50,12 +55,13 @@ export function BalancesCacheUpdater({ chainId }: { chainId: SupportedChainId }) }, } }) - }, [chainId, balances.values, setBalancesCache, setBalances]) + }, [chainId, account, balances.values, setBalancesCache]) // Restore balances from cache once - useEffect(() => { + useLayoutEffect(() => { const cache = balancesCache[chainId] + if (!account) return if (areBalancesRestoredFromCacheRef.current) return if (!cache) return @@ -83,7 +89,7 @@ export function BalancesCacheUpdater({ chainId }: { chainId: SupportedChainId }) }) return - }, [balancesCache, chainId, setBalances]) + }, [balancesCache, chainId, account, setBalances]) return null }