From 3a4377c7f3d04b0ab44299b70b9f89cfd2869969 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Thu, 5 Mar 2026 15:32:39 +0800 Subject: [PATCH 1/6] chore: royco --- AGENTS.md | 2 ++ src/config/leverage.ts | 15 +++++++++ src/hooks/useDeleverageTransaction.ts | 6 +++- src/hooks/useLeverageTransaction.ts | 7 ++-- src/modals/leverage/leverage-modal.tsx | 46 ++++++++++++++++++++++++++ 5 files changed, 73 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4782fead..b11265df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -154,7 +154,9 @@ When touching transaction and position flows, validation MUST include all releva 19. **APR/APY unit homogeneity**: in any reward/carry/net-rate calculation, normalize every term (base rate, each reward component, aggregates, and displayed subtotals) to the same selected mode before combining, so displayed formulas remain numerically consistent in both APR and APY modes. 20. **Rebalance objective integrity**: stepwise smart-rebalance planners must evaluate each candidate move by resulting **global weighted objective** (portfolio-level APY/APR), not by local/post-move market APR alone, and must fail safe (no-op) when projected objective is below current objective. 21. **Modal UX integrity**: transaction-modal help/fee tooltips must render above modal layers via shared tooltip z-index chokepoints, and per-flow input mode toggles (for example target LTV vs amount) must persist through shared settings across modal reopen. + 22. **Bundler residual-asset integrity**: any flash-loan transaction path that routes assets through Bundler/adapter balances (especially ERC4626 unwind paths) must end with explicit trailing asset sweeps to the intended recipient and must keep execute-time slippage bounds consistent with quote-time slippage settings. + ### REQUIRED: Regression Rule Capture After fixing any user-reported bug in a high-impact flow: diff --git a/src/config/leverage.ts b/src/config/leverage.ts index c490805e..6c44ed5b 100644 --- a/src/config/leverage.ts +++ b/src/config/leverage.ts @@ -1,3 +1,4 @@ +import type { Address } from 'viem'; import { MONARCH_FEE_RECIPIENT } from './smart-rebalance'; /** @@ -7,3 +8,17 @@ import { MONARCH_FEE_RECIPIENT } from './smart-rebalance'; * a single address. */ export const LEVERAGE_FEE_RECIPIENT = MONARCH_FEE_RECIPIENT; + +const SPECIAL_ERC4626_LEVERAGE_MARKET_UNIQUE_KEY_CONFIG = '0xacc49fbf58feb1ac971acce68f8adc177c43682d6a7087bbd4991a05cb7a2c67'; +const SPECIAL_ERC4626_LEVERAGE_BUNDLER_CONFIG = '0xaB27431E62ead49A40848958A6BaDA040BA2264f'; + +export const SPECIAL_ERC4626_LEVERAGE_CONFIG = { + marketUniqueKey: SPECIAL_ERC4626_LEVERAGE_MARKET_UNIQUE_KEY_CONFIG, + bundler: SPECIAL_ERC4626_LEVERAGE_BUNDLER_CONFIG as Address, + warningStorageKey: 'monarch_special_erc4626_bundler_warning_ack', + warningMessage: + 'Leveraging this market through ERC4626 deposit uses a whitelist-based bundler developed by Royco. Proceed only if you trust this contract.', +} as const; + +export const isSpecialErc4626LeverageMarket = (marketUniqueKey: string): boolean => + marketUniqueKey.toLowerCase() === SPECIAL_ERC4626_LEVERAGE_CONFIG.marketUniqueKey; diff --git a/src/hooks/useDeleverageTransaction.ts b/src/hooks/useDeleverageTransaction.ts index 9d05c355..ed983f8e 100644 --- a/src/hooks/useDeleverageTransaction.ts +++ b/src/hooks/useDeleverageTransaction.ts @@ -5,6 +5,7 @@ import morphoBundlerAbi from '@/abis/bundlerV2'; import { bundlerV3Abi } from '@/abis/bundlerV3'; import { morphoGeneralAdapterV1Abi } from '@/abis/morphoGeneralAdapterV1'; import { paraswapAdapterAbi } from '@/abis/paraswapAdapter'; +import { SPECIAL_ERC4626_LEVERAGE_CONFIG, isSpecialErc4626LeverageMarket } from '@/config/leverage'; import { buildVeloraTransactionPayload, isVeloraRateChangedError, type VeloraPriceRoute } from '@/features/swap/api/velora'; import { useBundlerAuthorizationStep } from '@/hooks/useBundlerAuthorizationStep'; import { useStyledToast } from '@/hooks/useStyledToast'; @@ -66,8 +67,11 @@ export function useDeleverageTransaction({ const useSignatureAuthorization = usePermit2Setting && !isSwapRoute; const bundlerAddress = useMemo
(() => { if (route?.kind === 'swap') return route.bundler3Address; + if (route?.kind === 'erc4626' && isSpecialErc4626LeverageMarket(market.uniqueKey)) { + return SPECIAL_ERC4626_LEVERAGE_CONFIG.bundler; + } return getBundlerV2(market.morphoBlue.chain.id) as Address; - }, [route, market.morphoBlue.chain.id]); + }, [route, market.uniqueKey, market.morphoBlue.chain.id]); const authorizationTarget = useMemo
(() => { if (route?.kind === 'swap') return route.generalAdapterAddress; return bundlerAddress; diff --git a/src/hooks/useLeverageTransaction.ts b/src/hooks/useLeverageTransaction.ts index 9073e96d..96844bf4 100644 --- a/src/hooks/useLeverageTransaction.ts +++ b/src/hooks/useLeverageTransaction.ts @@ -8,7 +8,7 @@ import { morphoGeneralAdapterV1Abi } from '@/abis/morphoGeneralAdapterV1'; import { paraswapAdapterAbi } from '@/abis/paraswapAdapter'; import permit2Abi from '@/abis/permit2'; import { getLeverageFee } from '@/config/fees'; -import { LEVERAGE_FEE_RECIPIENT } from '@/config/leverage'; +import { LEVERAGE_FEE_RECIPIENT, SPECIAL_ERC4626_LEVERAGE_CONFIG, isSpecialErc4626LeverageMarket } from '@/config/leverage'; import { buildVeloraTransactionPayload, isVeloraRateChangedError, type VeloraPriceRoute } from '@/features/swap/api/velora'; import { useERC20Approval } from '@/hooks/useERC20Approval'; import { useBundlerAuthorizationStep } from '@/hooks/useBundlerAuthorizationStep'; @@ -81,8 +81,11 @@ export function useLeverageTransaction({ if (route?.kind === 'swap') { return route.bundler3Address; } + if (route?.kind === 'erc4626' && isSpecialErc4626LeverageMarket(market.uniqueKey)) { + return SPECIAL_ERC4626_LEVERAGE_CONFIG.bundler; + } return getBundlerV2(market.morphoBlue.chain.id) as Address; - }, [route, market.morphoBlue.chain.id]); + }, [route, market.uniqueKey, market.morphoBlue.chain.id]); const authorizationTarget = useMemo
(() => { if (route?.kind === 'swap') { return route.generalAdapterAddress; diff --git a/src/modals/leverage/leverage-modal.tsx b/src/modals/leverage/leverage-modal.tsx index a1637063..96089cd1 100644 --- a/src/modals/leverage/leverage-modal.tsx +++ b/src/modals/leverage/leverage-modal.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { ChevronDownIcon } from '@radix-ui/react-icons'; +import { IoWarningOutline } from 'react-icons/io5'; import { RiSparklingFill } from 'react-icons/ri'; import { type Address, erc20Abi } from 'viem'; import { useConnection, useReadContract } from 'wagmi'; @@ -8,6 +9,7 @@ import { ModalIntentSwitcher } from '@/components/common/Modal/ModalIntentSwitch import { TokenIcon } from '@/components/shared/token-icon'; import { Badge } from '@/components/ui/badge'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; +import { SPECIAL_ERC4626_LEVERAGE_CONFIG, isSpecialErc4626LeverageMarket } from '@/config/leverage'; import { useLeverageRouteAvailability } from '@/hooks/leverage/useLeverageRouteAvailability'; import type { LeverageRoute } from '@/hooks/leverage/types'; import { cn } from '@/utils/components'; @@ -102,7 +104,9 @@ export function LeverageModal({ }: LeverageModalProps): JSX.Element { const [mode, setMode] = useState<'leverage' | 'deleverage'>(defaultMode); const [routeMode, setRouteMode] = useState('erc4626'); + const [hasAcknowledgedSpecialBundlerWarning, setHasAcknowledgedSpecialBundlerWarning] = useState(null); const { address: account } = useConnection(); + const isSpecialErc4626BundlerMarket = isSpecialErc4626LeverageMarket(market.uniqueKey); const { swapRoute, isErc4626ModeAvailable, availableRouteModes, isErc4626ProbeLoading, isErc4626ProbeRefetching } = useLeverageRouteAvailability({ @@ -147,6 +151,31 @@ export function LeverageModal({ ]); const isErc4626Route = route?.kind === 'erc4626'; const isSwapRoute = route?.kind === 'swap'; + const shouldShowSpecialBundlerWarning = isSpecialErc4626BundlerMarket && isErc4626Route && hasAcknowledgedSpecialBundlerWarning === false; + + useEffect(() => { + if (!isSpecialErc4626BundlerMarket) { + setHasAcknowledgedSpecialBundlerWarning(true); + return; + } + + try { + const hasAcknowledged = window.localStorage.getItem(SPECIAL_ERC4626_LEVERAGE_CONFIG.warningStorageKey) === '1'; + setHasAcknowledgedSpecialBundlerWarning(hasAcknowledged); + } catch { + setHasAcknowledgedSpecialBundlerWarning(false); + } + }, [isSpecialErc4626BundlerMarket]); + + const acknowledgeSpecialBundlerWarning = useCallback(() => { + try { + window.localStorage.setItem(SPECIAL_ERC4626_LEVERAGE_CONFIG.warningStorageKey, '1'); + } catch { + // ignore storage write failures and still hide warning for this session + } + setHasAcknowledgedSpecialBundlerWarning(true); + }, []); + const displayedRouteMode = useMemo(() => { if (route?.kind) return route.kind; if (availableRouteModes.length === 1) return availableRouteModes[0]; @@ -287,6 +316,23 @@ export function LeverageModal({ Swap route configuration is unavailable for this network. )} + {shouldShowSpecialBundlerWarning && ( +
+
+ +
+

{SPECIAL_ERC4626_LEVERAGE_CONFIG.warningMessage}

+ +
+
+
+ )} ); From 667754c2c4a701c85956b7e8ce5ad0414596c0d6 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Thu, 5 Mar 2026 17:28:50 +0800 Subject: [PATCH 2/6] chore: review fixes --- AGENTS.md | 1 + src/config/leverage.ts | 42 +++++++++++---- src/hooks/useDeleverageTransaction.ts | 9 ++-- src/hooks/useLeverageTransaction.ts | 9 ++-- src/modals/leverage/leverage-modal.tsx | 74 +++++++++++++++----------- 5 files changed, 81 insertions(+), 54 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b11265df..da89acc1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -154,6 +154,7 @@ When touching transaction and position flows, validation MUST include all releva 19. **APR/APY unit homogeneity**: in any reward/carry/net-rate calculation, normalize every term (base rate, each reward component, aggregates, and displayed subtotals) to the same selected mode before combining, so displayed formulas remain numerically consistent in both APR and APY modes. 20. **Rebalance objective integrity**: stepwise smart-rebalance planners must evaluate each candidate move by resulting **global weighted objective** (portfolio-level APY/APR), not by local/post-move market APR alone, and must fail safe (no-op) when projected objective is below current objective. 21. **Modal UX integrity**: transaction-modal help/fee tooltips must render above modal layers via shared tooltip z-index chokepoints, and per-flow input mode toggles (for example target LTV vs amount) must persist through shared settings across modal reopen. +22. **Chain-scoped special bundler integrity**: market-specific ERC4626 bundler overrides must be chain-scoped (`chainId` + canonical `market.uniqueKey`) and any third-party-bundler route requiring trust acknowledgement must fail closed until explicitly acknowledged. 22. **Bundler residual-asset integrity**: any flash-loan transaction path that routes assets through Bundler/adapter balances (especially ERC4626 unwind paths) must end with explicit trailing asset sweeps to the intended recipient and must keep execute-time slippage bounds consistent with quote-time slippage settings. diff --git a/src/config/leverage.ts b/src/config/leverage.ts index 6c44ed5b..a77ad5f6 100644 --- a/src/config/leverage.ts +++ b/src/config/leverage.ts @@ -1,4 +1,6 @@ import type { Address } from 'viem'; +import { getBundlerV2 } from '@/utils/morpho'; +import type { SupportedNetworks } from '@/utils/networks'; import { MONARCH_FEE_RECIPIENT } from './smart-rebalance'; /** @@ -9,16 +11,34 @@ import { MONARCH_FEE_RECIPIENT } from './smart-rebalance'; */ export const LEVERAGE_FEE_RECIPIENT = MONARCH_FEE_RECIPIENT; -const SPECIAL_ERC4626_LEVERAGE_MARKET_UNIQUE_KEY_CONFIG = '0xacc49fbf58feb1ac971acce68f8adc177c43682d6a7087bbd4991a05cb7a2c67'; -const SPECIAL_ERC4626_LEVERAGE_BUNDLER_CONFIG = '0xaB27431E62ead49A40848958A6BaDA040BA2264f'; +type SpecialErc4626LeverageConfig = { + marketUniqueKey: string; + bundler: Address; + warningStorageKey: string; + warningMessage: string; +}; -export const SPECIAL_ERC4626_LEVERAGE_CONFIG = { - marketUniqueKey: SPECIAL_ERC4626_LEVERAGE_MARKET_UNIQUE_KEY_CONFIG, - bundler: SPECIAL_ERC4626_LEVERAGE_BUNDLER_CONFIG as Address, - warningStorageKey: 'monarch_special_erc4626_bundler_warning_ack', - warningMessage: - 'Leveraging this market through ERC4626 deposit uses a whitelist-based bundler developed by Royco. Proceed only if you trust this contract.', -} as const; +const SPECIAL_ERC4626_LEVERAGE_CONFIG_BY_CHAIN: Partial> = { + 1: { + marketUniqueKey: '0xacc49fbf58feb1ac971acce68f8adc177c43682d6a7087bbd4991a05cb7a2c67', + bundler: '0xaB27431E62ead49A40848958A6BaDA040BA2264f', + warningStorageKey: 'monarch_special_erc4626_bundler_warning_ack_mainnet', + warningMessage: + 'Leveraging this market through ERC4626 deposit uses a whitelist-based bundler developed by Royco. Proceed only if you trust this contract.', + }, +}; -export const isSpecialErc4626LeverageMarket = (marketUniqueKey: string): boolean => - marketUniqueKey.toLowerCase() === SPECIAL_ERC4626_LEVERAGE_CONFIG.marketUniqueKey; +export const getSpecialErc4626LeverageConfig = (chainId: SupportedNetworks): SpecialErc4626LeverageConfig | null => + SPECIAL_ERC4626_LEVERAGE_CONFIG_BY_CHAIN[chainId] ?? null; + +export const isSpecialErc4626LeverageMarket = (chainId: SupportedNetworks, marketUniqueKey: string): boolean => { + const specialConfig = getSpecialErc4626LeverageConfig(chainId); + if (!specialConfig) return false; + return marketUniqueKey.toLowerCase() === specialConfig.marketUniqueKey; +}; + +export const resolveErc4626RouteBundler = (chainId: SupportedNetworks, marketUniqueKey: string): Address => { + const specialConfig = getSpecialErc4626LeverageConfig(chainId); + if (specialConfig && marketUniqueKey.toLowerCase() === specialConfig.marketUniqueKey) return specialConfig.bundler; + return getBundlerV2(chainId) as Address; +}; diff --git a/src/hooks/useDeleverageTransaction.ts b/src/hooks/useDeleverageTransaction.ts index ed983f8e..fec6bf38 100644 --- a/src/hooks/useDeleverageTransaction.ts +++ b/src/hooks/useDeleverageTransaction.ts @@ -5,7 +5,7 @@ import morphoBundlerAbi from '@/abis/bundlerV2'; import { bundlerV3Abi } from '@/abis/bundlerV3'; import { morphoGeneralAdapterV1Abi } from '@/abis/morphoGeneralAdapterV1'; import { paraswapAdapterAbi } from '@/abis/paraswapAdapter'; -import { SPECIAL_ERC4626_LEVERAGE_CONFIG, isSpecialErc4626LeverageMarket } from '@/config/leverage'; +import { resolveErc4626RouteBundler } from '@/config/leverage'; import { buildVeloraTransactionPayload, isVeloraRateChangedError, type VeloraPriceRoute } from '@/features/swap/api/velora'; import { useBundlerAuthorizationStep } from '@/hooks/useBundlerAuthorizationStep'; import { useStyledToast } from '@/hooks/useStyledToast'; @@ -14,7 +14,7 @@ import { useTransactionTracking } from '@/hooks/useTransactionTracking'; import { useUserMarketsCache } from '@/stores/useUserMarketsCache'; import { useAppSettings } from '@/stores/useAppSettings'; import { formatBalance } from '@/utils/balance'; -import { getBundlerV2, MONARCH_TX_IDENTIFIER } from '@/utils/morpho'; +import { MONARCH_TX_IDENTIFIER } from '@/utils/morpho'; import { toUserFacingTransactionErrorMessage } from '@/utils/transaction-errors'; import type { Market } from '@/utils/types'; import { type Bundler3Call, encodeBundler3Calls, getParaswapSellOffsets, readCalldataUint256 } from './leverage/bundler3'; @@ -67,10 +67,7 @@ export function useDeleverageTransaction({ const useSignatureAuthorization = usePermit2Setting && !isSwapRoute; const bundlerAddress = useMemo
(() => { if (route?.kind === 'swap') return route.bundler3Address; - if (route?.kind === 'erc4626' && isSpecialErc4626LeverageMarket(market.uniqueKey)) { - return SPECIAL_ERC4626_LEVERAGE_CONFIG.bundler; - } - return getBundlerV2(market.morphoBlue.chain.id) as Address; + return resolveErc4626RouteBundler(market.morphoBlue.chain.id, market.uniqueKey); }, [route, market.uniqueKey, market.morphoBlue.chain.id]); const authorizationTarget = useMemo
(() => { if (route?.kind === 'swap') return route.generalAdapterAddress; diff --git a/src/hooks/useLeverageTransaction.ts b/src/hooks/useLeverageTransaction.ts index 96844bf4..20b68e01 100644 --- a/src/hooks/useLeverageTransaction.ts +++ b/src/hooks/useLeverageTransaction.ts @@ -8,7 +8,7 @@ import { morphoGeneralAdapterV1Abi } from '@/abis/morphoGeneralAdapterV1'; import { paraswapAdapterAbi } from '@/abis/paraswapAdapter'; import permit2Abi from '@/abis/permit2'; import { getLeverageFee } from '@/config/fees'; -import { LEVERAGE_FEE_RECIPIENT, SPECIAL_ERC4626_LEVERAGE_CONFIG, isSpecialErc4626LeverageMarket } from '@/config/leverage'; +import { LEVERAGE_FEE_RECIPIENT, resolveErc4626RouteBundler } from '@/config/leverage'; import { buildVeloraTransactionPayload, isVeloraRateChangedError, type VeloraPriceRoute } from '@/features/swap/api/velora'; import { useERC20Approval } from '@/hooks/useERC20Approval'; import { useBundlerAuthorizationStep } from '@/hooks/useBundlerAuthorizationStep'; @@ -19,7 +19,7 @@ import { useTransactionTracking } from '@/hooks/useTransactionTracking'; import { useUserMarketsCache } from '@/stores/useUserMarketsCache'; import { useAppSettings } from '@/stores/useAppSettings'; import { formatBalance } from '@/utils/balance'; -import { getBundlerV2, getMorphoAddress, MONARCH_TX_IDENTIFIER } from '@/utils/morpho'; +import { getMorphoAddress, MONARCH_TX_IDENTIFIER } from '@/utils/morpho'; import { PERMIT2_ADDRESS } from '@/utils/permit2'; import { toUserFacingTransactionErrorMessage } from '@/utils/transaction-errors'; import type { Market } from '@/utils/types'; @@ -81,10 +81,7 @@ export function useLeverageTransaction({ if (route?.kind === 'swap') { return route.bundler3Address; } - if (route?.kind === 'erc4626' && isSpecialErc4626LeverageMarket(market.uniqueKey)) { - return SPECIAL_ERC4626_LEVERAGE_CONFIG.bundler; - } - return getBundlerV2(market.morphoBlue.chain.id) as Address; + return resolveErc4626RouteBundler(market.morphoBlue.chain.id, market.uniqueKey); }, [route, market.uniqueKey, market.morphoBlue.chain.id]); const authorizationTarget = useMemo
(() => { if (route?.kind === 'swap') { diff --git a/src/modals/leverage/leverage-modal.tsx b/src/modals/leverage/leverage-modal.tsx index 96089cd1..280728da 100644 --- a/src/modals/leverage/leverage-modal.tsx +++ b/src/modals/leverage/leverage-modal.tsx @@ -9,7 +9,7 @@ import { ModalIntentSwitcher } from '@/components/common/Modal/ModalIntentSwitch import { TokenIcon } from '@/components/shared/token-icon'; import { Badge } from '@/components/ui/badge'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; -import { SPECIAL_ERC4626_LEVERAGE_CONFIG, isSpecialErc4626LeverageMarket } from '@/config/leverage'; +import { getSpecialErc4626LeverageConfig, isSpecialErc4626LeverageMarket } from '@/config/leverage'; import { useLeverageRouteAvailability } from '@/hooks/leverage/useLeverageRouteAvailability'; import type { LeverageRoute } from '@/hooks/leverage/types'; import { cn } from '@/utils/components'; @@ -104,9 +104,10 @@ export function LeverageModal({ }: LeverageModalProps): JSX.Element { const [mode, setMode] = useState<'leverage' | 'deleverage'>(defaultMode); const [routeMode, setRouteMode] = useState('erc4626'); - const [hasAcknowledgedSpecialBundlerWarning, setHasAcknowledgedSpecialBundlerWarning] = useState(null); + const [hasAcknowledgedSpecialBundlerWarning, setHasAcknowledgedSpecialBundlerWarning] = useState(false); const { address: account } = useConnection(); - const isSpecialErc4626BundlerMarket = isSpecialErc4626LeverageMarket(market.uniqueKey); + const specialErc4626LeverageConfig = getSpecialErc4626LeverageConfig(market.morphoBlue.chain.id); + const isSpecialErc4626BundlerMarket = isSpecialErc4626LeverageMarket(market.morphoBlue.chain.id, market.uniqueKey); const { swapRoute, isErc4626ModeAvailable, availableRouteModes, isErc4626ProbeLoading, isErc4626ProbeRefetching } = useLeverageRouteAvailability({ @@ -151,30 +152,31 @@ export function LeverageModal({ ]); const isErc4626Route = route?.kind === 'erc4626'; const isSwapRoute = route?.kind === 'swap'; - const shouldShowSpecialBundlerWarning = isSpecialErc4626BundlerMarket && isErc4626Route && hasAcknowledgedSpecialBundlerWarning === false; + const shouldShowSpecialBundlerWarning = isSpecialErc4626BundlerMarket && isErc4626Route && !hasAcknowledgedSpecialBundlerWarning; useEffect(() => { - if (!isSpecialErc4626BundlerMarket) { + if (!isSpecialErc4626BundlerMarket || !specialErc4626LeverageConfig) { setHasAcknowledgedSpecialBundlerWarning(true); return; } try { - const hasAcknowledged = window.localStorage.getItem(SPECIAL_ERC4626_LEVERAGE_CONFIG.warningStorageKey) === '1'; + const hasAcknowledged = window.localStorage.getItem(specialErc4626LeverageConfig.warningStorageKey) === '1'; setHasAcknowledgedSpecialBundlerWarning(hasAcknowledged); } catch { setHasAcknowledgedSpecialBundlerWarning(false); } - }, [isSpecialErc4626BundlerMarket]); + }, [isSpecialErc4626BundlerMarket, specialErc4626LeverageConfig]); const acknowledgeSpecialBundlerWarning = useCallback(() => { + if (!specialErc4626LeverageConfig) return; try { - window.localStorage.setItem(SPECIAL_ERC4626_LEVERAGE_CONFIG.warningStorageKey, '1'); + window.localStorage.setItem(specialErc4626LeverageConfig.warningStorageKey, '1'); } catch { // ignore storage write failures and still hide warning for this session } setHasAcknowledgedSpecialBundlerWarning(true); - }, []); + }, [specialErc4626LeverageConfig]); const displayedRouteMode = useMemo(() => { if (route?.kind) return route.kind; @@ -220,6 +222,35 @@ export function LeverageModal({ const isRefreshingAnyData = isRefreshing || isFetchingCollateralTokenBalance; + const routeContent = useMemo((): JSX.Element | null => { + if (!route) return null; + + if (effectiveMode === 'leverage') { + return ( + + ); + } + + return ( + + ); + }, [route, effectiveMode, market, position, collateralTokenBalance, oraclePrice, handleRefreshAll, isRefreshingAnyData]); + const mainIcon = (
- {route ? ( - effectiveMode === 'leverage' ? ( - - ) : ( - - ) + {routeContent ? ( + routeContent ) : isErc4626ProbeLoading || isErc4626ProbeRefetching ? (
Checking available leverage routes...
) : ( @@ -321,7 +333,7 @@ export function LeverageModal({
-

{SPECIAL_ERC4626_LEVERAGE_CONFIG.warningMessage}

+

{specialErc4626LeverageConfig?.warningMessage}