1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +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, useNonceField: false,
usePhishDetect: true, usePhishDetect: true,
useTokenDetection: true, useTokenDetection: true,
useCurrencyRateCheck: true,
lostIdentities: {}, lostIdentities: {},
forgottenPassword: false, forgottenPassword: false,
ipfsGateway: 'dweb.link', ipfsGateway: 'dweb.link',

View File

@ -682,6 +682,9 @@
"close": { "close": {
"message": "Close" "message": "Close"
}, },
"coingecko": {
"message": "CoinGecko"
},
"collectibleAddFailedMessage": { "collectibleAddFailedMessage": {
"message": "NFT cant be added as the ownership details do not match. Make sure you have entered correct information." "message": "NFT cant be added as the ownership details do not match. Make sure you have entered correct information."
}, },
@ -894,9 +897,19 @@
"createPassword": { "createPassword": {
"message": "Create password" "message": "Create password"
}, },
"cryptoCompare": {
"message": "CryptoCompare"
},
"currencyConversion": { "currencyConversion": {
"message": "Currency conversion" "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": { "currencySymbol": {
"message": "Currency symbol" "message": "Currency symbol"
}, },

View File

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

View File

@ -11,8 +11,9 @@ import {
} from '@metamask/assets-controllers'; } from '@metamask/assets-controllers';
import { NETWORK_TYPES } from '../../../shared/constants/network'; import { NETWORK_TYPES } from '../../../shared/constants/network';
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
import { hexToDecimal } from '../../../shared/lib/metamask-controller-utils';
import DetectTokensController from './detect-tokens'; import DetectTokensController from './detect-tokens';
import NetworkController from './network'; import NetworkController, { NETWORK_EVENTS } from './network';
import PreferencesController from './preferences'; import PreferencesController from './preferences';
describe('DetectTokensController', function () { describe('DetectTokensController', function () {
@ -64,14 +65,34 @@ describe('DetectTokensController', function () {
onPreferencesStateChange: preferences.store.subscribe.bind( onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store, 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({ assetsContractController = new AssetsContractController({
onPreferencesStateChange: preferences.store.subscribe.bind( onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store, 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 sandbox

View File

@ -37,6 +37,7 @@ export default class PreferencesController {
// set to false will be using the static list from contract-metadata // set to false will be using the static list from contract-metadata
useTokenDetection: false, useTokenDetection: false,
useNftDetection: false, useNftDetection: false,
useCurrencyRateCheck: true,
openSeaEnabled: false, openSeaEnabled: false,
advancedGasFee: null, advancedGasFee: null,
@ -156,6 +157,15 @@ export default class PreferencesController {
this.store.updateState({ useNftDetection }); 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 * Setter for the `openSeaEnabled` property
* *

View File

@ -367,4 +367,23 @@ describe('preferences controller', function () {
assert.equal(preferencesController.store.getState().theme, 'dark'); 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) => { this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = { const modifiedNetworkState = {
...networkState, ...networkState,
provider: { providerConfig: {
...networkState.provider, ...networkState.provider,
chainId: hexToDecimal(networkState.provider.chainId), chainId: hexToDecimal(networkState.provider.chainId),
}, },
@ -291,9 +291,16 @@ export default class MetamaskController extends EventEmitter {
onPreferencesStateChange: this.preferencesController.store.subscribe.bind( onPreferencesStateChange: this.preferencesController.store.subscribe.bind(
this.preferencesController.store, this.preferencesController.store,
), ),
onNetworkStateChange: this.networkController.store.subscribe.bind( onNetworkStateChange: (cb) =>
this.networkController.store, this.networkController.store.subscribe((networkState) => {
), const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.provider,
},
};
return cb(modifiedNetworkState);
}),
config: { provider: this.provider }, config: { provider: this.provider },
state: initState.TokensController, state: initState.TokensController,
}); });
@ -307,7 +314,7 @@ export default class MetamaskController extends EventEmitter {
const networkState = this.networkController.store.getState(); const networkState = this.networkController.store.getState();
const modifiedNetworkState = { const modifiedNetworkState = {
...networkState, ...networkState,
provider: { providerConfig: {
...networkState.provider, ...networkState.provider,
chainId: hexToDecimal(networkState.provider.chainId), chainId: hexToDecimal(networkState.provider.chainId),
}, },
@ -327,9 +334,16 @@ export default class MetamaskController extends EventEmitter {
this.preferencesController.store.subscribe.bind( this.preferencesController.store.subscribe.bind(
this.preferencesController.store, this.preferencesController.store,
), ),
onNetworkStateChange: this.networkController.store.subscribe.bind( onNetworkStateChange: (cb) =>
this.networkController.store, this.networkController.store.subscribe((networkState) => {
), const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.provider,
},
};
return cb(modifiedNetworkState);
}),
getERC721AssetName: getERC721AssetName:
this.assetsContractController.getERC721AssetName.bind( this.assetsContractController.getERC721AssetName.bind(
this.assetsContractController, this.assetsContractController,
@ -504,7 +518,7 @@ export default class MetamaskController extends EventEmitter {
this.networkController.store.subscribe((networkState) => { this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = { const modifiedNetworkState = {
...networkState, ...networkState,
provider: { providerConfig: {
...networkState.provider, ...networkState.provider,
chainId: hexToDecimal(networkState.provider.chainId), chainId: hexToDecimal(networkState.provider.chainId),
}, },
@ -512,9 +526,29 @@ export default class MetamaskController extends EventEmitter {
return cb(modifiedNetworkState); return cb(modifiedNetworkState);
}), }),
}, },
undefined, {
disabled:
!this.preferencesController.store.getState().useCurrencyRateCheck,
},
initState.TokenRatesController, 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({ this.ensController = new EnsController({
provider: this.provider, provider: this.provider,
@ -1245,7 +1279,9 @@ export default class MetamaskController extends EventEmitter {
triggerNetworkrequests() { triggerNetworkrequests() {
this.accountTracker.start(); this.accountTracker.start();
this.incomingTransactionsController.start(); this.incomingTransactionsController.start();
this.currencyRateController.start(); if (this.preferencesController.store.getState().useCurrencyRateCheck) {
this.currencyRateController.start();
}
if (this.preferencesController.store.getState().useTokenDetection) { if (this.preferencesController.store.getState().useTokenDetection) {
this.tokenListController.start(); this.tokenListController.start();
} }
@ -1254,7 +1290,9 @@ export default class MetamaskController extends EventEmitter {
stopNetworkRequests() { stopNetworkRequests() {
this.accountTracker.stop(); this.accountTracker.stop();
this.incomingTransactionsController.stop(); this.incomingTransactionsController.stop();
this.currencyRateController.stop(); if (this.preferencesController.store.getState().useCurrencyRateCheck) {
this.currencyRateController.stop();
}
if (this.preferencesController.store.getState().useTokenDetection) { if (this.preferencesController.store.getState().useTokenDetection) {
this.tokenListController.stop(); this.tokenListController.stop();
} }
@ -1633,6 +1671,10 @@ export default class MetamaskController extends EventEmitter {
setUseNftDetection: preferencesController.setUseNftDetection.bind( setUseNftDetection: preferencesController.setUseNftDetection.bind(
preferencesController, preferencesController,
), ),
setUseCurrencyRateCheck:
preferencesController.setUseCurrencyRateCheck.bind(
preferencesController,
),
setOpenSeaEnabled: preferencesController.setOpenSeaEnabled.bind( setOpenSeaEnabled: preferencesController.setOpenSeaEnabled.bind(
preferencesController, preferencesController,
), ),

View File

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

View File

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

View File

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

View File

@ -209,7 +209,7 @@
"@metamask/address-book-controller": "^1.0.0", "@metamask/address-book-controller": "^1.0.0",
"@metamask/announcement-controller": "^1.0.0", "@metamask/announcement-controller": "^1.0.0",
"@metamask/approval-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/base-controller": "^1.0.0",
"@metamask/contract-metadata": "^2.1.0", "@metamask/contract-metadata": "^2.1.0",
"@metamask/controller-utils": "^1.0.0", "@metamask/controller-utils": "^1.0.0",

View File

@ -5,6 +5,11 @@ _supportLink = 'https://metamask-flask.zendesk.com/hc';
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
export const SUPPORT_LINK = _supportLink; 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 // TODO make sure these links are correct
export const ETHERSCAN_PRIVACY_LINK = 'https://etherscan.io/privacyPolicy'; export const ETHERSCAN_PRIVACY_LINK = 'https://etherscan.io/privacyPolicy';
export const CONSENSYS_PRIVACY_LINK = 'https://consensys.net/privacy-policy/'; export const CONSENSYS_PRIVACY_LINK = 'https://consensys.net/privacy-policy/';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import Box from '../../../ui/box'; import Box from '../../../ui/box';
import Typography from '../../../ui/typography'; import Typography from '../../../ui/typography';
@ -12,6 +13,7 @@ import {
} from '../../../../helpers/constants/design-system'; } from '../../../../helpers/constants/design-system';
import { useTokenTracker } from '../../../../hooks/useTokenTracker'; import { useTokenTracker } from '../../../../hooks/useTokenTracker';
import { useTokenFiatAmount } from '../../../../hooks/useTokenFiatAmount'; import { useTokenFiatAmount } from '../../../../hooks/useTokenFiatAmount';
import { getUseCurrencyRateCheck } from '../../../../selectors';
const DetectedTokenValues = ({ const DetectedTokenValues = ({
token, token,
@ -30,6 +32,8 @@ const DetectedTokenValues = ({
token.symbol, token.symbol,
); );
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
useEffect(() => { useEffect(() => {
setTokenSelection(tokensListDetected[token.address]?.selected); setTokenSelection(tokensListDetected[token.address]?.selected);
}, [tokensListDetected, token.address, tokenSelection, setTokenSelection]); }, [tokensListDetected, token.address, tokenSelection, setTokenSelection]);
@ -46,7 +50,9 @@ const DetectedTokenValues = ({
{`${balanceString || '0'} ${token.symbol}`} {`${balanceString || '0'} ${token.symbol}`}
</Typography> </Typography>
<Typography variant={TYPOGRAPHY.H7} color={COLORS.TEXT_ALTERNATIVE}> <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> </Typography>
</Box> </Box>
<Box className="detected-token-values__checkbox"> <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 { COLORS } from '../../../helpers/constants/design-system';
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'; import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
import { getPreferences } from '../../../selectors'; import { getPreferences, getUseCurrencyRateCheck } from '../../../selectors';
import { useGasFeeContext } from '../../../contexts/gasFee'; import { useGasFeeContext } from '../../../contexts/gasFee';
import { useI18nContext } from '../../../hooks/useI18nContext'; import { useI18nContext } from '../../../hooks/useI18nContext';
@ -29,6 +29,8 @@ const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences); const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences);
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
if (hasSimulationError && !userAcknowledgedGasMissing) { if (hasSimulationError && !userAcknowledgedGasMissing) {
return null; return null;
} }
@ -39,14 +41,16 @@ const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
detailTitle={<GasDetailsItemTitle />} detailTitle={<GasDetailsItemTitle />}
detailTitleColor={COLORS.TEXT_DEFAULT} detailTitleColor={COLORS.TEXT_DEFAULT}
detailText={ detailText={
<div className="gas-details-item__currency-container"> useCurrencyRateCheck && (
<LoadingHeartBeat estimateUsed={estimateUsed} /> <div className="gas-details-item__currency-container">
<UserPreferencedCurrencyDisplay <LoadingHeartBeat estimateUsed={estimateUsed} />
type={SECONDARY} <UserPreferencedCurrencyDisplay
value={hexMinimumTransactionFee} type={SECONDARY}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)} value={hexMinimumTransactionFee}
/> hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
</div> />
</div>
)
} }
detailTotal={ detailTotal={
<div className="gas-details-item__currency-container"> <div className="gas-details-item__currency-container">

View File

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

View File

@ -219,6 +219,13 @@ export const SETTINGS_CONSTANTS = [
icon: 'fa fa-flask', icon: 'fa fa-flask',
featureFlag: 'NFTS_V1', 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'), tabMessage: (t) => t('alerts'),
sectionMessage: (t) => t('alertSettingsUnconnectedAccount'), sectionMessage: (t) => t('alertSettingsUnconnectedAccount'),

View File

@ -137,6 +137,10 @@ const t = (key) => {
return 'Contact us'; return 'Contact us';
case 'snaps': case 'snaps':
return '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: default:
return ''; return '';
} }
@ -165,7 +169,7 @@ describe('Settings Search Utils', () => {
it('should get good security & privacy section number', () => { it('should get good security & privacy section number', () => {
expect( expect(
getNumberOfSettingsInSection(t, t('securityAndPrivacy')), getNumberOfSettingsInSection(t, t('securityAndPrivacy')),
).toStrictEqual(9); ).toStrictEqual(10);
}); });
it('should get good alerts section number', () => { it('should get good alerts section number', () => {

View File

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

View File

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

View File

@ -22,13 +22,20 @@ import {
showModal, showModal,
setIpfsGateway, setIpfsGateway,
showNetworkDropdown, showNetworkDropdown,
setUseCurrencyRateCheck,
} from '../../../store/actions'; } from '../../../store/actions';
import { ONBOARDING_PIN_EXTENSION_ROUTE } from '../../../helpers/constants/routes'; import { ONBOARDING_PIN_EXTENSION_ROUTE } from '../../../helpers/constants/routes';
import { Icon, TextField } from '../../../components/component-library'; import { Icon, TextField } from '../../../components/component-library';
import NetworkDropdown from '../../../components/app/dropdowns/network-dropdown'; import NetworkDropdown from '../../../components/app/dropdowns/network-dropdown';
import NetworkDisplay from '../../../components/app/network-display/network-display'; 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 { MetaMetricsContext } from '../../../contexts/metametrics';
import { EVENT_NAMES, EVENT } from '../../../../shared/constants/metametrics'; import { EVENT_NAMES, EVENT } from '../../../../shared/constants/metametrics';
import { Setting } from './setting'; import { Setting } from './setting';
export default function PrivacySettings() { export default function PrivacySettings() {
@ -37,6 +44,7 @@ export default function PrivacySettings() {
const history = useHistory(); const history = useHistory();
const [usePhishingDetection, setUsePhishingDetection] = useState(true); const [usePhishingDetection, setUsePhishingDetection] = useState(true);
const [turnOnTokenDetection, setTurnOnTokenDetection] = useState(true); const [turnOnTokenDetection, setTurnOnTokenDetection] = useState(true);
const [turnOnCurrencyRateCheck, setTurnOnCurrencyRateCheck] = useState(true);
const [showIncomingTransactions, setShowIncomingTransactions] = const [showIncomingTransactions, setShowIncomingTransactions] =
useState(true); useState(true);
const [ const [
@ -60,6 +68,7 @@ export default function PrivacySettings() {
dispatch( dispatch(
setUseMultiAccountBalanceChecker(isMultiAccountBalanceCheckerEnabled), setUseMultiAccountBalanceChecker(isMultiAccountBalanceCheckerEnabled),
); );
dispatch(setUseCurrencyRateCheck(turnOnCurrencyRateCheck));
dispatch(setCompletedOnboarding()); dispatch(setCompletedOnboarding());
if (ipfsURL && !ipfsError) { 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> </div>
<Button type="primary" rounded onClick={handleSubmit}> <Button type="primary" rounded onClick={handleSubmit}>
{t('done')} {t('done')}

View File

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

View File

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

View File

@ -236,27 +236,6 @@ exports[`SendContent Component render should match snapshot 1`] = `
<div <div
class="transaction-detail-item__detail-values" 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 <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" 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 <div
class="settings-page__content-padded" 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 <div
class="settings-page__content-row" class="settings-page__content-row"
> >

View File

@ -14,6 +14,9 @@ import {
} from '../../../helpers/utils/settings-search'; } from '../../../helpers/utils/settings-search';
import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics'; import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics';
import { import {
COINGECKO_LINK,
CRYPTOCOMPARE_LINK,
PRIVACY_POLICY_LINK,
AUTO_DETECT_TOKEN_LEARN_MORE_LINK, AUTO_DETECT_TOKEN_LEARN_MORE_LINK,
CONSENSYS_PRIVACY_LINK, CONSENSYS_PRIVACY_LINK,
ETHERSCAN_PRIVACY_LINK, ETHERSCAN_PRIVACY_LINK,
@ -45,6 +48,8 @@ export default class SecurityTab extends PureComponent {
ipfsGateway: PropTypes.string.isRequired, ipfsGateway: PropTypes.string.isRequired,
useMultiAccountBalanceChecker: PropTypes.bool.isRequired, useMultiAccountBalanceChecker: PropTypes.bool.isRequired,
setUseMultiAccountBalanceChecker: PropTypes.func.isRequired, setUseMultiAccountBalanceChecker: PropTypes.func.isRequired,
useCurrencyRateCheck: PropTypes.bool.isRequired,
setUseCurrencyRateCheck: PropTypes.func.isRequired,
useNftDetection: PropTypes.bool, useNftDetection: PropTypes.bool,
setUseNftDetection: PropTypes.func, setUseNftDetection: PropTypes.func,
setOpenSeaEnabled: PropTypes.func, setOpenSeaEnabled: PropTypes.func,
@ -348,7 +353,7 @@ export default class SecurityTab extends PureComponent {
return ( return (
<div <div
ref={this.settingsRefs[7]} ref={this.settingsRefs[4]}
className="settings-page__content-row" className="settings-page__content-row"
data-testid="advanced-setting-gas-fee-estimation" data-testid="advanced-setting-gas-fee-estimation"
> >
@ -395,7 +400,7 @@ export default class SecurityTab extends PureComponent {
this.props; this.props;
return ( 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"> <div className="settings-page__content-item">
<span>{t('useMultiAccountBalanceChecker')}</span> <span>{t('useMultiAccountBalanceChecker')}</span>
<div className="settings-page__content-description"> <div className="settings-page__content-description">
@ -437,7 +442,7 @@ export default class SecurityTab extends PureComponent {
} = this.props; } = this.props;
return ( 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"> <div className="settings-page__content-item">
<span>{t('useCollectibleDetection')}</span> <span>{t('useCollectibleDetection')}</span>
<div className="settings-page__content-description"> <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() { render() {
const { warning } = this.props; const { warning } = this.props;
@ -503,6 +559,7 @@ export default class SecurityTab extends PureComponent {
{this.context.t('transactions')} {this.context.t('transactions')}
</span> </span>
<div className="settings-page__content-padded"> <div className="settings-page__content-padded">
{this.renderCurrencyRateCheckToggle()}
{this.renderIncomingTransactionsOptIn()} {this.renderIncomingTransactionsOptIn()}
</div> </div>
<span className="settings-page__security-tab-sub-header"> <span className="settings-page__security-tab-sub-header">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -586,9 +586,12 @@ export function getShouldShowFiat(state) {
const isMainNet = getIsMainnet(state); const isMainNet = getIsMainnet(state);
const isCustomNetwork = getIsCustomNetwork(state); const isCustomNetwork = getIsCustomNetwork(state);
const conversionRate = getConversionRate(state); const conversionRate = getConversionRate(state);
const useCurrencyRateCheck = getUseCurrencyRateCheck(state);
const { showFiatInTestnets } = getPreferences(state); const { showFiatInTestnets } = getPreferences(state);
return Boolean( return Boolean(
(isMainNet || isCustomNetwork || showFiatInTestnets) && conversionRate, (isMainNet || isCustomNetwork || showFiatInTestnets) &&
useCurrencyRateCheck &&
conversionRate,
); );
} }
@ -1388,3 +1391,13 @@ export function getShouldShowSeedPhraseReminder(state) {
export function getCustomTokenAmount(state) { export function getCustomTokenAmount(state) {
return state.appState.customTokenAmount; 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, 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) { export function setOpenSeaEnabled(val) {
return (dispatch) => { return (dispatch) => {
dispatch(showLoadingIndication()); dispatch(showLoadingIndication());

View File

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