From c3c0a8036793e24e6e04d5b5de79c8c1f33c1455 Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Tue, 13 Jan 2026 11:34:42 +0200 Subject: [PATCH 01/18] feature: key value for address disable banner --- src/components/vault/NftMintBanner.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/vault/NftMintBanner.tsx b/src/components/vault/NftMintBanner.tsx index 797146a..2fb2303 100644 --- a/src/components/vault/NftMintBanner.tsx +++ b/src/components/vault/NftMintBanner.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { useVaultContext } from '@/contexts'; +import { useVaultContext, useAppContext } from '@/contexts'; export default function NftMintBanner() { const { @@ -7,19 +7,24 @@ export default function NftMintBanner() { isWhitelistedToMintNft, nftTotalSupply } = useVaultContext(); + const { address } = useAppContext(); const [isDismissed, setIsDismissed] = useState(true); useEffect(() => { - const dismissed = localStorage.getItem('nft_mint_banner_closed'); + if (!address) return; + const dismissed = localStorage.getItem(`nft_mint_banner_closed_${address.toLowerCase()}`); if (dismissed !== 'true') { setIsDismissed(false); + } else { + setIsDismissed(true); } - }, []); + }, [address]); const handleClose = () => { + if (!address) return; setIsDismissed(true); - localStorage.setItem('nft_mint_banner_closed', 'true'); + localStorage.setItem(`nft_mint_banner_closed_${address.toLowerCase()}`, 'true'); }; const isVisible = !isDismissed && !hasNft && isWhitelistedToMintNft && nftTotalSupply < 1024; From 59cb88e94a32923d81deb04db42ea2c3120ac0a3 Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Tue, 13 Jan 2026 13:30:36 +0200 Subject: [PATCH 02/18] fix: show wrap only for deposit & mint --- src/components/vault/FlashLoanDepositWithdrawHandler.tsx | 7 +++++-- src/components/vault/FlashLoanHelperHandler.tsx | 7 ++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx index f7f0e75..78c4dc1 100644 --- a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx @@ -136,8 +136,11 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa setWrapError(''); setWrapSuccess(''); - // Something very ugly here, should be rewrited in future - setUseEthWrapToWSTETH(true); + if (actionType === 'withdraw') { + setUseEthWrapToWSTETH(false); + } else { + setUseEthWrapToWSTETH(true); + } setShowWarning(false); }, [actionType]); diff --git a/src/components/vault/FlashLoanHelperHandler.tsx b/src/components/vault/FlashLoanHelperHandler.tsx index f92861f..dc268f9 100644 --- a/src/components/vault/FlashLoanHelperHandler.tsx +++ b/src/components/vault/FlashLoanHelperHandler.tsx @@ -125,10 +125,15 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa setPreviewedWstEthAmount(null); setEthToWrapValue(''); setEffectiveCollateralBalance(''); - setUseEthWrapToWSTETH(true); setHasInsufficientBalance(false); + if (helperType === 'redeem') { + setUseEthWrapToWSTETH(false); + } else { + setUseEthWrapToWSTETH(true); + } + setWrapError(''); setWrapSuccess(''); }, [helperType]); From aa24f44d3d492c9c3099c133a353eb5fe2e45091 Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Tue, 13 Jan 2026 18:43:08 +0200 Subject: [PATCH 03/18] fix: hide wrap preview on warning or erorr --- .../vault/FlashLoanDepositWithdrawHandler.tsx | 17 ++++++- .../vault/FlashLoanHelperHandler.tsx | 16 ++++++- src/utils/index.ts | 1 + src/utils/isShowWrapPreview.ts | 48 +++++++++++++++++++ 4 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 src/utils/isShowWrapPreview.ts diff --git a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx index f7f0e75..f7cdf37 100644 --- a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx @@ -9,7 +9,8 @@ import { formatUsdValue, wrapEthToWstEth, calculateEthWrapForFlashLoan, - processInput + processInput, + isShowWrapPreview } from '@/utils'; import { PreviewBox, @@ -493,6 +494,18 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa const userBalance = actionType === 'deposit' ? collateralTokenBalance : sharesBalance; const userBalanceToken = actionType === 'deposit' ? formatTokenSymbol(collateralTokenSymbol) : sharesSymbol; + const shouldShowWrapPreview = isShowWrapPreview({ + inputValue, + isInputMoreThanMax, + isAmountLessThanMin, + invalidRebalanceMode, + hasInsufficientBalance, + isErrorLoadingPreview, + showWarning, + isWrapping, + flashLoanLoading: flashLoan.loading + }); + return (
@@ -578,7 +591,7 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa

ETH will be automatically wrapped to wstETH

