Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Price Impact Warning #5635

Merged
merged 12 commits into from
Apr 18, 2024
7 changes: 6 additions & 1 deletion src/__swaps__/screens/Swap/Swap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { SwapInputAsset } from '@/__swaps__/screens/Swap/components/controls/Swa
import { SwapOutputAsset } from '@/__swaps__/screens/Swap/components/controls/SwapOutputAsset';
import { SwapNavbar } from '@/__swaps__/screens/Swap/components/SwapNavbar';
import { SwapAmountInputs } from '@/__swaps__/screens/Swap/components/controls/SwapAmountInputs';
import { SwapWarning } from './components/SwapWarning';

/** README
* This prototype is largely driven by Reanimated and Gesture Handler, which
Expand Down Expand Up @@ -63,7 +64,11 @@ export function SwapScreen() {
<SwapInputAsset />
<FlipButton />
<SwapOutputAsset />
<ExchangeRateBubble />
<Box alignItems="center" justifyContent="center" style={{ position: 'relative' }}>
<ExchangeRateBubble />
<SwapWarning />
</Box>

<SwapAmountInputs />
</Box>
<SwapNavbar />
Expand Down
3 changes: 1 addition & 2 deletions src/__swaps__/screens/Swap/components/ExchangeRateBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider
export const ExchangeRateBubble = () => {
const { isDarkMode } = useColorMode();
const { AnimatedSwapStyles, SwapInputController } = useSwapContext();

const [exchangeRateIndex, setExchangeRateIndex] = useState<number>(0);

const assetToSellPrice = useSharedValue(0);
Expand Down Expand Up @@ -149,7 +148,7 @@ export const ExchangeRateBubble = () => {
justifyContent="center"
paddingHorizontal="24px"
paddingVertical="12px"
style={[AnimatedSwapStyles.hideWhenInputsExpanded, { alignSelf: 'center' }]}
style={[AnimatedSwapStyles.hideWhenInputsExpandedOrPriceImpact, { alignSelf: 'center', position: 'absolute', top: 4 }]}
>
<Box
as={Animated.View}
Expand Down
107 changes: 107 additions & 0 deletions src/__swaps__/screens/Swap/components/SwapWarning.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from 'react';
import * as i18n from '@/languages';
import Animated, { useAnimatedStyle, useDerivedValue } from 'react-native-reanimated';
import { AnimatedText, Box, Inline, useForegroundColor } from '@/design-system';
import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider';
import { SwapWarningType } from '@/__swaps__/screens/Swap/hooks/useSwapWarning';

export const SwapWarning = () => {
const { AnimatedSwapStyles, SwapWarning } = useSwapContext();

const red = useForegroundColor('red');
const orange = useForegroundColor('orange');

const colorMap = {
[SwapWarningType.severe]: red,
[SwapWarningType.unknown]: red,
[SwapWarningType.long_wait]: orange,
[SwapWarningType.none]: orange,
[SwapWarningType.high]: orange,

// swap quote errors
[SwapWarningType.no_quote_available]: red,
[SwapWarningType.insufficient_liquidity]: red,
[SwapWarningType.fee_on_transfer]: red,
[SwapWarningType.no_route_found]: red,
};

const warningMessagesPrefix: Record<SwapWarningType, { title: string; subtext: string; addDisplayToTitle?: boolean }> = {
[SwapWarningType.none]: {
title: '',
subtext: '',
},
[SwapWarningType.high]: {
title: `􀇿 ${i18n.t(i18n.l.exchange.price_impact.you_are_losing)}`,
subtext: i18n.t(i18n.l.exchange.price_impact.small_market_try_smaller_amount),
addDisplayToTitle: true,
},
[SwapWarningType.unknown]: {
title: `􀇿 ${SwapWarning.swapWarning.value.display}`,
subtext: i18n.t(i18n.l.exchange.price_impact.unknown_price.description),
},
[SwapWarningType.severe]: {
title: `􀇿 ${i18n.t(i18n.l.exchange.price_impact.you_are_losing)}`,
subtext: i18n.t(i18n.l.exchange.price_impact.small_market_try_smaller_amount),
addDisplayToTitle: true,
},
[SwapWarningType.long_wait]: {
title: `􀇿 ${i18n.t(i18n.l.exchange.price_impact.long_wait.title)}`,
subtext: `${i18n.t(i18n.l.exchange.price_impact.long_wait.description)}`,
addDisplayToTitle: true,
},

// swap quote errors
[SwapWarningType.no_quote_available]: {
title: `􀇿 ${i18n.t(i18n.l.exchange.quote_errors.no_quote_available)}`,
subtext: '',
},
[SwapWarningType.insufficient_liquidity]: {
title: `􀇿 ${i18n.t(i18n.l.exchange.quote_errors.insufficient_liquidity)}`,
subtext: '',
},
[SwapWarningType.fee_on_transfer]: {
title: `􀇿 ${i18n.t(i18n.l.exchange.quote_errors.fee_on_transfer)}`,
subtext: '',
},
[SwapWarningType.no_route_found]: {
title: `􀇿 ${i18n.t(i18n.l.exchange.quote_errors.no_route_found)}`,
subtext: '',
},
};

const warningTitle = useDerivedValue(() => {
const potentialTitle = warningMessagesPrefix[SwapWarning.swapWarning.value.type].title;
const addDisplayToTitle = warningMessagesPrefix[SwapWarning.swapWarning.value.type].addDisplayToTitle;
return addDisplayToTitle ? `${potentialTitle} ${SwapWarning.swapWarning.value.display}` : potentialTitle;
});

const warningSubtext = useDerivedValue(() => {
return warningMessagesPrefix[SwapWarning.swapWarning.value.type].subtext;
});

const warningStyles = useAnimatedStyle(() => ({
color: colorMap[SwapWarning.swapWarning.value.type],
}));

const warningSubtextStyles = useAnimatedStyle(() => ({
display: warningSubtext.value.trim() !== '' ? 'flex' : 'none',
}));

return (
<Box
as={Animated.View}
alignItems="center"
justifyContent="center"
paddingHorizontal="24px"
paddingVertical="12px"
style={[AnimatedSwapStyles.hideWhenInputsExpandedOrNoPriceImpact, { alignSelf: 'center', position: 'absolute', top: 8 }]}
>
<Box as={Animated.View} alignItems="center" height={{ custom: 33 }} gap={6} justifyContent="center" paddingHorizontal="10px">
<Inline alignHorizontal="center" alignVertical="center" horizontalSpace="4px" wrap={false}>
<AnimatedText style={warningStyles} align="center" size="15pt" weight="heavy" text={warningTitle} />
</Inline>
<AnimatedText style={warningSubtextStyles} color="labelQuaternary" align="center" size="13pt" weight="bold" text={warningSubtext} />
</Box>
</Box>
);
};
31 changes: 27 additions & 4 deletions src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ import {
} from '@/__swaps__/screens/Swap/constants';
import { opacityWorklet } from '@/__swaps__/utils/swaps';
import { useSwapInputsController } from '@/__swaps__/screens/Swap/hooks/useSwapInputsController';
import { SwapWarningType, useSwapWarning } from '@/__swaps__/screens/Swap/hooks/useSwapWarning';
import { spinnerExitConfig } from '@/__swaps__/components/animations/AnimatedSpinner';

export function useAnimatedSwapStyles({
SwapInputController,
SwapWarning,
inputProgress,
outputProgress,
isFetching,
}: {
SwapInputController: ReturnType<typeof useSwapInputsController>;
SwapWarning: ReturnType<typeof useSwapWarning>;
inputProgress: SharedValue<number>;
outputProgress: SharedValue<number>;
isFetching: SharedValue<boolean>;
Expand All @@ -45,10 +48,29 @@ export function useAnimatedSwapStyles({
};
});

const hideWhenInputsExpanded = useAnimatedStyle(() => {
const hideWhenInputsExpandedOrNoPriceImpact = useAnimatedStyle(() => {
return {
opacity: inputProgress.value > 0 || outputProgress.value > 0 ? withTiming(0, fadeConfig) : withTiming(1, fadeConfig),
pointerEvents: inputProgress.value > 0 || outputProgress.value > 0 ? 'none' : 'auto',
opacity:
SwapWarning.swapWarning.value.type === SwapWarningType.none || inputProgress.value > 0 || outputProgress.value > 0
? withTiming(0, fadeConfig)
: withTiming(1, fadeConfig),
pointerEvents:
SwapWarning.swapWarning.value.type === SwapWarningType.none || inputProgress.value > 0 || outputProgress.value > 0
? 'none'
: 'auto',
};
});

const hideWhenInputsExpandedOrPriceImpact = useAnimatedStyle(() => {
return {
opacity:
SwapWarning.swapWarning.value.type !== SwapWarningType.none || inputProgress.value > 0 || outputProgress.value > 0
? withTiming(0, fadeConfig)
: withTiming(1, fadeConfig),
pointerEvents:
SwapWarning.swapWarning.value.type !== SwapWarningType.none || inputProgress.value > 0 || outputProgress.value > 0
? 'none'
: 'auto',
};
});

Expand Down Expand Up @@ -163,7 +185,8 @@ export function useAnimatedSwapStyles({
return {
flipButtonStyle,
focusedSearchStyle,
hideWhenInputsExpanded,
hideWhenInputsExpandedOrPriceImpact,
hideWhenInputsExpandedOrNoPriceImpact,
inputStyle,
inputTokenListStyle,
keyboardStyle,
Expand Down
5 changes: 4 additions & 1 deletion src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,9 @@ export function useSwapInputsController({
quoteParams.swapType === SwapType.crossChain ? await getCrosschainQuote(quoteParams) : await getQuote(quoteParams)
) as Quote | CrosschainQuote | QuoteError;

quote.value = quoteResponse;
logger.debug(`[useSwapInputsController] quote response`, { quoteResponse });

// todo - show quote error
if (!quoteResponse || (quoteResponse as QuoteError)?.error) {
logger.debug(`[useSwapInputsController] quote error`, { error: quoteResponse });
Expand Down Expand Up @@ -501,7 +504,6 @@ export function useSwapInputsController({
// TODO: Need to convert big number to native value properly here...
// example: "fee": "3672850000000000",
fee.value = isWrapOrUnwrapEth ? '0' : data.feeInEth.toString();
quote.value = data;

inputValues.modify(values => {
return {
Expand Down Expand Up @@ -1050,6 +1052,7 @@ export function useSwapInputsController({
assetToSellIconUrl,
assetToBuySymbol,
assetToBuyIconUrl,
quote,
source,
slippage,
flashbots,
Expand Down