diff --git a/src/components/transactions/Swap/constants/shared.constants.ts b/src/components/transactions/Swap/constants/shared.constants.ts index 3bf8a2aa45..6e61a9b7c1 100644 --- a/src/components/transactions/Swap/constants/shared.constants.ts +++ b/src/components/transactions/Swap/constants/shared.constants.ts @@ -1,3 +1,5 @@ +import { CustomMarket } from 'src/ui-config/marketsConfig'; + import { SwapType } from '../types'; export const SAFETY_MODULE_TOKENS = [ @@ -23,3 +25,8 @@ export const APP_CODE_PER_SWAP_TYPE: Record = { }; export const APP_CODE_VALUES = Object.values(APP_CODE_PER_SWAP_TYPE); + +export const isHorizonMarket = (currentMarket: string) => + currentMarket === CustomMarket.proto_horizon_v3 || + currentMarket === CustomMarket.proto_sepolia_horizon_v3 || + currentMarket === ('fork_proto_horizon_v3' as CustomMarket); diff --git a/src/components/transactions/Swap/errors/shared/FlashLoanDisabledBlockingGuard.tsx b/src/components/transactions/Swap/errors/shared/FlashLoanDisabledBlockingGuard.tsx index b9d9283f44..8177bf0cd6 100644 --- a/src/components/transactions/Swap/errors/shared/FlashLoanDisabledBlockingGuard.tsx +++ b/src/components/transactions/Swap/errors/shared/FlashLoanDisabledBlockingGuard.tsx @@ -14,6 +14,8 @@ export const hasFlashLoanDisabled = (state: SwapState): boolean => { ? state.sourceReserve?.reserve : state.destinationReserve?.reserve; + console.error('reserve symbol', reserve?.symbol, 'flashLoanEnabled', reserve?.flashLoanEnabled); + if (state.useFlashloan === true && reserve && !reserve.flashLoanEnabled) { return true; } diff --git a/src/components/transactions/Swap/modals/request/CollateralSwapModalContent.tsx b/src/components/transactions/Swap/modals/request/CollateralSwapModalContent.tsx index 4be3872ff5..9411b51291 100644 --- a/src/components/transactions/Swap/modals/request/CollateralSwapModalContent.tsx +++ b/src/components/transactions/Swap/modals/request/CollateralSwapModalContent.tsx @@ -12,6 +12,7 @@ import { TOKEN_LIST, TokenInfo } from 'src/ui-config/TokenList'; import { displayGhoForMintableMarket } from 'src/utils/ghoUtilities'; import { useShallow } from 'zustand/shallow'; +import { isHorizonMarket } from '../../constants/shared.constants'; import { invalidateAppStateForSwap } from '../../helpers/shared'; import { SwappableToken, SwapParams, SwapType, TokenType } from '../../types'; import { BaseSwapModalContent } from './BaseSwapModalContent'; @@ -23,6 +24,7 @@ export const CollateralSwapModalContent = ({ underlyingAsset }: { underlyingAsse ); const queryClient = useQueryClient(); const currentNetworkConfig = useRootStore((store) => store.currentNetworkConfig); + const isHorizon = isHorizonMarket(currentMarketName); const baseTokens: TokenInfo[] = reserves.map((reserve) => { return { address: reserve.underlyingAsset, @@ -34,8 +36,8 @@ export const CollateralSwapModalContent = ({ underlyingAsset }: { underlyingAsse }; }); - const tokensFrom = getTokensFrom(user, baseTokens, chainId); - const tokensTo = getTokensTo(user, reserves, baseTokens, currentMarketName, chainId); + const tokensFrom = getTokensFrom(user, baseTokens, chainId, isHorizon); + const tokensTo = getTokensTo(user, reserves, baseTokens, currentMarketName, chainId, isHorizon); const userSelectedInputToken = tokensFrom.find( (token) => token.underlyingAddress.toLowerCase() === underlyingAsset?.toLowerCase() @@ -135,11 +137,14 @@ const getDefaultOutputToken = ( const getTokensFrom = ( user: ExtendedFormattedUser | undefined, baseTokensInfo: TokenInfo[], - chainId: number + chainId: number, + isHorizon: boolean ): SwappableToken[] => { // Tokens From should be the supplied tokens const suppliedPositions = - user?.userReservesData.filter((userReserve) => userReserve.underlyingBalance !== '0') || []; + user?.userReservesData + .filter((userReserve) => userReserve.underlyingBalance !== '0') + .filter((userReserve) => !isHorizon || userReserve.reserve.borrowingEnabled) || []; return suppliedPositions .map((position) => { @@ -209,12 +214,14 @@ const getTokensTo = ( reserves: ComputedReserveData[], baseTokensInfo: TokenInfo[], currentMarket: string, - chainId: number + chainId: number, + isHorizon: boolean ): SwappableToken[] => { // Tokens To should be the potential supply tokens (so we have an aToken) const tokensToSupply = reserves.filter( (reserve: ComputedReserveData) => !(reserve.isFrozen || reserve.isPaused) && + (!isHorizon || reserve.borrowingEnabled) && !displayGhoForMintableMarket({ symbol: reserve.symbol, currentMarket: currentMarket }) ); diff --git a/src/components/transactions/Swap/modals/request/RepayWithCollateralModalContent.tsx b/src/components/transactions/Swap/modals/request/RepayWithCollateralModalContent.tsx index 85101d3eb4..81b0e1475c 100644 --- a/src/components/transactions/Swap/modals/request/RepayWithCollateralModalContent.tsx +++ b/src/components/transactions/Swap/modals/request/RepayWithCollateralModalContent.tsx @@ -13,6 +13,7 @@ import { fetchIconSymbolAndName } from 'src/ui-config/reservePatches'; import { TOKEN_LIST, TokenInfo } from 'src/ui-config/TokenList'; import { useShallow } from 'zustand/shallow'; +import { isHorizonMarket } from '../../constants/shared.constants'; import { invalidateAppStateForSwap } from '../../helpers/shared'; import { SwappableToken, SwapParams, SwapType } from '../../types'; import { BaseSwapModalContent } from './BaseSwapModalContent'; @@ -26,9 +27,10 @@ export const RepayWithCollateralModalContent = ({ }) => { const { user, reserves } = useAppDataContext(); const currentNetworkConfig = useRootStore((store) => store.currentNetworkConfig); - const [account, chainId] = useRootStore( - useShallow((store) => [store.account, store.currentChainId]) + const [account, chainId, currentMarket] = useRootStore( + useShallow((store) => [store.account, store.currentChainId, store.currentMarket]) ); + const isHorizon = isHorizonMarket(currentMarket); const baseTokens: TokenInfo[] = reserves.map((reserve) => { return { @@ -42,7 +44,8 @@ export const RepayWithCollateralModalContent = ({ }); const tokensFrom = getTokensFrom(user, currentNetworkConfig.wagmiChain.id, currentNetworkConfig); - const tokensTo = getTokensTo(user, baseTokens, currentNetworkConfig.wagmiChain.id); + const tokensTo = getTokensTo(user, baseTokens, currentNetworkConfig.wagmiChain.id, isHorizon); + const defaultInputToken = tokensFrom.find( (token) => token.underlyingAddress.toLowerCase() === underlyingAsset?.toLowerCase() ); @@ -55,6 +58,7 @@ export const RepayWithCollateralModalContent = ({ token.addressToSwap.toLowerCase() !== defaultInputToken?.addressToSwap.toLowerCase() && token.underlyingAddress.toLowerCase() !== defaultInputToken?.underlyingAddress.toLowerCase() ); + const defaultOutputToken = tokensWithoutInputToken.sort( (a, b) => Number(b.balance) - Number(a.balance) )[0]; @@ -74,7 +78,7 @@ export const RepayWithCollateralModalContent = ({ // allowLimitOrders: false, invalidateAppState, sourceTokens: tokensFrom, - destinationTokens: tokensTo, + destinationTokens: tokensWithoutInputToken, chainId, forcedInputToken: defaultInputToken, suggestedDefaultOutputToken: defaultOutputToken, @@ -185,11 +189,14 @@ const getTokensFrom = ( const getTokensTo = ( user: ExtendedFormattedUser | undefined, baseTokensInfo: TokenInfo[], - chainId: number + chainId: number, + isHorizon: boolean ): SwappableToken[] => { // Tokens From should be the supplied tokens const suppliedPositions = - user?.userReservesData.filter((userReserve) => userReserve.underlyingBalance !== '0') || []; + user?.userReservesData + .filter((userReserve) => userReserve.underlyingBalance !== '0') + .filter((userReserve) => !isHorizon || userReserve.reserve.borrowingEnabled) || []; return suppliedPositions .map((position) => { diff --git a/src/components/transactions/Swap/modals/request/WithdrawAndSwapModalContent.tsx b/src/components/transactions/Swap/modals/request/WithdrawAndSwapModalContent.tsx index 531f5b709b..593ddabd41 100644 --- a/src/components/transactions/Swap/modals/request/WithdrawAndSwapModalContent.tsx +++ b/src/components/transactions/Swap/modals/request/WithdrawAndSwapModalContent.tsx @@ -9,25 +9,32 @@ import { useRootStore } from 'src/store/root'; import { TOKEN_LIST, TokenInfo } from 'src/ui-config/TokenList'; import { useShallow } from 'zustand/shallow'; +import { isHorizonMarket } from '../../constants/shared.constants'; import { invalidateAppStateForSwap } from '../../helpers/shared'; import { SwappableToken, SwapParams, SwapType, TokenType } from '../../types'; import { BaseSwapModalContent } from './BaseSwapModalContent'; import { getDefaultOutputToken, getFilteredTokensForSwitch } from './SwapModalContent'; export const WithdrawAndSwapModalContent = ({ underlyingAsset }: { underlyingAsset: string }) => { - const { account, chainIdInApp: chainId } = useRootStore( + const { + account, + chainIdInApp: chainId, + currentMarket, + } = useRootStore( useShallow((store) => ({ account: store.account, chainIdInApp: store.currentChainId, + currentMarket: store.currentMarket, })) ); const queryClient = useQueryClient(); const { user } = useAppDataContext(); + const isHorizon = isHorizonMarket(currentMarket); const initialDefaultTokens = useMemo(() => getFilteredTokensForSwitch(chainId), [chainId]); - const tokensFrom = getTokensFrom(user, initialDefaultTokens, chainId); + const tokensFrom = getTokensFrom(user, initialDefaultTokens, chainId, isHorizon); const reserves = useAppDataContext().reserves; const { data: initialTokens, isFetching: tokensLoading } = useTokensBalance( @@ -41,6 +48,7 @@ export const WithdrawAndSwapModalContent = ({ underlyingAsset }: { underlyingAss const reserve = reserves.find( (reserve) => reserve.underlyingAsset.toLowerCase() === token.address.toLowerCase() ); + if (isHorizon && !reserve?.borrowingEnabled) return undefined; return { addressToSwap: token.address, addressForUsdPrice: token.address, @@ -57,6 +65,7 @@ export const WithdrawAndSwapModalContent = ({ underlyingAsset }: { underlyingAss tokenType: token.extensions?.isNative ? TokenType.NATIVE : TokenType.ERC20, }; }) + .filter((token) => token != undefined) .filter((token) => token.balance !== '0') .sort((a, b) => Number(b.balance) - Number(a.balance)); @@ -117,11 +126,14 @@ export const WithdrawAndSwapModalContent = ({ underlyingAsset }: { underlyingAss const getTokensFrom = ( user: ExtendedFormattedUser | undefined, baseTokensInfo: TokenInfo[], - chainId: number + chainId: number, + isHorizon: boolean ): SwappableToken[] => { // Tokens From should be the supplied tokens const suppliedPositions = - user?.userReservesData.filter((userReserve) => userReserve.underlyingBalance !== '0') || []; + user?.userReservesData + .filter((userReserve) => userReserve.underlyingBalance !== '0') + .filter((userReserve) => !isHorizon || userReserve.reserve.borrowingEnabled) || []; return suppliedPositions .map((position) => { diff --git a/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListItem.tsx b/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListItem.tsx index bc6c1e3ecb..58fbe470f7 100644 --- a/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListItem.tsx +++ b/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListItem.tsx @@ -1,6 +1,7 @@ import { ProtocolAction } from '@aave/contract-helpers'; import { Trans } from '@lingui/macro'; import { Button } from '@mui/material'; +import { isHorizonMarket } from 'src/components/transactions/Swap/constants/shared.constants'; import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider'; import { useAssetCaps } from 'src/hooks/useAssetCaps'; import { useModalContext } from 'src/hooks/useModal'; @@ -33,7 +34,10 @@ export const SuppliedPositionsListItem = ({ useShallow((store) => [store.trackEvent, store.currentMarketData, store.currentMarket]) ); - const showSwitchButton = isFeatureEnabled.liquiditySwap(currentMarketData); + const isHorizon = isHorizonMarket(currentMarket); + const collateralSwapEnabledForReserve = !isHorizon || reserve.borrowingEnabled; + const showSwitchButton = + isFeatureEnabled.liquiditySwap(currentMarketData) && collateralSwapEnabledForReserve; const canBeEnabledAsCollateral = user ? !debtCeiling.isMaxed && diff --git a/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListMobileItem.tsx b/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListMobileItem.tsx index 210bbd454a..42a5f5ee5a 100644 --- a/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListMobileItem.tsx +++ b/src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListMobileItem.tsx @@ -1,6 +1,7 @@ import { ProtocolAction } from '@aave/contract-helpers'; import { Trans } from '@lingui/macro'; import { Box, Button } from '@mui/material'; +import { isHorizonMarket } from 'src/components/transactions/Swap/constants/shared.constants'; import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider'; import { useAssetCaps } from 'src/hooks/useAssetCaps'; import { useRootStore } from 'src/store/root'; @@ -29,7 +30,12 @@ export const SuppliedPositionsListMobileItem = ({ ); const { openSupply, openCollateralSwap, openWithdraw, openCollateralChange } = useModalContext(); const { debtCeiling } = useAssetCaps(); - const isSwapButton = isFeatureEnabled.liquiditySwap(currentMarketData); + + const isHorizon = isHorizonMarket(currentMarket); + const collateralSwapEnabledForReserve = !isHorizon || reserve.borrowingEnabled; + const isSwapButton = + isFeatureEnabled.liquiditySwap(currentMarketData) && collateralSwapEnabledForReserve; + const { symbol, iconSymbol, diff --git a/src/ui-config/marketsConfig.tsx b/src/ui-config/marketsConfig.tsx index 3fed83fc4a..a7ed15611a 100644 --- a/src/ui-config/marketsConfig.tsx +++ b/src/ui-config/marketsConfig.tsx @@ -579,6 +579,12 @@ export const marketsData: { chainId: ChainId.mainnet, v3: true, logo: '/icons/markets/horizon.svg', + enabledFeatures: { + liquiditySwap: true, + withdrawAndSwitch: true, + collateralRepay: true, + debtSwitch: true, + }, addresses: { LENDING_POOL_ADDRESS_PROVIDER: '0x5D39E06b825C1F2B80bf2756a73e28eFAA128ba0', LENDING_POOL: '0xAe05Cd22df81871bc7cC2a04BeCfb516bFe332C8',