1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +01:00

Toggle option to enable/disable balance and Token rate checking for using third-party API (#16772)

This commit is contained in:
Niranjana Binoy 2023-01-17 10:23:04 -05:00 committed by GitHub
parent 554939cc66
commit a0bb4a6c5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 536 additions and 152 deletions

View File

@ -1225,6 +1225,7 @@ const state = {
useNonceField: false,
usePhishDetect: true,
useTokenDetection: true,
useCurrencyRateCheck: true,
lostIdentities: {},
forgottenPassword: false,
ipfsGateway: 'dweb.link',

View File

@ -682,6 +682,9 @@
"close": {
"message": "Close"
},
"coingecko": {
"message": "CoinGecko"
},
"collectibleAddFailedMessage": {
"message": "NFT cant be added as the ownership details do not match. Make sure you have entered correct information."
},
@ -894,9 +897,19 @@
"createPassword": {
"message": "Create password"
},
"cryptoCompare": {
"message": "CryptoCompare"
},
"currencyConversion": {
"message": "Currency conversion"
},
"currencyRateCheckToggle": {
"message": "Show balance and token price checker"
},
"currencyRateCheckToggleDescription": {
"message": "We use $1 and $2 APIs to display your balance and token price. $3",
"description": "$1 represents Coingecko, $2 represents CryptoCompare and $3 represents Privacy Policy"
},
"currencySymbol": {
"message": "Currency symbol"
},

View File

@ -116,7 +116,7 @@ export default class DetectTokensController {
const tokensToDetect = [];
for (const tokenAddress in tokenListUsed) {
if (
!this.tokenAddresses.find(({ address }) =>
!this.tokenAddresses.find((address) =>
isEqualCaseInsensitive(address, tokenAddress),
) &&
!this.hiddenTokens.find((address) =>

View File

@ -11,8 +11,9 @@ import {
} from '@metamask/assets-controllers';
import { NETWORK_TYPES } from '../../../shared/constants/network';
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
import { hexToDecimal } from '../../../shared/lib/metamask-controller-utils';
import DetectTokensController from './detect-tokens';
import NetworkController from './network';
import NetworkController, { NETWORK_EVENTS } from './network';
import PreferencesController from './preferences';
describe('DetectTokensController', function () {
@ -64,14 +65,34 @@ describe('DetectTokensController', function () {
onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store,
),
onNetworkStateChange: network.store.subscribe.bind(network.store),
onNetworkStateChange: (cb) =>
network.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.provider,
},
};
return cb(modifiedNetworkState);
}),
});
assetsContractController = new AssetsContractController({
onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store,
),
onNetworkStateChange: network.store.subscribe.bind(network.store),
onNetworkStateChange: (cb) =>
network.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
const networkState = network.store.getState();
const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.provider,
chainId: hexToDecimal(networkState.provider.chainId),
},
};
return cb(modifiedNetworkState);
}),
});
sandbox

View File

