From 4ec04e7263f79e48a7e1973bd9a10c637a3c9502 Mon Sep 17 00:00:00 2001 From: Jason Schwarz Date: Mon, 23 Mar 2026 16:10:24 -0300 Subject: [PATCH 1/3] fix: don't show "swap too small" on transient Barter API failures Network errors in barter validation were incorrectly setting amountTooSmall=true, causing miles estimates to flicker away and the "swap too small" message to appear briefly on valid swaps. --- src/hooks/use-barter-validation.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/use-barter-validation.ts b/src/hooks/use-barter-validation.ts index 86b68617..aec74abf 100644 --- a/src/hooks/use-barter-validation.ts +++ b/src/hooks/use-barter-validation.ts @@ -98,9 +98,9 @@ export function useBarterValidation({ setAmountTooSmall(shortfall > MAX_SLIPPAGE_PCT) } catch { - if (currentRequest === requestIdRef.current) { - setAmountTooSmall(true) - } + // Network errors should NOT flip the flag — keep whatever state we + // had before so the UI doesn't flicker "swap too small" on transient + // failures. The next successful validation will set the correct value. } finally { if (currentRequest === requestIdRef.current) { setSettled(true) From f2e9101bcd9f0ebe38acfb76f13ecf0139196cb3 Mon Sep 17 00:00:00 2001 From: Jason Schwarz Date: Mon, 23 Mar 2026 16:16:48 -0300 Subject: [PATCH 2/3] fix: freeze quote values on confirmation modal open Snapshot all quote-dependent props (amounts, rates, prices, miles, slippage limits) into a ref when the modal opens. Live quote refreshes behind the modal no longer shift displayed values or the values passed to the wallet on confirm. --- .../modals/SwapConfirmationModal.tsx | 98 ++++++++++++++++--- 1 file changed, 82 insertions(+), 16 deletions(-) diff --git a/src/components/modals/SwapConfirmationModal.tsx b/src/components/modals/SwapConfirmationModal.tsx index e502223f..e3423a69 100644 --- a/src/components/modals/SwapConfirmationModal.tsx +++ b/src/components/modals/SwapConfirmationModal.tsx @@ -182,21 +182,21 @@ function BuyReceiveValue({ value, className }: { value: string; className?: stri function SwapConfirmationModal({ open, onOpenChange, - tokenIn, - tokenOut, - amountIn, - amountOut, - minAmountOut, - slippageLimitFormatted, - isMaxIn = false, - exchangeRate, - priceImpact, - slippage, - deadline, - gasEstimate, - ethPrice, - fromTokenPrice, - toTokenPrice, + tokenIn: tokenInLive, + tokenOut: tokenOutLive, + amountIn: amountInLive, + amountOut: amountOutLive, + minAmountOut: minAmountOutLive, + slippageLimitFormatted: slippageLimitFormattedLive, + isMaxIn: isMaxInLive = false, + exchangeRate: exchangeRateLive, + priceImpact: priceImpactLive, + slippage: slippageLive, + deadline: deadlineLive, + gasEstimate: gasEstimateLive, + ethPrice: ethPriceLive, + fromTokenPrice: fromTokenPriceLive, + toTokenPrice: toTokenPriceLive, timeLeft, isLoading = false, refreshBalances, @@ -208,12 +208,78 @@ function SwapConfirmationModal({ approvalTxHash, onApprove, approveTokenSymbol, - estimatedMiles, + estimatedMiles: estimatedMilesLive, onRetryWithSlippage, autoExecute = false, onAutoExecuteConsumed, externalError, }: SwapConfirmationModalProps) { + // Snapshot quote-dependent values when the modal opens so they stay static + // during the review — live quote refreshes should not shift the numbers. + // We use a ref + synchronous assignment (not useEffect) so the snapshot is + // available on the very first render when `open` flips to true. + const snapshotRef = useRef<{ + tokenIn: Token | undefined + tokenOut: Token | undefined + amountIn: string + amountOut: string + minAmountOut: string + slippageLimitFormatted: string + isMaxIn: boolean + exchangeRate: number + priceImpact: number + slippage: string + deadline: number + gasEstimate: bigint | null + ethPrice: number | undefined + fromTokenPrice: number | null | undefined + toTokenPrice: number | null | undefined + estimatedMiles: number | null | undefined + } | null>(null) + const wasOpenRef = useRef(open) + + if (open && !wasOpenRef.current) { + // Modal just opened — capture current values + snapshotRef.current = { + tokenIn: tokenInLive, + tokenOut: tokenOutLive, + amountIn: amountInLive, + amountOut: amountOutLive, + minAmountOut: minAmountOutLive, + slippageLimitFormatted: slippageLimitFormattedLive, + isMaxIn: isMaxInLive, + exchangeRate: exchangeRateLive, + priceImpact: priceImpactLive, + slippage: slippageLive, + deadline: deadlineLive, + gasEstimate: gasEstimateLive, + ethPrice: ethPriceLive, + fromTokenPrice: fromTokenPriceLive, + toTokenPrice: toTokenPriceLive, + estimatedMiles: estimatedMilesLive, + } + } else if (!open && wasOpenRef.current) { + // Modal just closed — clear snapshot + snapshotRef.current = null + } + wasOpenRef.current = open + + const tokenIn = snapshotRef.current?.tokenIn ?? tokenInLive + const tokenOut = snapshotRef.current?.tokenOut ?? tokenOutLive + const amountIn = snapshotRef.current?.amountIn ?? amountInLive + const amountOut = snapshotRef.current?.amountOut ?? amountOutLive + const minAmountOut = snapshotRef.current?.minAmountOut ?? minAmountOutLive + const slippageLimitFormatted = snapshotRef.current?.slippageLimitFormatted ?? slippageLimitFormattedLive + const isMaxIn = snapshotRef.current?.isMaxIn ?? isMaxInLive + const exchangeRate = snapshotRef.current?.exchangeRate ?? exchangeRateLive + const priceImpact = snapshotRef.current?.priceImpact ?? priceImpactLive + const slippage = snapshotRef.current?.slippage ?? slippageLive + const deadline = snapshotRef.current?.deadline ?? deadlineLive + const gasEstimate = snapshotRef.current?.gasEstimate ?? gasEstimateLive + const ethPrice = snapshotRef.current?.ethPrice ?? ethPriceLive + const fromTokenPrice = snapshotRef.current?.fromTokenPrice ?? fromTokenPriceLive + const toTokenPrice = snapshotRef.current?.toTokenPrice ?? toTokenPriceLive + const estimatedMiles = snapshotRef.current?.estimatedMiles ?? estimatedMilesLive // --- EXTERNAL HOOKS --- const { chain: signerChain, isConnected } = useAccount() From cece6fd4c8696f88c4d3a92bcd4c1d6ebaf3f3fb Mon Sep 17 00:00:00 2001 From: Jason Schwarz Date: Mon, 23 Mar 2026 16:20:08 -0300 Subject: [PATCH 3/3] style: format long line in SwapConfirmationModal --- src/components/modals/SwapConfirmationModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/modals/SwapConfirmationModal.tsx b/src/components/modals/SwapConfirmationModal.tsx index e3423a69..ccc8d626 100644 --- a/src/components/modals/SwapConfirmationModal.tsx +++ b/src/components/modals/SwapConfirmationModal.tsx @@ -269,7 +269,8 @@ function SwapConfirmationModal({ const amountIn = snapshotRef.current?.amountIn ?? amountInLive const amountOut = snapshotRef.current?.amountOut ?? amountOutLive const minAmountOut = snapshotRef.current?.minAmountOut ?? minAmountOutLive - const slippageLimitFormatted = snapshotRef.current?.slippageLimitFormatted ?? slippageLimitFormattedLive + const slippageLimitFormatted = + snapshotRef.current?.slippageLimitFormatted ?? slippageLimitFormattedLive const isMaxIn = snapshotRef.current?.isMaxIn ?? isMaxInLive const exchangeRate = snapshotRef.current?.exchangeRate ?? exchangeRateLive const priceImpact = snapshotRef.current?.priceImpact ?? priceImpactLive