Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 1 addition & 14 deletions src/components/transactions/Bridge/BridgeModalContent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ChainId } from '@aave/contract-helpers';
import { AaveV3Monad } from '@aave-dao/aave-address-book';
import { ExternalLinkIcon, SwitchVerticalIcon } from '@heroicons/react/outline';
import { Trans } from '@lingui/macro';
import {
Expand Down Expand Up @@ -57,18 +56,6 @@ import { useTimeToDestination } from './useGetFinalityTime';
const defaultNetwork = supportedNetworksWithBridge[0];

function getUseBridgeTokensParams(chainId: number): UseBridgeTokensParams {
const tokenOracle = getConfigFor(chainId).tokenOracle;

if (chainId === 143) {
// no market config available yet for Monad, so values are set here
return {
chainId,
ghoTokenAddress: '0xfc421aD3C883Bf9E7C4f42dE845C4e4405799e73',
tokenOracle,
walletBalanceProviderAddress: AaveV3Monad.WALLET_BALANCE_PROVIDER,
};
}

const market = Object.values(marketsData).filter(
(md) => md.chainId === chainId && md.v3 === true && md.addresses.GHO_TOKEN_ADDRESS
)[0];
Expand All @@ -79,7 +66,7 @@ function getUseBridgeTokensParams(chainId: number): UseBridgeTokensParams {
return {
chainId,
ghoTokenAddress: market.addresses.GHO_TOKEN_ADDRESS,
tokenOracle,
tokenOracle: getConfigFor(chainId).tokenOracle,
walletBalanceProviderAddress: market.addresses.WALLET_BALANCE_PROVIDER,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { useAssetCaps } from 'src/hooks/useAssetCaps';
import { useModalContext } from 'src/hooks/useModal';
import { useZeroLTVBlockingWithdraw } from 'src/hooks/useZeroLTVBlockingWithdraw';
import { hasNonZeroEffectiveLtv } from 'src/utils/hfUtils';

import { GasEstimationError } from '../FlowCommons/GasEstimationError';
import { ModalWrapperProps } from '../FlowCommons/ModalWrapper';
Expand Down Expand Up @@ -49,9 +50,12 @@ export const CollateralChangeModalContent = ({

// Check if asset has non-zero LTV (base or in user's active e-mode)
const userEMode = poolReserve.eModes?.find((e) => e.id === user?.userEmodeCategoryId);
const hasNonZeroLtv =
poolReserve.baseLTVasCollateral !== '0' ||
(user?.isInEmode && userEMode?.collateralEnabled && !userEMode?.ltvzeroEnabled);
const hasNonZeroLtv = hasNonZeroEffectiveLtv({
baseLTVasCollateral: poolReserve.baseLTVasCollateral,
isInEmode: !!user?.isInEmode,
emodeEntry: userEMode,
isEModeIsolated: !!eModes[user.userEmodeCategoryId]?.isolated,
});

// Find e-mode categories where this asset can be used as collateral with non-zero LTV
const collateralEmodeCategories =
Expand Down
93 changes: 81 additions & 12 deletions src/components/transactions/Emode/EmodeModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { FormattedNumber } from 'src/components/primitives/FormattedNumber';
import { Link } from 'src/components/primitives/Link';
import { Row } from 'src/components/primitives/Row';
import { Warning } from 'src/components/primitives/Warning';
import { TextWithTooltip } from 'src/components/TextWithTooltip';
import { EmodeCategory } from 'src/helpers/types';
import {
ComputedReserveData,
Expand All @@ -28,6 +29,7 @@ import { useCurrentTimestamp } from 'src/hooks/useCurrentTimestamp';
import { useModalContext } from 'src/hooks/useModal';
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
import { useRootStore } from 'src/store/root';
import { getEmodeAdjustedReserves } from 'src/utils/hfUtils';
import { getNetworkConfig } from 'src/utils/marketsAndNetworksConfig';
import { replaceUnderscoresWithSpaces } from 'src/utils/utils';
import { useShallow } from 'zustand/shallow';
Expand Down Expand Up @@ -96,7 +98,10 @@ function getEModeCategoryBlockReason(
) {
zeroLtvCollateral.push(reserve.symbol);
} else if (!reserveTargetEmode || !reserveTargetEmode.collateralEnabled) {
if (Number(reserve.baseLTVasCollateral) === 0) {
// v3.7 isolated eMode rule: assets outside the category's collateralBitmap are
// forced to 0 LTV regardless of their base LTV, so entry reverts on-chain
// (InvalidCollateralInEmode) unless this collateral is disabled first.
if (Number(reserve.baseLTVasCollateral) === 0 || eMode.isolated) {
zeroLtvCollateral.push(reserve.symbol);
}
}
Expand Down Expand Up @@ -172,11 +177,12 @@ export const EmodeModalContent = ({ user }: { user: ExtendedFormattedUser }) =>
const networkConfig = getNetworkConfig(currentChainId);

// calcs
const targetEmodeId = disableEmode ? 0 : selectedEmode.id;
const newSummary = formatUserSummary({
currentTimestamp,
userReserves: userReserves,
formattedReserves: reserves,
userEmodeCategoryId: disableEmode ? 0 : selectedEmode.id,
formattedReserves: getEmodeAdjustedReserves(reserves, targetEmodeId, eModes),
userEmodeCategoryId: targetEmodeId,
marketReferenceCurrencyDecimals,
marketReferencePriceInUsd,
});
Expand Down Expand Up @@ -393,9 +399,29 @@ export const EmodeModalContent = ({ user }: { user: ExtendedFormattedUser }) =>
<Collapse in={!disableEmode}>
<Box>
<Stack direction="column">
<Typography mb={1} variant="caption" color="text.secondary">
<Trans>Asset category</Trans>
</Typography>
<Stack direction="row" alignItems="center" spacing={1} mb={1}>
<Typography variant="caption" color="text.secondary">
<Trans>Asset category</Trans>
</Typography>
{selectedEmode.isolated && (
<>
<Typography variant="caption" color="text.secondary">
-
</Typography>
<TextWithTooltip
text={<Trans>Isolated</Trans>}
variant="caption"
textColor="warning.main"
iconColor="warning.main"
>
<Trans>
Only the listed collateral contributes borrowing power in this category —
other collateral you hold will have 0% LTV while it&apos;s active.
</Trans>
</TextWithTooltip>
</>
)}
</Stack>
<Select
sx={{
mb: 3,
Expand All @@ -421,6 +447,42 @@ export const EmodeModalContent = ({ user }: { user: ExtendedFormattedUser }) =>
}}
value={selectedEmode.id}
onChange={(e) => selectEMode(Number(e.target.value))}
renderValue={(value) => {
const emode = eModeCategories[value as number];
return (
<Stack sx={{ width: '100%' }} direction="row" justifyContent="space-between">
<Typography
sx={{ opacity: emode.available ? 1 : 0.5 }}
fontStyle={emode.available ? 'normal' : 'italic'}
>
{replaceUnderscoresWithSpaces(emode.label)}
</Typography>
{emode.id === user.userEmodeCategoryId && (
<Box sx={{ display: 'inline-flex', alignItems: 'center' }}>
<Box
sx={{
width: '6px',
height: '6px',
borderRadius: '50%',
bgcolor: 'success.main',
boxShadow:
'0px 2px 1px rgba(0, 0, 0, 0.05), 0px 0px 1px rgba(0, 0, 0, 0.25)',
mr: '5px',
}}
/>
<Typography variant="subheader2" color="success.main">
<Trans>Enabled</Trans>
</Typography>
</Box>
)}
{!emode.available && (
<Typography variant="caption" color="text.secondary" fontStyle="italic">
<Trans>Unavailable</Trans>
</Typography>
)}
</Stack>
);
}}
>
{Object.values(eModeCategories)
.filter((emode) => emode.id !== 0 && emode.label !== 'USYC GHO')
Expand All @@ -434,12 +496,19 @@ export const EmodeModalContent = ({ user }: { user: ExtendedFormattedUser }) =>
.map((emode) => (
<MenuItem key={emode.id} value={emode.id}>
<Stack sx={{ width: '100%' }} direction="row" justifyContent="space-between">
<Typography
sx={{ opacity: emode.available ? 1 : 0.5 }}
fontStyle={emode.available ? 'normal' : 'italic'}
>
{replaceUnderscoresWithSpaces(emode.label)}
</Typography>
<Stack direction="row" alignItems="center" spacing={2}>
<Typography
sx={{ opacity: emode.available ? 1 : 0.5 }}
fontStyle={emode.available ? 'normal' : 'italic'}
>
{replaceUnderscoresWithSpaces(emode.label)}
</Typography>
{emode.isolated && (
<Typography variant="caption" color="warning.main" fontStyle="italic">
<Trans>Isolated</Trans>
</Typography>
)}
</Stack>
{emode.id === user.userEmodeCategoryId && (
<Box sx={{ display: 'inline-flex', alignItems: 'center' }}>
<Box
Expand Down
10 changes: 5 additions & 5 deletions src/components/transactions/FunCheckout/FunkitCheckout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ function InnerCheckout() {
// Live position + reserves for the health-factor preview. Kept in a ref so the
// `resolveHealthFactor` closure handed to funkit reads the latest values when
// the confirmation screen calls it — not a snapshot from config-build time.
const { user, reserves } = useAppDataContext();
const appDataRef = useRef({ user, reserves });
const { user, reserves, eModes } = useAppDataContext();
const appDataRef = useRef({ user, reserves, eModes });
useEffect(() => {
appDataRef.current = { user, reserves };
appDataRef.current = { user, reserves, eModes };
});

const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
Expand Down Expand Up @@ -197,7 +197,7 @@ function InnerCheckout() {
// confirmation screen shows the same number as the dashboard. Bound to this
// reserve's underlying; reads the latest position via `appDataRef`.
const resolveHealthFactor = (underlyingHumanAmount: string) => {
const { user, reserves } = appDataRef.current;
const { user, reserves, eModes } = appDataRef.current;
const poolReserve = reserves.find(
(r) => r.underlyingAsset.toLowerCase() === reserve.underlyingAsset.toLowerCase()
);
Expand All @@ -212,7 +212,7 @@ function InnerCheckout() {
}
return {
before: user.healthFactor,
after: calculateHFAfterSupply(user, poolReserve, amountInEth).toString(),
after: calculateHFAfterSupply(user, poolReserve, amountInEth, eModes).toString(),
};
};

Expand Down
42 changes: 37 additions & 5 deletions src/components/transactions/Supply/CollateralOptionsSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface CollateralOption {
isCurrentEmode: boolean;
blocked: boolean;
blockReason?: React.ReactNode;
isolated: boolean;
}

interface CollateralOptionsSelectorProps {
Expand Down Expand Up @@ -116,9 +117,11 @@ function getBlockReason(
</Trans>
);
}
// If asset is NOT in the category at all, it falls back to base LTV — check that too
// If asset is NOT in the category at all, it falls back to base LTV — except when the
// target category is isolated (v3.7): assets outside the collateralBitmap are always
// forced to 0 LTV there, regardless of base LTV.
if (!reserveTargetEmode || !reserveTargetEmode.collateralEnabled) {
if (Number(reserve.baseLTVasCollateral) === 0) {
if (Number(reserve.baseLTVasCollateral) === 0 || targetEmode?.isolated) {
return (
<Trans>
{reserve.symbol} collateral would have 0% LTV in this category. Disable{' '}
Expand Down Expand Up @@ -160,12 +163,13 @@ export function buildCollateralOptions(
// Mirrors getUserReserveLtv from the contract:
// - collateralEnabled + ltvzeroEnabled → 0
// - collateralEnabled + !ltvzeroEnabled → e-mode boosted LTV
// - !collateralEnabled → base reserve LTV
// - !collateralEnabled + isolated (v3.7) → 0, regardless of base LTV
// - !collateralEnabled + !isolated → base reserve LTV
let ltv: number;
if (reserveEmode.collateralEnabled) {
ltv = reserveEmode.ltvzeroEnabled ? 0 : Number(reserveEmode.eMode.ltv) / 10000;
} else {
ltv = Number(poolReserve.baseLTVasCollateral) / 10000;
ltv = globalEmode.isolated ? 0 : Number(poolReserve.baseLTVasCollateral) / 10000;
}

options.push({
Expand All @@ -182,6 +186,7 @@ export function buildCollateralOptions(
isCurrentEmode: isUserCurrentEmode,
blocked: !!blockReason,
blockReason,
isolated: !!globalEmode.isolated,
});
});

Expand All @@ -191,7 +196,7 @@ export function buildCollateralOptions(
const globalEmode = eModes[user.userEmodeCategoryId];
options.push({
emodeId: user.userEmodeCategoryId,
ltv: Number(poolReserve.baseLTVasCollateral) / 10000,
ltv: globalEmode?.isolated ? 0 : Number(poolReserve.baseLTVasCollateral) / 10000,
collateralAssets: [],
borrowableAssets: globalEmode
? globalEmode.assets
Expand All @@ -204,6 +209,7 @@ export function buildCollateralOptions(
isCurrentEmode: true,
blocked: false, // current e-mode is never blocked — no switch needed
blockReason: undefined,
isolated: !!globalEmode?.isolated,
});
}

Expand All @@ -218,6 +224,7 @@ export function buildCollateralOptions(
isCurrentEmode: user.userEmodeCategoryId === 0,
blocked: !!blockReason,
blockReason,
isolated: false, // category 0 ("no e-mode") is never isolated
});

// Sort: available first, then by LTV descending
Expand Down Expand Up @@ -349,6 +356,31 @@ export const CollateralOptionsSelector = React.memo(
<Trans>Default</Trans>
</Typography>
)}
{option.isolated && (
<Tooltip
title={
<Trans>
Only the listed collateral contributes borrowing power in this category
— other collateral you hold will have 0% LTV while in it.
</Trans>
}
arrow
>
<Typography
variant="caption"
sx={{
px: 1,
py: 0.25,
borderRadius: '4px',
bgcolor: 'warning.main',
color: '#fff',
fontSize: '10px',
}}
>
<Trans>Isolated</Trans>
</Typography>
</Tooltip>
)}
</Stack>
</Stack>

Expand Down
17 changes: 11 additions & 6 deletions src/components/transactions/Supply/SupplyModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
getMaxAmountAvailableToSupply,
remainingCap,
} from 'src/utils/getMaxAmountAvailableToSupply';
import { calculateHFAfterSupply } from 'src/utils/hfUtils';
import { calculateHFAfterSupply, getEmodeAdjustedReserves } from 'src/utils/hfUtils';
import { isFeatureEnabled } from 'src/utils/marketsAndNetworksConfig';
import { replaceUnderscoresWithSpaces, roundToTokenDecimals } from 'src/utils/utils';
import { useShallow } from 'zustand/shallow';
Expand Down Expand Up @@ -219,7 +219,7 @@ export const SupplyModalContent = React.memo(
const newSummary = formatUserSummary({
currentTimestamp,
userReserves,
formattedReserves: reserves,
formattedReserves: getEmodeAdjustedReserves(reserves, selectedEmodeId, eModes),
userEmodeCategoryId: selectedEmodeId,
marketReferenceCurrencyDecimals,
marketReferencePriceInUsd,
Expand Down Expand Up @@ -258,7 +258,7 @@ export const SupplyModalContent = React.memo(
.multipliedBy(newLTWeighted)
.dividedBy(newSummary.totalBorrowsMarketReferenceCurrency);
}
return calculateHFAfterSupply(user, poolReserve, amountInEth);
return calculateHFAfterSupply(user, poolReserve, amountInEth, eModes);
})();

// Override collateral type based on the selected e-mode's collateral eligibility,
Expand All @@ -278,7 +278,12 @@ export const SupplyModalContent = React.memo(
if (targetEmode && targetEmode.collateralEnabled && targetEmode.ltvzeroEnabled) {
return CollateralType.DISABLED;
}
// Not in this category's collateral bitmap — fall back to base
// Not in this category's collateral bitmap. If the category is isolated (v3.7),
// out-of-bitmap assets are forced to 0 LTV regardless of base LTV — otherwise fall
// back to base config.
if (eModes[selectedEmodeId]?.isolated) {
return CollateralType.DISABLED;
}
return collateralType;
})();

Expand Down Expand Up @@ -443,7 +448,7 @@ export const SupplyWrappedTokenModalContent = ({
isWrongNetwork,
user,
}: SupplyModalContentProps) => {
const { marketReferencePriceInUsd } = useAppDataContext();
const { marketReferencePriceInUsd, eModes } = useAppDataContext();
const currentMarketData = useRootStore((state) => state.currentMarketData);
const { mainTxState: supplyTxState, gasLimit, txError } = useModalContext();
const { walletBalances } = useWalletBalances(currentMarketData);
Expand Down Expand Up @@ -546,7 +551,7 @@ export const SupplyWrappedTokenModalContent = ({

const isMaxSelected = amount === maxAmountToSupply;

const healfthFactorAfterSupply = calculateHFAfterSupply(user, poolReserve, amountInEth);
const healfthFactorAfterSupply = calculateHFAfterSupply(user, poolReserve, amountInEth, eModes);

if (supplyTxState.success) {
const successModalAmount = supplyingWrappedToken
Expand Down
Loading
Loading