- {previewedWstEthAmount && ethToWrapValue && ( + {previewedWstEthAmount && ethToWrapValue && shouldShowWrapPreview && (

→ Will wrap ETH to ~ wstETH

diff --git a/src/components/vault/FlashLoanHelperHandler.tsx b/src/components/vault/FlashLoanHelperHandler.tsx index f92861f..70a8c62 100644 --- a/src/components/vault/FlashLoanHelperHandler.tsx +++ b/src/components/vault/FlashLoanHelperHandler.tsx @@ -9,7 +9,8 @@ import { formatUsdValue, wrapEthToWstEth, calculateEthWrapForFlashLoan, - processInput + processInput, + isShowWrapPreview } from '@/utils'; import { PreviewBox, @@ -413,6 +414,17 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa const userBalance = helperType === 'mint' ? effectiveCollateralBalance : sharesBalance; const userBalanceToken = helperType === 'mint' ? formatTokenSymbol(collateralTokenSymbol) : sharesSymbol; + const shouldShowWrapPreview = isShowWrapPreview({ + inputValue, + isInputMoreThanMax, + isAmountLessThanMin, + invalidRebalanceMode, + hasInsufficientBalance, + isErrorLoadingPreview, + isWrapping, + flashLoanLoading: flashLoan.loading + }); + return (
@@ -480,7 +492,7 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa

ETH will be wrapped to wstETH before the flash loan mint

- {previewedWstEthAmount && ethToWrapValue && ( + {previewedWstEthAmount && ethToWrapValue && shouldShowWrapPreview && (

→ Will receive ~ wstETH from wrapping and use ~ from balance

diff --git a/src/utils/index.ts b/src/utils/index.ts index c9780e0..1feadd8 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -23,4 +23,5 @@ export { formatUsdValue } from './formatUsdValue'; export { limitDecimals } from './limitDecimals'; export { processInput } from './processInput'; export { applyGasSlippage } from './applyGasSlippage'; +export { isShowWrapPreview } from './isShowWrapPreview'; export * from './api'; diff --git a/src/utils/isShowWrapPreview.ts b/src/utils/isShowWrapPreview.ts new file mode 100644 index 0000000..3363307 --- /dev/null +++ b/src/utils/isShowWrapPreview.ts @@ -0,0 +1,48 @@ +type ErrorFlags = { + isInputMoreThanMax: boolean; + isAmountLessThanMin: boolean; + invalidRebalanceMode: boolean; + hasInsufficientBalance: boolean; + isErrorLoadingPreview: boolean; + showWarning?: boolean; // for FlashLoanDepositWithdrawHandler + flashLoanLoading: boolean; + isWrapping: boolean; + inputValue?: string | null; +}; + +export function isShowWrapPreview(flags: ErrorFlags) { + const { + inputValue, + isInputMoreThanMax, + isAmountLessThanMin, + invalidRebalanceMode, + hasInsufficientBalance, + isErrorLoadingPreview, + showWarning = false, + flashLoanLoading, + isWrapping, + } = flags; + + const hasAnyErrorOrWarning = + !!inputValue && + ( + isInputMoreThanMax || + isAmountLessThanMin || + invalidRebalanceMode || + hasInsufficientBalance || + isErrorLoadingPreview || + showWarning + ) && + !flashLoanLoading && + !isWrapping; + + const shouldShowWrapPreview = + isWrapping || + ( + !!inputValue && + !flashLoanLoading && + !hasAnyErrorOrWarning + ); + + return shouldShowWrapPreview; +} From 1b1954979fc43e626084534411097c0f435ff2d7 Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Thu, 15 Jan 2026 12:00:32 +0200 Subject: [PATCH 04/18] fix: window between input and preview --- src/components/ui/PreviewBox.tsx | 3 - .../vault/FlashLoanDepositWithdrawHandler.tsx | 14 +-- .../vault/FlashLoanHelperHandler.tsx | 14 +-- src/hooks/useFlashLoanPreview.ts | 104 ++++++++++-------- 4 files changed, 73 insertions(+), 62 deletions(-) diff --git a/src/components/ui/PreviewBox.tsx b/src/components/ui/PreviewBox.tsx index 2fcd6de..f472adb 100644 --- a/src/components/ui/PreviewBox.tsx +++ b/src/components/ui/PreviewBox.tsx @@ -12,14 +12,12 @@ export interface PreviewItem { interface PreviewBoxProps { receive: PreviewItem[]; provide: PreviewItem[]; - isLoading: boolean; title?: string; } export const PreviewBox: React.FC = ({ receive, provide, - isLoading, title = 'Preview' }) => { const { @@ -61,7 +59,6 @@ export const PreviewBox: React.FC = ({

{title}

- {isLoading && " (Loading...)"}
{provide.length > 0 && ( diff --git a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx index f7f0e75..3afbc88 100644 --- a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx @@ -94,20 +94,18 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa const isWstETHVault = actionType === 'deposit' && collateralToken && isWstETHAddress(collateralTokenAddress || ''); const { - isLoadingPreview, + hasLoadedPreviewOnce, previewData, receive, provide, isErrorLoadingPreview, invalidRebalanceMode } = useFlashLoanPreview({ - sharesToProcess: estimatedShares, helperType: actionType === 'deposit' ? 'mint' : 'redeem', - mintHelper: flashLoanMintHelper, - redeemHelper: flashLoanRedeemHelper, - collateralTokenDecimals, + sharesToProcess: estimatedShares, sharesBalance, - sharesDecimals, + mintHelper: flashLoanMintHelper, + redeemHelper: flashLoanRedeemHelper }); const rawInputSymbol = actionType === 'deposit' ? (isWstETHVault ? 'ETH' : collateralTokenSymbol) : borrowTokenSymbol; @@ -608,7 +606,6 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa ) : null} @@ -627,7 +624,8 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa invalidRebalanceMode || isInputMoreThanMax || isMinMoreThanMax || - isAmountLessThanMin + isAmountLessThanMin || + !hasLoadedPreviewOnce } className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed" > diff --git a/src/components/vault/FlashLoanHelperHandler.tsx b/src/components/vault/FlashLoanHelperHandler.tsx index f92861f..b91c39b 100644 --- a/src/components/vault/FlashLoanHelperHandler.tsx +++ b/src/components/vault/FlashLoanHelperHandler.tsx @@ -92,20 +92,18 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa const isWstETHVault = helperType === 'mint' && collateralToken && isWstETHAddress(collateralTokenAddress || ''); const { - isLoadingPreview, + hasLoadedPreviewOnce, previewData, receive, provide, isErrorLoadingPreview, invalidRebalanceMode } = useFlashLoanPreview({ - sharesToProcess, helperType, - mintHelper: flashLoanMintHelper, - redeemHelper: flashLoanRedeemHelper, - collateralTokenDecimals, + sharesToProcess, sharesBalance, - sharesDecimals, + mintHelper: flashLoanMintHelper, + redeemHelper: flashLoanRedeemHelper }); const maxAmountUsd = useMaxAmountUsd({ @@ -508,7 +506,6 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa ) : null} @@ -526,7 +523,8 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa invalidRebalanceMode || isMinMoreThanMax || isInputMoreThanMax || - isAmountLessThanMin + isAmountLessThanMin || + !hasLoadedPreviewOnce } className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed" > diff --git a/src/hooks/useFlashLoanPreview.ts b/src/hooks/useFlashLoanPreview.ts index 2d4f04d..c606ba2 100644 --- a/src/hooks/useFlashLoanPreview.ts +++ b/src/hooks/useFlashLoanPreview.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { FlashLoanMintHelper, FlashLoanRedeemHelper } from '@/typechain-types'; import { TokenType } from '@/types/actions'; @@ -21,13 +21,11 @@ interface UseFlashLoanPreviewParams { helperType: HelperType; mintHelper: FlashLoanMintHelper | null; redeemHelper: FlashLoanRedeemHelper | null; - collateralTokenDecimals: bigint; sharesBalance: string; - sharesDecimals: bigint; } interface UseFlashLoanPreviewReturn { - isLoadingPreview: boolean; + hasLoadedPreviewOnce: boolean; previewData: PreviewData | null; receive: Array<{ amount: bigint; tokenType: TokenType }>; provide: Array<{ amount: bigint; tokenType: TokenType }>; @@ -42,91 +40,111 @@ export const useFlashLoanPreview = ({ redeemHelper, sharesBalance, }: UseFlashLoanPreviewParams): UseFlashLoanPreviewReturn => { - const [isLoadingPreview, setIsLoadingPreview] = useState(false); + const [hasLoadedPreviewOnce, setHasLoadedPreviewOnce] = useState(false); const [isErrorLoadingPreview, setIsErrorLoadingPreview] = useState(false); const [invalidRebalanceMode, setInvalidRebalanceMode] = useState(false); const [previewData, setPreviewData] = useState(null); - const loadPreview = async (shares: bigint | null) => { - if ( - shares === null || shares <= 0n || - helperType === 'mint' && !mintHelper || - helperType === 'redeem' && !redeemHelper - ) { + const intervalRef = useRef(null); + + const isValid = + sharesToProcess !== null && + sharesToProcess > 0n && + ( + (helperType === 'mint' && !!mintHelper) || + (helperType === 'redeem' && !!redeemHelper) + ); + + const loadPreview = async () => { + if (!isValid) { setPreviewData(null); return; } - + setIsErrorLoadingPreview(false); setInvalidRebalanceMode(false); - setIsLoadingPreview(true); try { let amount: bigint; + if (helperType === 'mint') { - // returns collateral required - amount = await mintHelper!.previewMintSharesWithFlashLoanCollateral(shares); + amount = await mintHelper!.previewMintSharesWithFlashLoanCollateral( + sharesToProcess! + ); } else { - // returns borrow tokens to receive - amount = await redeemHelper!.previewRedeemSharesWithCurveAndFlashLoanBorrow(shares); + amount = await redeemHelper!.previewRedeemSharesWithCurveAndFlashLoanBorrow( + sharesToProcess! + ); } + amount = reduceByPrecisionBuffer(amount); + setPreviewData({ amount }); + setHasLoadedPreviewOnce(true); } catch (err: any) { - setIsErrorLoadingPreview(true); console.error('Error loading preview:', err); + setIsErrorLoadingPreview(true); - if (err.message.includes('InvalidRebalanceMode')) { + if (err?.message?.includes('InvalidRebalanceMode')) { setInvalidRebalanceMode(true); } setPreviewData(null); - } finally { - setIsLoadingPreview(false); } }; + // Immediate first load + // Start auto-refresh every 6s AFTER first load useEffect(() => { - const timeoutId = setTimeout(() => { - loadPreview(sharesToProcess); - }, 500); + if (!isValid) { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + return; + } + + loadPreview(); - return () => clearTimeout(timeoutId); - }, [sharesToProcess, helperType, sharesBalance]); + // auto refresh + intervalRef.current = setInterval(() => { + loadPreview(); + }, 6000); + + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + }; + }, [sharesToProcess, helperType, mintHelper, redeemHelper, sharesBalance]); + + useEffect(() => { + setHasLoadedPreviewOnce(false); + }, [sharesToProcess]); - // Reset preview data and errors when helper type changes useEffect(() => { - setPreviewData(null); setIsErrorLoadingPreview(false); setInvalidRebalanceMode(false); + setPreviewData(null); }, [helperType]); - const getReceiveAndProvide = () => { - const receive: Array<{ amount: bigint; tokenType: TokenType }> = []; - const provide: Array<{ amount: bigint; tokenType: TokenType }> = []; - - if (!sharesToProcess || sharesToProcess <= 0n || !previewData) { - return { receive, provide }; - } + const receive: Array<{ amount: bigint; tokenType: TokenType }> = []; + const provide: Array<{ amount: bigint; tokenType: TokenType }> = []; + if (previewData && sharesToProcess && sharesToProcess > 0n) { if (helperType === 'mint') { - // For mint: provide collateral, receive shares provide.push({ amount: previewData.amount, tokenType: 'collateral' }); receive.push({ amount: sharesToProcess, tokenType: 'shares' }); } else { - // For redeem: provide shares, receive borrow tokens provide.push({ amount: sharesToProcess, tokenType: 'shares' }); receive.push({ amount: previewData.amount, tokenType: 'borrow' }); } - - return { receive, provide }; - }; - - const { receive, provide } = getReceiveAndProvide(); + } return { - isLoadingPreview, + hasLoadedPreviewOnce, previewData, receive, provide, From c6bbf7221421f05869ae49a049aba7db367294b7 Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Thu, 15 Jan 2026 12:10:19 +0200 Subject: [PATCH 05/18] fix: build error --- src/components/actions/ActionHandler.tsx | 3 +-- src/components/actions/SafeActionHandler.tsx | 3 +-- src/components/vault/AuctionHandler.tsx | 6 ------ src/components/vault/LowLevelRebalanceHandler.tsx | 6 ------ 4 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/components/actions/ActionHandler.tsx b/src/components/actions/ActionHandler.tsx index 7d5167c..837d052 100644 --- a/src/components/actions/ActionHandler.tsx +++ b/src/components/actions/ActionHandler.tsx @@ -96,7 +96,7 @@ export default function ActionHandler({ actionType, tokenType }: ActionHandlerPr const displayTokenSymbol = config.usesShares ? sharesSymbol : formatTokenSymbol(tokenSymbol); const displayDecimals = config.usesShares ? sharesDecimals : tokenDecimals; - const { isLoadingPreview, previewData, receive, provide } = useActionPreview({ + const { previewData, receive, provide } = useActionPreview({ amount, actionType, tokenType, @@ -314,7 +314,6 @@ export default function ActionHandler({ actionType, tokenType }: ActionHandlerPr ) : undefined diff --git a/src/components/actions/SafeActionHandler.tsx b/src/components/actions/SafeActionHandler.tsx index 0b411a9..67bed6e 100644 --- a/src/components/actions/SafeActionHandler.tsx +++ b/src/components/actions/SafeActionHandler.tsx @@ -105,7 +105,7 @@ export default function SafeActionHandler({ actionType, tokenType }: SafeActionH const displayTokenSymbol = config.usesShares ? sharesSymbol : formatTokenSymbol(tokenSymbol); const displayDecimals = config.usesShares ? sharesDecimals : tokenDecimals; - const { isLoadingPreview, previewData, receive, provide } = useActionPreview({ + const { previewData, receive, provide } = useActionPreview({ amount, actionType, tokenType, @@ -458,7 +458,6 @@ export default function SafeActionHandler({ actionType, tokenType }: SafeActionH ) : undefined diff --git a/src/components/vault/AuctionHandler.tsx b/src/components/vault/AuctionHandler.tsx index dfb54a8..184d558 100644 --- a/src/components/vault/AuctionHandler.tsx +++ b/src/components/vault/AuctionHandler.tsx @@ -15,7 +15,6 @@ export default function AuctionHandler({ futureBorrowAssets, futureCollateralAss const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); - const [isLoadingPreview, setIsLoadingPreview] = useState(false); const [isLoadingMax, setIsLoadingMax] = useState(false); const [previewData, setPreviewData] = useState<{ @@ -148,8 +147,6 @@ export default function AuctionHandler({ futureBorrowAssets, futureCollateralAss return; } - setIsLoadingPreview(true); - try { let result: bigint; @@ -169,8 +166,6 @@ export default function AuctionHandler({ futureBorrowAssets, futureCollateralAss } catch (err) { console.error('Error loading preview:', err); setPreviewData(null); - } finally { - setIsLoadingPreview(false); } }; @@ -345,7 +340,6 @@ export default function AuctionHandler({ futureBorrowAssets, futureCollateralAss ); diff --git a/src/components/vault/LowLevelRebalanceHandler.tsx b/src/components/vault/LowLevelRebalanceHandler.tsx index e2b6d88..c9ad298 100644 --- a/src/components/vault/LowLevelRebalanceHandler.tsx +++ b/src/components/vault/LowLevelRebalanceHandler.tsx @@ -19,7 +19,6 @@ export default function LowLevelRebalanceHandler({ rebalanceType, actionType }: const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); - const [isLoadingPreview, setIsLoadingPreview] = useState(false); const [isLoadingMax, setIsLoadingMax] = useState(false); const [previewData, setPreviewData] = useState<{ @@ -118,8 +117,6 @@ export default function LowLevelRebalanceHandler({ rebalanceType, actionType }: return; } - setIsLoadingPreview(true); - try { let result: any; @@ -145,8 +142,6 @@ export default function LowLevelRebalanceHandler({ rebalanceType, actionType }: } catch (err) { console.error('Error loading preview:', err); setPreviewData(null); - } finally { - setIsLoadingPreview(false); } }; @@ -469,7 +464,6 @@ export default function LowLevelRebalanceHandler({ rebalanceType, actionType }: ); From 94280144032477551f9d6513ead9b081d302fe1d Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Thu, 15 Jan 2026 12:40:21 +0200 Subject: [PATCH 06/18] fix: button blick issue --- src/components/vault/FlashLoanDepositWithdrawHandler.tsx | 4 ++-- src/components/vault/FlashLoanHelperHandler.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx index f7f0e75..2a666dd 100644 --- a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx @@ -443,14 +443,14 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa setWrapError ); - setIsWrapping(false); - if (!wrapResult) { + setIsWrapping(false); return; // Error already set by wrapEthToWstEth } // Refresh balances to get updated wstETH balance await refreshBalances(); + setIsWrapping(false); } const success = await flashLoan.execute(); diff --git a/src/components/vault/FlashLoanHelperHandler.tsx b/src/components/vault/FlashLoanHelperHandler.tsx index f92861f..fb1a736 100644 --- a/src/components/vault/FlashLoanHelperHandler.tsx +++ b/src/components/vault/FlashLoanHelperHandler.tsx @@ -374,14 +374,14 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa setWrapError ); - setIsWrapping(false); - if (!wrapResult) { + setIsWrapping(false); return; // Error already set by wrapEthToWstEth } // Refresh balances to get updated wstETH balance await refreshBalances(); + setIsWrapping(false); } const success = await flashLoan.execute(); From 877c6f9728dafc32c48884c7ba5c3784db46029b Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Fri, 16 Jan 2026 13:37:57 +0200 Subject: [PATCH 07/18] fix: insufficient balance blick --- .../vault/FlashLoanDepositWithdrawHandler.tsx | 10 ++++++++++ src/components/vault/FlashLoanHelperHandler.tsx | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx index f7f0e75..67d7a98 100644 --- a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx @@ -342,9 +342,12 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa return; } + let isEffectAborted = false; + // Helper type is deposit (mint) const determineRequiredWrapAmount = async () => { if (!useEthWrapToWSTETH || !isWstETHVault) { + if (isEffectAborted) return; setPreviewedWstEthAmount(null); setEthToWrapValue(''); @@ -360,6 +363,7 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa // If wrapping is enabled, we need previewData to calculate wrap amount if (!previewData) { + if (isEffectAborted) return; setHasInsufficientBalance(false); return; } @@ -373,6 +377,8 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa gasReserveWei: GAS_RESERVE_WEI }); + if (isEffectAborted) return; + if (result.shouldWrap) { const ethToWrap = applyMintSlippage(parseEther(result.ethToWrapValue)); setEthToWrapValue(formatUnits(ethToWrap, 18)); @@ -391,6 +397,10 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa }; determineRequiredWrapAmount(); + + return () => { + isEffectAborted = true; + }; }, [ previewData, estimatedShares, diff --git a/src/components/vault/FlashLoanHelperHandler.tsx b/src/components/vault/FlashLoanHelperHandler.tsx index f92861f..9fa7e4d 100644 --- a/src/components/vault/FlashLoanHelperHandler.tsx +++ b/src/components/vault/FlashLoanHelperHandler.tsx @@ -268,9 +268,12 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa return; } + let isEffectAborted = false; + // Helper type is mint const determineRequiredWrapAmount = async () => { if (!useEthWrapToWSTETH || !isWstETHVault) { + if (isEffectAborted) return; setPreviewedWstEthAmount(null); setEthToWrapValue(''); setEffectiveCollateralBalance(collateralTokenBalance); @@ -285,6 +288,7 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa // If wrapping is enabled, we need previewData to calculate wrap amount if (!previewData) { + if (isEffectAborted) return; setHasInsufficientBalance(false); return; } @@ -298,6 +302,8 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa gasReserveWei: GAS_RESERVE_WEI }); + if (isEffectAborted) return; + if (result.shouldWrap) { const ethToWrap = applyMintSlippage(parseEther(result.ethToWrapValue)); setEthToWrapValue(formatUnits(ethToWrap, 18)); @@ -322,6 +328,10 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa }; determineRequiredWrapAmount(); + + return () => { + isEffectAborted = true; + }; }, [ previewData, sharesToProcess, From e1cfb2a12212a9c6d225ef133c6bdf6a3a374a57 Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Fri, 30 Jan 2026 13:05:08 +0200 Subject: [PATCH 08/18] fix: remove unnecessary bool var --- src/components/vault/FlashLoanDepositWithdrawHandler.tsx | 3 +-- src/components/vault/FlashLoanHelperHandler.tsx | 3 +-- src/hooks/useFlashLoanPreview.ts | 9 --------- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx index 3afbc88..ac33d88 100644 --- a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx @@ -94,7 +94,6 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa const isWstETHVault = actionType === 'deposit' && collateralToken && isWstETHAddress(collateralTokenAddress || ''); const { - hasLoadedPreviewOnce, previewData, receive, provide, @@ -625,7 +624,7 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa isInputMoreThanMax || isMinMoreThanMax || isAmountLessThanMin || - !hasLoadedPreviewOnce + !previewData } className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed" > diff --git a/src/components/vault/FlashLoanHelperHandler.tsx b/src/components/vault/FlashLoanHelperHandler.tsx index b91c39b..4a83b17 100644 --- a/src/components/vault/FlashLoanHelperHandler.tsx +++ b/src/components/vault/FlashLoanHelperHandler.tsx @@ -92,7 +92,6 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa const isWstETHVault = helperType === 'mint' && collateralToken && isWstETHAddress(collateralTokenAddress || ''); const { - hasLoadedPreviewOnce, previewData, receive, provide, @@ -524,7 +523,7 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa isMinMoreThanMax || isInputMoreThanMax || isAmountLessThanMin || - !hasLoadedPreviewOnce + !previewData } className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed" > diff --git a/src/hooks/useFlashLoanPreview.ts b/src/hooks/useFlashLoanPreview.ts index c606ba2..fb2d917 100644 --- a/src/hooks/useFlashLoanPreview.ts +++ b/src/hooks/useFlashLoanPreview.ts @@ -25,7 +25,6 @@ interface UseFlashLoanPreviewParams { } interface UseFlashLoanPreviewReturn { - hasLoadedPreviewOnce: boolean; previewData: PreviewData | null; receive: Array<{ amount: bigint; tokenType: TokenType }>; provide: Array<{ amount: bigint; tokenType: TokenType }>; @@ -40,7 +39,6 @@ export const useFlashLoanPreview = ({ redeemHelper, sharesBalance, }: UseFlashLoanPreviewParams): UseFlashLoanPreviewReturn => { - const [hasLoadedPreviewOnce, setHasLoadedPreviewOnce] = useState(false); const [isErrorLoadingPreview, setIsErrorLoadingPreview] = useState(false); const [invalidRebalanceMode, setInvalidRebalanceMode] = useState(false); const [previewData, setPreviewData] = useState(null); @@ -80,8 +78,6 @@ export const useFlashLoanPreview = ({ amount = reduceByPrecisionBuffer(amount); setPreviewData({ amount }); - setHasLoadedPreviewOnce(true); - } catch (err: any) { console.error('Error loading preview:', err); setIsErrorLoadingPreview(true); @@ -120,10 +116,6 @@ export const useFlashLoanPreview = ({ }; }, [sharesToProcess, helperType, mintHelper, redeemHelper, sharesBalance]); - useEffect(() => { - setHasLoadedPreviewOnce(false); - }, [sharesToProcess]); - useEffect(() => { setIsErrorLoadingPreview(false); setInvalidRebalanceMode(false); @@ -144,7 +136,6 @@ export const useFlashLoanPreview = ({ } return { - hasLoadedPreviewOnce, previewData, receive, provide, From 9bba083a67d36bbb121eed504b08750a92f81458 Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Fri, 30 Jan 2026 16:59:47 +0200 Subject: [PATCH 09/18] fix: simplify reset logic --- src/components/vault/FlashLoanDepositWithdrawHandler.tsx | 6 +----- src/components/vault/FlashLoanHelperHandler.tsx | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx index 78c4dc1..5942ad5 100644 --- a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx @@ -136,11 +136,7 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa setWrapError(''); setWrapSuccess(''); - if (actionType === 'withdraw') { - setUseEthWrapToWSTETH(false); - } else { - setUseEthWrapToWSTETH(true); - } + setUseEthWrapToWSTETH(actionType !== 'withdraw'); setShowWarning(false); }, [actionType]); diff --git a/src/components/vault/FlashLoanHelperHandler.tsx b/src/components/vault/FlashLoanHelperHandler.tsx index dc268f9..e221f2a 100644 --- a/src/components/vault/FlashLoanHelperHandler.tsx +++ b/src/components/vault/FlashLoanHelperHandler.tsx @@ -128,11 +128,7 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa setHasInsufficientBalance(false); - if (helperType === 'redeem') { - setUseEthWrapToWSTETH(false); - } else { - setUseEthWrapToWSTETH(true); - } + setUseEthWrapToWSTETH(helperType !== 'redeem'); setWrapError(''); setWrapSuccess(''); From 61a4bf0a72acd10abfd17d5174d87b2e95c4b3fa Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Fri, 30 Jan 2026 17:53:29 +0200 Subject: [PATCH 10/18] fix: rewrite boolean logic --- src/utils/isShowWrapPreview.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/utils/isShowWrapPreview.ts b/src/utils/isShowWrapPreview.ts index 3363307..3c28257 100644 --- a/src/utils/isShowWrapPreview.ts +++ b/src/utils/isShowWrapPreview.ts @@ -24,17 +24,12 @@ export function isShowWrapPreview(flags: ErrorFlags) { } = flags; const hasAnyErrorOrWarning = - !!inputValue && - ( - isInputMoreThanMax || - isAmountLessThanMin || - invalidRebalanceMode || - hasInsufficientBalance || - isErrorLoadingPreview || - showWarning - ) && - !flashLoanLoading && - !isWrapping; + isInputMoreThanMax || + isAmountLessThanMin || + invalidRebalanceMode || + hasInsufficientBalance || + isErrorLoadingPreview || + showWarning; const shouldShowWrapPreview = isWrapping || From 3719baf180c9f7d0fff241524e0280b2de625f6d Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Mon, 2 Feb 2026 16:41:06 +0100 Subject: [PATCH 11/18] fix: alternative fix --- src/components/vault/FlashLoanDepositWithdrawHandler.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx index f7f0e75..42c6df3 100644 --- a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx @@ -91,7 +91,7 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa const helperAddress = actionType === 'deposit' ? flashLoanMintHelperAddress : flashLoanRedeemHelperAddress; // Check if this is a wstETH vault that supports ETH input - const isWstETHVault = actionType === 'deposit' && collateralToken && isWstETHAddress(collateralTokenAddress || ''); + const isWstETHVault = collateralToken && isWstETHAddress(collateralTokenAddress || ''); const { isLoadingPreview, @@ -394,7 +394,6 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa }, [ previewData, estimatedShares, - actionType, useEthWrapToWSTETH, isWstETHVault, collateralTokenBalance, From 1e328666196d8298d314a773b6cfdc3ecdf69bd7 Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Tue, 3 Feb 2026 17:16:46 +0200 Subject: [PATCH 12/18] Revert "fix: insufficient balance blick" This reverts commit 877c6f9728dafc32c48884c7ba5c3784db46029b. --- .../vault/FlashLoanDepositWithdrawHandler.tsx | 10 ---------- src/components/vault/FlashLoanHelperHandler.tsx | 10 ---------- 2 files changed, 20 deletions(-) diff --git a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx index 67d7a98..f7f0e75 100644 --- a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx @@ -342,12 +342,9 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa return; } - let isEffectAborted = false; - // Helper type is deposit (mint) const determineRequiredWrapAmount = async () => { if (!useEthWrapToWSTETH || !isWstETHVault) { - if (isEffectAborted) return; setPreviewedWstEthAmount(null); setEthToWrapValue(''); @@ -363,7 +360,6 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa // If wrapping is enabled, we need previewData to calculate wrap amount if (!previewData) { - if (isEffectAborted) return; setHasInsufficientBalance(false); return; } @@ -377,8 +373,6 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa gasReserveWei: GAS_RESERVE_WEI }); - if (isEffectAborted) return; - if (result.shouldWrap) { const ethToWrap = applyMintSlippage(parseEther(result.ethToWrapValue)); setEthToWrapValue(formatUnits(ethToWrap, 18)); @@ -397,10 +391,6 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa }; determineRequiredWrapAmount(); - - return () => { - isEffectAborted = true; - }; }, [ previewData, estimatedShares, diff --git a/src/components/vault/FlashLoanHelperHandler.tsx b/src/components/vault/FlashLoanHelperHandler.tsx index 9fa7e4d..f92861f 100644 --- a/src/components/vault/FlashLoanHelperHandler.tsx +++ b/src/components/vault/FlashLoanHelperHandler.tsx @@ -268,12 +268,9 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa return; } - let isEffectAborted = false; - // Helper type is mint const determineRequiredWrapAmount = async () => { if (!useEthWrapToWSTETH || !isWstETHVault) { - if (isEffectAborted) return; setPreviewedWstEthAmount(null); setEthToWrapValue(''); setEffectiveCollateralBalance(collateralTokenBalance); @@ -288,7 +285,6 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa // If wrapping is enabled, we need previewData to calculate wrap amount if (!previewData) { - if (isEffectAborted) return; setHasInsufficientBalance(false); return; } @@ -302,8 +298,6 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa gasReserveWei: GAS_RESERVE_WEI }); - if (isEffectAborted) return; - if (result.shouldWrap) { const ethToWrap = applyMintSlippage(parseEther(result.ethToWrapValue)); setEthToWrapValue(formatUnits(ethToWrap, 18)); @@ -328,10 +322,6 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa }; determineRequiredWrapAmount(); - - return () => { - isEffectAborted = true; - }; }, [ previewData, sharesToProcess, From 4e613ae7f439dc303972c46318f3cf907d3a4a85 Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Tue, 3 Feb 2026 17:19:09 +0200 Subject: [PATCH 13/18] fix: insufficient balance blick issue --- src/components/vault/FlashLoanHelperHandler.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/vault/FlashLoanHelperHandler.tsx b/src/components/vault/FlashLoanHelperHandler.tsx index f92861f..d95e676 100644 --- a/src/components/vault/FlashLoanHelperHandler.tsx +++ b/src/components/vault/FlashLoanHelperHandler.tsx @@ -89,7 +89,7 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa const helperAddress = helperType === 'mint' ? flashLoanMintHelperAddress : flashLoanRedeemHelperAddress; // Check if this is a wstETH vault that supports ETH input - const isWstETHVault = helperType === 'mint' && collateralToken && isWstETHAddress(collateralTokenAddress || ''); + const isWstETHVault = collateralToken && isWstETHAddress(collateralTokenAddress || ''); const { isLoadingPreview, @@ -325,7 +325,6 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa }, [ previewData, sharesToProcess, - helperType, useEthWrapToWSTETH, isWstETHVault, collateralTokenBalance, From 31876b53ffa0e6337e57eb044bd57641f9d800ea Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Tue, 13 Jan 2026 12:48:07 +0200 Subject: [PATCH 14/18] feature: disallow switching tabs while processing --- src/components/ui/Tabs.tsx | 39 ++++++++++++------- .../vault/FlashLoanDepositWithdraw.tsx | 13 ++++++- .../vault/FlashLoanDepositWithdrawForm.tsx | 13 ++++++- .../vault/FlashLoanDepositWithdrawHandler.tsx | 9 ++++- src/components/vault/FlashLoanHelper.tsx | 13 ++++++- .../vault/FlashLoanHelperHandler.tsx | 9 ++++- 6 files changed, 74 insertions(+), 22 deletions(-) diff --git a/src/components/ui/Tabs.tsx b/src/components/ui/Tabs.tsx index d572c20..8f25f5a 100644 --- a/src/components/ui/Tabs.tsx +++ b/src/components/ui/Tabs.tsx @@ -9,6 +9,7 @@ interface TabsPropsGeneric { activeTab: T; setActiveTab: React.Dispatch>; tabs: TabItem[]; + isProcessing?: boolean; className?: string; } @@ -16,23 +17,33 @@ interface TabsPropsAction { activeTab: ActionType; setActiveTab: React.Dispatch>; tabs?: never; + isProcessing?: boolean; className?: string; } -type TabsProps = T extends ActionType - ? TabsPropsAction +type TabsProps = T extends ActionType + ? TabsPropsAction : TabsPropsGeneric; -export default function Tabs({ - activeTab, - setActiveTab, +export default function Tabs({ + activeTab, + setActiveTab, tabs, - className + isProcessing, + className, }: TabsProps | TabsPropsGeneric) { + const safeSetActiveTab = (tab: T) => { + if (isProcessing && tab !== activeTab) return; + setActiveTab(tab as any); + }; + const tabClass = (tab: T) => - `flex-1 py-2 px-4 rounded-lg font-medium transition-colors focus:outline-none focus:ring-0 ${activeTab === tab - ? 'bg-indigo-600 text-white' - : 'bg-gray-100 text-gray-700 hover:bg-gray-200' + `flex-1 py-2 px-4 rounded-lg font-medium transition-colors focus:outline-none focus:ring-0 ${ + isProcessing && tab !== activeTab + ? 'bg-gray-200 text-gray-400 cursor-not-allowed opacity-60 border-0' + : activeTab === tab + ? 'bg-indigo-600 text-white' + : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }`; // If custom tabs are provided, use them @@ -43,7 +54,7 @@ export default function Tabs({ {tabs.map((tab) => ( -
- -
diff --git a/src/components/vault/FlashLoanDepositWithdraw.tsx b/src/components/vault/FlashLoanDepositWithdraw.tsx index 0d50a29..bd13a8c 100644 --- a/src/components/vault/FlashLoanDepositWithdraw.tsx +++ b/src/components/vault/FlashLoanDepositWithdraw.tsx @@ -24,6 +24,7 @@ export default function FlashLoanDepositWithdraw() { const [isOpen, setIsOpen] = useState(false); const [activeTab, setActiveTab] = useState(availableTabs[0]?.value || 'deposit'); + const [isProcessing, setIsProcessing] = useState(false); if (availableTabs.length === 0) { return null; @@ -52,10 +53,18 @@ export default function FlashLoanDepositWithdraw() { > {availableTabs.length > 1 && (
- +
)} - +
); diff --git a/src/components/vault/FlashLoanDepositWithdrawForm.tsx b/src/components/vault/FlashLoanDepositWithdrawForm.tsx index af69151..a474bb6 100644 --- a/src/components/vault/FlashLoanDepositWithdrawForm.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawForm.tsx @@ -11,6 +11,7 @@ export default function FlashLoanDepositWithdrawForm() { ] const [activeTab, setActiveTab] = useState(tabs[0]?.value || 'deposit'); + const [isProcessing, setIsProcessing] = useState(false); return (
@@ -19,10 +20,18 @@ export default function FlashLoanDepositWithdrawForm() { > {tabs.length > 1 && (
- +
)} - +
); diff --git a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx index e319448..aa56423 100644 --- a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx @@ -38,6 +38,7 @@ type ActionType = 'deposit' | 'withdraw'; interface FlashLoanDepositWithdrawHandlerProps { actionType: ActionType; + setIsProcessing: React.Dispatch>; } const GAS_RESERVE_MULTIPLIER = 3n; @@ -49,7 +50,10 @@ const MINT_SLIPPAGE_DIVIDER = 1000000; const FLASH_LOAN_DEPOSIT_WITHDRAW_PRECISION_DIVIDEND = 99999; const FLASH_LOAN_DEPOSIT_WITHDRAW_PRECISION_DIVIDER = 100000; -export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoanDepositWithdrawHandlerProps) { +export default function FlashLoanDepositWithdrawHandler({ + actionType, + setIsProcessing +}: FlashLoanDepositWithdrawHandlerProps) { const [inputValue, setInputValue] = useState(''); const [estimatedShares, setEstimatedShares] = useState(null); const [wrapError, setWrapError] = useState(''); @@ -424,6 +428,7 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa setWrapError(''); setWrapSuccess(''); + setIsProcessing(true); // If using ETH input for wstETH vault, wrap ETH to wstETH first if (useEthWrapToWSTETH && isWstETHVault && ethToWrapValue && provider && signer) { @@ -441,6 +446,7 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa if (!wrapResult) { setIsWrapping(false); + setIsProcessing(false); return; // Error already set by wrapEthToWstEth } @@ -450,6 +456,7 @@ export default function FlashLoanDepositWithdrawHandler({ actionType }: FlashLoa } const success = await flashLoan.execute(); + setIsProcessing(false); if (success) { setInputValue(''); diff --git a/src/components/vault/FlashLoanHelper.tsx b/src/components/vault/FlashLoanHelper.tsx index fc1e898..c679ef6 100644 --- a/src/components/vault/FlashLoanHelper.tsx +++ b/src/components/vault/FlashLoanHelper.tsx @@ -24,6 +24,7 @@ export default function FlashLoanHelper() { const [isOpen, setIsOpen] = useState(false); const [activeTab, setActiveTab] = useState(availableTabs[0]?.value || 'mint'); + const [isProcessing, setIsProcessing] = useState(false); if (availableTabs.length === 0) { return null; @@ -54,10 +55,18 @@ export default function FlashLoanHelper() { > {availableTabs.length > 1 && (
- +
)} - + ); diff --git a/src/components/vault/FlashLoanHelperHandler.tsx b/src/components/vault/FlashLoanHelperHandler.tsx index d62f94e..9b7b4e4 100644 --- a/src/components/vault/FlashLoanHelperHandler.tsx +++ b/src/components/vault/FlashLoanHelperHandler.tsx @@ -37,6 +37,7 @@ type HelperType = 'mint' | 'redeem'; interface FlashLoanHelperHandlerProps { helperType: HelperType; + setIsProcessing: React.Dispatch>; } const GAS_RESERVE_MULTIPLIER = 3n; @@ -48,7 +49,10 @@ const MINT_MAX_SLIPPAGE_DIVIDER = 1000000; const MINT_SLIPPAGE_DIVIDEND = 1000001; const MINT_SLIPPAGE_DIVIDER = 1000000; -export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHandlerProps) { +export default function FlashLoanHelperHandler({ + helperType, + setIsProcessing +}: FlashLoanHelperHandlerProps) { const [inputValue, setInputValue] = useState(''); const [sharesToProcess, setSharesToProcess] = useState(null); const [wrapError, setWrapError] = useState(''); @@ -357,6 +361,7 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa setWrapError(''); setWrapSuccess(''); + setIsProcessing(true); // If using ETH input for wstETH vault, wrap ETH to wstETH first if (useEthWrapToWSTETH && isWstETHVault && ethToWrapValue && provider && signer) { @@ -374,6 +379,7 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa if (!wrapResult) { setIsWrapping(false); + setIsProcessing(false); return; // Error already set by wrapEthToWstEth } @@ -383,6 +389,7 @@ export default function FlashLoanHelperHandler({ helperType }: FlashLoanHelperHa } const success = await flashLoan.execute(); + setIsProcessing(false); if (success) { setInputValue(''); From 59a157451be6563f4e6626eeb86eb08883ab0b7c Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Fri, 30 Jan 2026 18:16:30 +0200 Subject: [PATCH 15/18] fix: refreshBalances try catch --- .../vault/FlashLoanDepositWithdrawHandler.tsx | 61 +++++++++++-------- .../vault/FlashLoanHelperHandler.tsx | 60 ++++++++++-------- 2 files changed, 67 insertions(+), 54 deletions(-) diff --git a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx index aa56423..40280b1 100644 --- a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx @@ -430,38 +430,45 @@ export default function FlashLoanDepositWithdrawHandler({ setWrapSuccess(''); setIsProcessing(true); - // If using ETH input for wstETH vault, wrap ETH to wstETH first - if (useEthWrapToWSTETH && isWstETHVault && ethToWrapValue && provider && signer) { - setIsWrapping(true); - const ethAmount = parseEther(ethToWrapValue); + try { + // If using ETH input for wstETH vault, wrap ETH to wstETH first + if (useEthWrapToWSTETH && isWstETHVault && ethToWrapValue && provider && signer) { + setIsWrapping(true); + const ethAmount = parseEther(ethToWrapValue); + + const wrapResult = await wrapEthToWstEth( + provider, + signer, + ethAmount, + address, + setWrapSuccess, + setWrapError + ); + + if (!wrapResult) { + setIsWrapping(false); + setIsProcessing(false); + return; // Error already set by wrapEthToWstEth, finally will handle processing state + } - const wrapResult = await wrapEthToWstEth( - provider, - signer, - ethAmount, - address, - setWrapSuccess, - setWrapError - ); - - if (!wrapResult) { + // Refresh balances to get updated wstETH balance + await refreshBalances(); setIsWrapping(false); - setIsProcessing(false); - return; // Error already set by wrapEthToWstEth } - // Refresh balances to get updated wstETH balance - await refreshBalances(); - setIsWrapping(false); - } - - const success = await flashLoan.execute(); - setIsProcessing(false); + const success = await flashLoan.execute(); - if (success) { - setInputValue(''); - setEstimatedShares(null); - setEthToWrapValue(''); + if (success) { + setInputValue(''); + setEstimatedShares(null); + setEthToWrapValue(''); + } + } catch (err) { + console.error('Error in handling flash loan submit:', err); + } finally { + setIsProcessing(false); + // Safety check to ensure isWrapping is also disabled if something failed during wrap + setIsWrapping(false); } }; diff --git a/src/components/vault/FlashLoanHelperHandler.tsx b/src/components/vault/FlashLoanHelperHandler.tsx index 9b7b4e4..500f393 100644 --- a/src/components/vault/FlashLoanHelperHandler.tsx +++ b/src/components/vault/FlashLoanHelperHandler.tsx @@ -363,39 +363,45 @@ export default function FlashLoanHelperHandler({ setWrapSuccess(''); setIsProcessing(true); - // If using ETH input for wstETH vault, wrap ETH to wstETH first - if (useEthWrapToWSTETH && isWstETHVault && ethToWrapValue && provider && signer) { - setIsWrapping(true); - const ethAmount = parseEther(ethToWrapValue); + try { + // If using ETH input for wstETH vault, wrap ETH to wstETH first + if (useEthWrapToWSTETH && isWstETHVault && ethToWrapValue && provider && signer) { + setIsWrapping(true); + const ethAmount = parseEther(ethToWrapValue); + + const wrapResult = await wrapEthToWstEth( + provider, + signer, + ethAmount, + address, + setWrapSuccess, + setWrapError + ); + + if (!wrapResult) { + setIsWrapping(false); + setIsProcessing(false); + return; // Error already set by wrapEthToWstEth, finally will handle processing state + } - const wrapResult = await wrapEthToWstEth( - provider, - signer, - ethAmount, - address, - setWrapSuccess, - setWrapError - ); - - if (!wrapResult) { + // Refresh balances to get updated wstETH balance + await refreshBalances(); setIsWrapping(false); - setIsProcessing(false); - return; // Error already set by wrapEthToWstEth } - // Refresh balances to get updated wstETH balance - await refreshBalances(); + const success = await flashLoan.execute(); + + if (success) { + setInputValue(''); + setSharesToProcess(null); + setEthToWrapValue(''); + } + } catch (err) { + console.error('Error in handling flash loan helper submit:', err); + } finally { + setIsProcessing(false); setIsWrapping(false); } - - const success = await flashLoan.execute(); - setIsProcessing(false); - - if (success) { - setInputValue(''); - setSharesToProcess(null); - setEthToWrapValue(''); - } }; const handleInputChange = (value: string) => { From 263492f01e074a2adb9c2981cabaa5f6c08c6b45 Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Tue, 10 Feb 2026 17:34:43 +0200 Subject: [PATCH 16/18] fix: rewrite tabs component to more simple props --- src/components/ui/Tabs.tsx | 77 ++++++------------------ src/components/vault/Actions.tsx | 7 ++- src/components/vault/ActionsDropdown.tsx | 17 +----- src/constants.ts | 10 ++- 4 files changed, 37 insertions(+), 74 deletions(-) diff --git a/src/components/ui/Tabs.tsx b/src/components/ui/Tabs.tsx index 8f25f5a..d8c407b 100644 --- a/src/components/ui/Tabs.tsx +++ b/src/components/ui/Tabs.tsx @@ -1,88 +1,49 @@ -import { ActionType } from '@/types/actions'; - interface TabItem { value: T; label: string; } -interface TabsPropsGeneric { +interface TabsProps { activeTab: T; - setActiveTab: React.Dispatch>; + setActiveTab: React.Dispatch> | ((tab: T) => void); tabs: TabItem[]; isProcessing?: boolean; className?: string; } -interface TabsPropsAction { - activeTab: ActionType; - setActiveTab: React.Dispatch>; - tabs?: never; - isProcessing?: boolean; - className?: string; -} - -type TabsProps = T extends ActionType - ? TabsPropsAction - : TabsPropsGeneric; - -export default function Tabs({ +export default function Tabs({ activeTab, setActiveTab, tabs, isProcessing, className, -}: TabsProps | TabsPropsGeneric) { +}: TabsProps) { const safeSetActiveTab = (tab: T) => { if (isProcessing && tab !== activeTab) return; - setActiveTab(tab as any); + setActiveTab(tab); }; const tabClass = (tab: T) => - `flex-1 py-2 px-4 rounded-lg font-medium transition-colors focus:outline-none focus:ring-0 ${ - isProcessing && tab !== activeTab - ? 'bg-gray-200 text-gray-400 cursor-not-allowed opacity-60 border-0' - : activeTab === tab + `flex-1 py-2 px-4 rounded-lg font-medium transition-colors focus:outline-none focus:ring-0 ${isProcessing && tab !== activeTab + ? 'bg-gray-200 text-gray-400 cursor-not-allowed opacity-60 border-0' + : activeTab === tab ? 'bg-indigo-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }`; - // If custom tabs are provided, use them - if (tabs) { - return ( -
-
- {tabs.map((tab) => ( - - ))} -
-
- ); - } - - // Default action tabs layout return (
-
- - -
-
- - +
+ {tabs.map((tab) => ( + + ))}
); diff --git a/src/components/vault/Actions.tsx b/src/components/vault/Actions.tsx index 6ab7410..9f543e9 100644 --- a/src/components/vault/Actions.tsx +++ b/src/components/vault/Actions.tsx @@ -3,6 +3,7 @@ import Tabs from '@/components/ui/Tabs'; import ActionWrapper from '@/components/actions/ActionWrapper'; import { ActionType } from '@/types/actions'; import DexLink from './DexLink'; +import { ACTIONS_TABS } from '@/constants'; interface ActionsProps { isSafe?: boolean; @@ -13,7 +14,11 @@ export default function Actions({ isSafe = false }: ActionsProps) { return (
- +
diff --git a/src/components/vault/ActionsDropdown.tsx b/src/components/vault/ActionsDropdown.tsx index da3f6d1..6b50b49 100644 --- a/src/components/vault/ActionsDropdown.tsx +++ b/src/components/vault/ActionsDropdown.tsx @@ -3,12 +3,13 @@ import Tabs from '@/components/ui/Tabs'; import ActionWrapper from '@/components/actions/ActionWrapper'; import { ActionType } from '@/types/actions'; import DexLink from './DexLink'; +import { ACTIONS_TABS } from '@/constants'; interface ActionsProps { isSafe?: boolean; } -export default function ActionsDropdown({ isSafe = false } : ActionsProps ) { +export default function ActionsDropdown({ isSafe = false }: ActionsProps) { const [isOpen, setIsOpen] = useState(false); const [activeTab, setActiveTab] = useState('deposit'); @@ -33,22 +34,10 @@ export default function ActionsDropdown({ isSafe = false } : ActionsProps ) { className={`transition-all duration-200 overflow-hidden ${isOpen ? 'max-h-[2000px] opacity-100 p-3' : 'max-h-0 opacity-0 pb-0' }`} > - +
); } - -export function Actions({ isSafe = false }: ActionsProps) { - const [activeTab, setActiveTab] = useState('deposit'); - - return ( -
- - - -
- ); -} \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts index f2a3ed9..13237c8 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,4 +1,5 @@ import { parseEther } from "ethers"; +import { ActionType } from '@/types/actions'; const SEPOLIA_WETH_ADDRESSES = [ '0x2d5ee574e710219a521449679a4a7f2b43f046ad', @@ -103,4 +104,11 @@ export const SAFE_HELPER_ADDRESSES: Record Date: Thu, 15 Jan 2026 12:32:47 +0200 Subject: [PATCH 17/18] fix: is input zero or nan --- .../vault/FlashLoanDepositWithdrawHandler.tsx | 17 ++++++++++------- src/components/vault/FlashLoanHelperHandler.tsx | 11 +++++++---- src/utils/index.ts | 1 + src/utils/isZeroOrNan.ts | 4 ++++ 4 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 src/utils/isZeroOrNan.ts diff --git a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx index 40280b1..70ff7ee 100644 --- a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx @@ -10,7 +10,8 @@ import { wrapEthToWstEth, calculateEthWrapForFlashLoan, processInput, - isShowWrapPreview + isShowWrapPreview, + isZeroOrNan } from '@/utils'; import { PreviewBox, @@ -143,6 +144,8 @@ export default function FlashLoanDepositWithdrawHandler({ setShowWarning(false); }, [actionType]); + const isInputZeroOrNaN = isZeroOrNan(inputValue); + const isInputMoreThanMax = useIsAmountMoreThanMax({ amount: inputValue, max: maxAmount, @@ -222,7 +225,7 @@ export default function FlashLoanDepositWithdrawHandler({ }); const calculateShares = async () => { - if (!inputValue || !vaultLens) { + if (isInputZeroOrNaN || !vaultLens) { setEstimatedShares(null); setShowWarning(false); return; @@ -323,7 +326,7 @@ export default function FlashLoanDepositWithdrawHandler({ useEffect(() => { // Reset state if input is empty or invalid - if (!inputValue || !estimatedShares || estimatedShares <= 0n) { + if (isInputZeroOrNaN || !estimatedShares || estimatedShares <= 0n) { setPreviewedWstEthAmount(null); setEthToWrapValue(''); setHasInsufficientBalance(false); @@ -610,7 +613,7 @@ export default function FlashLoanDepositWithdrawHandler({ )} - {!inputValue ? null : + {isInputZeroOrNaN ? null : isInputMoreThanMax && !flashLoan.loading && !isWrapping ? ( ) : isErrorLoadingPreview ? ( - ) : estimatedShares !== null && estimatedShares > 0n && previewData && !!inputValue ? ( + ) : estimatedShares !== null && estimatedShares > 0n && previewData && !isInputZeroOrNaN ? ( diff --git a/src/components/vault/FlashLoanHelperHandler.tsx b/src/components/vault/FlashLoanHelperHandler.tsx index 500f393..7c2b50e 100644 --- a/src/components/vault/FlashLoanHelperHandler.tsx +++ b/src/components/vault/FlashLoanHelperHandler.tsx @@ -10,7 +10,8 @@ import { wrapEthToWstEth, calculateEthWrapForFlashLoan, processInput, - isShowWrapPreview + isShowWrapPreview, + isZeroOrNan } from '@/utils'; import { PreviewBox, @@ -136,6 +137,8 @@ export default function FlashLoanHelperHandler({ setWrapSuccess(''); }, [helperType]); + const isInputZeroOrNaN = isZeroOrNan(inputValue); + const isInputMoreThanMax = useIsAmountMoreThanMax({ amount: inputValue, max: maxAmount, @@ -512,7 +515,7 @@ export default function FlashLoanHelperHandler({ )} - {!inputValue ? null : isInputMoreThanMax && !flashLoan.loading ? ( + {isInputZeroOrNaN ? null : isInputMoreThanMax && !flashLoan.loading ? ( @@ -540,7 +543,6 @@ export default function FlashLoanHelperHandler({ flashLoan.loading || flashLoan.isApproving || isWrapping || - !inputValue || !sharesToProcess || hasInsufficientBalance || isErrorLoadingPreview || @@ -548,7 +550,8 @@ export default function FlashLoanHelperHandler({ isMinMoreThanMax || isInputMoreThanMax || isAmountLessThanMin || - !previewData + !previewData || + isInputZeroOrNaN } className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed" > diff --git a/src/utils/index.ts b/src/utils/index.ts index 1feadd8..36b873a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -24,4 +24,5 @@ export { limitDecimals } from './limitDecimals'; export { processInput } from './processInput'; export { applyGasSlippage } from './applyGasSlippage'; export { isShowWrapPreview } from './isShowWrapPreview'; +export { isZeroOrNan } from './isZeroOrNan'; export * from './api'; diff --git a/src/utils/isZeroOrNan.ts b/src/utils/isZeroOrNan.ts new file mode 100644 index 0000000..0b59472 --- /dev/null +++ b/src/utils/isZeroOrNan.ts @@ -0,0 +1,4 @@ +export const isZeroOrNan = (value: string) => { + const parsed = parseFloat(value); + return parsed === 0 || isNaN(parsed); +} \ No newline at end of file From 11f0c63045a13c1c155aadc565f0583e635e1ad7 Mon Sep 17 00:00:00 2001 From: Vsevolod Zhuravlov Date: Wed, 14 Jan 2026 19:34:13 +0200 Subject: [PATCH 18/18] feature: lens is lens --- .../vault/FlashLoanDepositWithdrawHandler.tsx | 20 ++-- .../vault/FlashLoanHelperHandler.tsx | 8 +- src/contexts/VaultContext.tsx | 19 ++++ src/hooks/useFlashLoanPreview.ts | 103 ++++++++---------- 4 files changed, 78 insertions(+), 72 deletions(-) diff --git a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx index 70ff7ee..a9f83c4 100644 --- a/src/components/vault/FlashLoanDepositWithdrawHandler.tsx +++ b/src/components/vault/FlashLoanDepositWithdrawHandler.tsx @@ -76,8 +76,8 @@ export default function FlashLoanDepositWithdrawHandler({ const { vaultLens, vaultAddress, - flashLoanMintHelper, - flashLoanRedeemHelper, + flashLoanMintHelperLens, + flashLoanRedeemHelperLens, flashLoanMintHelperAddress, flashLoanRedeemHelperAddress, collateralToken, @@ -109,8 +109,8 @@ export default function FlashLoanDepositWithdrawHandler({ helperType: actionType === 'deposit' ? 'mint' : 'redeem', sharesToProcess: estimatedShares, sharesBalance, - mintHelper: flashLoanMintHelper, - redeemHelper: flashLoanRedeemHelper + mintHelperLens: flashLoanMintHelperLens, + redeemHelperLens: flashLoanRedeemHelperLens }); const rawInputSymbol = actionType === 'deposit' ? (isWstETHVault ? 'ETH' : collateralTokenSymbol) : borrowTokenSymbol; @@ -240,8 +240,6 @@ export default function FlashLoanDepositWithdrawHandler({ } if (actionType === 'deposit') { - if (!flashLoanMintHelper || !publicProvider) return; - let shares = await vaultLens.convertToShares(inputAmount); if (!shares) return; @@ -255,11 +253,11 @@ export default function FlashLoanDepositWithdrawHandler({ return; } - if (!flashLoanRedeemHelper) return; + if (!flashLoanRedeemHelperLens) return; let shares = await findSharesForEthWithdraw({ amount: inputAmount, - helper: flashLoanRedeemHelper, + helper: flashLoanRedeemHelperLens, vaultLens }); @@ -283,7 +281,7 @@ export default function FlashLoanDepositWithdrawHandler({ useEffect(() => { const timeoutId = setTimeout(calculateShares, 500); return () => clearTimeout(timeoutId); - }, [inputValue, actionType, vaultLens, flashLoanMintHelper, flashLoanRedeemHelper, publicProvider, isMaxWithdraw]); + }, [inputValue, actionType, vaultLens, flashLoanRedeemHelperLens, publicProvider, isMaxWithdraw]); const setMaxDeposit = async () => { if (!vaultLens) return; @@ -301,14 +299,14 @@ export default function FlashLoanDepositWithdrawHandler({ }; const setMaxWithdraw = async () => { - if (!flashLoanRedeemHelper || !sharesBalance) { + if (!flashLoanRedeemHelperLens || !sharesBalance) { setMaxAmount('0'); return; } try { const rawShares = parseUnits(sharesBalance, Number(sharesDecimals)); - const maxWeth = await flashLoanRedeemHelper.previewRedeemSharesWithCurveAndFlashLoanBorrow(rawShares); + const maxWeth = await flashLoanRedeemHelperLens.previewRedeemSharesWithCurveAndFlashLoanBorrow(rawShares); setMaxAmount(formatEther(maxWeth)); } catch (err) { console.error("Error calculating max withdraw:", err); diff --git a/src/components/vault/FlashLoanHelperHandler.tsx b/src/components/vault/FlashLoanHelperHandler.tsx index 7c2b50e..e0e2a33 100644 --- a/src/components/vault/FlashLoanHelperHandler.tsx +++ b/src/components/vault/FlashLoanHelperHandler.tsx @@ -74,8 +74,8 @@ export default function FlashLoanHelperHandler({ const { vaultLens, vaultAddress, - flashLoanMintHelper, - flashLoanRedeemHelper, + flashLoanMintHelperLens, + flashLoanRedeemHelperLens, flashLoanMintHelperAddress, flashLoanRedeemHelperAddress, collateralToken, @@ -107,8 +107,8 @@ export default function FlashLoanHelperHandler({ helperType, sharesToProcess, sharesBalance, - mintHelper: flashLoanMintHelper, - redeemHelper: flashLoanRedeemHelper + mintHelperLens: flashLoanMintHelperLens, + redeemHelperLens: flashLoanRedeemHelperLens }); const maxAmountUsd = useMaxAmountUsd({ diff --git a/src/contexts/VaultContext.tsx b/src/contexts/VaultContext.tsx index 149c997..8c0dc6f 100644 --- a/src/contexts/VaultContext.tsx +++ b/src/contexts/VaultContext.tsx @@ -69,6 +69,8 @@ interface VaultContextType { // Flash loan helpers flashLoanMintHelper: FlashLoanMintHelper | null; flashLoanRedeemHelper: FlashLoanRedeemHelper | null; + flashLoanMintHelperLens: FlashLoanMintHelper | null; + flashLoanRedeemHelperLens: FlashLoanRedeemHelper | null; flashLoanMintHelperAddress: string | null; flashLoanRedeemHelperAddress: string | null; // Vault existence @@ -162,6 +164,8 @@ export const VaultContextProvider = ({ children, vaultAddress, params }: { child const [vaultLens, setVaultLens] = useState(null); const [borrowTokenLens, setBorrowTokenLens] = useState(null); const [collateralTokenLens, setCollateralTokenLens] = useState(null); + const [flashLoanMintHelperLens, setFlashLoanMintHelperLens] = useState(null); + const [flashLoanRedeemHelperLens, setFlashLoanRedeemHelperLens] = useState(null); const [sharesDecimals, setSharesDecimals] = useState(18n); const [borrowTokenDecimals, setBorrowTokenDecimals] = useState(18n); const [collateralTokenDecimals, setCollateralTokenDecimals] = useState(18n); @@ -323,6 +327,19 @@ export const VaultContextProvider = ({ children, vaultAddress, params }: { child } } + // Initialize lens helpers (always, using publicProvider) + if (vaultConfig?.flashLoanMintHelperAddress && vaultConfig.flashLoanMintHelperAddress !== '') { + setFlashLoanMintHelperLens(FlashLoanMintHelper__factory.connect(vaultConfig.flashLoanMintHelperAddress, publicProvider)); + } else { + setFlashLoanMintHelperLens(null); + } + + if (vaultConfig?.flashLoanRedeemHelperAddress && vaultConfig.flashLoanRedeemHelperAddress !== '') { + setFlashLoanRedeemHelperLens(FlashLoanRedeemHelper__factory.connect(vaultConfig.flashLoanRedeemHelperAddress, publicProvider)); + } else { + setFlashLoanRedeemHelperLens(null); + } + if (!vaultConfig?.sharesSymbol) { const symbol = await vaultLensInstance.symbol(); setSharesSymbol(symbol); @@ -1022,6 +1039,8 @@ export const VaultContextProvider = ({ children, vaultAddress, params }: { child description, flashLoanMintHelper, flashLoanRedeemHelper, + flashLoanMintHelperLens, + flashLoanRedeemHelperLens, flashLoanMintHelperAddress, flashLoanRedeemHelperAddress, vaultExists, diff --git a/src/hooks/useFlashLoanPreview.ts b/src/hooks/useFlashLoanPreview.ts index fb2d917..f618827 100644 --- a/src/hooks/useFlashLoanPreview.ts +++ b/src/hooks/useFlashLoanPreview.ts @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect } from 'react'; import { FlashLoanMintHelper, FlashLoanRedeemHelper } from '@/typechain-types'; import { TokenType } from '@/types/actions'; @@ -17,14 +17,15 @@ interface PreviewData { } interface UseFlashLoanPreviewParams { - sharesToProcess: bigint | null; helperType: HelperType; - mintHelper: FlashLoanMintHelper | null; - redeemHelper: FlashLoanRedeemHelper | null; sharesBalance: string; + sharesToProcess: bigint | null; + mintHelperLens: FlashLoanMintHelper | null; + redeemHelperLens: FlashLoanRedeemHelper | null; } interface UseFlashLoanPreviewReturn { + isLoadingPreview: boolean; previewData: PreviewData | null; receive: Array<{ amount: bigint; tokenType: TokenType }>; provide: Array<{ amount: bigint; tokenType: TokenType }>; @@ -35,107 +36,95 @@ interface UseFlashLoanPreviewReturn { export const useFlashLoanPreview = ({ sharesToProcess, helperType, - mintHelper, - redeemHelper, + mintHelperLens, + redeemHelperLens, sharesBalance, }: UseFlashLoanPreviewParams): UseFlashLoanPreviewReturn => { + const [isLoadingPreview, setIsLoadingPreview] = useState(false); const [isErrorLoadingPreview, setIsErrorLoadingPreview] = useState(false); const [invalidRebalanceMode, setInvalidRebalanceMode] = useState(false); const [previewData, setPreviewData] = useState(null); - const intervalRef = useRef(null); - - const isValid = - sharesToProcess !== null && - sharesToProcess > 0n && - ( - (helperType === 'mint' && !!mintHelper) || - (helperType === 'redeem' && !!redeemHelper) - ); - - const loadPreview = async () => { - if (!isValid) { + const loadPreview = async (shares: bigint | null) => { + if ( + shares === null || shares <= 0n || + helperType === 'mint' && !mintHelperLens || + helperType === 'redeem' && !redeemHelperLens + ) { setPreviewData(null); return; } setIsErrorLoadingPreview(false); setInvalidRebalanceMode(false); + setIsLoadingPreview(true); try { let amount: bigint; - if (helperType === 'mint') { - amount = await mintHelper!.previewMintSharesWithFlashLoanCollateral( - sharesToProcess! - ); + // returns collateral required + amount = await mintHelperLens!.previewMintSharesWithFlashLoanCollateral(shares); } else { - amount = await redeemHelper!.previewRedeemSharesWithCurveAndFlashLoanBorrow( - sharesToProcess! - ); + // returns borrow tokens to receive + amount = await redeemHelperLens!.previewRedeemSharesWithCurveAndFlashLoanBorrow(shares); } - amount = reduceByPrecisionBuffer(amount); - setPreviewData({ amount }); + } catch (err: any) { - console.error('Error loading preview:', err); setIsErrorLoadingPreview(true); + console.error('Error loading preview:', err); - if (err?.message?.includes('InvalidRebalanceMode')) { + if (err.message.includes('InvalidRebalanceMode')) { setInvalidRebalanceMode(true); } setPreviewData(null); + } finally { + setIsLoadingPreview(false); } }; - // Immediate first load - // Start auto-refresh every 6s AFTER first load useEffect(() => { - if (!isValid) { - if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = null; - } - return; - } - - loadPreview(); - - // auto refresh - intervalRef.current = setInterval(() => { - loadPreview(); - }, 6000); + const timeoutId = setTimeout(() => { + loadPreview(sharesToProcess); + }, 500); - return () => { - if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = null; - } - }; - }, [sharesToProcess, helperType, mintHelper, redeemHelper, sharesBalance]); + return () => clearTimeout(timeoutId); + }, [sharesToProcess, helperType, sharesBalance]); + // Reset preview data and errors when helper type changes useEffect(() => { + setPreviewData(null); setIsErrorLoadingPreview(false); setInvalidRebalanceMode(false); - setPreviewData(null); }, [helperType]); - const receive: Array<{ amount: bigint; tokenType: TokenType }> = []; - const provide: Array<{ amount: bigint; tokenType: TokenType }> = []; + const getReceiveAndProvide = () => { + const receive: Array<{ amount: bigint; tokenType: TokenType }> = []; + const provide: Array<{ amount: bigint; tokenType: TokenType }> = []; + + if (!sharesToProcess || sharesToProcess <= 0n || !previewData) { + return { receive, provide }; + } - if (previewData && sharesToProcess && sharesToProcess > 0n) { if (helperType === 'mint') { + // For mint: provide collateral, receive shares provide.push({ amount: previewData.amount, tokenType: 'collateral' }); receive.push({ amount: sharesToProcess, tokenType: 'shares' }); } else { + // For redeem: provide shares, receive borrow tokens provide.push({ amount: sharesToProcess, tokenType: 'shares' }); receive.push({ amount: previewData.amount, tokenType: 'borrow' }); } - } + + return { receive, provide }; + }; + + const { receive, provide } = getReceiveAndProvide(); return { + isLoadingPreview, previewData, receive, provide,