@ -37,6 +37,7 @@ export default class PreferencesController {
// set to false will be using the static list from contract-metadata
useTokenDetection: false,
useNftDetection: false,
useCurrencyRateCheck: true,
openSeaEnabled: false,
advancedGasFee: null,
@ -156,6 +157,15 @@ export default class PreferencesController {
this.store.updateState({ useNftDetection });
}
/**
* Setter for the `useCurrencyRateCheck` property
*
* @param {boolean} val - Whether or not the user prefers to use currency rate check for ETH and tokens.
*/
setUseCurrencyRateCheck(val) {
this.store.updateState({ useCurrencyRateCheck: val });
}
/**
* Setter for the `openSeaEnabled` property
*

View File

@ -367,4 +367,23 @@ describe('preferences controller', function () {
assert.equal(preferencesController.store.getState().theme, 'dark');
});
});
describe('setUseCurrencyRateCheck', function () {
it('should default to false', function () {
const state = preferencesController.store.getState();
assert.equal(state.useCurrencyRateCheck, true);
});
it('should set the useCurrencyRateCheck property in state', function () {
assert.equal(
preferencesController.store.getState().useCurrencyRateCheck,
true,
);
preferencesController.setUseCurrencyRateCheck(false);
assert.equal(
preferencesController.store.getState().useCurrencyRateCheck,
false,
);
});
});
});

View File

@ -266,7 +266,7 @@ export default class MetamaskController extends EventEmitter {
this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
provider: {
providerConfig: {
...networkState.provider,
chainId: hexToDecimal(networkState.provider.chainId),
},
@ -291,9 +291,16 @@ export default class MetamaskController extends EventEmitter {
onPreferencesStateChange: this.preferencesController.store.subscribe.bind(
this.preferencesController.store,
),
onNetworkStateChange: this.networkController.store.subscribe.bind(
this.networkController.store,
),
onNetworkStateChange: (cb) =>
this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.provider,
},
};
return cb(modifiedNetworkState);
}),
config: { provider: this.provider },
state: initState.TokensController,
});
@ -307,7 +314,7 @@ export default class MetamaskController extends EventEmitter {
const networkState = this.networkController.store.getState();
const modifiedNetworkState = {
...networkState,
provider: {
providerConfig: {
...networkState.provider,
chainId: hexToDecimal(networkState.provider.chainId),
},
@ -327,9 +334,16 @@ export default class MetamaskController extends EventEmitter {
this.preferencesController.store.subscribe.bind(
this.preferencesController.store,
),
onNetworkStateChange: this.networkController.store.subscribe.bind(
this.networkController.store,
),
onNetworkStateChange: (cb) =>
this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.provider,
},
};
return cb(modifiedNetworkState);
}),
getERC721AssetName:
this.assetsContractController.getERC721AssetName.bind(
this.assetsContractController,
@ -504,7 +518,7 @@ export default class MetamaskController extends EventEmitter {
this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
provider: {
providerConfig: {
...networkState.provider,
chainId: hexToDecimal(networkState.provider.chainId),
},
@ -512,9 +526,29 @@ export default class MetamaskController extends EventEmitter {
return cb(modifiedNetworkState);
}),
},
undefined,
{
disabled:
!this.preferencesController.store.getState().useCurrencyRateCheck,
},
initState.TokenRatesController,
);
this.preferencesController.store.subscribe(
previousValueComparator((prevState, currState) => {
const { useCurrencyRateCheck: prevUseCurrencyRateCheck } = prevState;
const { useCurrencyRateCheck: currUseCurrencyRateCheck } = currState;
if (currUseCurrencyRateCheck && !prevUseCurrencyRateCheck) {
this.currencyRateController.start();
this.tokenRatesController.configure(
{ disabled: false },
false,
false,
);
} else if (!currUseCurrencyRateCheck && prevUseCurrencyRateCheck) {
this.currencyRateController.stop();
this.tokenRatesController.configure({ disabled: true }, false, false);
}
}, this.preferencesController.store.getState()),
);
this.ensController = new EnsController({
provider: this.provider,
@ -1245,7 +1279,9 @@ export default class MetamaskController extends EventEmitter {
triggerNetworkrequests() {
this.accountTracker.start();
this.incomingTransactionsController.start();
this.currencyRateController.start();
if (this.preferencesController.store.getState().useCurrencyRateCheck) {
this.currencyRateController.start();
}
if (this.preferencesController.store.getState().useTokenDetection) {
this.tokenListController.start();
}
@ -1254,7 +1290,9 @@ export default class MetamaskController extends EventEmitter {
stopNetworkRequests() {
this.accountTracker.stop();
this.incomingTransactionsController.stop();
this.currencyRateController.stop();
if (this.preferencesController.store.getState().useCurrencyRateCheck) {
this.currencyRateController.stop();
}
if (this.preferencesController.store.getState().useTokenDetection) {
this.tokenListController.stop();
}
@ -1633,6 +1671,10 @@ export default class MetamaskController extends EventEmitter {
setUseNftDetection: preferencesController.setUseNftDetection.bind(
preferencesController,
),
setUseCurrencyRateCheck:
preferencesController.setUseCurrencyRateCheck.bind(
preferencesController,
),
setOpenSeaEnabled: preferencesController.setOpenSeaEnabled.bind(
preferencesController,
),

View File

@ -501,6 +501,7 @@
"URL": true,
"clearInterval": true,
"clearTimeout": true,
"console.info": true,
"console.log": true,
"setInterval": true,
"setTimeout": true
@ -508,10 +509,10 @@
"packages": {
"@ethersproject/contracts": true,
"@ethersproject/providers": true,
"@metamask/assets-controllers>@metamask/contract-metadata": true,
"@metamask/assets-controllers>abort-controller": true,
"@metamask/assets-controllers>multiformats": true,
"@metamask/base-controller": true,
"@metamask/contract-metadata": true,
"@metamask/controller-utils": true,
"@metamask/metamask-eth-abis": true,
"browserify>events": true,

View File

@ -501,6 +501,7 @@
"URL": true,
"clearInterval": true,
"clearTimeout": true,
"console.info": true,
"console.log": true,
"setInterval": true,
"setTimeout": true
@ -508,10 +509,10 @@
"packages": {
"@ethersproject/contracts": true,
"@ethersproject/providers": true,
"@metamask/assets-controllers>@metamask/contract-metadata": true,
"@metamask/assets-controllers>abort-controller": true,
"@metamask/assets-controllers>multiformats": true,
"@metamask/base-controller": true,
"@metamask/contract-metadata": true,
"@metamask/controller-utils": true,
"@metamask/metamask-eth-abis": true,
"browserify>events": true,

View File

@ -501,6 +501,7 @@
"URL": true,
"clearInterval": true,
"clearTimeout": true,
"console.info": true,
"console.log": true,
"setInterval": true,
"setTimeout": true
@ -508,10 +509,10 @@
"packages": {
"@ethersproject/contracts": true,
"@ethersproject/providers": true,
"@metamask/assets-controllers>@metamask/contract-metadata": true,
"@metamask/assets-controllers>abort-controller": true,
"@metamask/assets-controllers>multiformats": true,
"@metamask/base-controller": true,
"@metamask/contract-metadata": true,
"@metamask/controller-utils": true,
"@metamask/metamask-eth-abis": true,
"browserify>events": true,

View File

@ -209,7 +209,7 @@
"@metamask/address-book-controller": "^1.0.0",
"@metamask/announcement-controller": "^1.0.0",
"@metamask/approval-controller": "^1.0.0",
"@metamask/assets-controllers": "^1.0.1",
"@metamask/assets-controllers": "^3.0.1",
"@metamask/base-controller": "^1.0.0",
"@metamask/contract-metadata": "^2.1.0",
"@metamask/controller-utils": "^1.0.0",

View File

@ -5,6 +5,11 @@ _supportLink = 'https://metamask-flask.zendesk.com/hc';
///: END:ONLY_INCLUDE_IN
export const SUPPORT_LINK = _supportLink;
export const COINGECKO_LINK = 'https://www.coingecko.com/';
export const CRYPTOCOMPARE_LINK = 'https://www.cryptocompare.com/';
export const PRIVACY_POLICY_LINK = 'https://consensys.net/privacy-policy/';
// TODO make sure these links are correct
export const ETHERSCAN_PRIVACY_LINK = 'https://etherscan.io/privacyPolicy';
export const CONSENSYS_PRIVACY_LINK = 'https://consensys.net/privacy-policy/';

View File

@ -252,6 +252,7 @@
"unapprovedTypedMessages": {},
"unapprovedTypedMessagesCount": 0,
"useTokenDetection": true,
"useCurrencyRateCheck": true,
"advancedGasFee": {
"maxBaseFee": "75",
"priorityFee": "2"

View File

@ -239,6 +239,7 @@ function defaultFixture() {
useNonceField: false,
usePhishDetect: true,
useTokenDetection: false,
useCurrencyRateCheck: true,
useMultiAccountBalanceChecker: true,
},
SmartTransactionsController: {
@ -352,6 +353,7 @@ function onboardingFixture() {
useNonceField: false,
usePhishDetect: true,
useTokenDetection: false,
useCurrencyRateCheck: true,
useMultiAccountBalanceChecker: true,
},
SmartTransactionsController: {

View File

@ -46,6 +46,7 @@
"useNonceField": false,
"usePhishDetect": true,
"useTokenDetection": false,
"useCurrencyRateCheck": true,
"useMultiAccountBalanceChecker": true
}
}

View File

@ -218,6 +218,7 @@ export const createSwapsMockStore = () => {
postTxBalance: '19a61aaaf06e4bd1',
},
],
useCurrencyRateCheck: true,
conversionRate: 1,
contractExchangeRates: {
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': 2,

View File

@ -16,6 +16,7 @@ describe('CurrencyInput Component', () => {
preferences: {
showFiatInTestnets: true,
},
useCurrencyRateCheck: true,
},
};
describe('rendering', () => {

View File

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import Box from '../../../ui/box';
import Typography from '../../../ui/typography';
@ -12,6 +13,7 @@ import {
} from '../../../../helpers/constants/design-system';
import { useTokenTracker } from '../../../../hooks/useTokenTracker';
import { useTokenFiatAmount } from '../../../../hooks/useTokenFiatAmount';
import { getUseCurrencyRateCheck } from '../../../../selectors';
const DetectedTokenValues = ({
token,
@ -30,6 +32,8 @@ const DetectedTokenValues = ({
token.symbol,
);
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
useEffect(() => {
setTokenSelection(tokensListDetected[token.address]?.selected);
}, [tokensListDetected, token.address, tokenSelection, setTokenSelection]);
@ -46,7 +50,9 @@ const DetectedTokenValues = ({
{`${balanceString || '0'} ${token.symbol}`}
</Typography>
<Typography variant={TYPOGRAPHY.H7} color={COLORS.TEXT_ALTERNATIVE}>
{formattedFiatBalance || '$0'}
{useCurrencyRateCheck
? formattedFiatBalance || '$0' // since formattedFiatBalance will be when teh conversion rate is not obtained, should be replace the `$0` with `N/A`
: formattedFiatBalance}
</Typography>
</Box>
<Box className="detected-token-values__checkbox">

View File

@ -5,7 +5,7 @@ import { useSelector } from 'react-redux';
import { COLORS } from '../../../helpers/constants/design-system';
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
import { getPreferences } from '../../../selectors';
import { getPreferences, getUseCurrencyRateCheck } from '../../../selectors';
import { useGasFeeContext } from '../../../contexts/gasFee';
import { useI18nContext } from '../../../hooks/useI18nContext';
@ -29,6 +29,8 @@ const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences);
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
if (hasSimulationError && !userAcknowledgedGasMissing) {
return null;
}
@ -39,14 +41,16 @@ const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
detailTitle={<GasDetailsItemTitle />}
detailTitleColor={COLORS.TEXT_DEFAULT}
detailText={
<div className="gas-details-item__currency-container">
<LoadingHeartBeat estimateUsed={estimateUsed} />
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={hexMinimumTransactionFee}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</div>
useCurrencyRateCheck && (
<div className="gas-details-item__currency-container">
<LoadingHeartBeat estimateUsed={estimateUsed} />
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={hexMinimumTransactionFee}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</div>
)
}
detailTotal={
<div className="gas-details-item__currency-container">

View File

@ -3,7 +3,7 @@
exports[`Token Cell should match snapshot 1`] = `
<div>
<div
class="list-item asset-list-item token-cell"
class="list-item asset-list-item token-cell list-item--single-content-row"
role="button"
tabindex="0"
>
@ -77,15 +77,6 @@ exports[`Token Cell should match snapshot 1`] = `
</h2>
</button>
</div>
<div
class="list-item__subheading"
>
<h3
title="$0.52 USD"
>
$0.52 USD
</h3>
</div>
<div
class="list-item__right-content"
>

View File

@ -219,6 +219,13 @@ export const SETTINGS_CONSTANTS = [
icon: 'fa fa-flask',
featureFlag: 'NFTS_V1',
},
{
tabMessage: (t) => t('securityAndPrivacy'),
sectionMessage: (t) => t('currencyRateCheckToggle'),
descriptionMessage: (t) => t('currencyRateCheckToggleDescription'),
route: `${SECURITY_ROUTE}#price-checker`,
icon: 'fa fa-lock',
},
{
tabMessage: (t) => t('alerts'),
sectionMessage: (t) => t('alertSettingsUnconnectedAccount'),

View File

@ -137,6 +137,10 @@ const t = (key) => {
return 'Contact us';
case 'snaps':
return 'Snaps';
case 'currencyRateCheckToggle':
return 'Show balance and token price checker';
case 'currencyRateCheckToggleDescription':
return 'We use Coingecko and CryptoCompare APIs to display your balance and token price. Privacy Policy';
default:
return '';
}
@ -165,7 +169,7 @@ describe('Settings Search Utils', () => {
it('should get good security & privacy section number', () => {
expect(
getNumberOfSettingsInSection(t, t('securityAndPrivacy')),
).toStrictEqual(9);
).toStrictEqual(10);
});
it('should get good alerts section number', () => {

View File

@ -160,6 +160,7 @@ export default class ConfirmTransactionBase extends Component {
insightSnaps: PropTypes.arrayOf(PropTypes.object),
///: END:ONLY_INCLUDE_IN
assetStandard: PropTypes.string,
useCurrencyRateCheck: PropTypes.bool,
};
state = {
@ -349,6 +350,7 @@ export default class ConfirmTransactionBase extends Component {
isMultiLayerFeeNetwork,
nativeCurrency,
isBuyableChain,
useCurrencyRateCheck,
} = this.props;
const { t } = this.context;
const { userAcknowledgedGasMissing } = this.state;
@ -511,14 +513,16 @@ export default class ConfirmTransactionBase extends Component {
)
}
detailText={
<div className="confirm-page-container-content__currency-container test">
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={hexMinimumTransactionFee}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</div>
useCurrencyRateCheck && (
<div className="confirm-page-container-content__currency-container test">
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={hexMinimumTransactionFee}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</div>
)
}
detailTotal={
<div className="confirm-page-container-content__currency-container">
@ -637,7 +641,7 @@ export default class ConfirmTransactionBase extends Component {
<TransactionDetailItem
key="total-item"
detailTitle={t('total')}
detailText={renderTotalDetailText()}
detailText={useCurrencyRateCheck && renderTotalDetailText()}
detailTotal={renderTotalDetailTotal()}
subTitle={t('transactionDetailGasTotalSubtitle')}
subText={

View File

@ -36,6 +36,7 @@ import {
getEnsResolutionByAddress,
getUnapprovedTransaction,
getFullTxData,
getUseCurrencyRateCheck,
///: BEGIN:ONLY_INCLUDE_IN(flask)
getInsightSnaps,
///: END:ONLY_INCLUDE_IN
@ -251,6 +252,7 @@ const mapStateToProps = (state, ownProps) => {
///: BEGIN:ONLY_INCLUDE_IN(flask)
insightSnaps,
///: END:ONLY_INCLUDE_IN
useCurrencyRateCheck: getUseCurrencyRateCheck(state),
};
};

View File

@ -22,13 +22,20 @@ import {
showModal,
setIpfsGateway,
showNetworkDropdown,
setUseCurrencyRateCheck,
} from '../../../store/actions';
import { ONBOARDING_PIN_EXTENSION_ROUTE } from '../../../helpers/constants/routes';
import { Icon, TextField } from '../../../components/component-library';
import NetworkDropdown from '../../../components/app/dropdowns/network-dropdown';
import NetworkDisplay from '../../../components/app/network-display/network-display';
import {
COINGECKO_LINK,
CRYPTOCOMPARE_LINK,
PRIVACY_POLICY_LINK,
} from '../../../../shared/lib/ui-utils';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import { EVENT_NAMES, EVENT } from '../../../../shared/constants/metametrics';
import { Setting } from './setting';
export default function PrivacySettings() {
@ -37,6 +44,7 @@ export default function PrivacySettings() {
const history = useHistory();
const [usePhishingDetection, setUsePhishingDetection] = useState(true);
const [turnOnTokenDetection, setTurnOnTokenDetection] = useState(true);
const [turnOnCurrencyRateCheck, setTurnOnCurrencyRateCheck] = useState(true);
const [showIncomingTransactions, setShowIncomingTransactions] =
useState(true);
const [
@ -60,6 +68,7 @@ export default function PrivacySettings() {
dispatch(
setUseMultiAccountBalanceChecker(isMultiAccountBalanceCheckerEnabled),
);
dispatch(setUseCurrencyRateCheck(turnOnCurrencyRateCheck));
dispatch(setCompletedOnboarding());
if (ipfsURL && !ipfsError) {
@ -254,6 +263,37 @@ export default function PrivacySettings() {
</>
}
/>
<Setting
value={turnOnCurrencyRateCheck}
setValue={setTurnOnCurrencyRateCheck}
title={t('currencyRateCheckToggle')}
description={t('currencyRateCheckToggleDescription', [
<a
key="coingecko_link"
href={COINGECKO_LINK}
rel="noreferrer"
target="_blank"
>
{t('coingecko')}
</a>,
<a
key="cryptocompare_link"
href={CRYPTOCOMPARE_LINK}
rel="noreferrer"
target="_blank"
>
{t('cryptoCompare')}
</a>,
<a
key="privacy_policy_link"
href={PRIVACY_POLICY_LINK}
rel="noreferrer"
target="_blank"
>
{t('privacyMsg')}
</a>,
])}
/>
</div>
<Button type="primary" rounded onClick={handleSubmit}>
{t('done')}

View File

@ -22,6 +22,7 @@ describe('Privacy Settings Onboarding View', () => {
const setFeatureFlagStub = jest.fn();
const setUsePhishDetectStub = jest.fn();
const setUseTokenDetectionStub = jest.fn();
const setUseCurrencyRateCheckStub = jest.fn();
const completeOnboardingStub = jest
.fn()
.mockImplementation(() => Promise.resolve());
@ -31,6 +32,7 @@ describe('Privacy Settings Onboarding View', () => {
setFeatureFlag: setFeatureFlagStub,
setUsePhishDetect: setUsePhishDetectStub,
setUseTokenDetection: setUseTokenDetectionStub,
setUseCurrencyRateCheck: setUseCurrencyRateCheckStub,
completeOnboarding: completeOnboardingStub,
setUseMultiAccountBalanceChecker: setUseMultiAccountBalanceCheckerStub,
});
@ -45,6 +47,7 @@ describe('Privacy Settings Onboarding View', () => {
expect(setUsePhishDetectStub).toHaveBeenCalledTimes(0);
expect(setUseTokenDetectionStub).toHaveBeenCalledTimes(0);
expect(setUseMultiAccountBalanceCheckerStub).toHaveBeenCalledTimes(0);
expect(setUseCurrencyRateCheckStub).toHaveBeenCalledTimes(0);
const toggles = container.querySelectorAll('input[type=checkbox]');
const submitButton = getByText('Done');
@ -54,34 +57,42 @@ describe('Privacy Settings Onboarding View', () => {
fireEvent.click(toggles[1]);
fireEvent.click(toggles[2]);
fireEvent.click(toggles[3]);
fireEvent.click(toggles[4]);
fireEvent.click(submitButton);
expect(setFeatureFlagStub).toHaveBeenCalledTimes(1);
expect(setUsePhishDetectStub).toHaveBeenCalledTimes(1);
expect(setUseTokenDetectionStub).toHaveBeenCalledTimes(1);
expect(setUseMultiAccountBalanceCheckerStub).toHaveBeenCalledTimes(1);
expect(setUseCurrencyRateCheckStub).toHaveBeenCalledTimes(1);
expect(setFeatureFlagStub.mock.calls[0][1]).toStrictEqual(false);
expect(setUsePhishDetectStub.mock.calls[0][0]).toStrictEqual(false);
expect(setUseTokenDetectionStub.mock.calls[0][0]).toStrictEqual(false);
expect(setUseMultiAccountBalanceCheckerStub.mock.calls[0][0]).toStrictEqual(
false,
);
expect(setUseCurrencyRateCheckStub.mock.calls[0][0]).toStrictEqual(false);
// toggle back to true
fireEvent.click(toggles[0]);
fireEvent.click(toggles[1]);
fireEvent.click(toggles[2]);
fireEvent.click(toggles[3]);
fireEvent.click(toggles[4]);
fireEvent.click(submitButton);
expect(setFeatureFlagStub).toHaveBeenCalledTimes(2);
expect(setUsePhishDetectStub).toHaveBeenCalledTimes(2);
expect(setUseTokenDetectionStub).toHaveBeenCalledTimes(2);
expect(setUseMultiAccountBalanceCheckerStub).toHaveBeenCalledTimes(2);
expect(setUseCurrencyRateCheckStub).toHaveBeenCalledTimes(2);
expect(setFeatureFlagStub.mock.calls[1][1]).toStrictEqual(true);
expect(setUsePhishDetectStub.mock.calls[1][0]).toStrictEqual(true);
expect(setUseTokenDetectionStub.mock.calls[1][0]).toStrictEqual(true);
expect(setUseMultiAccountBalanceCheckerStub.mock.calls[1][0]).toStrictEqual(
true,
);
expect(setUseCurrencyRateCheckStub.mock.calls[1][0]).toStrictEqual(true);
});
});

View File

@ -38,6 +38,7 @@ import {
getIsBuyableChain,
transactionFeeSelector,
getIsMainnet,
getUseCurrencyRateCheck,
} from '../../../selectors';
import {
@ -57,6 +58,7 @@ export default function GasDisplay({ gasError }) {
const isMainnet = useSelector(getIsMainnet);
const isBuyableChain = useSelector(getIsBuyableChain);
const draftTransaction = useSelector(getCurrentDraftTransaction);
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences);
const { nativeCurrency, provider, unapprovedTxs } = useSelector(
(state) => state.metamask,
@ -197,14 +199,16 @@ export default function GasDisplay({ gasError }) {
}
detailTitleColor={COLORS.TEXT_DEFAULT}
detailText={
<Box className="gas-display__currency-container">
<LoadingHeartBeat estimateUsed={estimateUsed} />
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={hexMinimumTransactionFee}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</Box>
useCurrencyRateCheck && (
<Box className="gas-display__currency-container">
<LoadingHeartBeat estimateUsed={estimateUsed} />
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={hexMinimumTransactionFee}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</Box>
)
}
detailTotal={
<Box className="gas-display__currency-container">
@ -263,22 +267,24 @@ export default function GasDisplay({ gasError }) {
key="total-item"
detailTitle={t('total')}
detailText={
<Box
height={BLOCK_SIZES.MAX}
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
className="gas-display__total-value"
>
<LoadingHeartBeat
estimateUsed={transactionData?.userFeeLevel}
/>
<UserPreferencedCurrencyDisplay
type={SECONDARY}
key="total-detail-text"
value={hexTransactionTotal}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</Box>
useCurrencyRateCheck && (
<Box
height={BLOCK_SIZES.MAX}
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
className="gas-display__total-value"
>
<LoadingHeartBeat
estimateUsed={transactionData?.userFeeLevel}
/>
<UserPreferencedCurrencyDisplay
type={SECONDARY}
key="total-detail-text"
value={hexTransactionTotal}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</Box>
)
}
detailTotal={detailTotal}
subTitle={t('transactionDetailGasTotalSubtitle')}

View File

@ -236,27 +236,6 @@ exports[`SendContent Component render should match snapshot 1`] = `
<div
class="transaction-detail-item__detail-values"
>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h6 typography--weight-normal typography--style-normal typography--color-text-alternative"
>
<div
class="box gas-display__currency-container box--flex-direction-row"
>
<div
class="currency-display-component"
title="0.0000315"
>
<span
class="currency-display-component__prefix"
/>
<span
class="currency-display-component__text"
>
0.0000315
</span>
</div>
</div>
</h6>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--margin-left-1 box--flex-direction-row box--text-align-right typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
>

View File

@ -134,6 +134,103 @@ exports[`Security Tab should match snapshot 1`] = `
<div
class="settings-page__content-padded"
>
<div
class="settings-page__content-row"
>
<div
class="settings-page__content-item"
>
<span>
Show balance and token price checker
</span>
<div
class="settings-page__content-description"
>
<span>
We use
<a
href="https://www.coingecko.com/"
rel="noreferrer"
target="_blank"
>
CoinGecko
</a>
and
<a
href="https://www.cryptocompare.com/"
rel="noreferrer"
target="_blank"
>
CryptoCompare
</a>
APIs to display your balance and token price.
<a
href="https://consensys.net/privacy-policy/"
rel="noreferrer"
target="_blank"
>
Privacy policy
</a>
</span>
</div>
</div>
<div
class="settings-page__content-item"
>
<div
class="settings-page__content-item-col"
>
<label
class="toggle-button toggle-button--on"
tabindex="0"
>
<div
style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;"
>
<div
style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);"
>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;"
/>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgba(255, 255, 255, 0.6); bottom: 0px; margin-top: auto; margin-bottom: auto; padding-right: 5px; line-height: 0; width: 26px; height: 20px; opacity: 0;"
/>
</div>
<div
style="position: absolute; height: 100%; top: 0px; left: 0px; display: flex; flex: 1; align-self: stretch; align-items: center; justify-content: flex-start;"
>
<div
style="width: 18px; height: 18px; display: flex; align-self: center; box-shadow: none; border-radius: 50%; box-sizing: border-box; position: relative; background-color: rgb(3, 125, 214); left: 18px;"
/>
</div>
<input
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px;"
type="checkbox"
value="true"
/>
</div>
<div
class="toggle-button__status"
>
<span
class="toggle-button__label-off"
>
Off
</span>
<span
class="toggle-button__label-on"
>
On
</span>
</div>
</label>
</div>
</div>
</div>
<div
class="settings-page__content-row"
>

View File

@ -14,6 +14,9 @@ import {
} from '../../../helpers/utils/settings-search';
import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics';
import {
COINGECKO_LINK,
CRYPTOCOMPARE_LINK,
PRIVACY_POLICY_LINK,
AUTO_DETECT_TOKEN_LEARN_MORE_LINK,
CONSENSYS_PRIVACY_LINK,
ETHERSCAN_PRIVACY_LINK,
@ -45,6 +48,8 @@ export default class SecurityTab extends PureComponent {
ipfsGateway: PropTypes.string.isRequired,
useMultiAccountBalanceChecker: PropTypes.bool.isRequired,
setUseMultiAccountBalanceChecker: PropTypes.func.isRequired,
useCurrencyRateCheck: PropTypes.bool.isRequired,
setUseCurrencyRateCheck: PropTypes.func.isRequired,
useNftDetection: PropTypes.bool,
setUseNftDetection: PropTypes.func,
setOpenSeaEnabled: PropTypes.func,
@ -348,7 +353,7 @@ export default class SecurityTab extends PureComponent {
return (
<div
ref={this.settingsRefs[7]}
ref={this.settingsRefs[4]}
className="settings-page__content-row"
data-testid="advanced-setting-gas-fee-estimation"
>
@ -395,7 +400,7 @@ export default class SecurityTab extends PureComponent {
this.props;
return (
<div ref={this.settingsRefs[4]} className="settings-page__content-row">
<div ref={this.settingsRefs[8]} className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{t('useMultiAccountBalanceChecker')}</span>
<div className="settings-page__content-description">
@ -437,7 +442,7 @@ export default class SecurityTab extends PureComponent {
} = this.props;
return (
<div ref={this.settingsRefs[9]} className="settings-page__content-row">
<div ref={this.settingsRefs[7]} className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{t('useCollectibleDetection')}</span>
<div className="settings-page__content-description">
@ -477,6 +482,57 @@ export default class SecurityTab extends PureComponent {
);
}
renderCurrencyRateCheckToggle() {
const { t } = this.context;
const { useCurrencyRateCheck, setUseCurrencyRateCheck } = this.props;
return (
<div ref={this.settingsRefs[9]} className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{t('currencyRateCheckToggle')}</span>
<div className="settings-page__content-description">
{t('currencyRateCheckToggleDescription', [
<a
key="coingecko_link"
href={COINGECKO_LINK}
rel="noreferrer"
target="_blank"
>
{t('coingecko')}
</a>,
<a
key="cryptocompare_link"
href={CRYPTOCOMPARE_LINK}
rel="noreferrer"
target="_blank"
>
{t('cryptoCompare')}
</a>,
<a
key="privacy_policy_link"
href={PRIVACY_POLICY_LINK}
rel="noreferrer"
target="_blank"
>
{t('privacyMsg')}
</a>,
])}
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={useCurrencyRateCheck}
onToggle={(value) => setUseCurrencyRateCheck(!value)}
offLabel={t('off')}
onLabel={t('on')}
/>
</div>
</div>
</div>
);
}
render() {
const { warning } = this.props;
@ -503,6 +559,7 @@ export default class SecurityTab extends PureComponent {
{this.context.t('transactions')}
</span>
<div className="settings-page__content-padded">
{this.renderCurrencyRateCheckToggle()}
{this.renderIncomingTransactionsOptIn()}
</div>
<span className="settings-page__security-tab-sub-header">

View File

@ -8,6 +8,7 @@ import {
setUseTokenDetection,
setIpfsGateway,
setUseMultiAccountBalanceChecker,
setUseCurrencyRateCheck,
setUseNftDetection,
setOpenSeaEnabled,
} from '../../../store/actions';
@ -26,6 +27,7 @@ const mapStateToProps = (state) => {
useTokenDetection,
ipfsGateway,
useMultiAccountBalanceChecker,
useCurrencyRateCheck,
} = metamask;
return {
@ -36,6 +38,7 @@ const mapStateToProps = (state) => {
useTokenDetection,
ipfsGateway,
useMultiAccountBalanceChecker,
useCurrencyRateCheck,
useNftDetection: getUseNftDetection(state),
openSeaEnabled: getOpenSeaEnabled(state),
};
@ -48,6 +51,7 @@ const mapDispatchToProps = (dispatch) => {
setShowIncomingTransactionsFeatureFlag: (shouldShow) =>
dispatch(setFeatureFlag('showIncomingTransactions', shouldShow)),
setUsePhishDetect: (val) => dispatch(setUsePhishDetect(val)),
setUseCurrencyRateCheck: (val) => dispatch(setUseCurrencyRateCheck(val)),
setUseTokenDetection: (value) => {
return dispatch(setUseTokenDetection(value));
},

View File

@ -8,12 +8,14 @@ import SecurityTab from './security-tab.container';
const mockSetFeatureFlag = jest.fn();
const mockSetParticipateInMetaMetrics = jest.fn();
const mockSetUsePhishDetect = jest.fn();
const mockSetUseCurrencyRateCheck = jest.fn();
jest.mock('../../../store/actions.js', () => {
return {
setFeatureFlag: () => mockSetFeatureFlag,
setParticipateInMetaMetrics: () => mockSetParticipateInMetaMetrics,
setUsePhishDetect: () => mockSetUsePhishDetect,
setUseCurrencyRateCheck: () => mockSetUseCurrencyRateCheck,
};
});

View File

@ -69,6 +69,7 @@ import {
getTokenList,
isHardwareWallet,
getHardwareWalletType,
getUseCurrencyRateCheck,
} from '../../../selectors';
import { getValueFromWeiHex } from '../../../helpers/utils/conversions.util';
@ -154,6 +155,7 @@ export default function BuildQuote({
const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual);
const conversionRate = useSelector(getConversionRate);
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
const hardwareWalletUsed = useSelector(isHardwareWallet);
const hardwareWalletType = useSelector(getHardwareWalletType);
const smartTransactionsOptInStatus = useSelector(
@ -255,13 +257,13 @@ export default function BuildQuote({
fromTokenInputValue || 0,
fromTokenSymbol,
{
showFiat: true,
showFiat: useCurrencyRateCheck,
},
true,
);
const swapFromEthFiatValue = useEthFiatAmount(
fromTokenInputValue || 0,
{ showFiat: true },
{ showFiat: useCurrencyRateCheck },
true,
);
const swapFromFiatValue = isSwapsDefaultTokenSymbol(fromTokenSymbol, chainId)

View File

@ -1,4 +1,5 @@
import React, { useContext } from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { I18nContext } from '../../../contexts/i18n';
import InfoTooltip from '../../../components/ui/info-tooltip';
@ -13,6 +14,7 @@ import {
} from '../../../helpers/constants/design-system';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import { EVENT } from '../../../../shared/constants/metametrics';
import { getUseCurrencyRateCheck } from '../../../selectors';
const GAS_FEES_LEARN_MORE_URL =
'https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172';
@ -30,6 +32,7 @@ export default function FeeCard({
isBestQuote,
}) {
const t = useContext(I18nContext);
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
/* istanbul ignore next */
const getTranslatedNetworkName = () => {
@ -112,9 +115,10 @@ export default function FeeCard({
</>
}
detailText={primaryFee.fee}
detailTotal={secondaryFee.fee}
detailTotal={useCurrencyRateCheck && secondaryFee.fee}
subText={
secondaryFee?.maxFee !== undefined && (
(secondaryFee?.maxFee !== undefined ||
primaryFee?.maxFee !== undefined) && (
<>
<Typography
as="span"
@ -124,7 +128,9 @@ export default function FeeCard({
>
{t('maxFee')}
</Typography>
{`: ${secondaryFee.maxFee}`}
{useCurrencyRateCheck
? `: ${secondaryFee.maxFee}`
: `: ${primaryFee.maxFee}`}
</>
)
}

View File

@ -1,18 +1,18 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { useSelector } from 'react-redux';
import {
renderWithProvider,
createSwapsMockStore,
setBackgroundConnection,
MOCKS,
fireEvent,
} from '../../../../test/jest';
import { CHAIN_IDS } from '../../../../shared/constants/network';
import { checkNetworkAndAccountSupports1559 } from '../../../selectors';
import {
checkNetworkAndAccountSupports1559,
getUseCurrencyRateCheck,
} from '../../../selectors';
import {
getGasEstimateType,
getGasFeeEstimates,
@ -22,8 +22,6 @@ import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../helpers/constants/tran
import FeeCard from '.';
const middleware = [thunk];
jest.mock('../../../hooks/useGasFeeEstimates', () => ({
useGasFeeEstimates: jest.fn(),
}));
@ -50,6 +48,9 @@ const generateUseSelectorRouter = () => (selector) => {
if (selector === getIsGasEstimatesLoading) {
return false;
}
if (selector === getUseCurrencyRateCheck) {
return true;
}
return undefined;
};
@ -109,13 +110,12 @@ describe('FeeCard', () => {
});
it('renders the component with EIP-1559 enabled', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({
networkAndAccountSupports1559: true,
maxPriorityFeePerGasDecGWEI: '3',
maxFeePerGasDecGWEI: '4',
});
const { getByText } = renderWithProvider(<FeeCard {...props} />, store);
const { getByText } = renderWithProvider(<FeeCard {...props} />);
expect(getByText('Best of 6 quotes.')).toBeInTheDocument();
expect(getByText('Estimated gas fee')).toBeInTheDocument();
expect(getByText('Max fee')).toBeInTheDocument();
@ -129,7 +129,6 @@ describe('FeeCard', () => {
});
it('renders the component with smart transactions enabled and user opted in', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({
smartTransactionsOptInStatus: true,
smartTransactionsEnabled: true,
@ -138,7 +137,6 @@ describe('FeeCard', () => {
});
const { getByText, queryByTestId } = renderWithProvider(
<FeeCard {...props} />,
store,
);
expect(getByText('Best of 6 quotes.')).toBeInTheDocument();
expect(getByText('Estimated gas fee')).toBeInTheDocument();
@ -149,20 +147,18 @@ describe('FeeCard', () => {
});
it('renders the component with hidden token approval row', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({
hideTokenApprovalRow: true,
});
const { queryByText } = renderWithProvider(<FeeCard {...props} />, store);
const { queryByText } = renderWithProvider(<FeeCard {...props} />);
expect(queryByText('Edit limit')).not.toBeInTheDocument();
});
it('approves a token', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({
onTokenApprovalClick: jest.fn(),
});
const { queryByText } = renderWithProvider(<FeeCard {...props} />, store);
const { queryByText } = renderWithProvider(<FeeCard {...props} />);
fireEvent.click(queryByText('Edit limit'));
expect(props.onTokenApprovalClick).toHaveBeenCalled();
});

View File

@ -10,6 +10,7 @@ import { I18nContext } from '../../../../contexts/i18n';
import {
getCurrentChainId,
getRpcPrefsForCurrentProvider,
getUseCurrencyRateCheck,
} from '../../../../selectors';
import { EVENT } from '../../../../../shared/constants/metametrics';
import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../../shared/constants/swaps';
@ -36,7 +37,7 @@ export default function ItemList({
rpcPrefs.blockExplorerUrl ??
SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[chainId] ??
null;
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
const blockExplorerHostName = getURLHostName(blockExplorerLink);
const trackEvent = useContext(MetaMetricsContext);
@ -124,7 +125,7 @@ export default function ItemList({
{rightPrimaryLabel}
</span>
) : null}
{rightSecondaryLabel ? (
{rightSecondaryLabel && useCurrencyRateCheck ? (
<span className="searchable-item-list__right-secondary-label">
{rightSecondaryLabel}
</span>

View File

@ -1,8 +1,10 @@
import React, { useContext } from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { I18nContext } from '../../../../contexts/i18n';
import InfoTooltip from '../../../../components/ui/info-tooltip';
import ExchangeRateDisplay from '../../exchange-rate-display';
import { getUseCurrencyRateCheck } from '../../../../selectors';
const QuoteDetails = ({
slippage,
@ -18,6 +20,8 @@ const QuoteDetails = ({
hideEstimatedGasFee,
}) => {
const t = useContext(I18nContext);
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
return (
<div className="quote-details">
<div className="quote-details__row">
@ -67,7 +71,9 @@ const QuoteDetails = ({
</div>
<div className="quote-details__detail-content">
<span>{feeInEth}</span>
<span className="quote-details__light-grey">{` (${networkFees})`}</span>
<span className="quote-details__light-grey">
{useCurrencyRateCheck && ` (${networkFees})`}
</span>
</div>
</div>
)}

View File

@ -1,6 +1,8 @@
import React from 'react';
import { renderWithProvider } from '../../../../test/jest';
import configureStore from '../../../store/store';
import mockState from '../../../../test/data/mock-state.json';
import SelectQuotePopover from '.';
const createProps = (customProps = {}) => {
@ -14,11 +16,13 @@ const createProps = (customProps = {}) => {
...customProps,
};
};
const store = configureStore(mockState);
describe('SelectQuotePopover', () => {
it('renders the component with initial props', () => {
const { container } = renderWithProvider(
<SelectQuotePopover {...createProps()} />,
store,
);
expect(container).toMatchSnapshot();
});

View File

@ -1,4 +1,5 @@
import React, { useState, useContext, useMemo } from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import BigNumber from 'bignumber.js';
@ -6,6 +7,7 @@ import SunCheckIcon from '../../../../components/ui/icon/sun-check-icon.componen
import { I18nContext } from '../../../../contexts/i18n';
import { QUOTE_DATA_ROWS_PROPTYPES_SHAPE } from '../select-quote-popover-constants';
import InfoTooltip from '../../../../components/ui/info-tooltip';
import { getUseCurrencyRateCheck } from '../../../../selectors';
const ToggleArrows = () => (
<svg
@ -36,6 +38,7 @@ export default function SortList({
}) {
const t = useContext(I18nContext);
const [noRowHover, setRowNowHover] = useState(false);
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
const onColumnHeaderClick = (nextSortColumn) => {
if (nextSortColumn === sortColumn) {
@ -100,7 +103,7 @@ export default function SortList({
data-testid="select-quote-popover__network-fees-header"
onClick={() => onColumnHeaderClick('rawNetworkFees')}
>
{!hideEstimatedGasFee && (
{!hideEstimatedGasFee && useCurrencyRateCheck && (
<>
<span>{t('swapEstimatedNetworkFees')}</span>
<InfoTooltip
@ -161,7 +164,7 @@ export default function SortList({
)}
</div>
<div className="select-quote-popover__network-fees">
{!hideEstimatedGasFee && networkFees}
{!hideEstimatedGasFee && useCurrencyRateCheck && networkFees}
</div>
<div className="select-quote-popover__quote-source">
<div

View File

@ -1,6 +1,8 @@
import React from 'react';
import { renderWithProvider, fireEvent } from '../../../../../test/jest';
import MockState from '../../../../../test/data/mock-state.json';
import configureStore from '../../../../store/store';
import SortList from './sort-list';
jest.mock(
@ -71,8 +73,12 @@ const createProps = (customProps = {}) => {
};
describe('SortList', () => {
const store = configureStore(MockState);
it('renders the component with initial props', () => {
const { getByText } = renderWithProvider(<SortList {...createProps()} />);
const { getByText } = renderWithProvider(
<SortList {...createProps()} />,
store,
);
expect(getByText('$15.25')).toBeInTheDocument();
expect(getByText('$14.26')).toBeInTheDocument();
expect(getByText('$13.27')).toBeInTheDocument();
@ -89,28 +95,28 @@ describe('SortList', () => {
it('clicks on the "destinationTokenValue" header', () => {
const props = createProps();
const { getByTestId } = renderWithProvider(<SortList {...props} />);
const { getByTestId } = renderWithProvider(<SortList {...props} />, store);
fireEvent.click(getByTestId('select-quote-popover__receiving'));
expect(props.setSortColumn).toHaveBeenCalledWith('destinationTokenValue');
});
it('clicks on the "rawNetworkFees" header', () => {
const props = createProps();
const { getByTestId } = renderWithProvider(<SortList {...props} />);
const { getByTestId } = renderWithProvider(<SortList {...props} />, store);
fireEvent.click(getByTestId('select-quote-popover__network-fees-header'));
expect(props.setSortColumn).toHaveBeenCalledWith('rawNetworkFees');
});
it('clicks on the first aggregator', () => {
const props = createProps();
const { getByTestId } = renderWithProvider(<SortList {...props} />);
const { getByTestId } = renderWithProvider(<SortList {...props} />, store);
fireEvent.click(getByTestId('select-quote-popover-row-0'));
expect(props.onSelect).toHaveBeenCalledWith('Agg1');
});
it('clicks on a caret for the first aggregator', () => {
const props = createProps();
const { getByTestId } = renderWithProvider(<SortList {...props} />);
const { getByTestId } = renderWithProvider(<SortList {...props} />, store);
fireEvent.click(getByTestId('select-quote-popover__caret-right-0'));
expect(props.onCaretClick).toHaveBeenCalledWith('Agg1');
});

View File

@ -586,9 +586,12 @@ export function getShouldShowFiat(state) {
const isMainNet = getIsMainnet(state);
const isCustomNetwork = getIsCustomNetwork(state);
const conversionRate = getConversionRate(state);
const useCurrencyRateCheck = getUseCurrencyRateCheck(state);
const { showFiatInTestnets } = getPreferences(state);
return Boolean(
(isMainNet || isCustomNetwork || showFiatInTestnets) && conversionRate,
(isMainNet || isCustomNetwork || showFiatInTestnets) &&
useCurrencyRateCheck &&
conversionRate,
);
}
@ -1388,3 +1391,13 @@ export function getShouldShowSeedPhraseReminder(state) {
export function getCustomTokenAmount(state) {
return state.appState.customTokenAmount;
}
/**
* To get the useCurrencyRateCheck flag which to check if the user prefers currency conversion
*
* @param {*} state
* @returns Boolean
*/
export function getUseCurrencyRateCheck(state) {
return Boolean(state.metamask.useCurrencyRateCheck);
}

View File

@ -263,4 +263,9 @@ describe('Selectors', () => {
mockState.metamask.notifications.test,
]);
});
it('#getUseCurrencyRateCheck', () => {
const useCurrencyRateCheck = selectors.getUseCurrencyRateCheck(mockState);
expect(useCurrencyRateCheck).toStrictEqual(true);
});
});

View File

@ -2748,6 +2748,19 @@ export function setUseNftDetection(val) {
};
}
export function setUseCurrencyRateCheck(val) {
return (dispatch) => {
dispatch(showLoadingIndication());
log.debug(`background.setUseCurrencyRateCheck`);
callBackgroundMethod('setUseCurrencyRateCheck', [val], (err) => {
dispatch(hideLoadingIndication());
if (err) {
dispatch(displayWarning(err.message));
}
});
};
}
export function setOpenSeaEnabled(val) {
return (dispatch) => {
dispatch(showLoadingIndication());

View File

@ -3293,20 +3293,20 @@ __metadata:
languageName: node
linkType: hard
"@metamask/assets-controllers@npm:^1.0.1":
version: 1.0.1
resolution: "@metamask/assets-controllers@npm:1.0.1"
"@metamask/assets-controllers@npm:^3.0.1":
version: 3.0.1
resolution: "@metamask/assets-controllers@npm:3.0.1"
dependencies:
"@ethersproject/abi": ^5.7.0
"@ethersproject/bignumber": ^5.7.0
"@ethersproject/contracts": ^5.7.0
"@ethersproject/providers": ^5.7.0
"@metamask/base-controller": ~1.1.0
"@metamask/contract-metadata": ^1.35.0
"@metamask/controller-utils": ~1.0.0
"@metamask/base-controller": ^1.1.1
"@metamask/contract-metadata": ^2.1.0
"@metamask/controller-utils": ^1.0.0
"@metamask/metamask-eth-abis": 3.0.0
"@metamask/network-controller": ~1.0.0
"@metamask/preferences-controller": ~1.0.0
"@metamask/network-controller": ^2.0.0
"@metamask/preferences-controller": ^1.0.1
"@types/uuid": ^8.3.0
abort-controller: ^3.0.0
async-mutex: ^0.2.6
@ -3318,7 +3318,9 @@ __metadata:
multiformats: ^9.5.2
single-call-balance-checker-abi: ^1.0.0
uuid: ^8.3.2
checksum: 678f32126aa84de769065581661218a247166ef2e4918290a8e082dd9c7b175d84c5a4d4f431c884dc83201b183f324c2b5047bc69a2ea7825d710470cae5f87
peerDependencies:
"@metamask/network-controller": ^2.0.0
checksum: c356a820929738e3ad83a5cfe20993a1e7c4dac6835b308d8cb32efe8542c339664ae3046a1dedce6ee2a4c45cec5528015309366fa96674837dca7ea001998f
languageName: node
linkType: hard
@ -3336,7 +3338,7 @@ __metadata:
languageName: node
linkType: hard
"@metamask/base-controller@npm:^1.0.0, @metamask/base-controller@npm:^1.1.1, @metamask/base-controller@npm:~1.1.0":
"@metamask/base-controller@npm:^1.0.0, @metamask/base-controller@npm:^1.1.1":
version: 1.1.1
resolution: "@metamask/base-controller@npm:1.1.1"
dependencies:
@ -3375,13 +3377,6 @@ __metadata:
languageName: node
linkType: hard
"@metamask/contract-metadata@npm:^1.35.0":
version: 1.36.0
resolution: "@metamask/contract-metadata@npm:1.36.0"
checksum: 6b1bc0927536a7ed235f896dcb0dabbce1f7853eef5e58efdd6cc8294c2ef10dd0c46572d20df9df31563fb466a0a162101169d9e1595dd931c7eabb9fa5a7e9
languageName: node
linkType: hard
"@metamask/contract-metadata@npm:^2.1.0":
version: 2.1.0
resolution: "@metamask/contract-metadata@npm:2.1.0"
@ -3767,13 +3762,13 @@ __metadata:
languageName: node
linkType: hard
"@metamask/preferences-controller@npm:~1.0.0":
version: 1.0.0
resolution: "@metamask/preferences-controller@npm:1.0.0"
"@metamask/preferences-controller@npm:^1.0.1":
version: 1.0.1
resolution: "@metamask/preferences-controller@npm:1.0.1"
dependencies:
"@metamask/base-controller": ~1.0.0
"@metamask/controller-utils": ~1.0.0
checksum: dad15bf468031df470ebeb18fa71c8181f247f9c9fd2eac52f6ee71f77bda61d8b0b7f8dc492d4f4f1602e249dedc38360b5e90ca2902adbcbd7c56bbaec8282
"@metamask/base-controller": ^1.1.1
"@metamask/controller-utils": ^1.0.0
checksum: 3ad7dcf40cc1dc6679d37f705418d4d18da2acce120ce6bd09b0e6755484d193139fd85cf2c0b7374d867ab8a03d7f5bc74ebc8c2cdc003d5872f33d34c1e965
languageName: node
linkType: hard
@ -22758,7 +22753,7 @@ __metadata:
"@metamask/address-book-controller": ^1.0.0
"@metamask/announcement-controller": ^1.0.0
"@metamask/approval-controller": ^1.0.0
"@metamask/assets-controllers": ^1.0.1
"@metamask/assets-controllers": ^3.0.1
"@metamask/auto-changelog": ^2.1.0
"@metamask/base-controller": ^1.0.0
"@metamask/contract-metadata": ^2.1.0