diff --git a/.circleci/scripts/yarn-audit.sh b/.circleci/scripts/yarn-audit.sh index 717b5f456..48bed3ce6 100755 --- a/.circleci/scripts/yarn-audit.sh +++ b/.circleci/scripts/yarn-audit.sh @@ -5,7 +5,7 @@ set -o pipefail # use `improved-yarn-audit` since that allows for exclude # exclude 1002401 until we remove use of 3Box, 1002581 until we can find a better solution -yarn run improved-yarn-audit --ignore-dev-deps --min-severity moderate --exclude 1002401,1002581 +yarn run improved-yarn-audit --ignore-dev-deps --min-severity moderate --exclude 1002401,1002581,GHSA-93q8-gq69-wqmw,GHSA-257v-vj4p-3w2h audit_status="$?" # Use a bitmask to ignore INFO and LOW severity audit results diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c3d5aac7..d9819901d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#12727](https://github.com/MetaMask/metamask-extension/pull/12727): Make toggle buttons keyboard accessible - [#12729](https://github.com/MetaMask/metamask-extension/pull/12729): Swaps: Fix issue with wrapping and unwrapping when an address contains uppercase characters - [#12631](https://github.com/MetaMask/metamask-extension/pull/12631): Fix bug preventing sending high precision decimal amounts of tokens in the send flow +## [10.6.2] +### Fixed +- [#12770](https://github.com/MetaMask/metamask-extension/pull/12770): Fixed display of best quote in swaps quotes modal +- [#12786](https://github.com/MetaMask/metamask-extension/pull/12786): Ensure there is a single localhost option in network selector and that it is clickable + +## [10.6.1] +### Fixed +- [#12573](https://github.com/MetaMask/metamask-extension/pull/12573): Ensure metrics api errors do not impact user experience ## [10.6.0] ### Added @@ -2614,6 +2622,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.7.0...HEAD [10.7.0]: https://github.com/MetaMask/metamask-extension/compare/v10.6.0...v10.7.0 +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.6.2...HEAD +[10.6.2]: https://github.com/MetaMask/metamask-extension/compare/v10.6.1...v10.6.2 +[10.6.1]: https://github.com/MetaMask/metamask-extension/compare/v10.6.0...v10.6.1 [10.6.0]: https://github.com/MetaMask/metamask-extension/compare/v10.5.2...v10.6.0 [10.5.2]: https://github.com/MetaMask/metamask-extension/compare/v10.5.1...v10.5.2 [10.5.1]: https://github.com/MetaMask/metamask-extension/compare/v10.5.0...v10.5.1 diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 28e84d7b0..9e6ecfff7 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -15,6 +15,10 @@ const defaultCaptureException = (err) => { }); }; +const exceptionsToFilter = { + [`You must pass either an "anonymousId" or a "userId".`]: true, +}; + /** * @typedef {import('../../../shared/constants/metametrics').MetaMetricsContext} MetaMetricsContext * @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventPayload} MetaMetricsEventPayload @@ -61,7 +65,13 @@ export default class MetaMetricsController { initState, captureException = defaultCaptureException, }) { - this._captureException = captureException; + this._captureException = (err) => { + // This is a temporary measure. Currently there are errors flooding sentry due to a problem in how we are tracking anonymousId + // We intend on removing this as soon as we understand how to correctly solve that problem. + if (!exceptionsToFilter[err.message]) { + captureException(err); + } + }; const prefState = preferencesStore.getState(); this.chainId = getCurrentChainId(); this.network = getNetworkIdentifier(); diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js index 57cd11b9c..06945e4e6 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.js @@ -28,6 +28,7 @@ import { } from '../../../ui/pages/swaps/swaps.util'; import fetchWithCache from '../../../ui/helpers/utils/fetch-with-cache'; import { MINUTE, SECOND } from '../../../shared/constants/time'; +import { isEqualCaseInsensitive } from '../../../ui/helpers/utils/util'; import { NETWORK_EVENTS } from './network'; // The MAX_GAS_LIMIT is a number that is higher than the maximum gas costs we have observed on any aggregator @@ -91,7 +92,7 @@ export default class SwapsController { networkController, provider, getProviderConfig, - tokenRatesStore, + getTokenRatesState, fetchTradesInfo = defaultFetchTradesInfo, getCurrentChainId, getEIP1559GasFeeEstimates, @@ -105,7 +106,7 @@ export default class SwapsController { this._getEIP1559GasFeeEstimates = getEIP1559GasFeeEstimates; this.getBufferedGasLimit = getBufferedGasLimit; - this.tokenRatesStore = tokenRatesStore; + this.getTokenRatesState = getTokenRatesState; this.pollCount = 0; this.getProviderConfig = getProviderConfig; @@ -610,7 +611,9 @@ export default class SwapsController { } async _findTopQuoteAndCalculateSavings(quotes = {}) { - const tokenConversionRates = this.tokenRatesStore.contractExchangeRates; + const { + contractExchangeRates: tokenConversionRates, + } = this.getTokenRatesState(); const { swapsState: { customGasPrice, customMaxPriorityFeePerGas }, } = this.store.getState(); @@ -734,7 +737,12 @@ export default class SwapsController { decimalAdjustedDestinationAmount, ); - const tokenConversionRate = tokenConversionRates[destinationToken]; + const tokenConversionRate = + tokenConversionRates[ + Object.keys(tokenConversionRates).find((tokenAddress) => + isEqualCaseInsensitive(tokenAddress, destinationToken), + ) + ]; const conversionRateForSorting = tokenConversionRate || 1; const ethValueOfTokens = decimalAdjustedDestinationAmount.times( @@ -777,7 +785,17 @@ export default class SwapsController { isSwapsDefaultTokenAddress( newQuotes[topAggId].destinationToken, chainId, - ) || Boolean(tokenConversionRates[newQuotes[topAggId]?.destinationToken]); + ) || + Boolean( + tokenConversionRates[ + Object.keys(tokenConversionRates).find((tokenAddress) => + isEqualCaseInsensitive( + tokenAddress, + newQuotes[topAggId]?.destinationToken, + ), + ) + ], + ); let savings = null; diff --git a/app/scripts/controllers/swaps.test.js b/app/scripts/controllers/swaps.test.js index 7bc26a5aa..d0f4556ab 100644 --- a/app/scripts/controllers/swaps.test.js +++ b/app/scripts/controllers/swaps.test.js @@ -82,12 +82,12 @@ const MOCK_FETCH_METADATA = { chainId: MAINNET_CHAIN_ID, }; -const MOCK_TOKEN_RATES_STORE = { +const MOCK_TOKEN_RATES_STORE = () => ({ contractExchangeRates: { '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': 2, '0x1111111111111111111111111111111111111111': 0.1, }, -}; +}); const MOCK_GET_PROVIDER_CONFIG = () => ({ type: 'FAKE_NETWORK' }); @@ -161,7 +161,7 @@ describe('SwapsController', function () { networkController: getMockNetworkController(), provider, getProviderConfig: MOCK_GET_PROVIDER_CONFIG, - tokenRatesStore: MOCK_TOKEN_RATES_STORE, + getTokenRatesState: MOCK_TOKEN_RATES_STORE, fetchTradesInfo: fetchTradesInfoStub, getCurrentChainId: getCurrentChainIdStub, getEIP1559GasFeeEstimates: getEIP1559GasFeeEstimatesStub, @@ -211,7 +211,7 @@ describe('SwapsController', function () { networkController, provider, getProviderConfig: MOCK_GET_PROVIDER_CONFIG, - tokenRatesStore: MOCK_TOKEN_RATES_STORE, + getTokenRatesState: MOCK_TOKEN_RATES_STORE, fetchTradesInfo: fetchTradesInfoStub, getCurrentChainId: getCurrentChainIdStub, }); @@ -235,7 +235,7 @@ describe('SwapsController', function () { networkController, provider, getProviderConfig: MOCK_GET_PROVIDER_CONFIG, - tokenRatesStore: MOCK_TOKEN_RATES_STORE, + getTokenRatesState: MOCK_TOKEN_RATES_STORE, fetchTradesInfo: fetchTradesInfoStub, getCurrentChainId: getCurrentChainIdStub, }); @@ -259,7 +259,7 @@ describe('SwapsController', function () { networkController, provider, getProviderConfig: MOCK_GET_PROVIDER_CONFIG, - tokenRatesStore: MOCK_TOKEN_RATES_STORE, + getTokenRatesState: MOCK_TOKEN_RATES_STORE, fetchTradesInfo: fetchTradesInfoStub, getCurrentChainId: getCurrentChainIdStub, }); @@ -816,9 +816,10 @@ describe('SwapsController', function () { .stub(swapsController, '_getERC20Allowance') .resolves(ethers.BigNumber.from(1)); - swapsController.tokenRatesStore = { + swapsController.getTokenRatesState = () => ({ contractExchangeRates: {}, - }; + }); + const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes( MOCK_FETCH_PARAMS, MOCK_FETCH_METADATA, diff --git a/app/scripts/first-time-state.js b/app/scripts/first-time-state.js index 34969b58f..857921889 100644 --- a/app/scripts/first-time-state.js +++ b/app/scripts/first-time-state.js @@ -10,7 +10,15 @@ const initialState = { config: {}, PreferencesController: { - frequentRpcListDetail: [], + frequentRpcListDetail: [ + { + rpcUrl: 'http://localhost:8545', + chainId: '0x539', + ticker: 'ETH', + nickname: 'Localhost 8545', + rpcPrefs: {}, + }, + ], }, }; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 5e1014726..e92640540 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -564,7 +564,7 @@ export default class MetamaskController extends EventEmitter { getProviderConfig: this.networkController.getProviderConfig.bind( this.networkController, ), - tokenRatesStore: this.tokenRatesController.state, + getTokenRatesState: () => this.tokenRatesController.state, getCurrentChainId: this.networkController.getCurrentChainId.bind( this.networkController, ), diff --git a/package.json b/package.json index 35246ce48..14aea8770 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,8 @@ "netmask": "^2.0.1", "pubnub/superagent-proxy": "^3.0.0", "pull-ws": "^3.3.2", - "ws": "^7.4.6" + "ws": "^7.4.6", + "json-schema": "^0.4.0" }, "dependencies": { "3box": "^1.10.2", @@ -294,7 +295,7 @@ "gulp-watch": "^5.0.1", "gulp-zip": "^4.0.0", "history": "^5.0.0", - "improved-yarn-audit": "^2.3.3", + "improved-yarn-audit": "^3.0.0", "jest": "^26.6.3", "jsdom": "^11.2.0", "koa": "^2.7.0", diff --git a/test/e2e/tests/custom-rpc-history.spec.js b/test/e2e/tests/custom-rpc-history.spec.js index 687b64f96..6868808ac 100644 --- a/test/e2e/tests/custom-rpc-history.spec.js +++ b/test/e2e/tests/custom-rpc-history.spec.js @@ -84,7 +84,7 @@ describe('Stores custom RPC history', function () { await rpcUrlInput.clear(); await rpcUrlInput.sendKeys(duplicateRpcUrl); await driver.findElement({ - text: 'This URL is currently used by the localhost network.', + text: 'This URL is currently used by the Localhost 8545 network.', tag: 'h6', }); }, @@ -123,7 +123,8 @@ describe('Stores custom RPC history', function () { await chainIdInput.clear(); await chainIdInput.sendKeys(duplicateChainId); await driver.findElement({ - text: 'This Chain ID is currently used by the localhost network.', + text: + 'This Chain ID is currently used by the Localhost 8545 network.', tag: 'h6', }); }, diff --git a/ui/components/app/dropdowns/network-dropdown.js b/ui/components/app/dropdowns/network-dropdown.js index b446036e6..889ba9fa9 100644 --- a/ui/components/app/dropdowns/network-dropdown.js +++ b/ui/components/app/dropdowns/network-dropdown.js @@ -7,7 +7,10 @@ import classnames from 'classnames'; import Button from '../../ui/button'; import * as actions from '../../../store/actions'; import { openAlert as displayInvalidCustomNetworkAlert } from '../../../ducks/alerts/invalid-custom-network'; -import { NETWORK_TYPE_RPC } from '../../../../shared/constants/network'; +import { + NETWORK_TYPE_RPC, + LOCALHOST_RPC_URL, +} from '../../../../shared/constants/network'; import { isPrefixedFormattedHexString } from '../../../../shared/modules/network.utils'; import ColorIndicator from '../../ui/color-indicator'; @@ -68,7 +71,7 @@ function mapDispatchToProps(dispatch) { }), ); }, - hideTestNetMessage: () => dispatch(actions.hideTestNetMessage()), + hideTestNetMessage: () => actions.hideTestNetMessage(), }; } @@ -149,7 +152,7 @@ class NetworkDropdown extends Component { ); } - renderCustomRpcList(rpcListDetail, provider) { + renderCustomRpcList(rpcListDetail, provider, opts = {}) { const reversedRpcListDetail = rpcListDetail.slice().reverse(); return reversedRpcListDetail.map((entry) => { @@ -157,6 +160,14 @@ class NetworkDropdown extends Component { const isCurrentRpcTarget = provider.type === NETWORK_TYPE_RPC && rpcUrl === provider.rpcUrl; + let borderColor = COLORS.UI2; + if (isCurrentRpcTarget) { + borderColor = COLORS.WHITE; + } + if (opts.isLocalHost) { + borderColor = 'localhost'; + } + return ( ✓ )} rpc.rpcUrl !== LOCALHOST_RPC_URL, + ); + const rpcListDetailForLocalHost = rpcListDetail.filter( + (rpc) => rpc.rpcUrl === LOCALHOST_RPC_URL, + ); const isOpen = this.props.networkDropdownOpen; const { t } = this.context; @@ -340,7 +357,10 @@ class NetworkDropdown extends Component {
{this.renderNetworkEntry('mainnet')} - {this.renderCustomRpcList(rpcListDetail, this.props.provider)} + {this.renderCustomRpcList( + rpcListDetailWithoutLocalHost, + this.props.provider, + )}
diff --git a/ui/components/app/dropdowns/network-dropdown.test.js b/ui/components/app/dropdowns/network-dropdown.test.js index 263460b85..f8546f8b6 100644 --- a/ui/components/app/dropdowns/network-dropdown.test.js +++ b/ui/components/app/dropdowns/network-dropdown.test.js @@ -4,6 +4,7 @@ import thunk from 'redux-thunk'; import Button from '../../ui/button'; import { mountWithRouter } from '../../../../test/lib/render-helpers'; import ColorIndicator from '../../ui/color-indicator'; +import { LOCALHOST_RPC_URL } from '../../../../shared/constants/network'; import NetworkDropdown from './network-dropdown'; import { DropdownMenuItem } from './dropdown'; @@ -55,6 +56,7 @@ describe('Network Dropdown', () => { frequentRpcListDetail: [ { chainId: '0x1a', rpcUrl: 'http://localhost:7545' }, { rpcUrl: 'http://localhost:7546' }, + { rpcUrl: LOCALHOST_RPC_URL, nickname: 'localhost' }, ], }, appState: { diff --git a/ui/pages/settings/networks-tab/networks-tab.constants.js b/ui/pages/settings/networks-tab/networks-tab.constants.js index d5ad83541..26a57c7bd 100644 --- a/ui/pages/settings/networks-tab/networks-tab.constants.js +++ b/ui/pages/settings/networks-tab/networks-tab.constants.js @@ -5,9 +5,6 @@ import { KOVAN, KOVAN_CHAIN_ID, KOVAN_RPC_URL, - LOCALHOST, - LOCALHOST_CHAIN_ID, - LOCALHOST_RPC_URL, MAINNET, MAINNET_CHAIN_ID, MAINNET_RPC_URL, @@ -65,15 +62,6 @@ const defaultNetworksData = [ ticker: 'ETH', blockExplorerUrl: 'https://kovan.etherscan.io', }, - { - labelKey: LOCALHOST, - iconColor: '#29B6AF', - providerType: LOCALHOST, - rpcUrl: LOCALHOST_RPC_URL, - chainId: LOCALHOST_CHAIN_ID, - ticker: 'ETH', - blockExplorerUrl: '', - }, ]; export { defaultNetworksData }; diff --git a/yarn.lock b/yarn.lock index ec0324c22..86862adcb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6825,9 +6825,9 @@ ansi-regex@^4.1.0: integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-styles@^2.2.1: version "2.2.1" @@ -16580,10 +16580,10 @@ import-local@^3.0.2: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" -improved-yarn-audit@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/improved-yarn-audit/-/improved-yarn-audit-2.3.3.tgz#da0be78be4b678c73733066c9ccd21e1958fae8c" - integrity sha512-chZ7zPKGsA+CZeMExNPf9WZhETJLkC+u8cQlkQC9XyPZqQPctn3FavefTjXBXmX3Azin8WcoAbaok1FvjkLf6A== +improved-yarn-audit@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/improved-yarn-audit/-/improved-yarn-audit-3.0.0.tgz#dfb09cea1a3a92c790ea2b4056431f6fb1b99bfa" + integrity sha512-b7CrBYYwMidtPciCBkW62C7vqGjAV10bxcAWHeJvGrltrcMSEnG5I9CQgi14nmAlUKUQiSvpz47Lo3d7Z3Vjcg== imurmurhash@^0.1.4: version "0.1.4" @@ -18901,10 +18901,10 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.2.3, json-schema@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1"