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

chore: low return warning alert for bridging #29171

Merged
merged 9 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion shared/constants/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const ETH_USDT_ADDRESS = '0xdac17f958d2ee523a2206206994597c13d831ec7';
export const METABRIDGE_ETHEREUM_ADDRESS =
'0x0439e60F02a8900a951603950d8D4527f400C3f1';
export const BRIDGE_QUOTE_MAX_ETA_SECONDS = 60 * 60; // 1 hour
export const BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE = 0.8; // if a quote returns in x times less return than the best quote, ignore it
export const BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE = 0.5; // if a quote returns in x times less return than the best quote, ignore it

export const BRIDGE_PREFERRED_GAS_ESTIMATE = 'high';
export const BRIDGE_DEFAULT_SLIPPAGE = 0.5;
Expand Down
13 changes: 7 additions & 6 deletions ui/ducks/bridge/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1212,7 +1212,7 @@ describe('Bridge selectors', () => {
).toStrictEqual(false);
});

it('should return isEstimatedReturnLow=true return value is 20% less than sent funds', () => {
it('should return isEstimatedReturnLow=true return value is 50% less than sent funds', () => {
const state = createBridgeMockStore({
featureFlagOverrides: {
extensionConfig: {
Expand All @@ -1228,7 +1228,7 @@ describe('Bridge selectors', () => {
toToken: { address: zeroAddress(), symbol: 'TEST' },
fromTokenInputValue: '1',
fromTokenExchangeRate: 2524.25,
toTokenExchangeRate: 0.798781,
toTokenExchangeRate: 0.61,
},
bridgeStateOverrides: {
quotes: mockBridgeQuotesNativeErc20,
Expand Down Expand Up @@ -1264,11 +1264,11 @@ describe('Bridge selectors', () => {
expect(
getBridgeQuotes(state as never).activeQuote?.adjustedReturn
.valueInCurrency,
).toStrictEqual(new BigNumber('16.99676538473491988'));
).toStrictEqual(new BigNumber('12.38316502627291988'));
expect(result.isEstimatedReturnLow).toStrictEqual(true);
});

it('should return isEstimatedReturnLow=false when return value is more than 80% of sent funds', () => {
it('should return isEstimatedReturnLow=false when return value is more than 50% of sent funds', () => {
const state = createBridgeMockStore({
featureFlagOverrides: {
extensionConfig: {
Expand All @@ -1283,7 +1283,8 @@ describe('Bridge selectors', () => {
fromToken: { address: zeroAddress(), symbol: 'ETH' },
toToken: { address: zeroAddress(), symbol: 'TEST' },
fromTokenExchangeRate: 2524.25,
toTokenExchangeRate: 0.998781,
toTokenExchangeRate: 0.63,
fromTokenInputValue: 1,
},
bridgeStateOverrides: {
quotes: mockBridgeQuotesNativeErc20,
Expand Down Expand Up @@ -1320,7 +1321,7 @@ describe('Bridge selectors', () => {
expect(
getBridgeQuotes(state as never).activeQuote?.adjustedReturn
.valueInCurrency,
).toStrictEqual(new BigNumber('21.88454578473491988'));
).toStrictEqual(new BigNumber('12.87194306627291988'));
expect(result.isEstimatedReturnLow).toStrictEqual(false);
});

Expand Down
25 changes: 2 additions & 23 deletions ui/pages/bridge/prepare/bridge-input-group.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useRef } from 'react';
import { useSelector } from 'react-redux';
import { BigNumber } from 'bignumber.js';
import { getAddress } from 'ethers/lib/utils';
Expand All @@ -7,7 +7,6 @@ import {
TextField,
TextFieldType,
ButtonLink,
PopoverPosition,
Button,
ButtonSize,
} from '../../../components/component-library';
Expand All @@ -17,7 +16,7 @@ import { useI18nContext } from '../../../hooks/useI18nContext';
import { getLocale } from '../../../selectors';
import { getCurrentCurrency } from '../../../ducks/metamask/metamask';
import { formatCurrencyAmount, formatTokenAmount } from '../utils/quote';
import { Column, Row, Tooltip } from '../layout';
import { Column, Row } from '../layout';
import {
Display,
FontWeight,
Expand All @@ -27,7 +26,6 @@ import {
TextColor,
} from '../../../helpers/constants/design-system';
import { AssetType } from '../../../../shared/constants/transaction';
import { BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE } from '../../../../shared/constants/bridge';
import useLatestBalance from '../../../hooks/bridge/useLatestBalance';
import {
getBridgeQuotes,
Expand Down Expand Up @@ -87,8 +85,6 @@ export const BridgeInputGroup = ({

const inputRef = useRef<HTMLInputElement | null>(null);

const [isLowReturnTooltipOpen, setIsLowReturnTooltipOpen] = useState(true);

useEffect(() => {
if (inputRef.current) {
inputRef.current.value = amountFieldProps?.value?.toString() ?? '';
Expand Down Expand Up @@ -189,23 +185,6 @@ export const BridgeInputGroup = ({

<Row justifyContent={JustifyContent.spaceBetween}>
<Row>
{isAmountReadOnly &&
isEstimatedReturnLow &&
isLowReturnTooltipOpen && (
<Tooltip
title={t('lowEstimatedReturnTooltipTitle')}
position={PopoverPosition.TopStart}
isOpen={isLowReturnTooltipOpen}
onClose={() => setIsLowReturnTooltipOpen(false)}
triggerElement={<span />}
flip={false}
offset={[0, 80]}
>
{t('lowEstimatedReturnTooltipMessage', [
BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE * 100,
])}
</Tooltip>
)}
<Text
variant={TextVariant.bodyMd}
fontWeight={FontWeight.Normal}
Expand Down
8 changes: 8 additions & 0 deletions ui/pages/bridge/prepare/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@
border: none;
border-radius: 8px;

.row-with-warning {
padding-left: 16px;
padding-right: 12px;
margin-left: -16px;
max-width: calc(100% + 32px);
width: calc(100% + 32px);
}

[data-theme='light'],
.light {
box-shadow: 0 0 2px 0 #e2e4e9, 0 0 16px 0 rgba(226, 228, 233, 0.16);
Expand Down
7 changes: 5 additions & 2 deletions ui/pages/bridge/prepare/prepare-bridge-page.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@ const Wrapper = ({ children }) => (
);

const mockFeatureFlags = {
srcNetworkAllowlist: [CHAIN_IDS.MAINNET, CHAIN_IDS.LINEA_MAINNET],
destNetworkAllowlist: [CHAIN_IDS.MAINNET, CHAIN_IDS.LINEA_MAINNET],
extensionSupport: true,
extensionConfig: {
refreshRate: 30000,
maxRefreshCount: 5,
support: true,
chains: {
'0x1': { isActiveSrc: true, isActiveDest: true },
'0xa': { isActiveSrc: true, isActiveDest: true },
},
},
};
const mockBridgeSlice = {
Expand Down
42 changes: 37 additions & 5 deletions ui/pages/bridge/prepare/prepare-bridge-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import { useBridgeTokens } from '../../../hooks/bridge/useBridgeTokens';
import { getCurrentKeyring, getLocale } from '../../../selectors';
import { isHardwareKeyring } from '../../../helpers/utils/hardware';
import { SECOND } from '../../../../shared/constants/time';
import { BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE } from '../../../../shared/constants/bridge';
import { BridgeInputGroup } from './bridge-input-group';
import { BridgeCTAButton } from './bridge-cta-button';

Expand Down Expand Up @@ -151,10 +152,12 @@ const PrepareBridgePage = () => {

const ticker = useSelector(getNativeCurrency);
const {
isEstimatedReturnLow,
isNoQuotesAvailable,
isInsufficientGasForQuote,
isInsufficientBalance,
} = useSelector(getValidationErrors);
const { quotesRefreshCount } = useSelector(getBridgeQuotes);
const { openBuyCryptoInPdapp } = useRamps();

const { balanceAmount: nativeAssetBalance } = useLatestBalance(
Expand Down Expand Up @@ -190,6 +193,10 @@ const PrepareBridgePage = () => {

const [rotateSwitchTokens, setRotateSwitchTokens] = useState(false);

// Resets the banner visibility when the estimated return is low
const [isLowReturnBannerOpen, setIsLowReturnBannerOpen] = useState(true);
useEffect(() => setIsLowReturnBannerOpen(true), [quotesRefreshCount]);

// Background updates are debounced when the switch button is clicked
// To prevent putting the frontend in an unexpected state, prevent the user
// from switching tokens within the debounce period
Expand All @@ -211,16 +218,27 @@ const PrepareBridgePage = () => {
dispatch(resetBridgeState());
}, []);

const scrollRef = useRef<HTMLDivElement>(null);

// Scroll to bottom of the page when banners are shown
const insufficientBalanceBannerRef = useRef<HTMLDivElement>(null);
const isEstimatedReturnLowRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (isInsufficientGasForQuote(nativeAssetBalance)) {
scrollRef.current?.scrollIntoView({
insufficientBalanceBannerRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
if (isEstimatedReturnLow) {
isEstimatedReturnLowRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}, [isInsufficientGasForQuote(nativeAssetBalance)]);
}, [
isEstimatedReturnLow,
isInsufficientGasForQuote(nativeAssetBalance),
isLowReturnBannerOpen,
]);

const quoteParams = useMemo(
() => ({
Expand Down Expand Up @@ -605,12 +623,26 @@ const PrepareBridgePage = () => {
textAlign={TextAlign.Left}
/>
)}
{isEstimatedReturnLow && isLowReturnBannerOpen && (
<BannerAlert
ref={insufficientBalanceBannerRef}
marginInline={4}
marginBottom={3}
title={t('lowEstimatedReturnTooltipTitle')}
severity={BannerAlertSeverity.Warning}
description={t('lowEstimatedReturnTooltipMessage', [
BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE * 100,
])}
textAlign={TextAlign.Left}
onClose={() => setIsLowReturnBannerOpen(false)}
/>
)}
{!isLoading &&
activeQuote &&
!isInsufficientBalance(srcTokenBalance) &&
isInsufficientGasForQuote(nativeAssetBalance) && (
<BannerAlert
ref={scrollRef}
ref={isEstimatedReturnLowRef}
marginInline={4}
marginBottom={3}
title={t('bridgeValidationInsufficientGasTitle', [ticker])}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,30 +95,22 @@ exports[`BridgeQuoteCard should render the recommended quote 1`] = `
</div>
</div>
<div
class="mm-box mm-container mm-container--max-width-undefined mm-box--display-flex mm-box--flex-direction-row mm-box--flex-wrap-nowrap mm-box--justify-content-space-between mm-box--align-items-center"
class="mm-box mm-container mm-container--max-width-undefined row-with-warning mm-box--display-flex mm-box--flex-direction-row mm-box--flex-wrap-nowrap mm-box--justify-content-space-between mm-box--align-items-center"
>
<p
class="mm-box mm-text mm-text--body-md-medium mm-box--color-text-alternative-soft"
style="white-space: nowrap;"
>
Network fees
</p>
<div
class="mm-box mm-container mm-container--max-width-undefined mm-box--display-flex mm-box--gap-1 mm-box--flex-direction-row mm-box--flex-wrap-nowrap mm-box--justify-content-space-between mm-box--align-items-center"
>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
>
$2.52
</p>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
>
-
</p>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
class="mm-box mm-text mm-text--body-md"
style="white-space: nowrap; overflow: visible;"
>
$2.52
$2.52 - $2.52
</p>
<span
class="mm-box mm-icon mm-icon--size-md mm-box--display-inline-block mm-box--color-icon-alternative-soft"
Expand Down Expand Up @@ -259,30 +251,22 @@ exports[`BridgeQuoteCard should render the recommended quote while loading new q
</div>
</div>
<div
class="mm-box mm-container mm-container--max-width-undefined mm-box--display-flex mm-box--flex-direction-row mm-box--flex-wrap-nowrap mm-box--justify-content-space-between mm-box--align-items-center"
class="mm-box mm-container mm-container--max-width-undefined row-with-warning mm-box--display-flex mm-box--flex-direction-row mm-box--flex-wrap-nowrap mm-box--justify-content-space-between mm-box--align-items-center"
>
<p
class="mm-box mm-text mm-text--body-md-medium mm-box--color-text-alternative-soft"
style="white-space: nowrap;"
>
Network fees
</p>
<div
class="mm-box mm-container mm-container--max-width-undefined mm-box--display-flex mm-box--gap-1 mm-box--flex-direction-row mm-box--flex-wrap-nowrap mm-box--justify-content-space-between mm-box--align-items-center"
>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
>
$2.52
</p>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
>
-
</p>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
class="mm-box mm-text mm-text--body-md"
style="white-space: nowrap; overflow: visible;"
>
$2.52
$2.52 - $2.52
</p>
<span
class="mm-box mm-icon mm-icon--size-md mm-box--display-inline-block mm-box--color-icon-alternative-soft"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ exports[`BridgeQuotesModal should render the modal 1`] = `
</div>
</header>
<div
class="mm-box mm-container mm-container--max-width-undefined mm-box--padding-4 mm-box--sm:padding-3 mm-box--padding-bottom-1 mm-box--display-flex mm-box--flex-direction-row mm-box--flex-wrap-nowrap mm-box--justify-content-space-between mm-box--align-items-center"
class="mm-box mm-container mm-container--max-width-undefined mm-box--padding-top-3 mm-box--padding-bottom-1 mm-box--padding-inline-4 mm-box--display-flex mm-box--flex-direction-row mm-box--flex-wrap-nowrap mm-box--justify-content-space-between mm-box--align-items-center"
>
<button
class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md-medium mm-box--padding-0 mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
Expand Down
Loading
Loading