From 6acb5e1a8249911c5d847cd7bb70df482e72d866 Mon Sep 17 00:00:00 2001 From: Martin Grabina Date: Mon, 8 Jun 2026 15:50:14 -0300 Subject: [PATCH] fix(swap): route Paraswap collateral swaps through the adapter Non-flashloan Paraswap collateral swaps were sent to the plain SwapActionsViaParaswap path, which swaps the aTokens directly. That depends on Paraswap pricing the aTokens, which it doesn't do correctly on every chain: on Sonic it reports aUSDC as 18 decimals and values it at $0, so the quote is rejected (ESTIMATED_LOSS_GREATER_THAN_MAX_IMPACT) or the build fails (Source Decimals Mismatch). Restore the pre-refactor behavior: Paraswap collateral swaps always go through the collateral-swap adapter, which operates on the underlying tokens. It runs swapAndDeposit (no flashloan) or a flashloan depending on the health-factor impact, as before. The quote uses the underlying tokens too, which Paraswap prices correctly. --- .../CollateralSwap/CollateralSwapActions.tsx | 32 +++++++------------ ...llateralSwapActionsViaParaswapAdapters.tsx | 2 +- .../transactions/Swap/hooks/useSwapQuote.ts | 4 ++- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActions.tsx b/src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActions.tsx index 82ae9ad3e6..ec1f99bf81 100644 --- a/src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActions.tsx +++ b/src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActions.tsx @@ -3,7 +3,6 @@ import { Dispatch } from 'react'; import { TrackAnalyticsHandlers } from '../../analytics/useTrackAnalytics'; import { ProtocolSwapParams, ProtocolSwapState, SwapProvider, SwapState } from '../../types'; import { SwapActionsViaCoW } from '../SwapActions/SwapActionsViaCoW'; -import { SwapActionsViaParaswap } from '../SwapActions/SwapActionsViaParaswap'; import { CollateralSwapActionsViaCowAdapters } from './CollateralSwapActionsViaCoWAdapters'; import { CollateralSwapActionsViaParaswapAdapters } from './CollateralSwapActionsViaParaswapAdapters'; @@ -41,25 +40,16 @@ export const CollateralSwapActions = ({ ); } case SwapProvider.PARASWAP: - if (state.useFlashloan) { - return ( - - ); - } else { - // Essentially traditional aTokens swap - return ( - - ); - } + // Paraswap can't swap aTokens directly, so always use the adapter. It runs swapAndDeposit + // (no flashloan) or a flashloan based on state.useFlashloan, which useFlowSelector sets from + // the health-factor impact. + return ( + + ); } }; diff --git a/src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActionsViaParaswapAdapters.tsx b/src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActionsViaParaswapAdapters.tsx index c8567ed466..c7d0a53247 100644 --- a/src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActionsViaParaswapAdapters.tsx +++ b/src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActionsViaParaswapAdapters.tsx @@ -139,7 +139,7 @@ export const CollateralSwapActionsViaParaswapAdapters = ({ symbol: state.sourceToken.symbol, blocked: areActionsBlocked(state), isMaxSelected: isMaxSelected, - useFlashLoan: true, + useFlashLoan: state.useFlashloan ?? false, swapCallData: swapCallData, augustus: augustus, signature: signatureParams?.splitedSignature, diff --git a/src/components/transactions/Swap/hooks/useSwapQuote.ts b/src/components/transactions/Swap/hooks/useSwapQuote.ts index 414ac20386..56d3427863 100644 --- a/src/components/transactions/Swap/hooks/useSwapQuote.ts +++ b/src/components/transactions/Swap/hooks/useSwapQuote.ts @@ -48,12 +48,14 @@ const getTokenSelectionForQuote = ( const destTokenObj = invertedQuoteRoute ? state.sourceToken : state.destinationToken; // Quote tokens must match what the order will post on, otherwise CoW's per-pair volume-fee policy and gas estimate apply to a different pair than the order and the cushion computed in useSwapOrderAmounts comes out short. Read provider from the active query arg, not state.provider, which lags during provider transitions. + // Paraswap collateral swaps go through the adapter (swapAndDeposit or flashloan), which operates on the underlying tokens, so quote the underlying. Quoting the aToken depends on Paraswap pricing it correctly, which it doesn't for every chain (e.g. Sonic aUSDC comes back as 18 decimals / $0). const usesAddressToSwap = state.useFlashloan === false && (provider === SwapProvider.COW_PROTOCOL || (provider === SwapProvider.PARASWAP && state.swapType !== SwapType.WithdrawAndSwap && - state.swapType !== SwapType.RepayWithCollateral)); + state.swapType !== SwapType.RepayWithCollateral && + state.swapType !== SwapType.CollateralSwap)); const srcToken = usesAddressToSwap ? srcTokenObj.addressToSwap : srcTokenObj.underlyingAddress; const destToken = usesAddressToSwap ? destTokenObj.addressToSwap : destTokenObj.underlyingAddress;