Skip to content
Merged
12 changes: 12 additions & 0 deletions app/_locales/en/messages.json

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

12 changes: 12 additions & 0 deletions app/_locales/en_GB/messages.json

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

Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,22 @@ describe('<DappSwapComparisonBanner />', () => {
});

it('renders component without errors', () => {
mockUseDappSwapComparisonInfo.mockReturnValue({
selectedQuoteValueDifference: 0.1,
gasDifference: 0.01,
tokenAmountDifference: 0.01,
destinationTokenSymbol: 'TEST',
} as ReturnType<typeof useDappSwapComparisonInfo>);
const { getByText } = render();
expect(getByText('Current')).toBeInTheDocument();
expect(getByText('Save + Earn')).toBeInTheDocument();
expect(getByText('Save + Earn using MetaMask Swap:')).toBeInTheDocument();
expect(getByText('Save about $0.02')).toBeInTheDocument();
expect(
getByText(
'No additional cost • Priority support • Network fees refunded on failed swaps',
),
).toBeInTheDocument();
});

it('renders undefined for incorrect origin', () => {
Expand All @@ -57,8 +70,8 @@ describe('<DappSwapComparisonBanner />', () => {

it('renders undefined if suitable quote is not found', () => {
mockUseDappSwapComparisonInfo.mockReturnValue({
selectedQuote: undefined,
});
selectedQuoteValueDifference: 0.001,
} as ReturnType<typeof useDappSwapComparisonInfo>);
const { container } = render();
expect(container).toBeEmptyDOMElement();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import React, { useState } from 'react';
import React, { useCallback, useState } from 'react';
import {
Box,
BoxBackgroundColor,
BoxBorderColor,
Button,
ButtonIcon,
ButtonIconSize,
ButtonSize,
ButtonVariant,
IconName,
Text,
TextButton,
TextColor,
TextVariant,
} from '@metamask/design-system-react';
import { TransactionMeta } from '@metamask/transaction-controller';
import { useSelector } from 'react-redux';
Expand All @@ -16,6 +23,7 @@ import { useConfirmContext } from '../../../context/confirm';
import { useDappSwapComparisonInfo } from '../../../hooks/transactions/dapp-swap-comparison/useDappSwapComparisonInfo';

const DAPP_SWAP_COMPARISON_ORIGIN = 'https://app.uniswap.org';
const DAPP_SWAP_THRESHOLD = 0.01;

const enum SwapType {
Current = 'current',
Expand Down Expand Up @@ -57,45 +65,108 @@ const SwapButton = ({

const DappSwapComparisonInner = () => {
const t = useI18nContext();
const { selectedQuote } = useDappSwapComparisonInfo();
const {
selectedQuoteValueDifference,
gasDifference,
tokenAmountDifference,
destinationTokenSymbol,
} = useDappSwapComparisonInfo();
const [selectedSwapType, setSelectedSwapType] = useState<SwapType>(
SwapType.Current,
);
const [showDappSwapComparisonBanner, setShowDappSwapComparisonBanner] =
useState<boolean>(true);

const hideDappSwapComparisonBanner = useCallback(() => {
setShowDappSwapComparisonBanner(false);
}, [setShowDappSwapComparisonBanner]);

if (
process.env.DAPP_SWAP_SHIELD_ENABLED?.toString() !== 'true' ||
!selectedQuote
selectedQuoteValueDifference < DAPP_SWAP_THRESHOLD
) {
return null;
}

const dappTypeSelected = selectedSwapType === SwapType.Current;

return (
<Box
borderColor={BoxBorderColor.BorderMuted}
borderWidth={1}
className="dapp-swap_wrapper"
marginBottom={4}
marginTop={2}
padding={1}
>
<SwapButton
type={
selectedSwapType === SwapType.Current
? SwapButtonType.ButtonType
: SwapButtonType.Text
}
onClick={() => setSelectedSwapType(SwapType.Current)}
label={t('current')}
/>
<SwapButton
type={
selectedSwapType === SwapType.Metamask
? SwapButtonType.ButtonType
: SwapButtonType.Text
}
onClick={() => setSelectedSwapType(SwapType.Metamask)}
label={t('saveAndEarn')}
/>
<Box>
<Box
borderColor={BoxBorderColor.BorderMuted}
borderWidth={1}
className="dapp-swap_wrapper"
marginBottom={4}
marginTop={2}
padding={1}
>
<SwapButton
type={
selectedSwapType === SwapType.Current
? SwapButtonType.ButtonType
: SwapButtonType.Text
}
onClick={() => setSelectedSwapType(SwapType.Current)}
label={t('current')}
/>
<SwapButton
type={
selectedSwapType === SwapType.Metamask
? SwapButtonType.ButtonType
: SwapButtonType.Text
}
onClick={() => setSelectedSwapType(SwapType.Metamask)}
label={t('saveAndEarn')}
/>
</Box>
{showDappSwapComparisonBanner && (
<Box
className="dapp-swap_callout"
backgroundColor={BoxBackgroundColor.BackgroundAlternative}
marginBottom={4}
padding={4}
>
<ButtonIcon
className="dapp-swap_close-button"
iconName={IconName.Close}
size={ButtonIconSize.Sm}
onClick={hideDappSwapComparisonBanner}
ariaLabel="close-dapp-swap-comparison-banner"
/>
{dappTypeSelected && (
<>
<div className="dapp-swap_callout-arrow" />
<Text
className="dapp-swap_callout-text"
color={TextColor.TextDefault}
variant={TextVariant.BodySm}
>
{t('dappSwapAdvantage')}
</Text>
<Text
className="dapp-swap_text-save"
variant={TextVariant.BodySm}
>
{t('dappSwapQuoteDifference', [
`$${(gasDifference + tokenAmountDifference).toFixed(2)}`,
])}
</Text>
</>
)}
{!dappTypeSelected && (
<Text className="dapp-swap_text-save" variant={TextVariant.BodySm}>
{t('dappSwapQuoteDetails', [
`$${gasDifference.toFixed(2)}`,
`$${tokenAmountDifference.toFixed(2)}`,
destinationTokenSymbol?.toUpperCase(),
])}
</Text>
)}
<Text color={TextColor.TextAlternative} variant={TextVariant.BodyXs}>
{t('dappSwapBenefits')}
</Text>
</Box>
)}
</Box>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,42 @@ Disabling Stylelint's hex color rule here because the TypeScript migration dashb
text-decoration: none !important;
}
}

&_close-button {
position: absolute;
top: 0;
right: 0;
}

&_callout {
border-radius: 8px;
position: relative;
}

&_callout-text {
margin-bottom: 4px;
}

&_text-save {
color: #c9f570;
margin-bottom: 4px;
}

&_callout-arrow {
position: absolute;
top: -12px;
left: 75%;
transform: translateX(-50%);
width: 0;
height: 0;
}

&_callout-arrow::before {
content: "";
position: absolute;
width: 24px;
height: 12px;
background: var(--color-background-section);
clip-path: polygon(50% 0, 0 100%, 100% 100%);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ describe('useDappSwapComparisonInfo', () => {
srcChainId: 42161,
destChainId: 42161,
srcTokenAmount: '9913',
destTokenAmount: '9907',
minDestTokenAmount: '9708',
destTokenAmount: '1004000',
minDestTokenAmount: '972870',
walletAddress: '0x178239802520a9C99DCBD791f81326B70298d629',
destWalletAddress: '0x178239802520a9C99DCBD791f81326B70298d629',
bridges: ['okx'],
Expand All @@ -197,15 +197,15 @@ describe('useDappSwapComparisonInfo', () => {
from: '0x178239802520a9C99DCBD791f81326B70298d629',
value: '0x0',
data: '',
gasLimit: 63109,
gasLimit: 62000,
},
trade: {
chainId: 42161,
to: '0x9dDA6Ef3D919c9bC8885D5560999A3640431e8e6',
from: '0x178239802520a9C99DCBD791f81326B70298d629',
value: '0x0',
data: '',
gasLimit: 296174,
gasLimit: 80000,
},
estimatedProcessingTimeInSeconds: 0,
},
Expand Down Expand Up @@ -263,9 +263,9 @@ describe('useDappSwapComparisonInfo', () => {
// eslint-disable-next-line @typescript-eslint/naming-convention
swap_mm_from_token_simulated_value_usd: '1',
// eslint-disable-next-line @typescript-eslint/naming-convention
swap_mm_minimum_received_value_usd: '0.000000000000009706097232',
swap_mm_minimum_received_value_usd: '0.00000000000097267931748',
// eslint-disable-next-line @typescript-eslint/naming-convention
swap_mm_network_fee_usd: '0.01393686346576541082',
swap_mm_network_fee_usd: '0.00550828904272868',
// eslint-disable-next-line @typescript-eslint/naming-convention
swap_mm_quote_provider: 'openocean',
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand All @@ -275,7 +275,7 @@ describe('useDappSwapComparisonInfo', () => {
// eslint-disable-next-line @typescript-eslint/naming-convention
swap_mm_slippage: 2,
// eslint-disable-next-line @typescript-eslint/naming-convention
swap_mm_to_token_simulated_value_usd: '0.000000000000009905058228',
swap_mm_to_token_simulated_value_usd: '0.000000000001003803216',
},
sensitiveProperties: {
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand All @@ -292,4 +292,33 @@ describe('useDappSwapComparisonInfo', () => {
'f8172040-b3d0-11f0-a882-3f99aa2e9f0c',
);
});

it('return correct values', async () => {
jest.spyOn(Utils, 'fetchTokenExchangeRates').mockResolvedValue({
'0x0000000000000000000000000000000000000000': 4052.27,
'0x833589fcd6edb6e08f4c7c32d4f71b54bda02913': 0.999804,
'0xfdcc3dd6671eab0709a4c0f3f53de9a333d80798': 1,
});
jest.spyOn(TokenUtils, 'fetchAllTokenDetails').mockResolvedValue({
'0x833589fcd6edb6e08f4c7c32d4f71b54bda02913': {
symbol: 'USDC',
decimals: '6',
} as TokenStandAndDetails,
'0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9': {
symbol: 'USDT',
decimals: '6',
} as TokenStandAndDetails,
});

const {
selectedQuoteValueDifference,
gasDifference,
tokenAmountDifference,
destinationTokenSymbol,
} = await runHook();
expect(selectedQuoteValueDifference).toBe(0.012494042894187605);
expect(gasDifference).toBe(0.005686377458187605);
expect(tokenAmountDifference).toBe(0.006807665436);
expect(destinationTokenSymbol).toBe('USDC');
});
});
Loading