Skip to content

Commit cb12b61

Browse files
committed
feat: allow swaps on Horizon for borrowable assets
1 parent aec8621 commit cb12b61

File tree

8 files changed

+68
-17
lines changed

8 files changed

+68
-17
lines changed

src/components/transactions/Swap/constants/shared.constants.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { CustomMarket } from 'src/ui-config/marketsConfig';
2+
13
import { SwapType } from '../types';
24

35
export const SAFETY_MODULE_TOKENS = [
@@ -23,3 +25,8 @@ export const APP_CODE_PER_SWAP_TYPE: Record<SwapType, string> = {
2325
};
2426

2527
export const APP_CODE_VALUES = Object.values(APP_CODE_PER_SWAP_TYPE);
28+
29+
export const isHorizonMarket = (currentMarket: string) =>
30+
currentMarket === CustomMarket.proto_horizon_v3 ||
31+
currentMarket === CustomMarket.proto_sepolia_horizon_v3 ||
32+
currentMarket === ('fork_proto_horizon_v3' as CustomMarket);

src/components/transactions/Swap/errors/shared/FlashLoanDisabledBlockingGuard.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export const hasFlashLoanDisabled = (state: SwapState): boolean => {
1414
? state.sourceReserve?.reserve
1515
: state.destinationReserve?.reserve;
1616

17+
console.error('reserve symbol', reserve?.symbol, 'flashLoanEnabled', reserve?.flashLoanEnabled);
18+
1719
if (state.useFlashloan === true && reserve && !reserve.flashLoanEnabled) {
1820
return true;
1921
}

src/components/transactions/Swap/modals/request/CollateralSwapModalContent.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { TOKEN_LIST, TokenInfo } from 'src/ui-config/TokenList';
1212
import { displayGhoForMintableMarket } from 'src/utils/ghoUtilities';
1313
import { useShallow } from 'zustand/shallow';
1414

15+
import { isHorizonMarket } from '../../constants/shared.constants';
1516
import { invalidateAppStateForSwap } from '../../helpers/shared';
1617
import { SwappableToken, SwapParams, SwapType, TokenType } from '../../types';
1718
import { BaseSwapModalContent } from './BaseSwapModalContent';
@@ -23,6 +24,7 @@ export const CollateralSwapModalContent = ({ underlyingAsset }: { underlyingAsse
2324
);
2425
const queryClient = useQueryClient();
2526
const currentNetworkConfig = useRootStore((store) => store.currentNetworkConfig);
27+
const isHorizon = isHorizonMarket(currentMarketName);
2628
const baseTokens: TokenInfo[] = reserves.map((reserve) => {
2729
return {
2830
address: reserve.underlyingAsset,
@@ -34,8 +36,8 @@ export const CollateralSwapModalContent = ({ underlyingAsset }: { underlyingAsse
3436
};
3537
});
3638

37-
const tokensFrom = getTokensFrom(user, baseTokens, chainId);
38-
const tokensTo = getTokensTo(user, reserves, baseTokens, currentMarketName, chainId);
39+
const tokensFrom = getTokensFrom(user, baseTokens, chainId, isHorizon);
40+
const tokensTo = getTokensTo(user, reserves, baseTokens, currentMarketName, chainId, isHorizon);
3941

4042
const userSelectedInputToken = tokensFrom.find(
4143
(token) => token.underlyingAddress.toLowerCase() === underlyingAsset?.toLowerCase()
@@ -135,11 +137,14 @@ const getDefaultOutputToken = (
135137
const getTokensFrom = (
136138
user: ExtendedFormattedUser | undefined,
137139
baseTokensInfo: TokenInfo[],
138-
chainId: number
140+
chainId: number,
141+
isHorizon: boolean
139142
): SwappableToken[] => {
140143
// Tokens From should be the supplied tokens
141144
const suppliedPositions =
142-
user?.userReservesData.filter((userReserve) => userReserve.underlyingBalance !== '0') || [];
145+
user?.userReservesData
146+
.filter((userReserve) => userReserve.underlyingBalance !== '0')
147+
.filter((userReserve) => !isHorizon || userReserve.reserve.borrowingEnabled) || [];
143148

144149
return suppliedPositions
145150
.map<SwappableToken | undefined>((position) => {
@@ -209,12 +214,14 @@ const getTokensTo = (
209214
reserves: ComputedReserveData[],
210215
baseTokensInfo: TokenInfo[],
211216
currentMarket: string,
212-
chainId: number
217+
chainId: number,
218+
isHorizon: boolean
213219
): SwappableToken[] => {
214220
// Tokens To should be the potential supply tokens (so we have an aToken)
215221
const tokensToSupply = reserves.filter(
216222
(reserve: ComputedReserveData) =>
217223
!(reserve.isFrozen || reserve.isPaused) &&
224+
(!isHorizon || reserve.borrowingEnabled) &&
218225
!displayGhoForMintableMarket({ symbol: reserve.symbol, currentMarket: currentMarket })
219226
);
220227

src/components/transactions/Swap/modals/request/RepayWithCollateralModalContent.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { fetchIconSymbolAndName } from 'src/ui-config/reservePatches';
1313
import { TOKEN_LIST, TokenInfo } from 'src/ui-config/TokenList';
1414
import { useShallow } from 'zustand/shallow';
1515

16+
import { isHorizonMarket } from '../../constants/shared.constants';
1617
import { invalidateAppStateForSwap } from '../../helpers/shared';
1718
import { SwappableToken, SwapParams, SwapType } from '../../types';
1819
import { BaseSwapModalContent } from './BaseSwapModalContent';
@@ -26,9 +27,10 @@ export const RepayWithCollateralModalContent = ({
2627
}) => {
2728
const { user, reserves } = useAppDataContext();
2829
const currentNetworkConfig = useRootStore((store) => store.currentNetworkConfig);
29-
const [account, chainId] = useRootStore(
30-
useShallow((store) => [store.account, store.currentChainId])
30+
const [account, chainId, currentMarket] = useRootStore(
31+
useShallow((store) => [store.account, store.currentChainId, store.currentMarket])
3132
);
33+
const isHorizon = isHorizonMarket(currentMarket);
3234

3335
const baseTokens: TokenInfo[] = reserves.map((reserve) => {
3436
return {
@@ -42,7 +44,8 @@ export const RepayWithCollateralModalContent = ({
4244
});
4345

4446
const tokensFrom = getTokensFrom(user, currentNetworkConfig.wagmiChain.id, currentNetworkConfig);
45-
const tokensTo = getTokensTo(user, baseTokens, currentNetworkConfig.wagmiChain.id);
47+
const tokensTo = getTokensTo(user, baseTokens, currentNetworkConfig.wagmiChain.id, isHorizon);
48+
4649
const defaultInputToken = tokensFrom.find(
4750
(token) => token.underlyingAddress.toLowerCase() === underlyingAsset?.toLowerCase()
4851
);
@@ -55,6 +58,7 @@ export const RepayWithCollateralModalContent = ({
5558
token.addressToSwap.toLowerCase() !== defaultInputToken?.addressToSwap.toLowerCase() &&
5659
token.underlyingAddress.toLowerCase() !== defaultInputToken?.underlyingAddress.toLowerCase()
5760
);
61+
5862
const defaultOutputToken = tokensWithoutInputToken.sort(
5963
(a, b) => Number(b.balance) - Number(a.balance)
6064
)[0];
@@ -74,7 +78,7 @@ export const RepayWithCollateralModalContent = ({
7478
// allowLimitOrders: false,
7579
invalidateAppState,
7680
sourceTokens: tokensFrom,
77-
destinationTokens: tokensTo,
81+
destinationTokens: tokensWithoutInputToken,
7882
chainId,
7983
forcedInputToken: defaultInputToken,
8084
suggestedDefaultOutputToken: defaultOutputToken,
@@ -185,11 +189,14 @@ const getTokensFrom = (
185189
const getTokensTo = (
186190
user: ExtendedFormattedUser | undefined,
187191
baseTokensInfo: TokenInfo[],
188-
chainId: number
192+
chainId: number,
193+
isHorizon: boolean
189194
): SwappableToken[] => {
190195
// Tokens From should be the supplied tokens
191196
const suppliedPositions =
192-
user?.userReservesData.filter((userReserve) => userReserve.underlyingBalance !== '0') || [];
197+
user?.userReservesData
198+
.filter((userReserve) => userReserve.underlyingBalance !== '0')
199+
.filter((userReserve) => !isHorizon || userReserve.reserve.borrowingEnabled) || [];
193200

194201
return suppliedPositions
195202
.map<SwappableToken | undefined>((position) => {

src/components/transactions/Swap/modals/request/WithdrawAndSwapModalContent.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,32 @@ import { useRootStore } from 'src/store/root';
99
import { TOKEN_LIST, TokenInfo } from 'src/ui-config/TokenList';
1010
import { useShallow } from 'zustand/shallow';
1111

12+
import { isHorizonMarket } from '../../constants/shared.constants';
1213
import { invalidateAppStateForSwap } from '../../helpers/shared';
1314
import { SwappableToken, SwapParams, SwapType, TokenType } from '../../types';
1415
import { BaseSwapModalContent } from './BaseSwapModalContent';
1516
import { getDefaultOutputToken, getFilteredTokensForSwitch } from './SwapModalContent';
1617

1718
export const WithdrawAndSwapModalContent = ({ underlyingAsset }: { underlyingAsset: string }) => {
18-
const { account, chainIdInApp: chainId } = useRootStore(
19+
const {
20+
account,
21+
chainIdInApp: chainId,
22+
currentMarket,
23+
} = useRootStore(
1924
useShallow((store) => ({
2025
account: store.account,
2126
chainIdInApp: store.currentChainId,
27+
currentMarket: store.currentMarket,
2228
}))
2329
);
2430

2531
const queryClient = useQueryClient();
2632
const { user } = useAppDataContext();
33+
const isHorizon = isHorizonMarket(currentMarket);
2734

2835
const initialDefaultTokens = useMemo(() => getFilteredTokensForSwitch(chainId), [chainId]);
2936

30-
const tokensFrom = getTokensFrom(user, initialDefaultTokens, chainId);
37+
const tokensFrom = getTokensFrom(user, initialDefaultTokens, chainId, isHorizon);
3138

3239
const reserves = useAppDataContext().reserves;
3340
const { data: initialTokens, isFetching: tokensLoading } = useTokensBalance(
@@ -41,6 +48,7 @@ export const WithdrawAndSwapModalContent = ({ underlyingAsset }: { underlyingAss
4148
const reserve = reserves.find(
4249
(reserve) => reserve.underlyingAsset.toLowerCase() === token.address.toLowerCase()
4350
);
51+
if (isHorizon && !reserve?.borrowingEnabled) return undefined;
4452
return {
4553
addressToSwap: token.address,
4654
addressForUsdPrice: token.address,
@@ -57,6 +65,7 @@ export const WithdrawAndSwapModalContent = ({ underlyingAsset }: { underlyingAss
5765
tokenType: token.extensions?.isNative ? TokenType.NATIVE : TokenType.ERC20,
5866
};
5967
})
68+
.filter((token) => token != undefined)
6069
.filter((token) => token.balance !== '0')
6170
.sort((a, b) => Number(b.balance) - Number(a.balance));
6271

@@ -117,11 +126,14 @@ export const WithdrawAndSwapModalContent = ({ underlyingAsset }: { underlyingAss
117126
const getTokensFrom = (
118127
user: ExtendedFormattedUser | undefined,
119128
baseTokensInfo: TokenInfo[],
120-
chainId: number
129+
chainId: number,
130+
isHorizon: boolean
121131
): SwappableToken[] => {
122132
// Tokens From should be the supplied tokens
123133
const suppliedPositions =
124-
user?.userReservesData.filter((userReserve) => userReserve.underlyingBalance !== '0') || [];
134+
user?.userReservesData
135+
.filter((userReserve) => userReserve.underlyingBalance !== '0')
136+
.filter((userReserve) => !isHorizon || userReserve.reserve.borrowingEnabled) || [];
125137

126138
return suppliedPositions
127139
.map<SwappableToken | undefined>((position) => {

src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListItem.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ProtocolAction } from '@aave/contract-helpers';
22
import { Trans } from '@lingui/macro';
33
import { Button } from '@mui/material';
4+
import { isHorizonMarket } from 'src/components/transactions/Swap/constants/shared.constants';
45
import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider';
56
import { useAssetCaps } from 'src/hooks/useAssetCaps';
67
import { useModalContext } from 'src/hooks/useModal';
@@ -33,7 +34,10 @@ export const SuppliedPositionsListItem = ({
3334
useShallow((store) => [store.trackEvent, store.currentMarketData, store.currentMarket])
3435
);
3536

36-
const showSwitchButton = isFeatureEnabled.liquiditySwap(currentMarketData);
37+
const isHorizon = isHorizonMarket(currentMarket);
38+
const collateralSwapEnabledForReserve = !isHorizon || reserve.borrowingEnabled;
39+
const showSwitchButton =
40+
isFeatureEnabled.liquiditySwap(currentMarketData) && collateralSwapEnabledForReserve;
3741

3842
const canBeEnabledAsCollateral = user
3943
? !debtCeiling.isMaxed &&

src/modules/dashboard/lists/SuppliedPositionsList/SuppliedPositionsListMobileItem.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ProtocolAction } from '@aave/contract-helpers';
22
import { Trans } from '@lingui/macro';
33
import { Box, Button } from '@mui/material';
4+
import { isHorizonMarket } from 'src/components/transactions/Swap/constants/shared.constants';
45
import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider';
56
import { useAssetCaps } from 'src/hooks/useAssetCaps';
67
import { useRootStore } from 'src/store/root';
@@ -29,7 +30,12 @@ export const SuppliedPositionsListMobileItem = ({
2930
);
3031
const { openSupply, openCollateralSwap, openWithdraw, openCollateralChange } = useModalContext();
3132
const { debtCeiling } = useAssetCaps();
32-
const isSwapButton = isFeatureEnabled.liquiditySwap(currentMarketData);
33+
34+
const isHorizon = isHorizonMarket(currentMarket);
35+
const collateralSwapEnabledForReserve = !isHorizon || reserve.borrowingEnabled;
36+
const isSwapButton =
37+
isFeatureEnabled.liquiditySwap(currentMarketData) && collateralSwapEnabledForReserve;
38+
3339
const {
3440
symbol,
3541
iconSymbol,

src/ui-config/marketsConfig.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,12 @@ export const marketsData: {
579579
chainId: ChainId.mainnet,
580580
v3: true,
581581
logo: '/icons/markets/horizon.svg',
582+
enabledFeatures: {
583+
liquiditySwap: true,
584+
withdrawAndSwitch: true,
585+
collateralRepay: true,
586+
debtSwitch: true,
587+
},
582588
addresses: {
583589
LENDING_POOL_ADDRESS_PROVIDER: '0x5D39E06b825C1F2B80bf2756a73e28eFAA128ba0',
584590
LENDING_POOL: '0xAe05Cd22df81871bc7cC2a04BeCfb516bFe332C8',

0 commit comments

Comments
 (0)