From 4e6d61abcb2bbdc6fbfad623e554f3721a29a6aa Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Tue, 29 Mar 2022 17:41:58 -0400 Subject: [PATCH 01/92] Update What's new screen with Token Detection information (#14124) --- app/_locales/en/messages.json | 22 ++++++ app/images/token-detection.svg | 68 +++++++++++++++++++ shared/notifications/index.js | 33 +++++++++ .../app/whats-new-popup/whats-new-popup.js | 4 ++ ui/selectors/selectors.js | 2 + 5 files changed, 129 insertions(+) create mode 100644 app/images/token-detection.svg diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 95e2c1b45..00361ed49 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2097,6 +2097,28 @@ "notEnoughGas": { "message": "Not Enough Gas" }, + "notifications10ActionText": { + "message": "Visit in settings", + "description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page." + }, + "notifications10DescriptionOne": { + "message": "Improved token detection is currently available on Ethereum Mainnet, Polygon, BSC, and Avalanche networks. More to come!" + }, + "notifications10DescriptionThree": { + "message": "Token detection feature is ON by default. But you can disable it from Settings." + }, + "notifications10DescriptionTwo": { + "message": "We source tokens from third party tokens lists. Tokens listed on more than two token lists will be automatically detected." + }, + "notifications10Title": { + "message": "Improved token detection is here" + }, + "notifications11Description": { + "message": "Tokens can be created by anyone and can have duplicate names. If you see a token appear that you don’t trust or haven’t interacted with - it’s safer to not trust it." + }, + "notifications11Title": { + "message": "Scam and security risks" + }, "notifications1Description": { "message": "MetaMask Mobile users can now swap tokens inside their mobile wallet. Scan the QR code to get the mobile app and start swapping.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." diff --git a/app/images/token-detection.svg b/app/images/token-detection.svg new file mode 100644 index 000000000..c7246e8f7 --- /dev/null +++ b/app/images/token-detection.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shared/notifications/index.js b/shared/notifications/index.js index 38ae68c0a..52e0662a4 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -46,6 +46,18 @@ export const UI_NOTIFICATIONS = { width: '80%', }, }, + 10: { + id: 10, + date: '2022-04-18', + image: { + src: 'images/token-detection.svg', + width: '100%', + }, + }, + 11: { + id: 11, + date: '2022-04-18', + }, }; export const getTranslatedUINoficiations = (t, locale) => { @@ -132,5 +144,26 @@ export const getTranslatedUINoficiations = (t, locale) => { new Date(UI_NOTIFICATIONS[9].date), ), }, + 10: { + ...UI_NOTIFICATIONS[10], + title: t('notifications10Title'), + description: [ + t('notifications10DescriptionOne'), + t('notifications10DescriptionTwo'), + t('notifications10DescriptionThree'), + ], + actionText: t('notifications10ActionText'), + date: new Intl.DateTimeFormat(formattedLocale).format( + new Date(UI_NOTIFICATIONS[10].date), + ), + }, + 11: { + ...UI_NOTIFICATIONS[11], + title: t('notifications11Title'), + description: t('notifications11Description'), + date: new Intl.DateTimeFormat(formattedLocale).format( + new Date(UI_NOTIFICATIONS[11].date), + ), + }, }; }; diff --git a/ui/components/app/whats-new-popup/whats-new-popup.js b/ui/components/app/whats-new-popup/whats-new-popup.js index 51b425b4c..bba492040 100644 --- a/ui/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/components/app/whats-new-popup/whats-new-popup.js @@ -45,6 +45,10 @@ function getActionFunctionById(id, history) { updateViewedNotifications({ 8: true }); history.push(ADVANCED_ROUTE); }, + 10: () => { + updateViewedNotifications({ 10: true }); + history.push(`${ADVANCED_ROUTE}#token-description`); + }, }; return actionFunctions[id]; diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index e0e223d7a..5f2c62691 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -719,6 +719,8 @@ function getAllowedNotificationIds(state) { 7: false, 8: supportsWebHid && currentKeyringIsLedger && currentlyUsingLedgerLive, 9: getIsMainnet(state), + 10: Boolean(process.env.TOKEN_DETECTION_V2), + 11: Boolean(process.env.TOKEN_DETECTION_V2), }; } From 1521e63913083ab057aefcc315bc40378ad37c31 Mon Sep 17 00:00:00 2001 From: Jurriaan Den Toonder <1493561+Fastjur@users.noreply.github.com> Date: Wed, 30 Mar 2022 00:13:00 +0200 Subject: [PATCH 02/92] Change over FeeCard stories to use controls instead of knobs, update props in stories (#13766) This PR changes over the deprecated knobs and actions in `fee-card.stories.js` to controls. I attempted to create a `README.mdx`, but unfortunately the `` refused to render out any of the documentation comments I added to the `FeeCard.propTypes`, thus that has not been included (and no documentation has been added to the propTypes of FeeCard). _Please note:_ currently nested arg types are not possible in StoryBook, but discussions are open: https://github.com/storybookjs/storybook/issues/11486. This means that the StoryBook shows `primaryFee`, `primaryMaxFee`, `secondaryFee` and `secondaryMaxFee`, but these are mapped to the 2 `primaryFee` and `secondaryFee` props of the component. This is visible if the developer clicks on the `Show code` button at the docs page in StoryBook. Signed-off-by: Jurriaan Den Toonder <1493561+Fastjur@users.noreply.github.com> --- ui/pages/swaps/fee-card/fee-card.stories.js | 178 ++++++++++++-------- 1 file changed, 105 insertions(+), 73 deletions(-) diff --git a/ui/pages/swaps/fee-card/fee-card.stories.js b/ui/pages/swaps/fee-card/fee-card.stories.js index 5f2b0e0f2..1175d8f09 100644 --- a/ui/pages/swaps/fee-card/fee-card.stories.js +++ b/ui/pages/swaps/fee-card/fee-card.stories.js @@ -1,15 +1,7 @@ import React from 'react'; -import { action } from '@storybook/addon-actions'; -import { text, boolean, number, object } from '@storybook/addon-knobs'; import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network'; import FeeCard from './fee-card'; -const tokenApprovalTextComponent = ( - - ABC - -); - const containerStyle = { width: '300px', }; @@ -17,83 +9,123 @@ const containerStyle = { export default { title: 'Pages/Swaps/FeeCard', id: __filename, + component: FeeCard, + argTypes: { + primaryFee: { + control: { + type: 'text', + }, + }, + primaryMaxFee: { + control: { + type: 'text', + }, + }, + secondaryFee: { + control: { + type: 'text', + }, + }, + secondaryMaxFee: { + control: { + type: 'text', + }, + }, + onFeeCardMaxRowClick: { + action: 'Clicked max fee row edit link', + }, + hideTokenApprovalRow: { + control: { + type: 'boolean', + }, + }, + tokenApprovalSourceTokenSymbol: { + control: { + type: 'text', + }, + }, + onTokenApprovalClick: { + action: 'Clicked on token approval', + }, + metaMaskFee: { + control: { + type: 'text', + }, + }, + onQuotesClick: { + action: 'Clicked on quotes link', + }, + numberOfQuotes: { + control: { + type: 'number', + }, + }, + chainId: { + control: { + type: 'text', + }, + }, + smartTransactionsOptInStatus: { + control: { + type: 'boolean', + }, + }, + smartTransactionsEnabled: { + control: { + type: 'boolean', + }, + }, + isBestQuote: { + control: { + type: 'boolean', + }, + }, + supportsEIP1559V2: { + control: { + type: 'boolean', + }, + }, + }, + args: { + primaryFee: '1 ETH', + primaryMaxFee: '2 ETH', + secondaryFee: '100 USD', + secondaryMaxFee: '200 USD', + hideTokenApprovalRow: false, + tokenApprovalSourceTokenSymbol: 'ABC', + metaMaskFee: '0.875', + numberOfQuotes: 6, + chainId: MAINNET_CHAIN_ID, + isBestQuote: true, + }, }; -export const WithAllProps = () => { - return ( -
- -
- ); -}; +export const DefaultStory = (args) => { + // Please note, currently nested arg types are not possible, but discussions are open: + // https://github.com/storybookjs/storybook/issues/11486 + const { + primaryFee, + primaryMaxFee, + secondaryFee, + secondaryMaxFee, + ...rest + } = args; -export const WithoutThirdRow = () => { return (
); }; -export const WithOnlyRequiredProps = () => { - return ( -
- -
- ); -}; +DefaultStory.storyName = 'Default'; From 4f1cee4b87b6c3c68e1ef9f4236fbf15d729c24a Mon Sep 17 00:00:00 2001 From: jesseharte <38751829+jesseharte@users.noreply.github.com> Date: Wed, 30 Mar 2022 00:20:19 +0200 Subject: [PATCH 03/92] Change over ImportToken stories to use controls instead of knobs, update props in stories (#14246) --- ui/pages/import-token/README.mdx | 31 +++++ .../import-token/import-token.component.js | 58 +++++++++ ui/pages/import-token/import-token.stories.js | 111 +++++++++++++++++- 3 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 ui/pages/import-token/README.mdx diff --git a/ui/pages/import-token/README.mdx b/ui/pages/import-token/README.mdx new file mode 100644 index 000000000..83daea8ff --- /dev/null +++ b/ui/pages/import-token/README.mdx @@ -0,0 +1,31 @@ +import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; + +import ImportToken from './import-token.component'; + +import testData from '../../../.storybook/test-data'; +import configureStore from '../../store/store'; +const store = configureStore(testData); +const { metamask } = store.getState(); + +export const PersonalAddress = () => {metamask.selectedAddress} + +# ImportToken + +The `ImportToken` component allows a user to import custom tokens in one of two ways: +1. By searching for one +2. By importing one by `Token Contract Address` + + + + + +## Example inputs + +An example input that works, to enable the `Add Custom Token` button is `0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`. + +### Personal address error +To show the personal address detected error, input the address in the `Token Contract Address` field. + +## Component API + + diff --git a/ui/pages/import-token/import-token.component.js b/ui/pages/import-token/import-token.component.js index 33e0a0bb2..647829575 100644 --- a/ui/pages/import-token/import-token.component.js +++ b/ui/pages/import-token/import-token.component.js @@ -36,19 +36,77 @@ class ImportToken extends Component { }; static propTypes = { + /** + * History object of the router. + */ history: PropTypes.object, + + /** + * Set the state of `pendingTokens`, called when adding a token. + */ setPendingTokens: PropTypes.func, + + /** + * The current list of pending tokens to be added. + */ pendingTokens: PropTypes.object, + + /** + * Clear the list of pending tokens. Called when closing the modal. + */ clearPendingTokens: PropTypes.func, + + /** + * The list of already added tokens. + */ tokens: PropTypes.array, + + /** + * The identities/accounts that are currently added to the wallet. + */ identities: PropTypes.object, + + /** + * Boolean flag that shows/hides the search tab. + */ showSearchTab: PropTypes.bool.isRequired, + + /** + * The most recent overview page route, which is 'navigated' to when closing the modal. + */ mostRecentOverviewPage: PropTypes.string.isRequired, + + /** + * The active chainId in use. + */ chainId: PropTypes.string, + + /** + * The rpc preferences to use for the current provider. + */ rpcPrefs: PropTypes.object, + + /** + * The list of tokens available for search. + */ tokenList: PropTypes.object, + + /** + * Boolean flag indicating whether token detection is enabled or not. + * When disabled, shows an information alert in the search tab informing the + * user of the availability of this feature. + */ useTokenDetection: PropTypes.bool, + + /** + * Function called to fetch information about the token standard and + * details, see `actions.js`. + */ getTokenStandardAndDetails: PropTypes.func, + + /** + * The currently selected active address. + */ selectedAddress: PropTypes.string, }; diff --git a/ui/pages/import-token/import-token.stories.js b/ui/pages/import-token/import-token.stories.js index e6b1e089b..1b619804b 100644 --- a/ui/pages/import-token/import-token.stories.js +++ b/ui/pages/import-token/import-token.stories.js @@ -1,15 +1,120 @@ import React from 'react'; -import { boolean } from '@storybook/addon-knobs'; +import { Provider } from 'react-redux'; +import { action } from '@storybook/addon-actions'; +import { DEFAULT_ROUTE } from '../../helpers/constants/routes'; +import configureStore from '../../store/store'; +import testData from '../../../.storybook/test-data'; import ImportToken from './import-token.component'; +import README from './README.mdx'; + +const store = configureStore(testData); +const { metamask } = store.getState(); + +const { + frequentRpcListDetail, + identities, + pendingTokens, + selectedAddress, + tokenList, + tokens, +} = metamask; export default { title: 'Pages/ImportToken', id: __filename, + decorators: [(story) => {story()}], + component: ImportToken, + parameters: { + docs: { + page: README, + }, + }, + argTypes: { + history: { + control: { + type: 'object', + }, + }, + setPendingTokens: { + action: 'setPendingTokens', + }, + pendingTokens: { + control: { + type: 'object', + }, + }, + clearPendingTokens: { + action: 'clearPendingTokens', + }, + tokens: { + control: { + type: 'object', + }, + }, + identities: { + control: { + type: 'object', + }, + }, + showSearchTab: { + control: { + type: 'boolean', + }, + }, + mostRecentOverviewPage: { + control: { + type: 'text', + }, + }, + chainId: { + control: { + type: 'text', + }, + }, + rpcPrefs: { + control: { + type: 'object', + }, + }, + tokenList: { + control: { + type: 'object', + }, + }, + useTokenDetection: { + control: { + type: 'boolean', + }, + }, + getTokenStandardAndDetails: { + action: 'getTokenStandardAndDetails', + }, + selectedAddress: { + control: { + type: 'text', + }, + }, + }, + args: { + history: { + push: action('history.push()'), + }, + pendingTokens, + tokens, + identities, + showSearchTab: true, + mostRecentOverviewPage: DEFAULT_ROUTE, + chainId: frequentRpcListDetail[0].chainId, + rpcPrefs: frequentRpcListDetail[0].rpcPrefs, + tokenList, + useTokenDetection: false, + selectedAddress, + }, }; -export const DefaultStory = () => { - return ; +export const DefaultStory = (args) => { + return ; }; DefaultStory.storyName = 'Default'; From 69d7c51c567060b43e7c461b7dbae745a328ff15 Mon Sep 17 00:00:00 2001 From: Daniel <80175477+dan437@users.noreply.github.com> Date: Wed, 30 Mar 2022 10:02:55 +0200 Subject: [PATCH 04/92] Show STX switch for wrapping / unwrapping (#14225) --- ui/pages/swaps/build-quote/build-quote.js | 4 +- .../slippage-buttons/slippage-buttons.js | 177 +++++++++--------- 2 files changed, 96 insertions(+), 85 deletions(-) diff --git a/ui/pages/swaps/build-quote/build-quote.js b/ui/pages/swaps/build-quote/build-quote.js index bc7f7f39d..c53cd72df 100644 --- a/ui/pages/swaps/build-quote/build-quote.js +++ b/ui/pages/swaps/build-quote/build-quote.js @@ -825,7 +825,8 @@ export default function BuildQuote({ )} ))} - {!isDirectWrappingEnabled && ( + {(smartTransactionsEnabled || + (!smartTransactionsEnabled && !isDirectWrappingEnabled)) && (
{ @@ -837,6 +838,7 @@ export default function BuildQuote({ smartTransactionsOptInStatus={smartTransactionsOptInStatus} setSmartTransactionsOptInStatus={setSmartTransactionsOptInStatus} currentSmartTransactionsError={currentSmartTransactionsError} + isDirectWrappingEnabled={isDirectWrappingEnabled} />
)} diff --git a/ui/pages/swaps/slippage-buttons/slippage-buttons.js b/ui/pages/swaps/slippage-buttons/slippage-buttons.js index 597709bd5..b93ca1e52 100644 --- a/ui/pages/swaps/slippage-buttons/slippage-buttons.js +++ b/ui/pages/swaps/slippage-buttons/slippage-buttons.js @@ -24,6 +24,7 @@ export default function SlippageButtons({ smartTransactionsOptInStatus, setSmartTransactionsOptInStatus, currentSmartTransactionsError, + isDirectWrappingEnabled, }) { const t = useContext(I18nContext); const [customValue, setCustomValue] = useState(() => { @@ -103,94 +104,101 @@ export default function SlippageButtons({
{open && ( <> -
-
-
- {t('swapsMaxSlippage')} + {!isDirectWrappingEnabled && ( +
+
+
+ {t('swapsMaxSlippage')} +
+
- -
- - - - - -
+ + + + +
+ )} {smartTransactionsEnabled && ( Date: Wed, 30 Mar 2022 09:46:37 -0400 Subject: [PATCH 05/92] GasModalPageContainer story: convert knobs and actions to controls / args (#13516) --- .../swaps-gas-customization-modal.stories.js | 117 ++++++++++++++++-- 1 file changed, 107 insertions(+), 10 deletions(-) diff --git a/ui/pages/swaps/swaps-gas-customization-modal/swaps-gas-customization-modal.stories.js b/ui/pages/swaps/swaps-gas-customization-modal/swaps-gas-customization-modal.stories.js index 2503ed2de..4be1e5fcb 100644 --- a/ui/pages/swaps/swaps-gas-customization-modal/swaps-gas-customization-modal.stories.js +++ b/ui/pages/swaps/swaps-gas-customization-modal/swaps-gas-customization-modal.stories.js @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; import { Provider } from 'react-redux'; -import { number } from '@storybook/addon-knobs'; import configureStore from '../../../store/store'; import testData from '../../../../.storybook/test-data'; import { formatETHFee } from '../../../helpers/utils/formatters'; @@ -14,13 +13,112 @@ import { ETH } from '../../../helpers/constants/common'; import { calcGasTotal, isBalanceSufficient } from '../../send/send.utils'; import { conversionLessThan } from '../../../../shared/modules/conversion.utils'; import GasModalPageContainer from './swaps-gas-customization-modal.component'; - // Using Test Data For Redux const store = configureStore(testData); export default { title: 'Pages/Swaps/GasModalPageContainer', id: __filename, + component: GasModalPageContainer, + argTypes: { + sendAmountArg: { + name: 'Send Amount (this should be static)', + control: { type: 'number', min: 0, step: 0.01 }, + }, + walletBalance: { + name: 'Wallet Balance (this should be static)', + control: { type: 'number', min: 0, step: 0.01 }, + }, + averageGasPrice: { + name: 'Average Gas Price', + control: { type: 'number', min: 0, step: 0.01 }, + }, + insufficientBalance: { + table: { + disable: true, + }, + }, + gasPriceButtonGroupProps: { + table: { + disable: true, + }, + }, + infoRowProps: { + table: { + disable: true, + }, + }, + onSubmit: { + table: { + disable: true, + }, + }, + cancelAndClose: { + table: { + disable: true, + }, + }, + showCustomPriceTooLowWarning: { + table: { + disable: true, + }, + }, + disableSave: { + table: { + disable: true, + }, + }, + customGasLimitMessage: { + table: { + disable: true, + }, + }, + usdConversionRate: { + table: { + disable: true, + }, + }, + customGasPrice: { + table: { + disable: true, + }, + }, + customGasLimit: { + table: { + disable: true, + }, + }, + setSwapsCustomizationModalLimit: { + table: { + disable: true, + }, + }, + setSwapsCustomizationModalPrice: { + table: { + disable: true, + }, + }, + customTotalSupplement: { + table: { + disable: true, + }, + }, + gasEstimateLoadingHasFailed: { + table: { + disable: true, + }, + }, + minimumGasLimit: { + table: { + disable: true, + }, + }, + }, + args: { + sendAmountArg: 0.01, + walletBalance: 10, + averageGasPrice: 2, + }, decorators: [(story) => {story()}], }; @@ -38,17 +136,18 @@ const sumHexWEIsToRenderableEth = (hexWEIs, currencySymbol = 'ETH') => { ); }; -export const DefaultStory = () => { - // Send Amount Data +export const DefaultStory = (args) => { + const { sendAmountArg, walletBalance, averageGasPrice } = args; + const hexWei = getWeiHexFromDecimalValue({ - value: String(number('Send Amount (this should be static)', 0.01)), + value: sendAmountArg, fromCurrency: ETH, fromDenomination: ETH, }); // ETH Balance const balanceHexWei = getWeiHexFromDecimalValue({ - value: String(number('Wallet Balance (this should be static)', 1.582717)), + value: walletBalance, fromCurrency: ETH, fromDenomination: ETH, }); @@ -103,11 +202,9 @@ export const DefaultStory = () => { // Check If Gas Price Is Too Low const shouldShowCustomPriceTooLowWarning = () => { - const average = number('Average Gas Price', 2); - const customGasPrice = gasPrice; - if (!customGasPrice || average === undefined) { + if (!customGasPrice || averageGasPrice === undefined) { return false; } @@ -118,7 +215,7 @@ export const DefaultStory = () => { fromDenomination: 'WEI', toDenomination: 'GWEI', }, - { value: average, fromNumericBase: 'dec' }, + { value: averageGasPrice, fromNumericBase: 'dec' }, ); return customPriceRisksSwapFailure; From aac40c75eeee3268e0604b38cc47e55a00d2f943 Mon Sep 17 00:00:00 2001 From: Ariella Vu <20778143+digiwand@users.noreply.github.com> Date: Wed, 30 Mar 2022 11:13:25 -0300 Subject: [PATCH 06/92] TransactionsControllerTest: catch uncaught errors (#14196) --- app/scripts/controllers/transactions/index.js | 58 +++++++++---------- .../controllers/transactions/index.test.js | 24 ++++---- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 964be3bdb..27cf59277 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -361,13 +361,30 @@ export default class TransactionController extends EventEmitter { return transactions[txId]; } - _checkIfTxStatusIsUnapproved(txId) { + /** + * @param {number} txId + * @returns {boolean} + */ + _isUnapprovedTransaction(txId) { return ( this.txStateManager.getTransaction(txId).status === TRANSACTION_STATUSES.UNAPPROVED ); } + /** + * @param {number} txId + * @param {string} fnName + */ + _throwErrorIfNotUnapprovedTx(txId, fnName) { + if (!this._isUnapprovedTransaction(txId)) { + throw new Error( + `TransactionsController: Can only call ${fnName} on an unapproved transaction. + Current tx status: ${this.txStateManager.getTransaction(txId).status}`, + ); + } + } + _updateTransaction(txId, proposedUpdate, note) { const txMeta = this.txStateManager.getTransaction(txId); const updated = merge(txMeta, proposedUpdate); @@ -416,11 +433,7 @@ export default class TransactionController extends EventEmitter { * @returns {TransactionMeta} the txMeta of the updated transaction */ updateEditableParams(txId, { data, from, to, value, gas, gasPrice }) { - if (!this._checkIfTxStatusIsUnapproved(txId)) { - throw new Error( - 'Cannot call updateEditableParams on a transaction that is not in an unapproved state', - ); - } + this._throwErrorIfNotUnapprovedTx(txId, 'updateEditableParams'); const editableParams = { txParams: { @@ -474,11 +487,7 @@ export default class TransactionController extends EventEmitter { userFeeLevel, }, ) { - if (!this._checkIfTxStatusIsUnapproved(txId)) { - throw new Error( - 'Cannot call updateTransactionGasFees on a transaction that is not in an unapproved state', - ); - } + this._throwErrorIfNotUnapprovedTx(txId, 'updateTransactionGasFees'); let txGasFees = { txParams: { @@ -517,11 +526,10 @@ export default class TransactionController extends EventEmitter { txId, { estimatedBaseFee, decEstimatedBaseFee }, ) { - if (!this._checkIfTxStatusIsUnapproved(txId)) { - throw new Error( - 'Cannot call updateTransactionEstimatedBaseFee on a transaction that is not in an unapproved state', - ); - } + this._throwErrorIfNotUnapprovedTx( + txId, + 'updateTransactionEstimatedBaseFee', + ); let txEstimateBaseFees = { estimatedBaseFee, decEstimatedBaseFee }; // only update what is defined @@ -543,11 +551,7 @@ export default class TransactionController extends EventEmitter { * @returns {TransactionMeta} the txMeta of the updated transaction */ updateSwapApprovalTransaction(txId, { type, sourceTokenSymbol }) { - if (!this._checkIfTxStatusIsUnapproved(txId)) { - throw new Error( - 'Cannot call updateSwapApprovalTransaction on a transaction that is not in an unapproved state', - ); - } + this._throwErrorIfNotUnapprovedTx(txId, 'updateSwapApprovalTransaction'); let swapApprovalTransaction = { type, sourceTokenSymbol }; // only update what is defined @@ -589,11 +593,7 @@ export default class TransactionController extends EventEmitter { approvalTxId, }, ) { - if (!this._checkIfTxStatusIsUnapproved(txId)) { - throw new Error( - 'Cannot call updateSwapTransaction on a transaction that is not in an unapproved state', - ); - } + this._throwErrorIfNotUnapprovedTx(txId, 'updateSwapTransaction'); let swapTransaction = { sourceTokenSymbol, @@ -625,11 +625,7 @@ export default class TransactionController extends EventEmitter { * @returns {TransactionMeta} the txMeta of the updated transaction */ updateTransactionUserSettings(txId, { userEditedGasLimit, userFeeLevel }) { - if (!this._checkIfTxStatusIsUnapproved(txId)) { - throw new Error( - 'Cannot call updateTransactionUserSettings on a transaction that is not in an unapproved state', - ); - } + this._throwErrorIfNotUnapprovedTx(txId, 'updateTransactionUserSettings'); let userSettings = { userEditedGasLimit, userFeeLevel }; // only update what is defined diff --git a/app/scripts/controllers/transactions/index.test.js b/app/scripts/controllers/transactions/index.test.js index 0d99759bf..ce0a3339f 100644 --- a/app/scripts/controllers/transactions/index.test.js +++ b/app/scripts/controllers/transactions/index.test.js @@ -2186,10 +2186,10 @@ describe('Transaction Controller', function () { assert.equal(result.userFeeLevel, 'high'); }); - it('throws error if status is not unapproved', function () { + it('should not update and should throw error if status is not type "unapproved"', function () { txStateManager.addTransaction({ id: '4', - status: TRANSACTION_STATUSES.APPROVED, + status: TRANSACTION_STATUSES.DROPPED, metamaskNetworkId: currentNetworkId, txParams: { maxPriorityFeePerGas: '0x007', @@ -2200,14 +2200,18 @@ describe('Transaction Controller', function () { estimateUsed: '0x009', }); - try { - txController.updateTransactionGasFees('4', { maxFeePerGas: '0x0088' }); - } catch (e) { - assert.equal( - e.message, - 'Cannot call updateTransactionGasFees on a transaction that is not in an unapproved state', - ); - } + assert.throws( + () => + txController.updateTransactionGasFees('4', { + maxFeePerGas: '0x0088', + }), + Error, + `TransactionsController: Can only call updateTransactionGasFees on an unapproved transaction. + Current tx status: ${TRANSACTION_STATUSES.DROPPED}`, + ); + + const transaction = txStateManager.getTransaction('4'); + assert.equal(transaction.txParams.maxFeePerGas, '0x008'); }); it('does not update unknown parameters in update method', function () { From 085c9753de7e43f3e53d6f6e963b46567538e2a1 Mon Sep 17 00:00:00 2001 From: ryanml Date: Wed, 30 Mar 2022 10:54:01 -0700 Subject: [PATCH 07/92] Ensure Metafox follows cursor on Fetching quotes screen (#14261) --- ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js b/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js index ea5b10f6e..df97c48b9 100644 --- a/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js +++ b/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js @@ -149,7 +149,6 @@ export default function LoadingSwapsQuotes({ animationEventEmitter={animationEventEmitter.current} width="90" height="90" - followMouse={false} lookAtTarget={midPointTarget} />
From 68f07ce1f7b9d246a519e28a490f077f31f3507d Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Wed, 30 Mar 2022 16:21:27 -0400 Subject: [PATCH 08/92] Token Aggregators component for Tokens Detected page (#14157) --- app/_locales/en/messages.json | 3 ++ ui/components/app/app-components.scss | 1 + .../detected-token-aggregators.js | 50 +++++++++++++++++++ .../detected-token-aggregators.stories.js | 43 ++++++++++++++++ .../detected-token-aggregators.test.js | 43 ++++++++++++++++ .../detected-token-aggregators/index.scss | 13 +++++ 6 files changed, 153 insertions(+) create mode 100644 ui/components/app/detected-token/detected-token-aggregators/detected-token-aggregators.js create mode 100644 ui/components/app/detected-token/detected-token-aggregators/detected-token-aggregators.stories.js create mode 100644 ui/components/app/detected-token/detected-token-aggregators/detected-token-aggregators.test.js create mode 100644 ui/components/app/detected-token/detected-token-aggregators/index.scss diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 00361ed49..35f16c6fb 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1258,6 +1258,9 @@ "message": "From: $1", "description": "$1 is the address to include in the From label. It is typically shortened first using shortenAddress" }, + "fromTokenLists": { + "message": "From token lists: $1" + }, "functionApprove": { "message": "Function: Approve" }, diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 4a49ad110..7e4507d55 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -85,3 +85,4 @@ @import 'advanced-gas-fee-popover/advanced-gas-fee-defaults/index'; @import 'currency-input/index'; @import 'detected-token/detected-token-address/index'; +@import 'detected-token/detected-token-aggregators/index'; diff --git a/ui/components/app/detected-token/detected-token-aggregators/detected-token-aggregators.js b/ui/components/app/detected-token/detected-token-aggregators/detected-token-aggregators.js new file mode 100644 index 000000000..7cb66701a --- /dev/null +++ b/ui/components/app/detected-token/detected-token-aggregators/detected-token-aggregators.js @@ -0,0 +1,50 @@ +import React, { useContext, useState } from 'react'; +import PropTypes from 'prop-types'; +import { I18nContext } from '../../../../contexts/i18n'; + +import Box from '../../../ui/box'; +import Button from '../../../ui/button'; +import Typography from '../../../ui/typography/typography'; +import { + DISPLAY, + FONT_WEIGHT, + TYPOGRAPHY, +} from '../../../../helpers/constants/design-system'; + +const DetectedTokenAggregators = ({ aggregatorsList }) => { + const t = useContext(I18nContext); + const numOfHiddenAggregators = parseInt(aggregatorsList.length, 10) - 2; + const [displayMore, setDisplayMore] = useState(false); + + return ( + + + {t('fromTokenLists', [ + numOfHiddenAggregators > 0 && !displayMore ? ( + + {`${aggregatorsList.slice(0, 2).join(', ')}`} + + + ) : ( + + {`${aggregatorsList.join(', ')}.`} + + ), + ])} + + + ); +}; + +DetectedTokenAggregators.propTypes = { + aggregatorsList: PropTypes.array.isRequired, +}; + +export default DetectedTokenAggregators; diff --git a/ui/components/app/detected-token/detected-token-aggregators/detected-token-aggregators.stories.js b/ui/components/app/detected-token/detected-token-aggregators/detected-token-aggregators.stories.js new file mode 100644 index 000000000..c4c96b684 --- /dev/null +++ b/ui/components/app/detected-token/detected-token-aggregators/detected-token-aggregators.stories.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { DISPLAY } from '../../../../helpers/constants/design-system'; + +import Box from '../../../ui/box'; +import DetectedTokenAggregators from './detected-token-aggregators'; + +export default { + title: 'Components/App/DetectedToken/DetectedTokenAggregators', + id: __filename, + argTypes: { + aggregatorsList: { control: 'array' }, + }, + args: { + aggregatorsList1: [ + 'Aave', + 'Bancor', + 'CMC', + 'Crypto.com', + 'CoinGecko', + '1inch', + 'Paraswap', + 'PMM', + 'Synthetix', + 'Zapper', + 'Zerion', + '0x', + ], + aggregatorsList2: ['Aave', 'Bancor'], + }, +}; + +const Template = (args) => { + return ( + + + + + ); +}; + +export const DefaultStory = Template.bind({}); + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/detected-token/detected-token-aggregators/detected-token-aggregators.test.js b/ui/components/app/detected-token/detected-token-aggregators/detected-token-aggregators.test.js new file mode 100644 index 000000000..27757fb32 --- /dev/null +++ b/ui/components/app/detected-token/detected-token-aggregators/detected-token-aggregators.test.js @@ -0,0 +1,43 @@ +import * as React from 'react'; +import { + renderWithProvider, + screen, + fireEvent, +} from '../../../../../test/jest'; +import configureStore from '../../../../store/store'; + +import DetectedTokenAggregators from './detected-token-aggregators'; + +describe('DetectedTokenAggregators', () => { + const args = { + aggregatorsList: [ + 'Aave', + 'Bancor', + 'CMC', + 'Crypto.com', + 'CoinGecko', + '1inch', + 'Paraswap', + 'PMM', + 'Synthetix', + 'Zapper', + 'Zerion', + '0x', + ], + }; + + it('should render the detected token aggregators', async () => { + const store = configureStore({}); + renderWithProvider(, store); + + expect(screen.getByText('From token lists:')).toBeInTheDocument(); + expect(screen.getByText('Aave, Bancor')).toBeInTheDocument(); + expect(screen.getByText('+ 10 more')).toBeInTheDocument(); + fireEvent.click(screen.getByText('+ 10 more')); + expect( + screen.getByText( + 'Aave, Bancor, CMC, Crypto.com, CoinGecko, 1inch, Paraswap, PMM, Synthetix, Zapper, Zerion, 0x.', + ), + ).toBeInTheDocument(); + }); +}); diff --git a/ui/components/app/detected-token/detected-token-aggregators/index.scss b/ui/components/app/detected-token/detected-token-aggregators/index.scss new file mode 100644 index 000000000..37767a3b7 --- /dev/null +++ b/ui/components/app/detected-token/detected-token-aggregators/index.scss @@ -0,0 +1,13 @@ +.detected-token-aggregators { + .typography { + display: inline; + } + + & &__link { + @include H7; + + padding: 0; + display: inline; + margin-left: 4px; + } +} From 4ac0f821257eef66b6ae1a0dd3b37a3e5d07ab83 Mon Sep 17 00:00:00 2001 From: Olusegun Akintayo Date: Thu, 31 Mar 2022 00:25:49 +0400 Subject: [PATCH 09/92] Add token standard to Token Added event. (#14253) Signed-off-by: Akintayo A. Olusegun --- .../confirm-add-suggested-token/confirm-add-suggested-token.js | 2 ++ ui/pages/confirm-import-token/confirm-import-token.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ui/pages/confirm-add-suggested-token/confirm-add-suggested-token.js b/ui/pages/confirm-add-suggested-token/confirm-add-suggested-token.js index cb59f95d6..31c58c13b 100644 --- a/ui/pages/confirm-add-suggested-token/confirm-add-suggested-token.js +++ b/ui/pages/confirm-add-suggested-token/confirm-add-suggested-token.js @@ -13,6 +13,7 @@ import ZENDESK_URLS from '../../helpers/constants/zendesk-url'; import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; import { getSuggestedAssets } from '../../selectors'; import { rejectWatchAsset, acceptWatchAsset } from '../../store/actions'; +import { TOKEN_STANDARDS } from '../../helpers/constants/common'; function getTokenName(name, symbol) { return name === undefined ? symbol : `${name} (${symbol})`; @@ -120,6 +121,7 @@ const ConfirmAddSuggestedToken = () => { token_decimal_precision: asset.decimals, unlisted: asset.unlisted, source: 'dapp', + token_standard: TOKEN_STANDARDS.ERC20, }, }); }), diff --git a/ui/pages/confirm-import-token/confirm-import-token.js b/ui/pages/confirm-import-token/confirm-import-token.js index 07ab83ef9..3a0a33811 100644 --- a/ui/pages/confirm-import-token/confirm-import-token.js +++ b/ui/pages/confirm-import-token/confirm-import-token.js @@ -13,6 +13,7 @@ import { MetaMetricsContext as NewMetaMetricsContext } from '../../contexts/meta import { getMostRecentOverviewPage } from '../../ducks/history/history'; import { getPendingTokens } from '../../ducks/metamask/metamask'; import { addTokens, clearPendingTokens } from '../../store/actions'; +import { TOKEN_STANDARDS } from '../../helpers/constants/common'; const getTokenName = (name, symbol) => { return name === undefined ? symbol : `${name} (${symbol})`; @@ -43,6 +44,7 @@ const ConfirmImportToken = () => { token_decimal_precision: pendingToken.decimals, unlisted: pendingToken.unlisted, source: pendingToken.isCustom ? 'custom' : 'list', + token_standard: TOKEN_STANDARDS.ERC20, }, }); }); From 8c7b643eac955ccd3fadfa06d24d30d685ceb8c1 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Wed, 30 Mar 2022 16:54:10 -0500 Subject: [PATCH 10/92] Dark Mode: Ensure actionable message button colors are the same color as previously (#14271) --- ui/components/ui/actionable-message/index.scss | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ui/components/ui/actionable-message/index.scss b/ui/components/ui/actionable-message/index.scss index 0bfdda370..e9f3e7f1f 100644 --- a/ui/components/ui/actionable-message/index.scss +++ b/ui/components/ui/actionable-message/index.scss @@ -107,6 +107,11 @@ .actionable-message__action--secondary { text-decoration: underline; } + + button { + background: var(--color-warning-default); + color: var(--color-warning-inverse); + } } &--danger { @@ -120,6 +125,11 @@ .actionable-message__message { text-align: left; } + + button { + background: var(--color-error-default); + color: var(--color-error-inverse); + } } &--success { @@ -128,6 +138,11 @@ &::before { background: var(--color-success-muted); } + + button { + background: var(--color-success-default); + color: var(--color-success-inverse); + } } &--left-aligned { From 9d7f4d20056d07cdd23c24413277614b09271a60 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Wed, 30 Mar 2022 16:54:25 -0500 Subject: [PATCH 11/92] Ensure proper color for swaps edit link (#14273) --- ui/pages/swaps/fee-card/index.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/pages/swaps/fee-card/index.scss b/ui/pages/swaps/fee-card/index.scss index 403b75296..c7ac11f7d 100644 --- a/ui/pages/swaps/fee-card/index.scss +++ b/ui/pages/swaps/fee-card/index.scss @@ -152,7 +152,7 @@ } &__edit-link { - ccolor: var(--color-primary-default); + color: var(--color-primary-default); cursor: pointer; padding-left: 6px; } From 97be10becdb6d82b82bbf85948e4aeda5ea7c988 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Wed, 30 Mar 2022 17:29:59 -0500 Subject: [PATCH 12/92] Dark Mode: Fix colors in toggle button (#14280) --- .../ui/toggle-button/toggle-button.component.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/components/ui/toggle-button/toggle-button.component.js b/ui/components/ui/toggle-button/toggle-button.component.js index 7e5eb252e..c14cdbcef 100644 --- a/ui/components/ui/toggle-button/toggle-button.component.js +++ b/ui/components/ui/toggle-button/toggle-button.component.js @@ -31,18 +31,18 @@ const thumbStyle = { const colors = { activeThumb: { - base: 'var(--color-primary-default)', + base: '#037DD6', }, inactiveThumb: { - base: 'var(--color-icon-default)', + base: '#6A737D', }, active: { - base: 'var(--color-background-default)', - hover: 'var(--color-background-default)', + base: '#F2F4F6', + hover: '#F2F4F6', }, inactive: { - base: 'var(--color-background-alternative)', - hover: 'var(color-background-alternative)', + base: '#F2F4F6', + hover: '#F2F4F6', }, }; From 00025afe4c914be0d2843a7a4043b3cbf8b7fb7a Mon Sep 17 00:00:00 2001 From: David Walsh Date: Wed, 30 Mar 2022 17:42:18 -0500 Subject: [PATCH 13/92] Dark Mode: Remove unwanted background for price quote (#14278) --- ui/pages/swaps/view-quote/index.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/pages/swaps/view-quote/index.scss b/ui/pages/swaps/view-quote/index.scss index 80da6c9b5..33b3c16c8 100644 --- a/ui/pages/swaps/view-quote/index.scss +++ b/ui/pages/swaps/view-quote/index.scss @@ -58,6 +58,10 @@ &.medium, &.high { .actionable-message { + &::before { + background: none; + } + .actionable-message__message { color: inherit; } From 3c2e4e9c9ea1e6eed8761bc1a2dfceca87c223f8 Mon Sep 17 00:00:00 2001 From: Olusegun Akintayo Date: Thu, 31 Mar 2022 11:43:26 +0400 Subject: [PATCH 14/92] Add asset_type to 'Tokens Added' event. (#14252) Signed-off-by: Akintayo A. Olusegun --- .../confirm-add-suggested-token/confirm-add-suggested-token.js | 2 ++ ui/pages/confirm-import-token/confirm-import-token.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ui/pages/confirm-add-suggested-token/confirm-add-suggested-token.js b/ui/pages/confirm-add-suggested-token/confirm-add-suggested-token.js index 31c58c13b..47d8f6acd 100644 --- a/ui/pages/confirm-add-suggested-token/confirm-add-suggested-token.js +++ b/ui/pages/confirm-add-suggested-token/confirm-add-suggested-token.js @@ -14,6 +14,7 @@ import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; import { getSuggestedAssets } from '../../selectors'; import { rejectWatchAsset, acceptWatchAsset } from '../../store/actions'; import { TOKEN_STANDARDS } from '../../helpers/constants/common'; +import { ASSET_TYPES } from '../../../shared/constants/transaction'; function getTokenName(name, symbol) { return name === undefined ? symbol : `${name} (${symbol})`; @@ -122,6 +123,7 @@ const ConfirmAddSuggestedToken = () => { unlisted: asset.unlisted, source: 'dapp', token_standard: TOKEN_STANDARDS.ERC20, + asset_type: ASSET_TYPES.TOKEN, }, }); }), diff --git a/ui/pages/confirm-import-token/confirm-import-token.js b/ui/pages/confirm-import-token/confirm-import-token.js index 3a0a33811..c1df7317e 100644 --- a/ui/pages/confirm-import-token/confirm-import-token.js +++ b/ui/pages/confirm-import-token/confirm-import-token.js @@ -14,6 +14,7 @@ import { getMostRecentOverviewPage } from '../../ducks/history/history'; import { getPendingTokens } from '../../ducks/metamask/metamask'; import { addTokens, clearPendingTokens } from '../../store/actions'; import { TOKEN_STANDARDS } from '../../helpers/constants/common'; +import { ASSET_TYPES } from '../../../shared/constants/transaction'; const getTokenName = (name, symbol) => { return name === undefined ? symbol : `${name} (${symbol})`; @@ -45,6 +46,7 @@ const ConfirmImportToken = () => { unlisted: pendingToken.unlisted, source: pendingToken.isCustom ? 'custom' : 'list', token_standard: TOKEN_STANDARDS.ERC20, + asset_type: ASSET_TYPES.TOKEN, }, }); }); From 9361fab1870bf5ec30721e5c292ad2b0a498754e Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Thu, 31 Mar 2022 09:47:19 -0400 Subject: [PATCH 15/92] Enable Token Detection by default on supported networks (#14002) --- app/scripts/controllers/preferences.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 73e3b7963..2b0fdbb41 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -38,7 +38,7 @@ export default class PreferencesController { // set to true means the dynamic list from the API is being used // set to false will be using the static list from contract-metadata - useTokenDetection: false, + useTokenDetection: Boolean(process.env.TOKEN_DETECTION_V2), useCollectibleDetection: false, openSeaEnabled: false, advancedGasFee: null, From 9f7c4eb658ad53be7ac3ffdfc860d6cf48906cea Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Thu, 31 Mar 2022 09:48:05 -0400 Subject: [PATCH 16/92] Enable Token search functionality on supported networks (#14034) --- app/_locales/en/messages.json | 9 ++ shared/constants/network.js | 3 + .../import-token/import-token.component.js | 133 +++++++++++++----- .../import-token/import-token.container.js | 9 +- ui/pages/import-token/index.scss | 8 ++ ui/selectors/selectors.js | 51 +++++++ 6 files changed, 170 insertions(+), 43 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 35f16c6fb..3739bc1e9 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -733,6 +733,12 @@ "customToken": { "message": "Custom Token" }, + "customTokenWarningInNonTokenDetectionNetwork": { + "message": "Token detection is not available on this network yet. Please import token manually and make sure you trust it. Learn about $1" + }, + "customTokenWarningInTokenDetectionNetwork": { + "message": "Before manually importing a token, make sure you trust it. Learn about $1." + }, "customerSupport": { "message": "customer support" }, @@ -3583,6 +3589,9 @@ "tokenDetection": { "message": "Token detection" }, + "tokenDetectionAlertMessage": { + "message": "Token detection is currently available on $1. $2" + }, "tokenDetectionAnnouncement": { "message": "New! Improved token detection is available on Ethereum Mainnet as an experimental feature. $1" }, diff --git a/shared/constants/network.js b/shared/constants/network.js index 844533a0c..0d644fd6e 100644 --- a/shared/constants/network.js +++ b/shared/constants/network.js @@ -39,6 +39,9 @@ export const KOVAN_DISPLAY_NAME = 'Kovan'; export const MAINNET_DISPLAY_NAME = 'Ethereum Mainnet'; export const GOERLI_DISPLAY_NAME = 'Goerli'; export const LOCALHOST_DISPLAY_NAME = 'Localhost 8545'; +export const BSC_DISPLAY_NAME = 'Binance Smart Chain'; +export const POLYGON_DISPLAY_NAME = 'Polygon'; +export const AVALANCHE_DISPLAY_NAME = 'Avalanche'; const infuraProjectId = process.env.INFURA_PROJECT_ID; export const getRpcUrl = ({ network, excludeProjectId = false }) => diff --git a/ui/pages/import-token/import-token.component.js b/ui/pages/import-token/import-token.component.js index 647829575..c12db1b74 100644 --- a/ui/pages/import-token/import-token.component.js +++ b/ui/pages/import-token/import-token.component.js @@ -12,6 +12,7 @@ import { ADD_COLLECTIBLE_ROUTE, CONFIRM_IMPORT_TOKEN_ROUTE, EXPERIMENTAL_ROUTE, + ADVANCED_ROUTE, } from '../../helpers/constants/routes'; import TextField from '../../components/ui/text-field'; import PageContainer from '../../components/ui/page-container'; @@ -25,6 +26,9 @@ import Button from '../../components/ui/button'; import TokenSearch from './token-search'; import TokenList from './token-list'; +/*eslint-disable prefer-destructuring*/ +const TOKEN_DETECTION_V2 = process.env.TOKEN_DETECTION_V2; + const emptyAddr = '0x0000000000000000000000000000000000000000'; const MIN_DECIMAL_VALUE = 0; @@ -108,6 +112,8 @@ class ImportToken extends Component { * The currently selected active address. */ selectedAddress: PropTypes.string, + isTokenDetectionSupported: PropTypes.bool.isRequired, + networkName: PropTypes.string.isRequired, }; static defaultProps = { @@ -382,6 +388,7 @@ class ImportToken extends Component { } renderCustomTokenForm() { + const { t } = this.context; const { customAddress, customSymbol, @@ -396,7 +403,7 @@ class ImportToken extends Component { collectibleAddressError, } = this.state; - const { chainId, rpcPrefs } = this.props; + const { chainId, rpcPrefs, isTokenDetectionSupported } = this.props; const blockExplorerTokenLink = getTokenTrackerLink( customAddress, chainId, @@ -406,31 +413,61 @@ class ImportToken extends Component { ); const blockExplorerLabel = rpcPrefs?.blockExplorerUrl ? getURLHostName(blockExplorerTokenLink) - : this.context.t('etherscan'); + : t('etherscan'); return (
- - {this.context.t('learnScamRisk')} - , - ])} - type="warning" - withRightButton - useIcon - iconFillColor="var(--color-warning-default)" - /> + {TOKEN_DETECTION_V2 ? ( + + {t('learnScamRisk')} + , + ], + )} + withRightButton + useIcon + iconFillColor={ + isTokenDetectionSupported + ? 'var(--color-warning-default)' + : 'var(--color-info-default)' + } + /> + ) : ( + + {this.context.t('learnScamRisk')} + , + ])} + type="warning" + withRightButton + useIcon + iconFillColor="var(--color-warning-default)" + /> + )} this.handleCustomAddressChange(e.target.value)} @@ -446,14 +483,14 @@ class ImportToken extends Component { label={
- {this.context.t('tokenSymbol')} + {t('tokenSymbol')} {symbolAutoFilled && !forceEditSymbol && (
this.setState({ forceEditSymbol: true })} > - {this.context.t('edit')} + {t('edit')}
)}
@@ -468,7 +505,7 @@ class ImportToken extends Component { /> this.handleCustomDecimalsChange(e.target.value)} @@ -487,13 +524,13 @@ class ImportToken extends Component { variant={TYPOGRAPHY.H7} fontWeight={FONT_WEIGHT.BOLD} > - {this.context.t('tokenDecimalFetchFailed')} + {t('tokenDecimalFetchFailed')} - {this.context.t('verifyThisTokenDecimalOn', [ + {t('verifyThisTokenDecimalOn', [ , - ])} + message={ + TOKEN_DETECTION_V2 + ? t('tokenDetectionAlertMessage', [ + networkName, + , + ]) + : this.context.t('tokenDetectionAnnouncement', [ + , + ]) + } withRightButton useIcon iconFillColor="var(--color-primary-default)" @@ -559,18 +613,19 @@ class ImportToken extends Component { } renderTabs() { + const { t } = this.context; const { showSearchTab } = this.props; const tabs = []; if (showSearchTab) { tabs.push( - + {this.renderSearchToken()} , ); } tabs.push( - + {this.renderCustomTokenForm()} , ); diff --git a/ui/pages/import-token/import-token.container.js b/ui/pages/import-token/import-token.container.js index ae3d97228..b5953c5fa 100644 --- a/ui/pages/import-token/import-token.container.js +++ b/ui/pages/import-token/import-token.container.js @@ -8,7 +8,8 @@ import { import { getMostRecentOverviewPage } from '../../ducks/history/history'; import { getRpcPrefsForCurrentProvider, - getIsMainnet, + getIsTokenDetectionSupported, + getTokenDetectionSupportNetworkByChainId, } from '../../selectors/selectors'; import ImportToken from './import-token.component'; @@ -24,10 +25,8 @@ const mapStateToProps = (state) => { selectedAddress, }, } = state; - const showSearchTabCustomNetwork = - useTokenDetection && Boolean(Object.keys(tokenList).length); const showSearchTab = - getIsMainnet(state) || showSearchTabCustomNetwork || process.env.IN_TEST; + getIsTokenDetectionSupported(state) || process.env.IN_TEST; return { identities, mostRecentOverviewPage: getMostRecentOverviewPage(state), @@ -39,6 +38,8 @@ const mapStateToProps = (state) => { tokenList, useTokenDetection, selectedAddress, + isTokenDetectionSupported: getIsTokenDetectionSupported(state), + networkName: getTokenDetectionSupportNetworkByChainId(state), }; }; const mapDispatchToProps = (dispatch) => { diff --git a/ui/pages/import-token/index.scss b/ui/pages/import-token/index.scss index a6e320750..76558a410 100644 --- a/ui/pages/import-token/index.scss +++ b/ui/pages/import-token/index.scss @@ -60,6 +60,14 @@ margin-top: 0; } + &__close { + color: var(--color-icon-default); + background: none; + flex: 0; + align-self: flex-start; + padding-right: 0; + } + &__collectible-address-error-link { color: var(--color-primary-default); cursor: pointer; diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 5f2c62691..270315831 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -11,6 +11,13 @@ import { OPTIMISM_CHAIN_ID, OPTIMISM_TESTNET_CHAIN_ID, BUYABLE_CHAINS_MAP, + MAINNET_DISPLAY_NAME, + BSC_CHAIN_ID, + POLYGON_CHAIN_ID, + AVALANCHE_CHAIN_ID, + BSC_DISPLAY_NAME, + POLYGON_DISPLAY_NAME, + AVALANCHE_DISPLAY_NAME, } from '../../shared/constants/network'; import { KEYRING_TYPES, @@ -901,3 +908,47 @@ export function getIsAdvancedGasFeeDefault(state) { Boolean(advancedGasFee?.maxBaseFee) && Boolean(advancedGasFee?.priorityFee) ); } + +/** + * @param state + * @returns string e.g. ethereum, bsc or polygon + */ +export const getTokenDetectionSupportNetworkByChainId = (state) => { + const chainId = getCurrentChainId(state); + switch (chainId) { + case MAINNET_CHAIN_ID: + return MAINNET_DISPLAY_NAME; + case BSC_CHAIN_ID: + return BSC_DISPLAY_NAME; + case POLYGON_CHAIN_ID: + return POLYGON_DISPLAY_NAME; + case AVALANCHE_CHAIN_ID: + return AVALANCHE_DISPLAY_NAME; + default: + return ''; + } +}; +/** + * To check for the chainId that supports token detection , + * currently it returns true for Ethereum Mainnet, Polygon, BSC and Avalanche + * + * @param {*} state + * @returns Boolean + */ +export function getIsTokenDetectionSupported(state) { + const chainId = getCurrentChainId(state); + return [ + MAINNET_CHAIN_ID, + BSC_CHAIN_ID, + POLYGON_CHAIN_ID, + AVALANCHE_CHAIN_ID, + ].includes(chainId); +} + +export function getTokenDetectionNoticeDismissed(state) { + return state.metamask.tokenDetectionNoticeDismissed; +} + +export function getTokenDetectionWarningDismissed(state) { + return state.metamask.tokenDetectionWarningDismissed; +} From 4bb25822003315f7275c04d27fd678e445cd2f1b Mon Sep 17 00:00:00 2001 From: George Marshall Date: Thu, 31 Mar 2022 06:58:09 -0700 Subject: [PATCH 17/92] Swaps select quote highlight fix (#14270) --- ui/pages/swaps/select-quote-popover/index.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/pages/swaps/select-quote-popover/index.scss b/ui/pages/swaps/select-quote-popover/index.scss index d221f893c..4ecf938d9 100644 --- a/ui/pages/swaps/select-quote-popover/index.scss +++ b/ui/pages/swaps/select-quote-popover/index.scss @@ -96,8 +96,8 @@ } &--selected { - color: var(--color-text-default); - background: linear-gradient(90deg, var(--color-primary-default) 0%, var(--primary-alternative) 101.32%); + color: var(--color-primary-inverse); + background: linear-gradient(90deg, var(--color-primary-default) 0%, var(--color-primary-alternative) 101.32%); box-shadow: 0 10px 39px rgba(3, 125, 214, 0.15); border-radius: 8px; height: 64px; From 8174aaa6b5c1a72bd152a16da371c48be5270822 Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Thu, 31 Mar 2022 10:02:47 -0400 Subject: [PATCH 18/92] Adjusting the padding in Alert / error state (#13819) --- app/_locales/de/messages.json | 3 - app/_locales/el/messages.json | 3 - app/_locales/en/messages.json | 6 +- app/_locales/es_419/messages.json | 3 - app/_locales/fr/messages.json | 3 - app/_locales/hi/messages.json | 3 - app/_locales/id/messages.json | 3 - app/_locales/ja/messages.json | 3 - app/_locales/ko/messages.json | 3 - app/_locales/pt_BR/messages.json | 3 - app/_locales/ru/messages.json | 3 - app/_locales/tl/messages.json | 3 - app/_locales/tr/messages.json | 3 - app/_locales/vi/messages.json | 3 - app/_locales/zh_CN/messages.json | 3 - .../collectibles-detection-notice.js | 22 ++++--- .../collectibles-detection-notice/index.scss | 26 ++++---- .../app/collectibles-tab/collectibles-tab.js | 60 ++++++++++--------- ui/pages/add-collectible/add-collectible.js | 4 +- ui/pages/home/home.component.js | 18 +++--- ui/pages/home/index.scss | 32 +++++++--- 21 files changed, 95 insertions(+), 115 deletions(-) diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 4c7749a01..a4ee3e2a1 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -1738,9 +1738,6 @@ "newNFTsDetected": { "message": "Neu! NFT-Erkennung" }, - "newNFTsDetectedInfo": { - "message": "Erlaube der MetaMaske, NFTs automatisch von Opensea zu erkennen und in deiner MetaMask Wallet anzuzeigen." - }, "newNetworkAdded": { "message": "“$1” wurde erfolgreich hinzugefügt!" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index f91fc7653..0b14618d9 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -1738,9 +1738,6 @@ "newNFTsDetected": { "message": "Νέο! Εντοπισμός NFT" }, - "newNFTsDetectedInfo": { - "message": "Επιτρέψτε στο MetaMask να ανιχνεύει αυτόματα NFTs από το Opensea και να εμφανίζεται στο πορτοφόλι σας MetaMask." - }, "newNetworkAdded": { "message": "Το “$1” προστέθηκε με επιτυχία!" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 3739bc1e9..1437fb8a5 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2009,12 +2009,12 @@ "newContract": { "message": "New Contract" }, + "newNFTDetectedMessage": { + "message": "Allow MetaMask to automatically detect NFTs from Opensea and display in your wallet." + }, "newNFTsDetected": { "message": "New! NFT detection" }, - "newNFTsDetectedInfo": { - "message": "Allow MetaMask to automatically detect NFTs from Opensea and display in your MetaMask wallet." - }, "newNetworkAdded": { "message": "“$1” was successfully added!" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 759bc10bf..fdcc7c46f 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -1787,9 +1787,6 @@ "newNFTsDetected": { "message": "¡Nuevo! Detección NFT" }, - "newNFTsDetectedInfo": { - "message": "Permitir que MetaMask detecte automáticamente NFT de Opensea y los muestre en su cartera MetaMask." - }, "newNetworkAdded": { "message": "¡\"$1\" se añadió con éxito!" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 69d8a2d5a..9933807df 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -1738,9 +1738,6 @@ "newNFTsDetected": { "message": "Nouveau ! Détection de NFT" }, - "newNFTsDetectedInfo": { - "message": "Cela permet à MetaMask de détecter automatiquement les NFT d’Opensea et de les afficher dans votre portefeuille MetaMask." - }, "newNetworkAdded": { "message": "« $1 » a été ajouté avec succès !" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 69b111159..cf4f05860 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -1738,9 +1738,6 @@ "newNFTsDetected": { "message": "नया! NFT डिटेक्शन" }, - "newNFTsDetectedInfo": { - "message": "MetaMask को Opensea से NFT का स्वचालित रूप से पता लगाने और अपने MetaMask वॉलेट में प्रदर्शित करने की अनुमति दें।" - }, "newNetworkAdded": { "message": "\"$1\" सफलतापूर्वक जोड़ा गया था!" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 720cbd97d..287ef03e7 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -1738,9 +1738,6 @@ "newNFTsDetected": { "message": "Baru! Deteksi NFT" }, - "newNFTsDetectedInfo": { - "message": "Izinkan MetaMask untuk mendeteksi NFT dari Opensea secara otomatis dan menampilkannya di dompet MetaMask Anda." - }, "newNetworkAdded": { "message": "“$1” berhasil ditambahkan!" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 4107b4396..2407e8cc4 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -1738,9 +1738,6 @@ "newNFTsDetected": { "message": "新機能! NFT検出" }, - "newNFTsDetectedInfo": { - "message": "MetaMaskがOpenseからNFTを自動的に検出し、MetaMaskウォレットに表示できるようにします。" - }, "newNetworkAdded": { "message": "「$1」が追加されました!" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 35c9e2c4d..20340caee 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -1738,9 +1738,6 @@ "newNFTsDetected": { "message": "신규! NFT 감지" }, - "newNFTsDetectedInfo": { - "message": "MetaMask가 Opensea에서 자동으로 NFT를 감지하고 MetaMask 지갑에 표시하도록 허용합니다." - }, "newNetworkAdded": { "message": "“$1”가 성공적으로 추가되었습니다!" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index 793a95650..911e931ed 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -1771,9 +1771,6 @@ "newNFTsDetected": { "message": "Novidade! Detecção de NFT" }, - "newNFTsDetectedInfo": { - "message": "Autorize que a MetaMask detecte NFTs automaticamente do Opensea e os exiba na sua carteira MetaMask." - }, "newNetworkAdded": { "message": "“$1” foi adicionado com sucesso!" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index fed0f9550..9acd6789c 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -1738,9 +1738,6 @@ "newNFTsDetected": { "message": "Новинка! Обнаружение NFT" }, - "newNFTsDetectedInfo": { - "message": "Разрешите MetaMask автоматически обнаруживать NFT из Opensea и отображать их в вашем кошельке MetaMask." - }, "newNetworkAdded": { "message": "«$1» успешно добавлен!" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index fa2d0773f..7c4160138 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -1738,9 +1738,6 @@ "newNFTsDetected": { "message": "Bago! Pag-detect ng NFT" }, - "newNFTsDetectedInfo": { - "message": "Payagan ang MetaMask na awtomatikong i-detect ang mga NFT mula sa Opensea at ipakita sa iyong MetaMask wallet." - }, "newNetworkAdded": { "message": "Ang “$1” matagumpay na naidagdag!" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 6633eccba..136d444dc 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -1738,9 +1738,6 @@ "newNFTsDetected": { "message": "Yeni! NFT algılama" }, - "newNFTsDetectedInfo": { - "message": "MetaMask'in otomatik olarak Opensea'den NFT'leri algılamasına ve MetaMask cüzdanınızda görüntülemesine izin verin." - }, "newNetworkAdded": { "message": "\"$1\" başarılı bir şekilde eklendi!" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index d2e38f2f1..ddce0ae1e 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -1738,9 +1738,6 @@ "newNFTsDetected": { "message": "Mới! Phát hiện NFT" }, - "newNFTsDetectedInfo": { - "message": "Cho phép MetaMask tự động phát hiện NFT từ Opensea và hiển thị trong ví MetaMask của bạn." - }, "newNetworkAdded": { "message": "“$1” đã được thêm thành công!" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 94bc07f8f..24bd792bf 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -1738,9 +1738,6 @@ "newNFTsDetected": { "message": "新功能!NFT 检测" }, - "newNFTsDetectedInfo": { - "message": "允许 MetaMask自动检测Opensea 的 NFT,并在您的 MetaMask钱包中显示。" - }, "newNetworkAdded": { "message": "成功添加了“$1”!" }, diff --git a/ui/components/app/collectibles-detection-notice/collectibles-detection-notice.js b/ui/components/app/collectibles-detection-notice/collectibles-detection-notice.js index d2fd732e0..3cf95e716 100644 --- a/ui/components/app/collectibles-detection-notice/collectibles-detection-notice.js +++ b/ui/components/app/collectibles-detection-notice/collectibles-detection-notice.js @@ -20,22 +20,28 @@ export default function CollectiblesDetectionNotice() { const history = useHistory(); return ( - + + + {t('noNFTs')} + + + - + )} -
+ ); } diff --git a/ui/pages/add-collectible/add-collectible.js b/ui/pages/add-collectible/add-collectible.js index c5192cec3..c768403eb 100644 --- a/ui/pages/add-collectible/add-collectible.js +++ b/ui/pages/add-collectible/add-collectible.js @@ -93,7 +93,7 @@ export default function AddCollectible() { }} disabled={disabled} contentComponent={ - + {isMainnet && !useCollectibleDetection && !collectibleDetectionNoticeDismissed ? ( @@ -122,7 +122,7 @@ export default function AddCollectible() { } /> )} - + - + +
+ } /> ) : null} @@ -332,8 +334,8 @@ export default class Home extends PureComponent { type="success" className="home__new-network-notification" message={ -
- + +
+ } /> ) : null} diff --git a/ui/pages/home/index.scss b/ui/pages/home/index.scss index 96d88061d..d36b8447a 100644 --- a/ui/pages/home/index.scss +++ b/ui/pages/home/index.scss @@ -138,24 +138,42 @@ margin-bottom: 50px; } - &__new-network-notification-message { + &__new-network-notification { display: flex; flex-direction: row; align-items: center; - &--icon { + &-icon { margin-right: 8px; + padding-top: 8px; color: var(--color-success-default); } - } - &__close { - color: var(--color-text-default); - background: none; - margin-left: 20px; + &-close { + color: var(--color-icon-default); + background: none; + margin-left: 20px; + } } &__error-message { left: 8px; } + + &__new-nft-notification { + margin-bottom: 24px; + margin-right: 8px; + + &-icon { + margin-right: 10px; + padding-top: 6px; + color: var(--color-success-default); + } + + &-close { + color: var(--color-icon-default); + background: none; + margin-left: 44px; + } + } } From 8184b150cb289a5a1d8e7b280881780762933e5b Mon Sep 17 00:00:00 2001 From: VSaric <92527393+VSaric@users.noreply.github.com> Date: Thu, 31 Mar 2022 16:41:39 +0200 Subject: [PATCH 19/92] Redesign screen/page "List of networks" (#13560) Co-authored-by: George Marshall Co-authored-by: Elliot Winkler --- .storybook/test-data.js | 97 +++++++++++++++ app/_locales/en/messages.json | 8 +- shared/constants/network.js | 7 ++ .../custom-content-search.js | 114 ++++++++++++++++++ .../custom-content-search.stories.js | 23 ++++ .../custom-content-search.test.js | 110 +++++++++++++++++ .../custom-content-search/index.js | 1 + ui/pages/settings/networks-tab/index.scss | 109 +++++++++++++---- .../networks-list-item/networks-list-item.js | 58 +++++++-- .../networks-list/network-list.stories.js | 28 +++++ .../networks-list/networks-list.js | 64 +++++++++- .../networks-list/networks-list.test.js | 2 + .../networks-tab-content.test.js | 10 +- .../settings/networks-tab/networks-tab.js | 7 +- 14 files changed, 598 insertions(+), 40 deletions(-) create mode 100644 ui/pages/settings/networks-tab/custom-content-search/custom-content-search.js create mode 100644 ui/pages/settings/networks-tab/custom-content-search/custom-content-search.stories.js create mode 100644 ui/pages/settings/networks-tab/custom-content-search/custom-content-search.test.js create mode 100644 ui/pages/settings/networks-tab/custom-content-search/index.js create mode 100644 ui/pages/settings/networks-tab/networks-list/network-list.stories.js diff --git a/.storybook/test-data.js b/.storybook/test-data.js index ba5ef0b53..e5e72382f 100644 --- a/.storybook/test-data.js +++ b/.storybook/test-data.js @@ -13,6 +13,103 @@ const state = { protocol: 'https:', url: 'https://metamask.github.io/test-dapp/', }, + networkList: [ + { + blockExplorerUrl: "https://etherscan.io", + chainId: "0x1", + iconColor: 'var(--mainnet)', + isATestNetwork: false, + labelKey: "mainnet", + providerType: "mainnet", + rpcUrl: "https://mainnet.infura.io/v3/", + ticker: "ETH", + viewOnly: true, + }, + { + blockExplorerUrl: "https://ropsten.etherscan.io", + chainId: "0x3", + iconColor: 'var(--ropsten)', + isATestNetwork: true, + labelKey: "ropsten", + providerType: "ropsten", + rpcUrl: "https://ropsten.infura.io/v3/", + ticker: "ETH", + viewOnly: true, + }, + { + blockExplorerUrl: "https://rinkeby.etherscan.io", + chainId: "0x4", + iconColor: 'var(--rinkeby)', + isATestNetwork: true, + labelKey: "rinkeby", + providerType: "rinkeby", + rpcUrl: "https://rinkeby.infura.io/v3/", + ticker: "ETH", + viewOnly: true, + }, + { + blockExplorerUrl: "https://goerli.etherscan.io", + chainId: "0x5", + iconColor: 'var(--goerli)', + isATestNetwork: true, + labelKey: "goerli", + providerType: "goerli", + rpcUrl: "https://goerli.infura.io/v3/", + ticker: "ETH", + viewOnly: true, + }, + { + blockExplorerUrl: "https://kovan.etherscan.io", + chainId: "0x2a", + iconColor: 'var(--kovan)', + isATestNetwork: true, + labelKey: "kovan", + providerType: "kovan", + rpcUrl: "https://kovan.infura.io/v3/", + ticker: "ETH", + viewOnly: true, + }, + { + blockExplorerUrl: "", + chainId: "0x539", + iconColor: 'var(--localhost)', + isATestNetwork: true, + label: "Localhost 8545", + providerType: "rpc", + rpcUrl: "http://localhost:8545", + ticker: "ETH", + }, + { + blockExplorerUrl: "https://bscscan.com", + chainId: "0x38", + iconColor: 'var(--localhost)', + isATestNetwork: false, + label: "Binance Smart Chain", + providerType: "rpc", + rpcUrl: "https://bsc-dataseed.binance.org/", + ticker: "BNB", + }, + { + blockExplorerUrl: "https://cchain.explorer.avax.network/", + chainId: "0xa86a", + iconColor: 'var(--localhost)', + isATestNetwork: false, + label: "Avalanche", + providerType: "rpc", + rpcUrl: "https://api.avax.network/ext/bc/C/rpc", + ticker: "AVAX", + }, + { + blockExplorerUrl: "https://polygonscan.com", + chainId: "0x89", + iconColor: 'var(--localhost)', + isATestNetwork: false, + label: "Polygon", + providerType: "rpc", + rpcUrl: "https://polygon-rpc.com", + ticker: "MATIC", + }, + ], metamask: { tokenList: { '0x6b175474e89094c44da98b954eedeac495271d0f': { diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 1437fb8a5..4ae002665 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -714,6 +714,9 @@ "custom": { "message": "Advanced" }, + "customContentSearch": { + "message": "Search for a previously added network" + }, "customGas": { "message": "Customize Gas" }, @@ -2798,7 +2801,7 @@ "message": "Settings" }, "settingsSearchMatchingNotFound": { - "message": "No matching results found" + "message": "No matching results found." }, "shorthandVersion": { "message": "v$1", @@ -3539,6 +3542,9 @@ "testFaucet": { "message": "Test Faucet" }, + "testNetworks": { + "message": "Test networks" + }, "theme": { "message": "Theme" }, diff --git a/shared/constants/network.js b/shared/constants/network.js index 0d644fd6e..dd3d78684 100644 --- a/shared/constants/network.js +++ b/shared/constants/network.js @@ -130,6 +130,13 @@ export const CHAIN_ID_TO_RPC_URL_MAP = { [LOCALHOST_CHAIN_ID]: LOCALHOST_RPC_URL, }; +export const CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP = { + [MAINNET_CHAIN_ID]: ETH_TOKEN_IMAGE_URL, + [AVALANCHE_CHAIN_ID]: AVAX_TOKEN_IMAGE_URL, + [BSC_CHAIN_ID]: BNB_TOKEN_IMAGE_URL, + [POLYGON_CHAIN_ID]: MATIC_TOKEN_IMAGE_URL, +}; + export const CHAIN_ID_TO_NETWORK_ID_MAP = Object.values( NETWORK_TYPE_TO_ID_MAP, ).reduce((chainIdToNetworkIdMap, { chainId, networkId }) => { diff --git a/ui/pages/settings/networks-tab/custom-content-search/custom-content-search.js b/ui/pages/settings/networks-tab/custom-content-search/custom-content-search.js new file mode 100644 index 000000000..b3f7f8a34 --- /dev/null +++ b/ui/pages/settings/networks-tab/custom-content-search/custom-content-search.js @@ -0,0 +1,114 @@ +import React, { useState, useContext } from 'react'; +import PropTypes from 'prop-types'; +import Fuse from 'fuse.js'; +import InputAdornment from '@material-ui/core/InputAdornment'; +import TextField from '../../../../components/ui/text-field'; +import { I18nContext } from '../../../../contexts/i18n'; +import SearchIcon from '../../../../components/ui/search-icon'; + +export default function CustomContentSearch({ + onSearch, + error, + networksList, + searchQueryInput, +}) { + const t = useContext(I18nContext); + const [searchIconColor, setSearchIconColor] = useState( + 'var(--color-icon-muted)', + ); + + const networksListArray = Object.values(networksList); + const networksSearchFuse = new Fuse(networksListArray, { + shouldSort: true, + threshold: 0.2, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 1, + keys: ['label', 'labelKey'], + }); + + const handleSearch = async (searchQuery) => { + if (searchQuery === '') { + setSearchIconColor('var(--color-icon-muted)'); + } else { + setSearchIconColor('var(--color-icon-default)'); + } + + const fuseSearchResult = networksSearchFuse.search(searchQuery); + const results = searchQuery ? [...fuseSearchResult] : networksListArray; + await onSearch({ searchQuery, results }); + }; + + const renderStartAdornment = () => { + return ( + + + + ); + }; + + const renderEndAdornment = () => { + return ( + <> + {searchQueryInput && ( + handleSearch('')} + > + + + )} + + ); + }; + + return ( + handleSearch(e.target.value)} + error={error} + fullWidth + autoFocus + autoComplete="off" + classes={{ + inputRoot: 'networks-tab__networks-list__custom-search-network', + }} + startAdornment={renderStartAdornment()} + endAdornment={renderEndAdornment()} + /> + ); +} + +CustomContentSearch.propTypes = { + /** + * The function searches the list of networks depending on + * the entered parameter and returns the entire list of + * networks when the user clicks on 'X' on the search tab + */ + onSearch: PropTypes.func, + /** + * An error message is displayed when a user searches for a specific + * network on the search tab and that network does not exist + * in the networks list + */ + error: PropTypes.string, + /** + * The list of networks available for search. + */ + networksList: PropTypes.array, + /** + * Search for a specific network(s) by label or labelKey + */ + searchQueryInput: PropTypes.string, +}; diff --git a/ui/pages/settings/networks-tab/custom-content-search/custom-content-search.stories.js b/ui/pages/settings/networks-tab/custom-content-search/custom-content-search.stories.js new file mode 100644 index 000000000..5ad9ca377 --- /dev/null +++ b/ui/pages/settings/networks-tab/custom-content-search/custom-content-search.stories.js @@ -0,0 +1,23 @@ +import React from 'react'; +import testData from '../../../../../.storybook/test-data'; +import CustomContentSearch from './custom-content-search'; + +export default { + title: 'Pages/Settings/NetworksTab/CustomContentSearch', + id: __filename, + argTypes: { + error: { + control: 'text', + }, + searchQueryInput: { + control: 'text', + }, + onSearch: { + action: 'onSearch', + }, + }, +}; + +export const CustomContentSearchComponent = (args) => { + return ; +}; diff --git a/ui/pages/settings/networks-tab/custom-content-search/custom-content-search.test.js b/ui/pages/settings/networks-tab/custom-content-search/custom-content-search.test.js new file mode 100644 index 000000000..ab569b183 --- /dev/null +++ b/ui/pages/settings/networks-tab/custom-content-search/custom-content-search.test.js @@ -0,0 +1,110 @@ +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import Fuse from 'fuse.js'; +import configureStore from '../../../../store/store'; +import { renderWithProvider } from '../../../../../test/lib/render-helpers'; +import testData from '../../../../../.storybook/test-data'; +import CustomContentSearch from './custom-content-search'; + +function renderComponent({ componentProps = {} } = {}) { + const store = configureStore({}); + return renderWithProvider(, store); +} + +describe('CustomContentSearch', () => { + it('should render custom content search correctly', () => { + const onSearch = jest.fn(); + const wrapper = renderComponent({ + componentProps: { onSearch, networksList: testData.networkList }, + }); + expect(wrapper.getByTestId('search-networks')).toBeDefined(); + }); + + it('should check placeholder text in TextField input', () => { + const onSearch = jest.fn(); + const wrapper = renderComponent({ + componentProps: { onSearch, networksList: testData.networkList }, + }); + const { getByPlaceholderText } = wrapper; + expect( + getByPlaceholderText('Search for a previously added network'), + ).toBeInTheDocument(); + }); + + it('re-render the same component with different props', () => { + const onSearch = jest.fn(); + const { rerender } = render( + , + ); + const input = screen.getByTestId('search-networks'); + expect(input.value).toBe(''); + rerender( + , + ); + expect(input.value).toBe('Polygon'); + }); + + it('should call onSearch prop with input value', () => { + const onSearch = jest.fn(); + const wrapper = renderComponent({ + componentProps: { + onSearch, + networksList: [], + searchQueryInput: 'Avalanche', + }, + }); + const input = wrapper.getByTestId('search-networks'); + fireEvent.change(input, { target: { value: 'Polygon' } }); + expect(input.value).toBe('Avalanche'); + }); + + it('should check if error is shown if search does not return any network from the list', () => { + const onSearch = jest.fn(); + const networksSearchFuse = new Fuse(testData.networkList, { + keys: ['label', 'labelKey'], + }); + const fuseSearchResult = networksSearchFuse.search('Optimism'); + const wrapper = renderComponent({ + componentProps: { + onSearch, + networksList: testData.networkList, + searchQueryInput: 'Optimism', + error: 'No matching results found.', + }, + }); + const input = wrapper.getByTestId('search-networks'); + expect(fuseSearchResult).toHaveLength(0); + fireEvent.change(input, { + target: { error: 'No matching results found.' }, + }); + expect(input.error).toBe('No matching results found.'); + }); + + it('should check if error is not shown if search return some network from the list', () => { + const onSearch = jest.fn(); + const networksSearchFuse = new Fuse(testData.networkList, { + keys: ['label', 'labelKey'], + }); + const fuseSearchResult = networksSearchFuse.search('ropsten'); + const wrapper = renderComponent({ + componentProps: { + onSearch, + networksList: testData.networkList, + searchQueryInput: 'Avalanche', + error: '', + }, + }); + const input = wrapper.getByTestId('search-networks'); + expect(fuseSearchResult).toHaveLength(1); + fireEvent.change(input, { target: { error: '' } }); + expect(input.error).toBe(''); + }); +}); diff --git a/ui/pages/settings/networks-tab/custom-content-search/index.js b/ui/pages/settings/networks-tab/custom-content-search/index.js new file mode 100644 index 000000000..35209b817 --- /dev/null +++ b/ui/pages/settings/networks-tab/custom-content-search/index.js @@ -0,0 +1 @@ +export { default } from './custom-content-search'; diff --git a/ui/pages/settings/networks-tab/index.scss b/ui/pages/settings/networks-tab/index.scss index 53c083971..6c57a4b5d 100644 --- a/ui/pages/settings/networks-tab/index.scss +++ b/ui/pages/settings/networks-tab/index.scss @@ -1,10 +1,46 @@ +@use "design-system"; + .networks-tab { + &__imageclose { + cursor: pointer; + color: var(--color-icon-default); + } + &__content { display: flex; height: 100%; - max-width: 739px; + max-width: 779px; justify-content: space-between; + &__custom-image { + border: 1px solid var(--color-border-default); + margin-inline-start: 8px; + } + + &__check-icon { + margin-inline-end: 10px; + color: var(--color-success-default); + + &__transparent { + color: transparent; + width: 16px; + margin-inline-end: 10px; + } + } + + &__icon-with-fallback { + padding: 0 1px 2px 2px; + color: var(--color-primary-inverse); // TODO: design-tokens needs network colors + margin-inline-start: 8px; + + @each $variant, $color in design-system.$color-map { + &--color-#{$variant} { + background: var($color); + color: var(--color-primary-inverse); // TODO: design-tokens needs network colors + } + } + } + @media screen and (max-width: $break-small) { margin-top: 0; flex-direction: column; @@ -52,7 +88,9 @@ flex-direction: column; justify-content: space-between; max-height: 465px; + max-width: 400px; margin-top: 24px; + padding-inline-start: 24px; .page-container__footer { border-top: none; @@ -74,6 +112,7 @@ align-items: center; width: 90%; margin-top: 10px; + padding: 0; } } @@ -103,8 +142,38 @@ &__networks-list { flex: 0.5 0 auto; - max-width: 343px; - margin-top: 24px; + max-width: 350px; + border-right: 1px solid var(--color-border-default); + + &__custom-search-network { + margin-top: 24px; + } + + .MuiInput-input { + font-size: 14px; + + @media screen and (max-width: $break-small) { + font-size: 12px; + } + } + + .MuiTextField-root { + padding-inline-end: 16px; + + #search-networks-helper-text { + color: var(--color-text-alternative); + } + + @media screen and (max-width: $break-small) { + padding: 0 24px 0 24px; + } + } + + &__label { + @media screen and (max-width: $break-small) { + margin-inline-start: 58px; + } + } @media screen and (max-width: $break-small) { flex: 1; @@ -149,9 +218,10 @@ &__networks-list-item { display: flex; - padding: 13px 0 13px 17px; + padding: 12px 24px 12px 0; position: relative; align-items: center; + width: 311px; .color-indicator { &:hover { @@ -159,13 +229,12 @@ } @media screen and (max-width: $break-small) { - margin: 0 4px 0 10px; + margin: 0 4px 0 20px; } } @media screen and (max-width: $break-small) { - padding: 20px 23px 21px 17px; - border-bottom: 1px solid var(--color-border-default); + padding: 12px 0 12px 24px; max-width: 351px; } } @@ -191,35 +260,31 @@ } svg { - margin-left: 10px; + margin-inline-start: 15px; padding-top: 3px; } - } - - &__networks-list-arrow { - display: none; @media screen and (max-width: $break-small) { - display: block; - right: 10px; - cursor: pointer; - position: absolute; - margin: 0 5px; - - [dir='rtl'] & { - transform: rotate(180deg); - } + color: var(--color-text-default); } } &__networks-list-name--selected { font-weight: bold; color: var(--color-text-default); + + @media screen and (max-width: $break-small) { + font-weight: normal; + color: var(--color-text-default); + } } &__networks-list-name--disabled { - font-weight: 300; color: var(--color-text-muted); + + @media screen and (max-width: $break-small) { + color: var(--color-text-default); + } } &__network-form-footer { diff --git a/ui/pages/settings/networks-tab/networks-list-item/networks-list-item.js b/ui/pages/settings/networks-tab/networks-list-item/networks-list-item.js index ed1b0dd74..5ee11bf71 100644 --- a/ui/pages/settings/networks-tab/networks-list-item/networks-list-item.js +++ b/ui/pages/settings/networks-tab/networks-list-item/networks-list-item.js @@ -4,16 +4,19 @@ import classnames from 'classnames'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { useI18nContext } from '../../../../hooks/useI18nContext'; -import { NETWORK_TYPE_RPC } from '../../../../../shared/constants/network'; -import { SIZES } from '../../../../helpers/constants/design-system'; -import ColorIndicator from '../../../../components/ui/color-indicator'; +import { + NETWORK_TYPE_RPC, + CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP, +} from '../../../../../shared/constants/network'; import LockIcon from '../../../../components/ui/lock-icon'; -import IconCaretRight from '../../../../components/ui/icon/icon-caret-right'; +import IconCheck from '../../../../components/ui/icon/icon-check'; import { NETWORKS_FORM_ROUTE } from '../../../../helpers/constants/routes'; import { setSelectedSettingsRpcUrl } from '../../../../store/actions'; import { getEnvironmentType } from '../../../../../app/scripts/lib/util'; import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../shared/constants/app'; import { getProvider } from '../../../../selectors'; +import Identicon from '../../../../components/ui/identicon'; +import UrlIcon from '../../../../components/ui/url-icon'; import { handleHooksSettingsRefs } from '../../../../helpers/utils/settings-search'; @@ -22,6 +25,8 @@ const NetworksListItem = ({ networkIsSelected, selectedRpcUrl, networkIndex, + setSearchQuery, + setSearchedNetworks, }) => { const t = useI18nContext(); const history = useHistory(); @@ -45,6 +50,9 @@ const NetworksListItem = ({ (listItemUrlIsProviderUrl || listItemTypeIsProviderNonRpcType); const displayNetworkListItemAsSelected = listItemNetworkIsSelected || listItemNetworkIsCurrentProvider; + const isCurrentRpcTarget = + listItemUrlIsProviderUrl || listItemTypeIsProviderNonRpcType; + const settingsRefs = useRef(); useEffect(() => { @@ -57,17 +65,46 @@ const NetworksListItem = ({ key={`settings-network-list-item:${rpcUrl}`} className="networks-tab__networks-list-item" onClick={() => { + setSearchQuery(''); + setSearchedNetworks([]); dispatch(setSelectedSettingsRpcUrl(rpcUrl)); if (!isFullScreen) { history.push(NETWORKS_FORM_ROUTE); } }} > - + {isCurrentRpcTarget ? ( + + ) : ( +
+ )} + {network.chainId in CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP ? ( + + ) : ( + !network.isATestNetwork && ( + + ) + )} + {network.isATestNetwork && ( + + )}
)}
- ); }; @@ -91,6 +127,8 @@ NetworksListItem.propTypes = { networkIsSelected: PropTypes.bool, selectedRpcUrl: PropTypes.string, networkIndex: PropTypes.number, + setSearchQuery: PropTypes.func, + setSearchedNetworks: PropTypes.func, }; export default NetworksListItem; diff --git a/ui/pages/settings/networks-tab/networks-list/network-list.stories.js b/ui/pages/settings/networks-tab/networks-list/network-list.stories.js new file mode 100644 index 000000000..ae54ead9d --- /dev/null +++ b/ui/pages/settings/networks-tab/networks-list/network-list.stories.js @@ -0,0 +1,28 @@ +import React from 'react'; +import testData from '../../../../../.storybook/test-data'; +import NetworksList from './networks-list'; + +export default { + title: 'Pages/Settings/NetworksTab/NetworksList', + id: __filename, + argTypes: { + networkDefaultedToProvider: { + control: 'boolean', + }, + networkIsSelected: { + control: 'boolean', + }, + networksToRender: { + control: 'array', + }, + }, + args: { + networkDefaultedToProvider: false, + networkIsSelected: false, + networksToRender: testData.networkList, + }, +}; + +export const NetworksListComponent = (args) => { + return ; +}; diff --git a/ui/pages/settings/networks-tab/networks-list/networks-list.js b/ui/pages/settings/networks-tab/networks-list/networks-list.js index fa91ca798..fc3fbe5c4 100644 --- a/ui/pages/settings/networks-tab/networks-list/networks-list.js +++ b/ui/pages/settings/networks-tab/networks-list/networks-list.js @@ -1,6 +1,13 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import CustomContentSearch from '../custom-content-search'; +import Typography from '../../../../components/ui/typography'; +import { + COLORS, + TYPOGRAPHY, +} from '../../../../helpers/constants/design-system'; import NetworksListItem from '../networks-list-item'; const NetworksList = ({ @@ -9,6 +16,20 @@ const NetworksList = ({ networkDefaultedToProvider, selectedRpcUrl, }) => { + const t = useI18nContext(); + const [searchedNetworks, setSearchedNetworks] = useState([]); + const [searchQuery, setSearchQuery] = useState(''); + const searchedNetworksToRender = + searchedNetworks.length === 0 && searchQuery === '' + ? networksToRender + : searchedNetworks; + const searchedNetworksToRenderThatAreNotTestNetworks = searchedNetworksToRender.filter( + (network) => !network.isATestNetwork, + ); + const searchedNetworksToRenderThatAreTestNetworks = searchedNetworksToRender.filter( + (network) => network.isATestNetwork, + ); + return (
- {networksToRender.map((network, index) => ( + { + setSearchedNetworks(newResults); + setSearchQuery(newSearchQuery); + }} + error={ + searchedNetworksToRender.length === 0 + ? t('settingsSearchMatchingNotFound') + : null + } + networksList={networksToRender} + searchQueryInput={searchQuery} + /> + {searchedNetworksToRenderThatAreNotTestNetworks.map((network, index) => ( + + ))} + {searchQuery === '' && ( + + {t('testNetworks')} + + )} + {searchedNetworksToRenderThatAreTestNetworks.map((network, index) => ( ))}
diff --git a/ui/pages/settings/networks-tab/networks-list/networks-list.test.js b/ui/pages/settings/networks-tab/networks-list/networks-list.test.js index fcb04e7be..2abf5623a 100644 --- a/ui/pages/settings/networks-tab/networks-list/networks-list.test.js +++ b/ui/pages/settings/networks-tab/networks-list/networks-list.test.js @@ -25,6 +25,7 @@ const renderComponent = (props) => { const defaultNetworks = defaultNetworksData.map((network) => ({ ...network, viewOnly: true, + isATestNetwork: true, })); const props = { @@ -32,6 +33,7 @@ const props = { networkIsSelected: false, networksToRender: defaultNetworks, selectedRpcUrl: 'http://localhost:8545', + isATestNetwork: true, }; describe('NetworksList Component', () => { diff --git a/ui/pages/settings/networks-tab/networks-tab-content/networks-tab-content.test.js b/ui/pages/settings/networks-tab/networks-tab-content/networks-tab-content.test.js index dd357da5a..479b9f0df 100644 --- a/ui/pages/settings/networks-tab/networks-tab-content/networks-tab-content.test.js +++ b/ui/pages/settings/networks-tab/networks-tab-content/networks-tab-content.test.js @@ -26,6 +26,7 @@ const renderComponent = (props) => { const defaultNetworks = defaultNetworksData.map((network) => ({ ...network, viewOnly: true, + isATestNetwork: true, })); const props = { @@ -40,13 +41,16 @@ const props = { blockExplorerUrl: '', viewOnly: false, rpcPrefs: {}, + isATestNetwork: true, }, shouldRenderNetworkForm: true, }; describe('NetworksTabContent Component', () => { it('should render networks tab content correctly', async () => { - const { queryByText, getByDisplayValue } = renderComponent(props); + const { queryByText, getByDisplayValue, getAllByText } = renderComponent( + props, + ); expect(queryByText('Ethereum Mainnet')).toBeInTheDocument(); expect(queryByText('Ropsten Test Network')).toBeInTheDocument(); @@ -68,9 +72,7 @@ describe('NetworksTabContent Component', () => { getByDisplayValue(props.selectedNetwork.chainId), ).toBeInTheDocument(); expect(getByDisplayValue(props.selectedNetwork.ticker)).toBeInTheDocument(); - expect( - getByDisplayValue(props.selectedNetwork.blockExplorerUrl), - ).toBeInTheDocument(); + expect(getAllByText(props.selectedNetwork.blockExplorerUrl)).toBeDefined(); fireEvent.change(getByDisplayValue(props.selectedNetwork.label), { target: { value: 'LocalHost 8545' }, diff --git a/ui/pages/settings/networks-tab/networks-tab.js b/ui/pages/settings/networks-tab/networks-tab.js index df7f052ac..71028c3cc 100644 --- a/ui/pages/settings/networks-tab/networks-tab.js +++ b/ui/pages/settings/networks-tab/networks-tab.js @@ -16,7 +16,10 @@ import { getNetworksTabSelectedRpcUrl, getProvider, } from '../../../selectors'; -import { NETWORK_TYPE_RPC } from '../../../../shared/constants/network'; +import { + NETWORK_TYPE_RPC, + TEST_CHAINS, +} from '../../../../shared/constants/network'; import { defaultNetworksData } from './networks-tab.constants'; import NetworksTabContent from './networks-tab-content'; import NetworksForm from './networks-form'; @@ -25,6 +28,7 @@ import NetworksFormSubheader from './networks-tab-subheader'; const defaultNetworks = defaultNetworksData.map((network) => ({ ...network, viewOnly: true, + isATestNetwork: TEST_CHAINS.includes(network.chainId), })); const NetworksTab = ({ addNewNetwork }) => { @@ -50,6 +54,7 @@ const NetworksTab = ({ addNewNetwork }) => { chainId: rpc.chainId, ticker: rpc.ticker, blockExplorerUrl: rpc.rpcPrefs?.blockExplorerUrl || '', + isATestNetwork: TEST_CHAINS.includes(rpc.chainId), }; }); From 5b9a8a295ea8bee2ad4aa8aca144e5b1e7929054 Mon Sep 17 00:00:00 2001 From: George Marshall Date: Thu, 31 Mar 2022 08:11:23 -0700 Subject: [PATCH 20/92] Updating design-tokens to 1.5.1 (#14286) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 83524c3d7..759414cd3 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "@material-ui/core": "^4.11.0", "@metamask/contract-metadata": "^1.31.0", "@metamask/controllers": "^27.0.0", - "@metamask/design-tokens": "^1.4.2", + "@metamask/design-tokens": "^1.5.1", "@metamask/eth-ledger-bridge-keyring": "^0.10.0", "@metamask/eth-token-tracker": "^4.0.0", "@metamask/etherscan-link": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index 5971498be..ab5f2c9d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2797,10 +2797,10 @@ web3 "^0.20.7" web3-provider-engine "^16.0.3" -"@metamask/design-tokens@^1.4.2": - version "1.4.4" - resolved "https://registry.yarnpkg.com/@metamask/design-tokens/-/design-tokens-1.4.4.tgz#85a80f0ba5ff34a595bd1d879dbf4219c0fbfe7a" - integrity sha512-OzlUv3GSBbmVlO4EcFrkK2/InxaBMH31O2ncVabJvySc/HbhpXMEm7ZtowPqxlBI05V0WdVxSv/0MZtRkbIyXA== +"@metamask/design-tokens@^1.5.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@metamask/design-tokens/-/design-tokens-1.5.1.tgz#723f10bc5fe03ce14d47b1ad6190a835df62745a" + integrity sha512-HLXpuzQGnVPZHOvHpzOVQoe/1mvjNOTNxvAgR1na3BAUiO3NhnUxhYE2RzV0rpbc3UUUGbjVB+dceMZ4FtFfRw== "@metamask/eslint-config-jest@^9.0.0": version "9.0.0" From e50ef0c787749cec5158034308f4491b6eb319a2 Mon Sep 17 00:00:00 2001 From: Rob Dawson Date: Thu, 31 Mar 2022 10:10:02 -0700 Subject: [PATCH 21/92] =?UTF-8?q?M=C4=AFgrated=20list=20item=20stories=20f?= =?UTF-8?q?rom=20addon=20knobs=20to=20use=20controls.=20(#13627)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * mįgrated list item stories from addon knobs to use controls. * Updates Co-authored-by: georgewrmarshall --- .../ui/list-item/list-item.stories.js | 86 +++++++++++++------ 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/ui/components/ui/list-item/list-item.stories.js b/ui/components/ui/list-item/list-item.stories.js index e56586b0e..bcd5e2f1b 100644 --- a/ui/components/ui/list-item/list-item.stories.js +++ b/ui/components/ui/list-item/list-item.stories.js @@ -1,6 +1,5 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { text } from '@storybook/addon-knobs'; import Send from '../icon/send-icon.component'; import Interaction from '../icon/interaction-icon.component'; import Approve from '../icon/approve-icon.component'; @@ -12,6 +11,29 @@ import ListItem from './list-item.component'; export default { title: 'Components/UI/ListItem', id: __filename, + argTypes: { + title: { + control: 'text', + }, + subtitle: { + control: 'text', + }, + primaryCurrency: { + control: 'text', + }, + secondaryCurrency: { + control: 'text', + }, + className: { + control: 'text', + }, + }, + args: { + title: 'Send DAI', + subtitle: 'Sept 20 · To: 00X4...3058', + primaryCurrency: '2 ETH', + secondaryCurrency: '70 USD', + }, }; function Currencies({ primary, secondary }) { @@ -31,34 +53,46 @@ Currencies.propTypes = { const okColor = 'var(--color-primary-default)'; const failColor = 'var(--color-error-default'; -export const SendComponent = () => ( +export const SendComponent = (args) => ( } titleIcon={} - title={text('title', 'Send DAI')} - className="list-item" - subtitle={text('subtitle', 'Sept 20 · To: 00X4...3058')} + title={args.title} + subtitle={args.subtitle} + className={args.className} rightContent={ } >
- +
); -export const PendingComponent = () => ( +SendComponent.argTypes = { + secondaryButtonText: { + control: 'text', + defaultValue: 'Speed Up', + }, + cancelButtonText: { + control: 'text', + defaultValue: 'Cancel', + }, +}; + +export const PendingComponent = (args) => ( } - title={text('title', 'Hatch Turtles')} - className="list-item" + className={args.className} subtitleStatus={ @@ -67,41 +101,39 @@ export const PendingComponent = () => ( ·{' '} } - subtitle={text('subtitle', 'Turtlefarm.com')} rightContent={ } /> ); -export const ApproveComponent = () => ( +export const ApproveComponent = (args) => ( } - title={text('title', 'Approve spend limit')} - className="list-item" - subtitle={text('subtitle', 'Sept 20 · oxuniverse.com')} + className={args.className} rightContent={ } /> ); -export const ReceiveComponent = () => ( +export const ReceiveComponent = (args) => ( } - title={text('title', 'Hatch Turtles')} - className="list-item" - subtitle={text('subtitle', 'Sept 20 · From: 00X4...3058')} + className={args.className} rightContent={ } /> From 59131f5da5a914a9bec6c14dc484cde923ccf771 Mon Sep 17 00:00:00 2001 From: filipsekulic Date: Thu, 31 Mar 2022 19:10:19 +0200 Subject: [PATCH 22/92] Deleted the old metametrics.js (#14292) --- .storybook/metametrics.js | 8 -- ui/contexts/metametrics.js | 152 ------------------------------------- ui/pages/index.js | 26 +++---- 3 files changed, 9 insertions(+), 177 deletions(-) delete mode 100644 ui/contexts/metametrics.js diff --git a/.storybook/metametrics.js b/.storybook/metametrics.js index 387b0d467..dca27aadd 100644 --- a/.storybook/metametrics.js +++ b/.storybook/metametrics.js @@ -1,8 +1,4 @@ import React from 'react'; -import { - MetaMetricsProvider, - LegacyMetaMetricsProvider, -} from '../ui/contexts/metametrics'; import { MetaMetricsProvider as NewMetaMetricsProvider, LegacyMetaMetricsProvider as NewLegacyMetaMetricsProvider, @@ -10,15 +6,11 @@ import { const MetaMetricsProviderStorybook = (props) => ( - - {props.children} - - ); export default MetaMetricsProviderStorybook \ No newline at end of file diff --git a/ui/contexts/metametrics.js b/ui/contexts/metametrics.js deleted file mode 100644 index 869cb84e2..000000000 --- a/ui/contexts/metametrics.js +++ /dev/null @@ -1,152 +0,0 @@ -import React, { - Component, - createContext, - useEffect, - useCallback, - useState, -} from 'react'; -import { useSelector } from 'react-redux'; -import PropTypes from 'prop-types'; -import { useHistory } from 'react-router-dom'; -import { captureException } from '@sentry/browser'; - -import { - getAccountType, - getNumberOfAccounts, - getNumberOfTokens, -} from '../selectors/selectors'; -import { getSendAsset } from '../ducks/send'; -import { txDataSelector } from '../selectors/confirm-transaction'; -import { getEnvironmentType } from '../../app/scripts/lib/util'; -import { trackMetaMetricsEvent } from '../store/actions'; -import { getNativeCurrency } from '../ducks/metamask/metamask'; -import { ASSET_TYPES } from '../../shared/constants/transaction'; - -export const MetaMetricsContext = createContext(() => { - captureException( - Error( - `MetaMetrics context function was called from a react node that is not a descendant of a MetaMetrics context provider`, - ), - ); -}); - -export function MetaMetricsProvider({ children }) { - const txData = useSelector(txDataSelector) || {}; - const environmentType = getEnvironmentType(); - const activeAsset = useSelector(getSendAsset); - const nativeAssetSymbol = useSelector(getNativeCurrency); - const accountType = useSelector(getAccountType); - const confirmTransactionOrigin = txData.origin; - const numberOfTokens = useSelector(getNumberOfTokens); - const numberOfAccounts = useSelector(getNumberOfAccounts); - const history = useHistory(); - const [state, setState] = useState(() => ({ - currentPath: new URL(window.location.href).pathname, - previousPath: '', - })); - - const { currentPath } = state; - - useEffect(() => { - const unlisten = history.listen(() => - setState((prevState) => ({ - currentPath: new URL(window.location.href).pathname, - previousPath: prevState.currentPath, - })), - ); - // remove this listener if the component is no longer mounted - return unlisten; - }, [history]); - - const metricsEvent = useCallback( - (config = {}, overrides = {}) => { - const { eventOpts = {} } = config; - const referrer = confirmTransactionOrigin - ? { url: confirmTransactionOrigin } - : undefined; - const page = { - path: currentPath, - }; - return trackMetaMetricsEvent( - { - event: eventOpts.name, - category: eventOpts.category, - properties: { - action: eventOpts.action, - number_of_tokens: numberOfTokens, - number_of_accounts: numberOfAccounts, - active_currency: - activeAsset.type === ASSET_TYPES.NATIVE - ? nativeAssetSymbol - : activeAsset?.details?.symbol, - account_type: accountType, - is_new_visit: config.is_new_visit, - // the properties coming from this key will not match our standards for - // snake_case on properties, and they may be redundant and/or not in the - // proper location (origin not as a referrer, for example). This is a temporary - // solution to not lose data, and the entire event system will be reworked in - // forthcoming PRs to deprecate the old Matomo events in favor of the new schema. - ...config.customVariables, - }, - page, - referrer, - environmentType, - }, - { - isOptIn: config.isOptIn, - excludeMetaMetricsId: - eventOpts.excludeMetaMetricsId ?? - overrides.excludeMetaMetricsId ?? - false, - metaMetricsId: config.metaMetricsId, - matomoEvent: true, - flushImmediately: config.flushImmediately, - }, - ); - }, - [ - accountType, - currentPath, - confirmTransactionOrigin, - activeAsset, - nativeAssetSymbol, - numberOfTokens, - numberOfAccounts, - environmentType, - ], - ); - - return ( - - {children} - - ); -} - -MetaMetricsProvider.propTypes = { children: PropTypes.node }; - -export class LegacyMetaMetricsProvider extends Component { - static propTypes = { - children: PropTypes.node, - }; - - static defaultProps = { - children: undefined, - }; - - static contextType = MetaMetricsContext; - - static childContextTypes = { - metricsEvent: PropTypes.func, - }; - - getChildContext() { - return { - metricsEvent: this.context, - }; - } - - render() { - return this.props.children; - } -} diff --git a/ui/pages/index.js b/ui/pages/index.js index 3033e2946..05dc5072e 100644 --- a/ui/pages/index.js +++ b/ui/pages/index.js @@ -4,10 +4,6 @@ import { Provider } from 'react-redux'; import { HashRouter } from 'react-router-dom'; import * as Sentry from '@sentry/browser'; import { I18nProvider, LegacyI18nProvider } from '../contexts/i18n'; -import { - MetaMetricsProvider, - LegacyMetaMetricsProvider, -} from '../contexts/metametrics'; import { MetaMetricsProvider as NewMetaMetricsProvider, LegacyMetaMetricsProvider as NewLegacyMetaMetricsProvider, @@ -45,19 +41,15 @@ class Index extends PureComponent { return ( - - - - - - - - - - - - - + + + + + + + + + ); From aef2541e57da92645a5b7880deef6193f06fbd60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E5=AE=A6=E6=88=90?= Date: Fri, 1 Apr 2022 02:30:15 +0800 Subject: [PATCH 23/92] Convert UrlIcon knobs and actions to controls / args (#13981) Co-authored-by: George Marshall Co-authored-by: georgewrmarshall --- ui/components/ui/url-icon/url-icon.stories.js | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/ui/components/ui/url-icon/url-icon.stories.js b/ui/components/ui/url-icon/url-icon.stories.js index ed9144fec..6a5f09b82 100644 --- a/ui/components/ui/url-icon/url-icon.stories.js +++ b/ui/components/ui/url-icon/url-icon.stories.js @@ -1,10 +1,32 @@ import React from 'react'; -import { text } from '@storybook/addon-knobs'; import UrlIcon from './url-icon'; export default { title: 'Components/UI/UrlIcon', id: __filename, + argType: { + name: { control: 'text' }, + url: { control: 'text' }, + className: { control: 'text' }, + fallbackClassName: { control: 'text' }, + }, + args: { + name: 'AST', + url: 'AST.png', + className: '', + fallbackClassName: '', + }, +}; + +export const DefaultStory = (args) => { + return ( + + ); }; export const AST = () => { @@ -14,14 +36,3 @@ export const AST = () => { export const BAT = () => { return ; }; - -export const CustomProps = () => { - return ( - - ); -}; From cb1890d928dc4a83de0387913599b1b21d77bb55 Mon Sep 17 00:00:00 2001 From: Pieter Tolsma Date: Thu, 31 Mar 2022 20:31:31 +0200 Subject: [PATCH 24/92] Convert knobs and actions to controls/args for CountdownTimer (#14222) Co-authored-by: Pieter Tolsma --- ui/pages/swaps/countdown-timer/README.mdx | 16 ++++ .../swaps/countdown-timer/countdown-timer.js | 28 ++++++ .../countdown-timer.stories.js | 92 ++++++++----------- 3 files changed, 81 insertions(+), 55 deletions(-) create mode 100644 ui/pages/swaps/countdown-timer/README.mdx diff --git a/ui/pages/swaps/countdown-timer/README.mdx b/ui/pages/swaps/countdown-timer/README.mdx new file mode 100644 index 000000000..b0904c880 --- /dev/null +++ b/ui/pages/swaps/countdown-timer/README.mdx @@ -0,0 +1,16 @@ +import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; + +import CountdownTimer from '.'; + +# CountdownTimer + +A CountdownTimer displays a timer that ticks down, communicating to the user the time left to perform some aciton. + + + + + +## Component API + + + diff --git a/ui/pages/swaps/countdown-timer/countdown-timer.js b/ui/pages/swaps/countdown-timer/countdown-timer.js index b34301ebb..02dc67ce2 100644 --- a/ui/pages/swaps/countdown-timer/countdown-timer.js +++ b/ui/pages/swaps/countdown-timer/countdown-timer.js @@ -113,10 +113,38 @@ export default function CountdownTimer({ } CountdownTimer.propTypes = { + /** + * Unix timestamp that indicates the time at which this timer has started + * running. + */ timeStarted: PropTypes.number, + + /** + * Boolean indicating whether to display only the time (`true`) or to also + * display a label (`false`), given by the `labelKey` parameter. + */ timeOnly: PropTypes.bool, + + /** + * The duration of this timer in milliseconds. + */ timerBase: PropTypes.number, + + /** + * The time at which this timer should turn red, indicating it has almost run + * out of time. Given in the format `mm:ss`. + */ warningTime: PropTypes.string, + + /** + * The key of the label to display next to the timer, defined in + * `app/_locales/`. + */ labelKey: PropTypes.string, + + /** + * The key of the label to display in the tooltip when hovering over the info + * icon, defined in `app/_locales/`. + */ infoTooltipLabelKey: PropTypes.string, }; diff --git a/ui/pages/swaps/countdown-timer/countdown-timer.stories.js b/ui/pages/swaps/countdown-timer/countdown-timer.stories.js index bdb4d062c..69394f045 100644 --- a/ui/pages/swaps/countdown-timer/countdown-timer.stories.js +++ b/ui/pages/swaps/countdown-timer/countdown-timer.stories.js @@ -1,66 +1,48 @@ import React from 'react'; -import { number } from '@storybook/addon-knobs'; import CountdownTimer from './countdown-timer'; +import README from './README.mdx'; export default { title: 'Pages/Swaps/CountdownTimer', id: __filename, + component: CountdownTimer, + parameters: { + docs: { + page: README, + }, + }, + argTypes: { + timeStarted: { + type: 'number', + }, + timeOnly: { + type: 'boolean', + }, + timerBase: { + type: 'number', + }, + labelKey: { + type: 'string', + }, + infoTooltipLabelKey: { + type: 'string', + }, + warningTime: { + type: 'string', + }, + }, + args: { + timeStarted: Date.now(), + timeOnly: false, + timerBase: 20000, + labelKey: 'disconnectPrompt', + infoTooltipLabelKey: 'disconnectAllAccountsConfirmationDescription', + warningTime: '0:15', + }, }; -const getTimeStartedFromDecrimentSeconds = (seconds) => - Date.now() - seconds * 1000; - -export const DefaultStory = () => { - const timeStartedSecondDecriment = number( - 'Set timeStarted to curren time minus X seconds', - 10, - ); - - return ( - - ); +export const DefaultStory = (args) => { + return ; }; DefaultStory.storyName = 'Default'; - -export const CustomTimerBase = () => { - const timeStartedSecondDecriment = number( - 'Set timeStarted to curren time minus X seconds', - 10, - ); - - return ( - - ); -}; - -// Label keys used in below stories are just for demonstration purposes -export const WithLabelInfoTooltipAndWarning = () => { - const timeStartedSecondDecriment = number( - 'Set timeStarted to curren time minus X seconds', - 0, - ); - - return ( - - ); -}; From d2c59624634a5de9342043e0e284092437f6d5c1 Mon Sep 17 00:00:00 2001 From: PeterYinusa <53189696+PeterYinusa@users.noreply.github.com> Date: Thu, 31 Mar 2022 20:11:07 +0100 Subject: [PATCH 25/92] add/switch chain e2e tests (#14275) * add and switch chain e2e tests * update test names --- test/e2e/tests/chain-interactions.spec.js | 114 ++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 test/e2e/tests/chain-interactions.spec.js diff --git a/test/e2e/tests/chain-interactions.spec.js b/test/e2e/tests/chain-interactions.spec.js new file mode 100644 index 000000000..c1879ccef --- /dev/null +++ b/test/e2e/tests/chain-interactions.spec.js @@ -0,0 +1,114 @@ +const { strict: assert } = require('assert'); +const { convertToHexValue, withFixtures } = require('../helpers'); + +describe('Chain Interactions', function () { + it('should add the XDAI chain and not switch the network', async function () { + const ganacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: convertToHexValue(25000000000000000000), + }, + ], + }; + await withFixtures( + { + dapp: true, + fixtures: 'connected-state', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // trigger add chain confirmation + await driver.openNewPage('http://127.0.0.1:8080/'); + await driver.clickElement('#addEthereumChain'); + await driver.waitUntilXWindowHandles(3); + const windowHandles = await driver.getAllWindowHandles(); + const extension = windowHandles[0]; + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ); + + // verify chain details + const [networkName, networkUrl, chainId] = await driver.findElements( + '.definition-list dd', + ); + assert.equal(await networkName.getText(), 'xDAI Chain'); + assert.equal(await networkUrl.getText(), 'https://dai.poa.network'); + assert.equal(await chainId.getText(), '100'); + + // approve add chain, cancel switch chain + await driver.clickElement({ text: 'Approve', tag: 'button' }); + await driver.clickElement({ text: 'Cancel', tag: 'button' }); + + // switch to extension + await driver.waitUntilXWindowHandles(2); + await driver.switchToWindow(extension); + + // verify networks + const networkDisplay = await driver.findElement('.network-display'); + await networkDisplay.click(); + assert.equal(await networkDisplay.getText(), 'Localhost 8545'); + const xDaiChain = await driver.findElements({ + text: 'xDAI Chain', + tag: 'span', + }); + assert.ok(xDaiChain.length, 1); + }, + ); + }); + + it('should add the XDAI chain and switch the network', async function () { + const ganacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: convertToHexValue(25000000000000000000), + }, + ], + }; + await withFixtures( + { + dapp: true, + fixtures: 'connected-state', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // trigger add chain confirmation + await driver.openNewPage('http://127.0.0.1:8080/'); + await driver.clickElement('#addEthereumChain'); + await driver.waitUntilXWindowHandles(3); + const windowHandles = await driver.getAllWindowHandles(); + const extension = windowHandles[0]; + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ); + + // approve and switch chain + await driver.clickElement({ text: 'Approve', tag: 'button' }); + await driver.clickElement({ text: 'Switch network', tag: 'button' }); + + // switch to extension + await driver.waitUntilXWindowHandles(2); + await driver.switchToWindow(extension); + + // verify current network + const networkDisplay = await driver.findElement('.network-display'); + assert.equal(await networkDisplay.getText(), 'xDAI Chain'); + }, + ); + }); +}); From f088db99a33b07af92072aec8e9d3d21028dd477 Mon Sep 17 00:00:00 2001 From: Ariella Vu <20778143+digiwand@users.noreply.github.com> Date: Thu, 31 Mar 2022 16:21:01 -0300 Subject: [PATCH 26/92] MetaMetrics: add segment.identify (#14195) * MetaMetrics: add segment.identify * MetaMetrics: rm non-existent typedef * MetaMetrics: WIP segment.identify pt. 2 * Revert "MetaMetrics: WIP segment.identify pt. 2" This reverts commit 8126de4dff9312aab2275cba81d0432bfcdb097b. * MetaMetrics: add identify tests * MetaMetricsController: update identify w/ fix * MetaMetricsController: fix identify tests * MetaMetricsController#identify: warn instead of throw * MetaMetrics: transform date into ISO string * MetaMetricsController: alphabetize test * MetaMetricsController: restore asset in test * MetaMetrics: rn traits -> userTraits * MetaMetrics: restore trackEvent location * update to follow analytics-node api * MetaMetricsController: update tests + @link * MetaMetrics: destimation -> destination Co-authored-by: brad-decker --- app/scripts/controllers/metametrics.js | 123 +++++++++++++++++++- app/scripts/controllers/metametrics.test.js | 86 ++++++++++++++ 2 files changed, 208 insertions(+), 1 deletion(-) diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 5207eda11..a3fd4eead 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -281,6 +281,30 @@ export default class MetaMetricsController { this.store.updateState({ fragments }); } + /** + * Calls this._identify with validated metaMetricsId and user traits if user is participating + * in the MetaMetrics analytics program + * + * @param {Object} userTraits + */ + identify(userTraits) { + const { metaMetricsId, participateInMetaMetrics } = this.state; + + if (!participateInMetaMetrics || !metaMetricsId || !userTraits) { + return; + } + if (typeof userTraits !== 'object') { + console.warn( + `MetaMetricsController#identify: userTraits parameter must be an object. Received type: ${typeof userTraits}`, + ); + return; + } + + const allValidTraits = this._buildValidTraits(userTraits); + + this._identify(allValidTraits); + } + /** * Setter for the `participateInMetaMetrics` property * @@ -434,7 +458,7 @@ export default class MetaMetricsController { handleMetaMaskStateUpdate(newState) { const userTraits = this._buildUserTraitsObject(newState); if (userTraits) { - // this.identify(userTraits); + this.identify(userTraits); } } @@ -535,6 +559,103 @@ export default class MetaMetricsController { return null; } + /** + * Returns a new object of all valid user traits. For dates, we transform them into ISO-8601 timestamp strings. + * + * @see {@link https://segment.com/docs/connections/spec/common/#timestamps} + * @param {Object} userTraits + * @returns {Object} + */ + _buildValidTraits(userTraits) { + return Object.entries(userTraits).reduce((validTraits, [key, value]) => { + if (this._isValidTraitDate(value)) { + validTraits[key] = value.toISOString(); + } else if (this._isValidTrait(value)) { + validTraits[key] = value; + } else { + console.warn( + `MetaMetricsController: "${key}" value is not a valid trait type`, + ); + } + return validTraits; + }, {}); + } + + /** + * Calls segment.identify with given user traits + * + * @see {@link https://segment.com/docs/connections/sources/catalog/libraries/server/node/#identify} + * @private + * @param {Object} userTraits + */ + _identify(userTraits) { + const { metaMetricsId } = this.state; + + if (!userTraits || Object.keys(userTraits).length === 0) { + console.warn('MetaMetricsController#_identify: No userTraits found'); + return; + } + + try { + this.segment.identify({ + userId: metaMetricsId, + traits: userTraits, + }); + } catch (err) { + this._captureException(err); + } + } + + /** + * Validates the trait value. Segment accepts any data type. We are adding validation here to + * support data types for our Segment destination(s) e.g. MixPanel + * + * @param {*} value + * @returns {boolean} + */ + _isValidTrait(value) { + const type = typeof value; + + return ( + type === 'string' || + type === 'boolean' || + type === 'number' || + this._isValidTraitArray(value) || + this._isValidTraitDate(value) + ); + } + + /** + * Segment accepts any data type value. We have special logic to validate arrays. + * + * @param {*} value + * @returns {boolean} + */ + _isValidTraitArray = (value) => { + return ( + Array.isArray(value) && + (value.every((element) => { + return typeof element === 'string'; + }) || + value.every((element) => { + return typeof element === 'boolean'; + }) || + value.every((element) => { + return typeof element === 'number'; + })) + ); + }; + + /** + * Returns true if the value is an accepted date type + * + * @param {*} value + * @returns {boolean} + */ + _isValidTraitDate = (value) => { + return Object.prototype.toString.call(value) === '[object Date]'; + }; + /** * Perform validation on the payload and update the id type to use before * sending to Segment. Also examines the options to route and handle the diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 3c3461aa3..9d5819f2d 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -23,6 +23,20 @@ const FAKE_CHAIN_ID = '0x1338'; const LOCALE = 'en_US'; const TEST_META_METRICS_ID = '0xabc'; +const MOCK_TRAITS = { + test_boolean: true, + test_string: 'abc', + test_number: 123, + test_bool_array: [true, true, false], + test_string_array: ['test', 'test', 'test'], + test_boolean_array: [1, 2, 3], +}; + +const MOCK_INVALID_TRAITS = { + test_null: null, + test_array_multi_types: [true, 'a', 1], +}; + const DEFAULT_TEST_CONTEXT = { app: { name: 'MetaMask Extension', version: VERSION }, page: METAMETRICS_BACKGROUND_PAGE_OBJECT, @@ -216,6 +230,78 @@ describe('MetaMetricsController', function () { }); }); + describe('identify', function () { + it('should call segment.identify for valid traits if user is participating in metametrics', async function () { + const metaMetricsController = getMetaMetricsController({ + participateInMetaMetrics: true, + metaMetricsId: TEST_META_METRICS_ID, + }); + const mock = sinon.mock(segment); + + mock + .expects('identify') + .once() + .withArgs({ userId: TEST_META_METRICS_ID, traits: MOCK_TRAITS }); + + metaMetricsController.identify({ + ...MOCK_TRAITS, + ...MOCK_INVALID_TRAITS, + }); + mock.verify(); + }); + + it('should transform date type traits into ISO-8601 timestamp strings', async function () { + const metaMetricsController = getMetaMetricsController({ + participateInMetaMetrics: true, + metaMetricsId: TEST_META_METRICS_ID, + }); + const mock = sinon.mock(segment); + + const mockDate = new Date(); + const mockDateISOString = mockDate.toISOString(); + + mock + .expects('identify') + .once() + .withArgs({ + userId: TEST_META_METRICS_ID, + traits: { + test_date: mockDateISOString, + }, + }); + + metaMetricsController.identify({ + test_date: mockDate, + }); + mock.verify(); + }); + + it('should not call segment.identify if user is not participating in metametrics', function () { + const metaMetricsController = getMetaMetricsController({ + participateInMetaMetrics: false, + }); + const mock = sinon.mock(segment); + + mock.expects('identify').never(); + + metaMetricsController.identify(MOCK_TRAITS); + mock.verify(); + }); + + it('should not call segment.identify if there are no valid traits to identify', async function () { + const metaMetricsController = getMetaMetricsController({ + participateInMetaMetrics: true, + metaMetricsId: TEST_META_METRICS_ID, + }); + const mock = sinon.mock(segment); + + mock.expects('identify').never(); + + metaMetricsController.identify(MOCK_INVALID_TRAITS); + mock.verify(); + }); + }); + describe('setParticipateInMetaMetrics', function () { it('should update the value of participateInMetaMetrics', function () { const metaMetricsController = getMetaMetricsController({ From 8edeb9a2811854665441d11231fd1182ff38f9c1 Mon Sep 17 00:00:00 2001 From: George Marshall Date: Thu, 31 Mar 2022 13:11:06 -0700 Subject: [PATCH 27/92] Fix/14230 dark mode fixes (#14305) * Dark mode fixes * removing unused icon svg --- app/images/warning.svg | 8 --- .../app/app-header/app-header.component.js | 7 +-- .../app/app-header/app-header.stories.js | 49 +++++++++++++++++++ ui/components/app/app-header/index.scss | 11 ----- ui/components/ui/page-container/index.scss | 3 +- ui/css/base-styles.scss | 11 +++-- ui/pages/keychains/reveal-seed.js | 6 +-- ui/pages/keychains/reveal-seed.stories.js | 22 +++++++++ 8 files changed, 81 insertions(+), 36 deletions(-) delete mode 100644 app/images/warning.svg create mode 100644 ui/components/app/app-header/app-header.stories.js create mode 100644 ui/pages/keychains/reveal-seed.stories.js diff --git a/app/images/warning.svg b/app/images/warning.svg deleted file mode 100644 index bcccab618..000000000 --- a/app/images/warning.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/ui/components/app/app-header/app-header.component.js b/ui/components/app/app-header/app-header.component.js index 91ef1fc46..274c4ed2e 100644 --- a/ui/components/app/app-header/app-header.component.js +++ b/ui/components/app/app-header/app-header.component.js @@ -97,7 +97,6 @@ export default class AppHeader extends PureComponent { render() { const { history, - isUnlocked, hideNetworkIndicator, disableNetworkIndicator, disabled, @@ -105,11 +104,7 @@ export default class AppHeader extends PureComponent { } = this.props; return ( -
+
; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/app-header/index.scss b/ui/components/app/app-header/index.scss index 0277ea16c..a5906af7f 100644 --- a/ui/components/app/app-header/index.scss +++ b/ui/components/app/app-header/index.scss @@ -16,17 +16,6 @@ @media screen and (min-width: $break-large) { height: 75px; justify-content: center; - - &--back-drop { - &::after { - content: ''; - position: absolute; - width: 100%; - height: 32px; - background: var(--color-background-alternative); - bottom: -32px; - } - } } &__metafox-logo { diff --git a/ui/components/ui/page-container/index.scss b/ui/components/ui/page-container/index.scss index 8f37ff560..0128b1d59 100644 --- a/ui/components/ui/page-container/index.scss +++ b/ui/components/ui/page-container/index.scss @@ -152,7 +152,7 @@ } &__warning-container { - background: var(--color-warning-muted); + background: var(--color-error-muted); padding: 20px; display: flex; align-items: start; @@ -168,6 +168,7 @@ &__warning-icon { padding-top: 5px; + color: var(--color-error-default); } } diff --git a/ui/css/base-styles.scss b/ui/css/base-styles.scss index c3bd9a106..3935c6dc1 100644 --- a/ui/css/base-styles.scss +++ b/ui/css/base-styles.scss @@ -89,29 +89,30 @@ input.form-control { padding-left: 10px; font-size: 14px; height: 40px; - border: 1px solid var(--color-border-muted); + border: 1px solid var(--color-border-default); + color: 1px solid var(--color-text-default); background: transparent; border-radius: 3px; width: 100%; &::-webkit-input-placeholder { font-weight: 100; - color: var(--color-text-alternative); + color: var(--color-text-muted); } &::-moz-placeholder { font-weight: 100; - color: var(--color-text-alternative); + color: var(--color-text-muted); } &:-ms-input-placeholder { font-weight: 100; - color: var(--color-text-alternative); + color: var(--color-text-muted); } &:-moz-placeholder { font-weight: 100; - color: var(--color-text-alternative); + color: var(--color-text-muted); } &--error { diff --git a/ui/pages/keychains/reveal-seed.js b/ui/pages/keychains/reveal-seed.js index 8e9d991de..384b6e0a5 100644 --- a/ui/pages/keychains/reveal-seed.js +++ b/ui/pages/keychains/reveal-seed.js @@ -40,11 +40,7 @@ class RevealSeedPage extends Component { renderWarning() { return (
- +
{this.context.t('revealSeedWordsWarningTitle')} diff --git a/ui/pages/keychains/reveal-seed.stories.js b/ui/pages/keychains/reveal-seed.stories.js new file mode 100644 index 000000000..e27a307f9 --- /dev/null +++ b/ui/pages/keychains/reveal-seed.stories.js @@ -0,0 +1,22 @@ +import React from 'react'; +import RevealSeedPage from './reveal-seed'; + +export default { + title: 'Pages/Keychains/RevealSeedPage', + id: __filename, + argTypes: { + requestRevealSeedWords: { + action: 'requestRevealSeedWords', + }, + history: { + control: 'object', + }, + mostRecentOverviewPage: { + control: 'text', + }, + }, +}; + +export const DefaultStory = (args) => ; + +DefaultStory.storyName = 'Default'; From fd32d3eb2b92ed6b244500667f274f197d1b6940 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 31 Mar 2022 16:11:59 -1000 Subject: [PATCH 28/92] Update metamaskbot-build-announce.js (#14320) * Update metamaskbot-build-announce.js * Update metamaskbot-build-announce.js --- development/metamaskbot-build-announce.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/development/metamaskbot-build-announce.js b/development/metamaskbot-build-announce.js index 8f1a0bece..9310a92d8 100755 --- a/development/metamaskbot-build-announce.js +++ b/development/metamaskbot-build-announce.js @@ -19,6 +19,8 @@ async function start() { console.log('CIRCLE_SHA1', CIRCLE_SHA1); const { CIRCLE_BUILD_NUM } = process.env; console.log('CIRCLE_BUILD_NUM', CIRCLE_BUILD_NUM); + const { CIRCLE_WORKFLOW_JOB_ID } = process.env; + console.log('CIRCLE_WORKFLOW_JOB_ID', CIRCLE_WORKFLOW_JOB_ID); if (!CIRCLE_PULL_REQUEST) { console.warn(`No pull request detected for commit "${CIRCLE_SHA1}"`); @@ -27,7 +29,7 @@ async function start() { const CIRCLE_PR_NUMBER = CIRCLE_PULL_REQUEST.split('/').pop(); const SHORT_SHA1 = CIRCLE_SHA1.slice(0, 7); - const BUILD_LINK_BASE = `https://${CIRCLE_BUILD_NUM}-42009758-gh.circle-artifacts.com/0`; + const BUILD_LINK_BASE = `https://output.circle-artifacts.com/output/job/${CIRCLE_WORKFLOW_JOB_ID}/artifacts/0`; // build the github comment content From 44b4b9b012d8799fcc272594d80efca3ca68eda1 Mon Sep 17 00:00:00 2001 From: George Marshall Date: Fri, 1 Apr 2022 06:58:02 -0700 Subject: [PATCH 29/92] Fixing account details label colors (#14315) --- .../editable-label/editable-label.stories.js | 33 +++++++++++++++++++ ui/components/ui/editable-label/index.scss | 2 ++ 2 files changed, 35 insertions(+) create mode 100644 ui/components/ui/editable-label/editable-label.stories.js diff --git a/ui/components/ui/editable-label/editable-label.stories.js b/ui/components/ui/editable-label/editable-label.stories.js new file mode 100644 index 000000000..8eb93d362 --- /dev/null +++ b/ui/components/ui/editable-label/editable-label.stories.js @@ -0,0 +1,33 @@ +import React from 'react'; +import EditableLabel from '.'; + +export default { + title: 'Components/UI/EditableLabel', + id: __filename, + argTypes: { + onSubmit: { + action: 'onSubmit', + }, + defaultValue: { + control: 'text', + }, + className: { + control: 'text', + }, + accountsNames: { + control: 'array', + }, + }, + args: { + defaultValue: 'Account 3', + accountsNames: ['Account 1', 'Account 2'], + }, +}; + +export const DefaultStory = (args) => ( +
+ +
+); + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/ui/editable-label/index.scss b/ui/components/ui/editable-label/index.scss index fc2c28b08..e74ac41e3 100644 --- a/ui/components/ui/editable-label/index.scss +++ b/ui/components/ui/editable-label/index.scss @@ -14,6 +14,8 @@ &__input { @include H6; + color: var(--color-text-default); + background-color: var(--color-background-default); width: 250px; text-align: center; border: 1px solid var(--color-border-default); From c5167603823224830ac9b0c54a4b4db6e5a662bd Mon Sep 17 00:00:00 2001 From: George Marshall Date: Fri, 1 Apr 2022 06:59:46 -0700 Subject: [PATCH 30/92] Fix/14303 show private keys (#14314) --- .../export-private-key-modal.stories.js | 37 +++++++++++++++++++ .../export-private-key-modal/index.scss | 3 +- .../modals/qr-scanner/qr-scanner.component.js | 2 +- .../qr-hardware-popover/enhanced-reader.js | 2 +- .../transaction-decoding.component.js | 2 +- .../loading-screen.component.js | 2 +- 6 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 ui/components/app/modals/export-private-key-modal/export-private-key-modal.stories.js diff --git a/ui/components/app/modals/export-private-key-modal/export-private-key-modal.stories.js b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.stories.js new file mode 100644 index 000000000..74901533b --- /dev/null +++ b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.stories.js @@ -0,0 +1,37 @@ +import React from 'react'; +import ExportPrivateKeyModal from '.'; + +export default { + title: 'Components/App/Modals/ExportPrivateKeyModal', + id: __filename, + argTypes: { + exportAccount: { + action: 'exportAccount', + }, + selectedIdentity: { + control: 'object', + }, + warning: { + control: 'node', + }, + showAccountDetailModal: { + action: 'showAccountDetailModal', + }, + hideModal: { + action: 'hideModal', + }, + hideWarning: { + action: 'hideWarning', + }, + clearAccountDetails: { + action: 'clearAccountDetails', + }, + previousModalState: { + control: 'text', + }, + }, +}; + +export const DefaultStory = (args) => ; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/modals/export-private-key-modal/index.scss b/ui/components/app/modals/export-private-key-modal/index.scss index 27b7c55f2..3734d04a4 100644 --- a/ui/components/app/modals/export-private-key-modal/index.scss +++ b/ui/components/app/modals/export-private-key-modal/index.scss @@ -28,7 +28,7 @@ &__password--error { @include H6; - color: var(--color-text-muted); + color: var(--color-text-default); margin-bottom: 10px; } @@ -76,6 +76,7 @@ @include Paragraph; color: var(--color-error-default); + background-color: var(--color-background-default); border: none; height: 75px; width: 100%; diff --git a/ui/components/app/modals/qr-scanner/qr-scanner.component.js b/ui/components/app/modals/qr-scanner/qr-scanner.component.js index cab88a139..68cd8444f 100644 --- a/ui/components/app/modals/qr-scanner/qr-scanner.component.js +++ b/ui/components/app/modals/qr-scanner/qr-scanner.component.js @@ -251,7 +251,7 @@ export default class QrScanner extends Component { }} /> {ready === READY_STATE.READY ? null : ( - + )}
diff --git a/ui/components/app/qr-hardware-popover/enhanced-reader.js b/ui/components/app/qr-hardware-popover/enhanced-reader.js index c9aa354b4..ae753145e 100644 --- a/ui/components/app/qr-hardware-popover/enhanced-reader.js +++ b/ui/components/app/qr-hardware-popover/enhanced-reader.js @@ -55,7 +55,7 @@ const EnhancedReader = ({ handleScan }) => { filter: 'blur(4px)', }} /> - {canplay ? null : } + {canplay ? null : }
); }; diff --git a/ui/components/app/transaction-decoding/transaction-decoding.component.js b/ui/components/app/transaction-decoding/transaction-decoding.component.js index 04e80eb2e..00b858d4f 100644 --- a/ui/components/app/transaction-decoding/transaction-decoding.component.js +++ b/ui/components/app/transaction-decoding/transaction-decoding.component.js @@ -202,7 +202,7 @@ export default function TransactionDecoding({ to = '', inputData: data = '' }) { if (loading) { return (
- +
); } diff --git a/ui/components/ui/loading-screen/loading-screen.component.js b/ui/components/ui/loading-screen/loading-screen.component.js index 85cb16c05..3d8ad9298 100644 --- a/ui/components/ui/loading-screen/loading-screen.component.js +++ b/ui/components/ui/loading-screen/loading-screen.component.js @@ -35,7 +35,7 @@ class LoadingScreen extends Component {
{this.props.showLoadingSpinner && ( )} From e62678464c31d52e61140dc06cea53950e321e60 Mon Sep 17 00:00:00 2001 From: George Marshall Date: Fri, 1 Apr 2022 07:00:33 -0700 Subject: [PATCH 31/92] Fix/14302 about fox logo (#14308) --- app/images/info-logo.png | Bin 18071 -> 0 bytes app/phishing.html | 4 ++-- ui/pages/settings/info-tab/index.scss | 9 +++------ .../settings/info-tab/info-tab.component.js | 6 +++++- ui/pages/settings/info-tab/info-tab.stories.js | 11 +++++++++++ 5 files changed, 21 insertions(+), 9 deletions(-) delete mode 100644 app/images/info-logo.png create mode 100644 ui/pages/settings/info-tab/info-tab.stories.js diff --git a/app/images/info-logo.png b/app/images/info-logo.png deleted file mode 100644 index ab777d44e40e8eba9c6034598a6a60a799f8739b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18071 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 diff --git a/app/phishing.html b/app/phishing.html index f7c159437..e854d79fa 100644 --- a/app/phishing.html +++ b/app/phishing.html @@ -75,7 +75,7 @@ } .content__header img { margin-bottom: 3em; - width: 160px; + width: 130px; } .content__body { background-color: var(--color-background-alternative); @@ -95,7 +95,7 @@
- + MetaMask Logo

MetaMask Phishing Detection diff --git a/ui/pages/settings/info-tab/index.scss b/ui/pages/settings/info-tab/index.scss index ccbc9f9ea..f08538ea8 100644 --- a/ui/pages/settings/info-tab/index.scss +++ b/ui/pages/settings/info-tab/index.scss @@ -1,15 +1,12 @@ .info-tab { &__logo-wrapper { - height: 80px; margin-bottom: 20px; + display: flex; + justify-content: center; } &__logo { - max-height: 100%; - max-width: 50%; - display: block; - margin-left: auto; - margin-right: auto; + height: 80px; } &__item { diff --git a/ui/pages/settings/info-tab/info-tab.component.js b/ui/pages/settings/info-tab/info-tab.component.js index c4de92b31..80ef996ad 100644 --- a/ui/pages/settings/info-tab/info-tab.component.js +++ b/ui/pages/settings/info-tab/info-tab.component.js @@ -142,7 +142,11 @@ export default class InfoTab extends PureComponent { {this.renderInfoLinks()}

- + MetaMask Logo
); diff --git a/ui/pages/settings/info-tab/info-tab.stories.js b/ui/pages/settings/info-tab/info-tab.stories.js new file mode 100644 index 000000000..ea7800a43 --- /dev/null +++ b/ui/pages/settings/info-tab/info-tab.stories.js @@ -0,0 +1,11 @@ +import React from 'react'; +import InfoTab from '.'; + +export default { + title: 'Pages/Settings/InfoTab', + id: __filename, +}; + +export const DefaultStory = () => ; + +DefaultStory.storyName = 'Default'; From 6f5cc9c6c70aea2020ebb34711da4520c587beb1 Mon Sep 17 00:00:00 2001 From: ryanml Date: Fri, 1 Apr 2022 08:51:30 -0700 Subject: [PATCH 32/92] Fix Buy Ether via Moonpay and Wyre actions (#14322) --- .../deposit-ether-modal.component.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js b/ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js index 90f707f75..4d77a53d5 100644 --- a/ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js +++ b/ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js @@ -154,11 +154,12 @@ export default class DepositEtherModal extends Component { text: t('buyCryptoWithMoonPayDescription', [symbol]), buttonLabel: t('continueToMoonPay'), onButtonClick: () => { - this.context.metricsEvent({ - eventOpts: { - category: 'Accounts', - action: 'Deposit tokens', - name: 'Click buy tokens via MoonPay', + this.context.trackEvent({ + category: 'Accounts', + event: 'Click buy Ether via MoonPay', + properties: { + action: 'Deposit Ether', + legacy_event: true, }, }); toMoonPay(address, chainId); @@ -171,11 +172,12 @@ export default class DepositEtherModal extends Component { text: t('buyWithWyreDescription'), buttonLabel: t('continueToWyre'), onButtonClick: () => { - this.context.metricsEvent({ - eventOpts: { - category: 'Accounts', + this.context.trackEvent({ + category: 'Accounts', + event: 'Click buy Ether via Wyre', + properties: { action: 'Deposit Ether', - name: 'Click buy Ether via Wyre', + legacy_event: true, }, }); toWyre(address); From afcc3a8f2620caf87f2e203506029b20076a1c6c Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Fri, 1 Apr 2022 11:52:22 -0400 Subject: [PATCH 33/92] Token Value component for Tokens Detected page (#14274) --- .storybook/test-data.js | 24 ++++++++ ui/components/app/app-components.scss | 1 + .../detected-token-values.js | 57 +++++++++++++++++++ .../detected-token-values.stories.js | 43 ++++++++++++++ .../detected-token-values.test.js | 37 ++++++++++++ .../detected-token-values/index.scss | 5 ++ 6 files changed, 167 insertions(+) create mode 100644 ui/components/app/detected-token/detected-token-values/detected-token-values.js create mode 100644 ui/components/app/detected-token/detected-token-values/detected-token-values.stories.js create mode 100644 ui/components/app/detected-token/detected-token-values/detected-token-values.test.js create mode 100644 ui/components/app/detected-token/detected-token-values/index.scss diff --git a/.storybook/test-data.js b/.storybook/test-data.js index e5e72382f..6188a6122 100644 --- a/.storybook/test-data.js +++ b/.storybook/test-data.js @@ -112,6 +112,29 @@ const state = { ], metamask: { tokenList: { + '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f': { + address: '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', + symbol: 'SNX', + decimals: 18, + name: 'Synthetix Network Token', + iconUrl: 'https://assets.coingecko.com/coins/images/3406/large/SNX.png', + aggregators: [ + 'Aave', + 'Bancor', + 'CMC', + 'Crypto.com', + 'CoinGecko', + '1inch', + 'Paraswap', + 'PMM', + 'Synthetix', + 'Zapper', + 'Zerion', + '0x', + ], + occurrences: 12, + unlisted: false + }, '0x6b175474e89094c44da98b954eedeac495271d0f': { address: '0x6b175474e89094c44da98b954eedeac495271d0f', symbol: 'META', @@ -1086,6 +1109,7 @@ const state = { suggestedAssets: [], useNonceField: false, usePhishDetect: true, + useTokenDetection: true, lostIdentities: {}, forgottenPassword: false, ipfsGateway: 'dweb.link', diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 7e4507d55..4a7812116 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -86,3 +86,4 @@ @import 'currency-input/index'; @import 'detected-token/detected-token-address/index'; @import 'detected-token/detected-token-aggregators/index'; +@import 'detected-token/detected-token-values/index'; diff --git a/ui/components/app/detected-token/detected-token-values/detected-token-values.js b/ui/components/app/detected-token/detected-token-values/detected-token-values.js new file mode 100644 index 000000000..d234b67c2 --- /dev/null +++ b/ui/components/app/detected-token/detected-token-values/detected-token-values.js @@ -0,0 +1,57 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; + +import Box from '../../../ui/box'; +import Typography from '../../../ui/typography'; +import CheckBox from '../../../ui/check-box'; + +import { + COLORS, + DISPLAY, + TYPOGRAPHY, +} from '../../../../helpers/constants/design-system'; +import { useTokenTracker } from '../../../../hooks/useTokenTracker'; +import { useTokenFiatAmount } from '../../../../hooks/useTokenFiatAmount'; + +const DetectedTokenValues = ({ token }) => { + const [selectedTokens, setSelectedTokens] = useState(false); + const { tokensWithBalances } = useTokenTracker([token]); + const balanceToRender = tokensWithBalances[0]?.string; + const balance = tokensWithBalances[0]?.balance; + const formattedFiatBalance = useTokenFiatAmount( + token.address, + balanceToRender, + token.symbol, + ); + + return ( + + + + {`${balance || '0'} ${token.symbol}`} + + + {formattedFiatBalance || '$0'} + + + + setSelectedTokens((checked) => !checked)} + /> + + + ); +}; + +DetectedTokenValues.propTypes = { + token: PropTypes.shape({ + address: PropTypes.string.isRequired, + decimals: PropTypes.number, + symbol: PropTypes.string, + iconUrl: PropTypes.string, + aggregators: PropTypes.array, + }), +}; + +export default DetectedTokenValues; diff --git a/ui/components/app/detected-token/detected-token-values/detected-token-values.stories.js b/ui/components/app/detected-token/detected-token-values/detected-token-values.stories.js new file mode 100644 index 000000000..6a3d12c39 --- /dev/null +++ b/ui/components/app/detected-token/detected-token-values/detected-token-values.stories.js @@ -0,0 +1,43 @@ +import React from 'react'; + +import DetectedTokenValues from './detected-token-values'; + +export default { + title: 'Components/App/DetectedToken/DetectedTokenValues', + id: __filename, + argTypes: { + address: { control: 'text' }, + symbol: { control: 'text' }, + decimals: { control: 'text' }, + iconUrl: { control: 'text' }, + aggregators: { control: 'array' }, + }, + args: { + address: '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', + symbol: 'SNX', + decimals: 18, + iconUrl: 'https://assets.coingecko.com/coins/images/3406/large/SNX.png', + aggregators: [ + 'aave', + 'bancor', + 'cmc', + 'cryptocom', + 'coinGecko', + 'oneInch', + 'paraswap', + 'pmm', + 'synthetix', + 'zapper', + 'zerion', + 'zeroEx', + ], + }, +}; + +const Template = (args) => { + return ; +}; + +export const DefaultStory = Template.bind({}); + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/detected-token/detected-token-values/detected-token-values.test.js b/ui/components/app/detected-token/detected-token-values/detected-token-values.test.js new file mode 100644 index 000000000..18a5aae86 --- /dev/null +++ b/ui/components/app/detected-token/detected-token-values/detected-token-values.test.js @@ -0,0 +1,37 @@ +import * as React from 'react'; +import { renderWithProvider, screen } from '../../../../../test/jest'; +import configureStore from '../../../../store/store'; +import testData from '../../../../../.storybook/test-data'; + +import DetectedTokenValues from './detected-token-values'; + +describe('DetectedTokenValues', () => { + const args = { + address: '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', + symbol: 'SNX', + decimals: 18, + iconUrl: 'https://assets.coingecko.com/coins/images/3406/large/SNX.png', + aggregators: [ + 'aave', + 'bancor', + 'cmc', + 'cryptocom', + 'coinGecko', + 'oneInch', + 'paraswap', + 'pmm', + 'synthetix', + 'zapper', + 'zerion', + 'zeroEx', + ], + }; + + it('should render the detected token address', async () => { + const store = configureStore(testData); + renderWithProvider(, store); + + expect(screen.getByText('0 SNX')).toBeInTheDocument(); + expect(screen.getByText('$0')).toBeInTheDocument(); + }); +}); diff --git a/ui/components/app/detected-token/detected-token-values/index.scss b/ui/components/app/detected-token/detected-token-values/index.scss new file mode 100644 index 000000000..4024c7fd8 --- /dev/null +++ b/ui/components/app/detected-token/detected-token-values/index.scss @@ -0,0 +1,5 @@ +.detected-token-values { + &__checkbox { + margin-left: auto; + } +} From cb963f3c299b99123b46ea671ce462535a6605da Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Fri, 1 Apr 2022 18:14:48 +0200 Subject: [PATCH 34/92] snaps-skunkworks@0.10.7 (#14323) * snaps-skunkworks@0.10.7 * Bump iframe-execution-environment --- app/scripts/metamask-controller.js | 2 +- package.json | 6 ++-- yarn.lock | 52 +++++++++++++++--------------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index f4f482891..3a55881d8 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -596,7 +596,7 @@ export default class MetamaskController extends EventEmitter { this.workerController = new IframeExecutionService({ onError: this.onExecutionEnvironmentError.bind(this), iframeUrl: new URL( - 'https://metamask.github.io/iframe-execution-environment/0.4.2', + 'https://metamask.github.io/iframe-execution-environment/0.4.3', ), messenger: this.controllerMessenger.getRestricted({ name: 'ExecutionService', diff --git a/package.json b/package.json index 759414cd3..db83b67cb 100644 --- a/package.json +++ b/package.json @@ -119,17 +119,17 @@ "@metamask/eth-ledger-bridge-keyring": "^0.10.0", "@metamask/eth-token-tracker": "^4.0.0", "@metamask/etherscan-link": "^2.1.0", - "@metamask/iframe-execution-environment-service": "^0.10.6", + "@metamask/iframe-execution-environment-service": "^0.10.7", "@metamask/jazzicon": "^2.0.0", "@metamask/logo": "^3.1.1", "@metamask/metamask-eth-abis": "^3.0.0", "@metamask/obs-store": "^5.0.0", "@metamask/post-message-stream": "^4.0.0", "@metamask/providers": "^8.1.1", - "@metamask/rpc-methods": "^0.10.6", + "@metamask/rpc-methods": "^0.10.7", "@metamask/slip44": "^2.0.0", "@metamask/smart-transactions-controller": "^1.9.1", - "@metamask/snap-controllers": "^0.10.6", + "@metamask/snap-controllers": "^0.10.7", "@ngraveio/bc-ur": "^1.1.6", "@popperjs/core": "^2.4.0", "@reduxjs/toolkit": "^1.6.2", diff --git a/yarn.lock b/yarn.lock index ab5f2c9d5..b6f0c19a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2866,15 +2866,15 @@ resolved "https://registry.yarnpkg.com/@metamask/etherscan-link/-/etherscan-link-2.1.0.tgz#c0be8e68445b7b83cf85bcc03a56cdf8e256c973" integrity sha512-ADuWlTUkFfN2vXlz81Bg/0BA+XRor+CdK1055p6k7H6BLIPoDKn9SBOFld9haQFuR9cKh/JYHcnlSIv5R4fUEw== -"@metamask/execution-environments@^0.10.6": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@metamask/execution-environments/-/execution-environments-0.10.6.tgz#930ff056e63accf7ab7a59a28bd99064d217599c" - integrity sha512-6ID8vzmIiy418LqRiDKPuDl0RBhHmDMzhgyb65ia6zZNDXKhsBotF/lNMhtHlsAbD9+3XYhi/Nl6YprJxaRisA== +"@metamask/execution-environments@^0.10.7": + version "0.10.7" + resolved "https://registry.yarnpkg.com/@metamask/execution-environments/-/execution-environments-0.10.7.tgz#aedc37c9c371a7229600e74e62a0157690152f2c" + integrity sha512-jD8Bzqo/QiDHCbo8D9pvQE7TZNGQJLW0nkz5vY2wLoJ1kLLlyJzyjRU77oZCSZMW0feLZWRifalUQT5tOrGA8w== dependencies: "@metamask/object-multiplex" "^1.2.0" "@metamask/post-message-stream" "^4.0.0" "@metamask/providers" "^8.1.1" - "@metamask/snap-types" "^0.10.6" + "@metamask/snap-types" "^0.10.7" cross-fetch "^3.1.5" eth-rpc-errors "^4.0.3" pump "^3.0.0" @@ -2886,17 +2886,17 @@ resolved "https://registry.yarnpkg.com/@metamask/forwarder/-/forwarder-1.1.0.tgz#13829d8244bbf19ea658c0b20d21a77b67de0bdd" integrity sha512-Hggj4y0QIjDzKGTXzarhEPIQyFSB2bi2y6YLJNwaT4JmP30UB5Cj6gqoY0M4pj3QT57fzp0BUuGp7F/AUe28tw== -"@metamask/iframe-execution-environment-service@^0.10.6": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@metamask/iframe-execution-environment-service/-/iframe-execution-environment-service-0.10.6.tgz#ba7e5499efba2018cef38fff916eaabbfbe1b27b" - integrity sha512-BZbE4+CffNmD1J7Mpvh/evvKcDuP82Jl9panIV0JKzddG8zzOi9xlGtImYf8QWjwN5zx2TyTHAb/WxA+R+nFzw== +"@metamask/iframe-execution-environment-service@^0.10.7": + version "0.10.7" + resolved "https://registry.yarnpkg.com/@metamask/iframe-execution-environment-service/-/iframe-execution-environment-service-0.10.7.tgz#787093962cb04e46759a994b21ab7d8d3b63a518" + integrity sha512-pHrWrv5ywdFmp8PdewPqYcDql9djlvsywsdISVCsAIn3oIH65+ICAuycSNXWTaPQ0VMO5/mE9taynEyVOvw8pw== dependencies: "@metamask/controllers" "^26.0.0" - "@metamask/execution-environments" "^0.10.6" + "@metamask/execution-environments" "^0.10.7" "@metamask/object-multiplex" "^1.2.0" "@metamask/post-message-stream" "^4.0.0" - "@metamask/snap-controllers" "^0.10.6" - "@metamask/snap-types" "^0.10.6" + "@metamask/snap-controllers" "^0.10.7" + "@metamask/snap-types" "^0.10.7" eth-rpc-errors "^4.0.3" json-rpc-engine "^6.1.0" json-rpc-middleware-stream "^3.0.0" @@ -2990,14 +2990,14 @@ pump "^3.0.0" webextension-polyfill-ts "^0.25.0" -"@metamask/rpc-methods@^0.10.6": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@metamask/rpc-methods/-/rpc-methods-0.10.6.tgz#6e91c2bf33c909e203db438effec6663da4e8692" - integrity sha512-cPqAQExwZGRLqtl22HUBVblr/tIAm/aI3ZMt6QKhwymwU7f/yM+4BOZB1M3+dVlhcIfX1SFFxDW3rFe+MsCksA== +"@metamask/rpc-methods@^0.10.7": + version "0.10.7" + resolved "https://registry.yarnpkg.com/@metamask/rpc-methods/-/rpc-methods-0.10.7.tgz#d663599de9207bd35f7cf74e4a1ebb4c27b3ee2e" + integrity sha512-K83zkffYDMxzocxHC8eIGWl/XP+YzJu82dK3Ii43f2MEr66oE/bPPPIZAivDmLDxhc/cWE7RUhZCHaiWBR+SGQ== dependencies: "@metamask/controllers" "^26.0.0" "@metamask/key-tree" "^3.0.1" - "@metamask/snap-controllers" "^0.10.6" + "@metamask/snap-controllers" "^0.10.7" "@metamask/types" "^1.1.0" eth-rpc-errors "^4.0.2" @@ -3024,13 +3024,13 @@ isomorphic-fetch "^3.0.0" lodash "^4.17.21" -"@metamask/snap-controllers@^0.10.6": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@metamask/snap-controllers/-/snap-controllers-0.10.6.tgz#72fc52271264c4ae8c986ce4d1a64c34505c7a8b" - integrity sha512-Pa8g1dBVKtjkemRXFUPDQLnP8gcVmQpSgkTMK/m7tVqUinyZX7Gufq5wuApEh1pwRjMQ2R7he/zQn7oZ4qwTJw== +"@metamask/snap-controllers@^0.10.7": + version "0.10.7" + resolved "https://registry.yarnpkg.com/@metamask/snap-controllers/-/snap-controllers-0.10.7.tgz#df4f049264bcc805c3a5876f6c298b2c37c6734e" + integrity sha512-wXuelvjLI06RSxZjCGCCCxxAAyA3djMcjwcjmPbrMPI/6X0neUjrIJrQ4LTBxX2GPzSfU/4z/gFTdAsqi5bHKg== dependencies: "@metamask/controllers" "^26.0.0" - "@metamask/execution-environments" "^0.10.6" + "@metamask/execution-environments" "^0.10.7" "@metamask/object-multiplex" "^1.1.0" "@metamask/obs-store" "^7.0.0" "@metamask/post-message-stream" "4.0.0" @@ -3053,10 +3053,10 @@ semver "^7.3.5" tar-stream "^2.2.0" -"@metamask/snap-types@^0.10.6": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@metamask/snap-types/-/snap-types-0.10.6.tgz#83e3eab797829a9f31906a27fa967a8096e5b704" - integrity sha512-6eSc9hHhC+X133Nw6/WSUy0RWRipdOF3NBKgGFYJxCpvhkY4jGxQ1HAKDUZRzZ3eSewsmQ4hhhIQ+zczj0Subw== +"@metamask/snap-types@^0.10.7": + version "0.10.7" + resolved "https://registry.yarnpkg.com/@metamask/snap-types/-/snap-types-0.10.7.tgz#a314ba1082e65a41bde021735bbc57034416aa53" + integrity sha512-AHOwQ/7SxDBRUSrdlWRa5X549mSM1MOl8iErOccxfKzuHdJjmIZIv1Ob34bgdMUxvhkt7ztFCW/lcObmdCIGjA== dependencies: "@metamask/controllers" "^26.0.0" From 8cb1f4af2e021a0bf70a5f60f3deedac406b366e Mon Sep 17 00:00:00 2001 From: filipsekulic Date: Fri, 1 Apr 2022 21:11:12 +0200 Subject: [PATCH 35/92] Renamed metametrics.new.js to metametrics.js (#14327) --- .storybook/metametrics.js | 14 +++++++------- .../app/asset-list-item/asset-list-item.js | 2 +- ui/components/app/asset-list/asset-list.js | 2 +- .../app/create-new-vault/create-new-vault.js | 2 +- .../edit-gas-display/edit-gas-display.component.js | 2 +- .../import-token-link.component.js | 2 +- ui/components/app/menu-bar/account-options-menu.js | 2 +- ui/components/app/menu-bar/menu-bar.js | 2 +- .../transaction-list-item.component.js | 2 +- ui/components/app/wallet-overview/eth-overview.js | 2 +- .../app/wallet-overview/token-overview.js | 2 +- ui/contexts/{metametrics.new.js => metametrics.js} | 0 ui/pages/asset/components/native-asset.js | 2 +- ui/pages/asset/components/token-asset.js | 2 +- .../confirm-add-suggested-token.js | 4 ++-- .../confirm-import-token/confirm-import-token.js | 4 ++-- ui/pages/index.js | 14 +++++++------- .../create-password/create-password.js | 2 +- .../creation-successful/creation-successful.js | 2 +- .../onboarding-flow/metametrics/metametrics.js | 2 +- .../amount-max-button/amount-max-button.js | 2 +- ui/pages/send/send.js | 2 +- .../awaiting-signatures/awaiting-signatures.js | 2 +- ui/pages/swaps/awaiting-swap/awaiting-swap.js | 2 +- .../view-on-ether-scan-link.js | 2 +- ui/pages/swaps/build-quote/build-quote.js | 2 +- .../dropdown-search-list/dropdown-search-list.js | 2 +- ui/pages/swaps/fee-card/fee-card.js | 2 +- ui/pages/swaps/index.js | 2 +- .../loading-swaps-quotes/loading-swaps-quotes.js | 2 +- .../item-list/item-list.component.js | 2 +- .../smart-transaction-status.js | 2 +- ui/pages/swaps/view-quote/view-quote.js | 2 +- 33 files changed, 46 insertions(+), 46 deletions(-) rename ui/contexts/{metametrics.new.js => metametrics.js} (100%) diff --git a/.storybook/metametrics.js b/.storybook/metametrics.js index dca27aadd..aafd1b182 100644 --- a/.storybook/metametrics.js +++ b/.storybook/metametrics.js @@ -1,16 +1,16 @@ import React from 'react'; import { - MetaMetricsProvider as NewMetaMetricsProvider, - LegacyMetaMetricsProvider as NewLegacyMetaMetricsProvider, -} from '../ui/contexts/metametrics.new'; + MetaMetricsProvider, + LegacyMetaMetricsProvider +} from '../ui/contexts/metametrics'; const MetaMetricsProviderStorybook = (props) => ( - - + + {props.children} - - + + ); export default MetaMetricsProviderStorybook \ No newline at end of file diff --git a/ui/components/app/asset-list-item/asset-list-item.js b/ui/components/app/asset-list-item/asset-list-item.js index 8b743ac7a..2b54fce6c 100644 --- a/ui/components/app/asset-list-item/asset-list-item.js +++ b/ui/components/app/asset-list-item/asset-list-item.js @@ -14,7 +14,7 @@ import { SEND_ROUTE } from '../../../helpers/constants/routes'; import { SEVERITIES } from '../../../helpers/constants/design-system'; import { INVALID_ASSET_TYPE } from '../../../helpers/constants/error-keys'; import { ASSET_TYPES } from '../../../../shared/constants/transaction'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; const AssetListItem = ({ className, diff --git a/ui/components/app/asset-list/asset-list.js b/ui/components/app/asset-list/asset-list.js index 75be4008d..adc404738 100644 --- a/ui/components/app/asset-list/asset-list.js +++ b/ui/components/app/asset-list/asset-list.js @@ -25,7 +25,7 @@ import { JUSTIFY_CONTENT, } from '../../../helpers/constants/design-system'; import { useI18nContext } from '../../../hooks/useI18nContext'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; const AssetList = ({ onClickAsset }) => { const t = useI18nContext(); diff --git a/ui/components/app/create-new-vault/create-new-vault.js b/ui/components/app/create-new-vault/create-new-vault.js index 7d442ecd2..bfb744c94 100644 --- a/ui/components/app/create-new-vault/create-new-vault.js +++ b/ui/components/app/create-new-vault/create-new-vault.js @@ -1,7 +1,7 @@ import React, { useCallback, useContext, useState } from 'react'; import PropTypes from 'prop-types'; import { useI18nContext } from '../../../hooks/useI18nContext'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import TextField from '../../ui/text-field'; import Button from '../../ui/button'; import CheckBox from '../../ui/check-box'; diff --git a/ui/components/app/edit-gas-display/edit-gas-display.component.js b/ui/components/app/edit-gas-display/edit-gas-display.component.js index dd1af320a..59518f605 100644 --- a/ui/components/app/edit-gas-display/edit-gas-display.component.js +++ b/ui/components/app/edit-gas-display/edit-gas-display.component.js @@ -35,7 +35,7 @@ import ActionableMessage from '../../ui/actionable-message/actionable-message'; import { I18nContext } from '../../../contexts/i18n'; import GasTiming from '../gas-timing'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; export default function EditGasDisplay({ mode = EDIT_GAS_MODES.MODIFY_IN_PLACE, diff --git a/ui/components/app/import-token-link/import-token-link.component.js b/ui/components/app/import-token-link/import-token-link.component.js index 4147a9256..d89323d46 100644 --- a/ui/components/app/import-token-link/import-token-link.component.js +++ b/ui/components/app/import-token-link/import-token-link.component.js @@ -7,7 +7,7 @@ import Button from '../../ui/button'; import Box from '../../ui/box/box'; import { TEXT_ALIGN } from '../../../helpers/constants/design-system'; import { detectNewTokens } from '../../../store/actions'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; export default function ImportTokenLink({ isMainnet }) { const trackEvent = useContext(MetaMetricsContext); diff --git a/ui/components/app/menu-bar/account-options-menu.js b/ui/components/app/menu-bar/account-options-menu.js index 9e83f3788..5aa821a22 100644 --- a/ui/components/app/menu-bar/account-options-menu.js +++ b/ui/components/app/menu-bar/account-options-menu.js @@ -17,7 +17,7 @@ import { import { useI18nContext } from '../../../hooks/useI18nContext'; import { getEnvironmentType } from '../../../../app/scripts/lib/util'; import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../shared/constants/app'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; export default function AccountOptionsMenu({ anchorElement, onClose }) { const t = useI18nContext(); diff --git a/ui/components/app/menu-bar/menu-bar.js b/ui/components/app/menu-bar/menu-bar.js index 9d8af1066..b2e59d62b 100644 --- a/ui/components/app/menu-bar/menu-bar.js +++ b/ui/components/app/menu-bar/menu-bar.js @@ -9,7 +9,7 @@ import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; import { CONNECTED_ACCOUNTS_ROUTE } from '../../../helpers/constants/routes'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { getOriginOfCurrentTab } from '../../../selectors'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import AccountOptionsMenu from './account-options-menu'; export default function MenuBar() { diff --git a/ui/components/app/transaction-list-item/transaction-list-item.component.js b/ui/components/app/transaction-list-item/transaction-list-item.component.js index f911dcff8..bdc58988c 100644 --- a/ui/components/app/transaction-list-item/transaction-list-item.component.js +++ b/ui/components/app/transaction-list-item/transaction-list-item.component.js @@ -37,7 +37,7 @@ import CancelButton from '../cancel-button'; import CancelSpeedupPopover from '../cancel-speedup-popover'; import EditGasFeePopover from '../edit-gas-fee-popover'; import EditGasPopover from '../edit-gas-popover'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; function TransactionListItemInner({ transactionGroup, diff --git a/ui/components/app/wallet-overview/eth-overview.js b/ui/components/app/wallet-overview/eth-overview.js index 95ef5aec6..cf5073bf1 100644 --- a/ui/components/app/wallet-overview/eth-overview.js +++ b/ui/components/app/wallet-overview/eth-overview.js @@ -30,7 +30,7 @@ import SendIcon from '../../ui/icon/overview-send-icon.component'; import { setSwapsFromToken } from '../../../ducks/swaps/swaps'; import IconButton from '../../ui/icon-button'; import { isHardwareKeyring } from '../../../helpers/utils/hardware'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import WalletOverview from './wallet-overview'; const EthOverview = ({ className }) => { diff --git a/ui/components/app/wallet-overview/token-overview.js b/ui/components/app/wallet-overview/token-overview.js index c2860e727..fc2caa1b7 100644 --- a/ui/components/app/wallet-overview/token-overview.js +++ b/ui/components/app/wallet-overview/token-overview.js @@ -27,7 +27,7 @@ import SendIcon from '../../ui/icon/overview-send-icon.component'; import IconButton from '../../ui/icon-button'; import { INVALID_ASSET_TYPE } from '../../../helpers/constants/error-keys'; import { showModal } from '../../../store/actions'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import { ASSET_TYPES } from '../../../../shared/constants/transaction'; import WalletOverview from './wallet-overview'; diff --git a/ui/contexts/metametrics.new.js b/ui/contexts/metametrics.js similarity index 100% rename from ui/contexts/metametrics.new.js rename to ui/contexts/metametrics.js diff --git a/ui/pages/asset/components/native-asset.js b/ui/pages/asset/components/native-asset.js index c4d894c92..218c250e8 100644 --- a/ui/pages/asset/components/native-asset.js +++ b/ui/pages/asset/components/native-asset.js @@ -14,7 +14,7 @@ import { import { showModal } from '../../../store/actions'; import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'; import { getURLHostName } from '../../../helpers/utils/util'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import AssetNavigation from './asset-navigation'; import AssetOptions from './asset-options'; diff --git a/ui/pages/asset/components/token-asset.js b/ui/pages/asset/components/token-asset.js index 6ccf944c4..870dac025 100644 --- a/ui/pages/asset/components/token-asset.js +++ b/ui/pages/asset/components/token-asset.js @@ -16,7 +16,7 @@ import { } from '../../../helpers/constants/routes'; import { getURLHostName } from '../../../helpers/utils/util'; import { showModal } from '../../../store/actions'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import AssetNavigation from './asset-navigation'; import AssetOptions from './asset-options'; diff --git a/ui/pages/confirm-add-suggested-token/confirm-add-suggested-token.js b/ui/pages/confirm-add-suggested-token/confirm-add-suggested-token.js index 47d8f6acd..249bec4d8 100644 --- a/ui/pages/confirm-add-suggested-token/confirm-add-suggested-token.js +++ b/ui/pages/confirm-add-suggested-token/confirm-add-suggested-token.js @@ -6,7 +6,7 @@ import Button from '../../components/ui/button'; import Identicon from '../../components/ui/identicon'; import TokenBalance from '../../components/ui/token-balance'; import { I18nContext } from '../../contexts/i18n'; -import { MetaMetricsContext as NewMetaMetricsContext } from '../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../contexts/metametrics'; import { getMostRecentOverviewPage } from '../../ducks/history/history'; import { getTokens } from '../../ducks/metamask/metamask'; import ZENDESK_URLS from '../../helpers/constants/zendesk-url'; @@ -67,7 +67,7 @@ const ConfirmAddSuggestedToken = () => { const suggestedAssets = useSelector(getSuggestedAssets); const tokens = useSelector(getTokens); - const trackEvent = useContext(NewMetaMetricsContext); + const trackEvent = useContext(MetaMetricsContext); const knownTokenActionableMessage = useMemo(() => { return ( diff --git a/ui/pages/confirm-import-token/confirm-import-token.js b/ui/pages/confirm-import-token/confirm-import-token.js index c1df7317e..54b3efb82 100644 --- a/ui/pages/confirm-import-token/confirm-import-token.js +++ b/ui/pages/confirm-import-token/confirm-import-token.js @@ -9,7 +9,7 @@ import Button from '../../components/ui/button'; import Identicon from '../../components/ui/identicon'; import TokenBalance from '../../components/ui/token-balance'; import { I18nContext } from '../../contexts/i18n'; -import { MetaMetricsContext as NewMetaMetricsContext } from '../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../contexts/metametrics'; import { getMostRecentOverviewPage } from '../../ducks/history/history'; import { getPendingTokens } from '../../ducks/metamask/metamask'; import { addTokens, clearPendingTokens } from '../../store/actions'; @@ -24,7 +24,7 @@ const ConfirmImportToken = () => { const t = useContext(I18nContext); const dispatch = useDispatch(); const history = useHistory(); - const trackEvent = useContext(NewMetaMetricsContext); + const trackEvent = useContext(MetaMetricsContext); const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage); const pendingTokens = useSelector(getPendingTokens); diff --git a/ui/pages/index.js b/ui/pages/index.js index 05dc5072e..b2535958d 100644 --- a/ui/pages/index.js +++ b/ui/pages/index.js @@ -5,9 +5,9 @@ import { HashRouter } from 'react-router-dom'; import * as Sentry from '@sentry/browser'; import { I18nProvider, LegacyI18nProvider } from '../contexts/i18n'; import { - MetaMetricsProvider as NewMetaMetricsProvider, - LegacyMetaMetricsProvider as NewLegacyMetaMetricsProvider, -} from '../contexts/metametrics.new'; + MetaMetricsProvider, + LegacyMetaMetricsProvider, +} from '../contexts/metametrics'; import ErrorPage from './error'; import Routes from './routes'; @@ -41,15 +41,15 @@ class Index extends PureComponent { return ( - - + + - - + + ); diff --git a/ui/pages/onboarding-flow/create-password/create-password.js b/ui/pages/onboarding-flow/create-password/create-password.js index 1de0171c7..0dba880d9 100644 --- a/ui/pages/onboarding-flow/create-password/create-password.js +++ b/ui/pages/onboarding-flow/create-password/create-password.js @@ -29,7 +29,7 @@ import { import ZENDESK_URLS from '../../../helpers/constants/zendesk-url'; import { getFirstTimeFlowType } from '../../../selectors'; import { FIRST_TIME_FLOW_TYPES } from '../../../helpers/constants/onboarding'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; export default function CreatePassword({ createNewAccount, diff --git a/ui/pages/onboarding-flow/creation-successful/creation-successful.js b/ui/pages/onboarding-flow/creation-successful/creation-successful.js index e5d858d86..1ff9c09c5 100644 --- a/ui/pages/onboarding-flow/creation-successful/creation-successful.js +++ b/ui/pages/onboarding-flow/creation-successful/creation-successful.js @@ -16,7 +16,7 @@ import { } from '../../../helpers/constants/routes'; import { setCompletedOnboarding } from '../../../store/actions'; import { getFirstTimeFlowType } from '../../../selectors'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; export default function CreationSuccessful() { const firstTimeFlowTypeNameMap = { diff --git a/ui/pages/onboarding-flow/metametrics/metametrics.js b/ui/pages/onboarding-flow/metametrics/metametrics.js index e9d43ee4f..05116a5a4 100644 --- a/ui/pages/onboarding-flow/metametrics/metametrics.js +++ b/ui/pages/onboarding-flow/metametrics/metametrics.js @@ -17,7 +17,7 @@ import { getParticipateInMetaMetrics, } from '../../../selectors'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; const firstTimeFlowTypeNameMap = { create: 'Selected Create New Wallet', diff --git a/ui/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.js b/ui/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.js index 5144ae928..f2ae4bcaf 100644 --- a/ui/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.js +++ b/ui/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.js @@ -7,7 +7,7 @@ import { toggleSendMaxMode, } from '../../../../../ducks/send'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; -import { MetaMetricsContext } from '../../../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../../../contexts/metametrics'; export default function AmountMaxButton() { const isDraftTransactionInvalid = useSelector(isSendFormInvalid); diff --git a/ui/pages/send/send.js b/ui/pages/send/send.js index aa9c38c6b..b917373ff 100644 --- a/ui/pages/send/send.js +++ b/ui/pages/send/send.js @@ -16,7 +16,7 @@ import { import { getCurrentChainId, isCustomPriceExcessive } from '../../selectors'; import { getSendHexDataFeatureFlagState } from '../../ducks/metamask/metamask'; import { showQrScanner } from '../../store/actions'; -import { MetaMetricsContext } from '../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../contexts/metametrics'; import SendHeader from './send-header'; import AddRecipient from './send-content/add-recipient'; import SendContent from './send-content'; diff --git a/ui/pages/swaps/awaiting-signatures/awaiting-signatures.js b/ui/pages/swaps/awaiting-signatures/awaiting-signatures.js index b7369b5e8..2d0a3c6f0 100644 --- a/ui/pages/swaps/awaiting-signatures/awaiting-signatures.js +++ b/ui/pages/swaps/awaiting-signatures/awaiting-signatures.js @@ -32,7 +32,7 @@ import { DISPLAY, } from '../../../helpers/constants/design-system'; import SwapsFooter from '../swaps-footer'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import SwapStepIcon from './swap-step-icon'; export default function AwaitingSignatures() { diff --git a/ui/pages/swaps/awaiting-swap/awaiting-swap.js b/ui/pages/swaps/awaiting-swap/awaiting-swap.js index b4007b776..26478198d 100644 --- a/ui/pages/swaps/awaiting-swap/awaiting-swap.js +++ b/ui/pages/swaps/awaiting-swap/awaiting-swap.js @@ -7,7 +7,7 @@ import isEqual from 'lodash/isEqual'; import { getBlockExplorerLink } from '@metamask/etherscan-link'; import { I18nContext } from '../../../contexts/i18n'; import { SUPPORT_LINK } from '../../../helpers/constants/common'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import { getCurrentChainId, diff --git a/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/view-on-ether-scan-link.js b/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/view-on-ether-scan-link.js index f58187d86..7ab005cf1 100644 --- a/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/view-on-ether-scan-link.js +++ b/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/view-on-ether-scan-link.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import { I18nContext } from '../../../../contexts/i18n'; import { getURLHostName } from '../../../../helpers/utils/util'; -import { MetaMetricsContext } from '../../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../../contexts/metametrics'; export default function ViewOnEtherScanLink({ txHash, diff --git a/ui/pages/swaps/build-quote/build-quote.js b/ui/pages/swaps/build-quote/build-quote.js index c53cd72df..a4331c146 100644 --- a/ui/pages/swaps/build-quote/build-quote.js +++ b/ui/pages/swaps/build-quote/build-quote.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; import { uniqBy, isEqual } from 'lodash'; import { useHistory } from 'react-router-dom'; import { getTokenTrackerLink } from '@metamask/etherscan-link'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import { useTokensToSearch, getRenderableTokenData, diff --git a/ui/pages/swaps/dropdown-search-list/dropdown-search-list.js b/ui/pages/swaps/dropdown-search-list/dropdown-search-list.js index 1b167199b..519bba30a 100644 --- a/ui/pages/swaps/dropdown-search-list/dropdown-search-list.js +++ b/ui/pages/swaps/dropdown-search-list/dropdown-search-list.js @@ -28,7 +28,7 @@ import { getSmartTransactionsEnabled, getCurrentSmartTransactionsEnabled, } from '../../../ducks/swaps/swaps'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; export default function DropdownSearchList({ searchListClassName, diff --git a/ui/pages/swaps/fee-card/fee-card.js b/ui/pages/swaps/fee-card/fee-card.js index e6bf1988e..c10a41a94 100644 --- a/ui/pages/swaps/fee-card/fee-card.js +++ b/ui/pages/swaps/fee-card/fee-card.js @@ -19,7 +19,7 @@ import { FONT_WEIGHT, } from '../../../helpers/constants/design-system'; import GasDetailsItemTitle from '../../../components/app/gas-details-item/gas-details-item-title'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; const GAS_FEES_LEARN_MORE_URL = 'https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172'; diff --git a/ui/pages/swaps/index.js b/ui/pages/swaps/index.js index 479fb7537..1a01c38c1 100644 --- a/ui/pages/swaps/index.js +++ b/ui/pages/swaps/index.js @@ -85,7 +85,7 @@ import { useGasFeeEstimates } from '../../hooks/useGasFeeEstimates'; import FeatureToggledRoute from '../../helpers/higher-order-components/feature-toggled-route'; import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction'; import ActionableMessage from '../../components/ui/actionable-message'; -import { MetaMetricsContext } from '../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../contexts/metametrics'; import { fetchTokens, fetchTopAssets, diff --git a/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js b/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js index df97c48b9..85eac4427 100644 --- a/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js +++ b/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js @@ -18,7 +18,7 @@ import { getHardwareWalletType, } from '../../../selectors/selectors'; import { I18nContext } from '../../../contexts/i18n'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import Mascot from '../../../components/ui/mascot'; import SwapsFooter from '../swaps-footer'; import BackgroundAnimation from './background-animation'; diff --git a/ui/pages/swaps/searchable-item-list/item-list/item-list.component.js b/ui/pages/swaps/searchable-item-list/item-list/item-list.component.js index 6f51dbbed..c92fdc398 100644 --- a/ui/pages/swaps/searchable-item-list/item-list/item-list.component.js +++ b/ui/pages/swaps/searchable-item-list/item-list/item-list.component.js @@ -13,7 +13,7 @@ import { } from '../../../../selectors'; import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../../shared/constants/swaps'; import { getURLHostName } from '../../../../helpers/utils/util'; -import { MetaMetricsContext } from '../../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../../contexts/metametrics'; export default function ItemList({ results = [], diff --git a/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js b/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js index 674451444..adb45215e 100644 --- a/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js +++ b/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js @@ -44,7 +44,7 @@ import { SMART_TRANSACTION_STATUSES } from '../../../../shared/constants/transac import SwapsFooter from '../swaps-footer'; import { calcTokenAmount } from '../../../helpers/utils/token-util'; import { showRemainingTimeInMinAndSec } from '../swaps.util'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import SuccessIcon from './success-icon'; import RevertedIcon from './reverted-icon'; import CanceledIcon from './canceled-icon'; diff --git a/ui/pages/swaps/view-quote/view-quote.js b/ui/pages/swaps/view-quote/view-quote.js index a3e920fd1..a6e702e1e 100644 --- a/ui/pages/swaps/view-quote/view-quote.js +++ b/ui/pages/swaps/view-quote/view-quote.js @@ -17,7 +17,7 @@ import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount'; import { useEqualityCheck } from '../../../hooks/useEqualityCheck'; import { usePrevious } from '../../../hooks/usePrevious'; import { useGasFeeInputs } from '../../../hooks/gasFeeInput/useGasFeeInputs'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import FeeCard from '../fee-card'; import EditGasPopover from '../../../components/app/edit-gas-popover/edit-gas-popover.component'; import { From 5a159ef99196768b252dcec37c731a93bc4dc4ca Mon Sep 17 00:00:00 2001 From: David Walsh Date: Fri, 1 Apr 2022 14:53:14 -0500 Subject: [PATCH 36/92] Dark Mode: Fix QR display (#14312) --- .../qr-hardware-popover/qr-hardware-sign-request/player.js | 4 +++- ui/css/utilities/colors.scss | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/components/app/qr-hardware-popover/qr-hardware-sign-request/player.js b/ui/components/app/qr-hardware-popover/qr-hardware-sign-request/player.js index c79dec547..ea55f6b09 100644 --- a/ui/components/app/qr-hardware-popover/qr-hardware-sign-request/player.js +++ b/ui/components/app/qr-hardware-popover/qr-hardware-sign-request/player.js @@ -43,7 +43,9 @@ const Player = ({ type, cbor, cancelQRHardwareSignRequest, toRead }) => { alignItems={ALIGN_ITEMS.CENTER} flexDirection={FLEX_DIRECTION.COLUMN} > - +
+ +
diff --git a/ui/css/utilities/colors.scss b/ui/css/utilities/colors.scss index fc3bdcd43..2d99683d9 100644 --- a/ui/css/utilities/colors.scss +++ b/ui/css/utilities/colors.scss @@ -8,4 +8,8 @@ --goerli: #3099f2; --localhost: #29b6af; --flask-purple: #8b45b6; + + // DO NOT CHANGE + // Required for the QR reader to work properly + --qr-code-white-background: '#fff'; } From dfd3e233e81f557aff0cde35a7c23fc076a44928 Mon Sep 17 00:00:00 2001 From: George Marshall Date: Sat, 2 Apr 2022 06:40:33 -0700 Subject: [PATCH 37/92] color fix for hover on swaps quote (#14334) --- ui/pages/swaps/select-quote-popover/index.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/pages/swaps/select-quote-popover/index.scss b/ui/pages/swaps/select-quote-popover/index.scss index 4ecf938d9..987a323b5 100644 --- a/ui/pages/swaps/select-quote-popover/index.scss +++ b/ui/pages/swaps/select-quote-popover/index.scss @@ -103,7 +103,6 @@ height: 64px; &:hover { - color: var(--color-text-default); background: linear-gradient(90deg, var(--color-primary-default) 0%, var(--color-primary-alternative) 101.32%); width: 100%; padding-left: 20px; From 96610742cea5514be628ca587eee8a6b1efc9cd7 Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Sat, 2 Apr 2022 17:33:35 -0400 Subject: [PATCH 38/92] Token Details component for Tokens Detected page (#14169) --- ui/components/app/app-components.scss | 1 + .../detected-token-details.js | 41 +++++++++++++++++++ .../detected-token-details.stories.js | 26 ++++++++++++ .../detected-token-details.test.js | 35 ++++++++++++++++ .../detected-token-details/index.scss | 9 ++++ 5 files changed, 112 insertions(+) create mode 100644 ui/components/app/detected-token/detected-token-details/detected-token-details.js create mode 100644 ui/components/app/detected-token/detected-token-details/detected-token-details.stories.js create mode 100644 ui/components/app/detected-token/detected-token-details/detected-token-details.test.js create mode 100644 ui/components/app/detected-token/detected-token-details/index.scss diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 4a7812116..0493d234a 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -87,3 +87,4 @@ @import 'detected-token/detected-token-address/index'; @import 'detected-token/detected-token-aggregators/index'; @import 'detected-token/detected-token-values/index'; +@import 'detected-token/detected-token-details/index' diff --git a/ui/components/app/detected-token/detected-token-details/detected-token-details.js b/ui/components/app/detected-token/detected-token-details/detected-token-details.js new file mode 100644 index 000000000..0b6d4d59a --- /dev/null +++ b/ui/components/app/detected-token/detected-token-details/detected-token-details.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; + +import Box from '../../../ui/box'; +import Identicon from '../../../ui/identicon'; +import DetectedTokenValues from '../detected-token-values/detected-token-values'; +import DetectedTokenAddress from '../detected-token-address/detected-token-address'; +import DetectedTokenAggregators from '../detected-token-aggregators/detected-token-aggregators'; +import { DISPLAY } from '../../../../helpers/constants/design-system'; +import { getTokenList } from '../../../../selectors'; + +const DetectedTokenDetails = ({ tokenAddress }) => { + const tokenList = useSelector(getTokenList); + const token = tokenList[tokenAddress]; + + return ( + + + + + + + + + ); +}; + +DetectedTokenDetails.propTypes = { + tokenAddress: PropTypes.string, +}; + +export default DetectedTokenDetails; diff --git a/ui/components/app/detected-token/detected-token-details/detected-token-details.stories.js b/ui/components/app/detected-token/detected-token-details/detected-token-details.stories.js new file mode 100644 index 000000000..fe95977f6 --- /dev/null +++ b/ui/components/app/detected-token/detected-token-details/detected-token-details.stories.js @@ -0,0 +1,26 @@ +import React from 'react'; + +import DetectedTokenDetails from './detected-token-details'; + +export default { + title: 'Components/App/DetectedToken/DetectedTokenDetails', + id: __filename, + argTypes: { + address: { control: 'text' }, + }, + args: { + address: '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', + }, +}; + +const Template = (args) => { + return ( +
+ +
+ ); +}; + +export const DefaultStory = Template.bind({}); + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/detected-token/detected-token-details/detected-token-details.test.js b/ui/components/app/detected-token/detected-token-details/detected-token-details.test.js new file mode 100644 index 000000000..4bebcb8eb --- /dev/null +++ b/ui/components/app/detected-token/detected-token-details/detected-token-details.test.js @@ -0,0 +1,35 @@ +import * as React from 'react'; +import { + renderWithProvider, + screen, + fireEvent, +} from '../../../../../test/jest'; +import configureStore from '../../../../store/store'; +import testData from '../../../../../.storybook/test-data'; + +import DetectedTokenDetails from './detected-token-details'; + +describe('DetectedTokenDetails', () => { + const args = { + tokenAddress: '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', + }; + + it('should render the detected token details', async () => { + const store = configureStore(testData); + renderWithProvider(, store); + + expect(screen.getByText('0 SNX')).toBeInTheDocument(); + expect(screen.getByText('$0')).toBeInTheDocument(); + expect(screen.getByText('Token address:')).toBeInTheDocument(); + expect(screen.getByText('0xc01...2a6f')).toBeInTheDocument(); + expect(screen.getByText('From token lists:')).toBeInTheDocument(); + expect(screen.getByText('Aave, Bancor')).toBeInTheDocument(); + expect(screen.getByText('+ 10 more')).toBeInTheDocument(); + fireEvent.click(screen.getByText('+ 10 more')); + expect( + screen.getByText( + 'Aave, Bancor, CMC, Crypto.com, CoinGecko, 1inch, Paraswap, PMM, Synthetix, Zapper, Zerion, 0x.', + ), + ).toBeInTheDocument(); + }); +}); diff --git a/ui/components/app/detected-token/detected-token-details/index.scss b/ui/components/app/detected-token/detected-token-details/index.scss new file mode 100644 index 000000000..1722f3517 --- /dev/null +++ b/ui/components/app/detected-token/detected-token-details/index.scss @@ -0,0 +1,9 @@ +.detected-token-details { + &__identicon { + margin-top: 4px; + } + + &__data { + flex-grow: 1; + } +} From fc17fbe1ad61762194f1b3e9ef302635ec4137e1 Mon Sep 17 00:00:00 2001 From: Daniel <80175477+dan437@users.noreply.github.com> Date: Mon, 4 Apr 2022 15:14:50 +0200 Subject: [PATCH 39/92] Swaps timer improvements, optional refresh rates and fix for a notification UI (#14300) * Set up correct timer value for fetching new quotes * Show red timer in Swaps if quotes fetching will happen in less than 10s (previously it was 30s) * Fix a UI issue with the notification close button * Make stx refresh rates optional, since not every network supports them --- app/scripts/controllers/swaps.js | 4 +--- ui/pages/swaps/countdown-timer/countdown-timer.js | 13 +++++++++++-- ui/pages/swaps/index.scss | 2 +- ui/pages/swaps/view-quote/view-quote.js | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js index 6eb800793..e4df14746 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.js @@ -139,9 +139,7 @@ export default class SwapsController { if ( !refreshRates || typeof refreshRates.quotes !== 'number' || - typeof refreshRates.quotesPrefetching !== 'number' || - typeof refreshRates.stxGetTransactions !== 'number' || - typeof refreshRates.stxBatchStatus !== 'number' + typeof refreshRates.quotesPrefetching !== 'number' ) { throw new Error( `MetaMask - invalid response for refreshRates: ${response}`, diff --git a/ui/pages/swaps/countdown-timer/countdown-timer.js b/ui/pages/swaps/countdown-timer/countdown-timer.js index 02dc67ce2..87ccf1b4f 100644 --- a/ui/pages/swaps/countdown-timer/countdown-timer.js +++ b/ui/pages/swaps/countdown-timer/countdown-timer.js @@ -5,7 +5,10 @@ import classnames from 'classnames'; import { Duration } from 'luxon'; import { I18nContext } from '../../../contexts/i18n'; import InfoTooltip from '../../../components/ui/info-tooltip'; -import { getSwapsQuoteRefreshTime } from '../../../ducks/swaps/swaps'; +import { + getSwapsQuoteRefreshTime, + getSwapsQuotePrefetchingRefreshTime, +} from '../../../ducks/swaps/swaps'; import { SECOND } from '../../../../shared/constants/time'; import TimerIcon from './timer-icon'; @@ -43,7 +46,13 @@ export default function CountdownTimer({ const initialTimeStartedRef = useRef(); const swapsQuoteRefreshTime = useSelector(getSwapsQuoteRefreshTime); - const timerStart = Number(timerBase) || swapsQuoteRefreshTime; + const swapsQuotePrefetchingRefreshTime = useSelector( + getSwapsQuotePrefetchingRefreshTime, + ); + const refreshTime = initialTimeStartedRef.current + ? swapsQuoteRefreshTime + : swapsQuotePrefetchingRefreshTime; + const timerStart = Number(timerBase) || refreshTime; const [currentTime, setCurrentTime] = useState(() => Date.now()); const [timer, setTimer] = useState(() => diff --git a/ui/pages/swaps/index.scss b/ui/pages/swaps/index.scss index ed0c06a33..faa2e8b9e 100644 --- a/ui/pages/swaps/index.scss +++ b/ui/pages/swaps/index.scss @@ -121,7 +121,7 @@ flex: 1; } - &__notification-close-button { + .actionable-message__message &__notification-close-button { background-color: transparent; position: absolute; right: 0; diff --git a/ui/pages/swaps/view-quote/view-quote.js b/ui/pages/swaps/view-quote/view-quote.js index a6e702e1e..d1fd19d17 100644 --- a/ui/pages/swaps/view-quote/view-quote.js +++ b/ui/pages/swaps/view-quote/view-quote.js @@ -955,7 +955,7 @@ export default function ViewQuote() {
From ae3953fd01728b226a287d870aa7c7cbd43d7b71 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Mon, 4 Apr 2022 08:54:08 -0500 Subject: [PATCH 40/92] Update eth-ledger-bridge-keyring to 0.11.0 (#14317) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index db83b67cb..46acc0881 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "@metamask/contract-metadata": "^1.31.0", "@metamask/controllers": "^27.0.0", "@metamask/design-tokens": "^1.5.1", - "@metamask/eth-ledger-bridge-keyring": "^0.10.0", + "@metamask/eth-ledger-bridge-keyring": "^0.11.0", "@metamask/eth-token-tracker": "^4.0.0", "@metamask/etherscan-link": "^2.1.0", "@metamask/iframe-execution-environment-service": "^0.10.7", diff --git a/yarn.lock b/yarn.lock index b6f0c19a7..bdd50daa9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2827,10 +2827,10 @@ resolved "https://registry.yarnpkg.com/@metamask/eslint-config/-/eslint-config-9.0.0.tgz#22d4911b705f7e4e566efbdda0e37912da33e30f" integrity sha512-mWlLGQKjXXFOj9EtDClKSoTLeQuPW2kM1w3EpUMf4goYAQ+kLXCCa8pEff6h8ApWAnjhYmXydA1znQ2J4XvD+A== -"@metamask/eth-ledger-bridge-keyring@^0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@metamask/eth-ledger-bridge-keyring/-/eth-ledger-bridge-keyring-0.10.0.tgz#9d5103be22221f4ef71393a2e11f24b788e343a5" - integrity sha512-ewcnEFmIL2lkUta811yQeJVWhTjll9U62GdbuauvxdQ0c6VBGZnf02GU3gcxyMOcEvZBnlU+d5LWpURQA8iNZQ== +"@metamask/eth-ledger-bridge-keyring@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-ledger-bridge-keyring/-/eth-ledger-bridge-keyring-0.11.0.tgz#8502e2fd36c89aff7de6724354217274917cecd3" + integrity sha512-fCwM8LYC6SXLfsKc4oNiAatz2X8p/pjbM5zMfm4nb4sZPshBAWU32M4vnB3BSVeQEsisGuLfOWCOWhxmq25n+Q== dependencies: "@ethereumjs/tx" "^3.2.0" eth-sig-util "^2.0.0" From 6e73c34e2b2f15020a4810f9d6a404aa28debd3e Mon Sep 17 00:00:00 2001 From: George Marshall Date: Mon, 4 Apr 2022 07:17:54 -0700 Subject: [PATCH 41/92] Updatin contract deployment text label to be darker (#14333) --- .../confirm-page-container-summary/index.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss index 24a3fa620..05e7132e4 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss @@ -23,7 +23,7 @@ &__action { @include H7; - color: var(--color-text-muted); + color: var(--color-text-alternative); padding: 3px 8px; border: 1px solid var(--color-border-default); border-radius: 4px; From e77ff0bce8e9e531c33a99c9de511b3afa5168f7 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Mon, 4 Apr 2022 16:32:49 +0200 Subject: [PATCH 42/92] Fix a few Snap state issues (#14339) * Fix clearSnapState * Simplify getSnapState --- app/scripts/metamask-controller.js | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 3a55881d8..9f5311d05 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1034,10 +1034,10 @@ export default class MetamaskController extends EventEmitter { 'SnapController:add', ), clearSnapState: (fromSubject) => - this.controllerMessenger( - 'SnapController:updateSnap', + this.controllerMessenger.call( + 'SnapController:updateSnapState', fromSubject, - {}, + null, ), getMnemonic: this.getPrimaryKeyringMnemonic.bind(this), getSnap: this.controllerMessenger.call.bind( @@ -1048,16 +1048,10 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger, 'SnapController:getRpcMessageHandler', ), - getSnapState: async (...args) => { - // TODO:flask Just return the action result directly in the next - // @metamask/snap-controllers update. - return ( - (await this.controllerMessenger.call( - 'SnapController:getSnapState', - ...args, - )) ?? null - ); - }, + getSnapState: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapController:getSnapState', + ), showConfirmation: (origin, confirmationData) => this.approvalController.addAndShowApprovalRequest({ origin, From beb7a0d7b5bb32225c0ad3f2c1f0c786e8108a03 Mon Sep 17 00:00:00 2001 From: PeterYinusa <53189696+PeterYinusa@users.noreply.github.com> Date: Mon, 4 Apr 2022 16:02:29 +0100 Subject: [PATCH 43/92] Mock token list (#14338) * mock token list * remove redundant state --- test/e2e/fixtures/custom-token/state.json | 4 - test/e2e/fixtures/imported-account/state.json | 355 ------------------ .../fixtures/navigate-transactions/state.json | 355 ------------------ test/e2e/mock-e2e.js | 30 ++ 4 files changed, 30 insertions(+), 714 deletions(-) diff --git a/test/e2e/fixtures/custom-token/state.json b/test/e2e/fixtures/custom-token/state.json index 158ab303b..da6098fcf 100644 --- a/test/e2e/fixtures/custom-token/state.json +++ b/test/e2e/fixtures/custom-token/state.json @@ -123,10 +123,6 @@ "usePhishDetect": true, "useStaticTokenList": false }, - "TokenListController": { - "tokenList": {}, - "tokensChainsCache": {} - }, "TokensController": { "allTokens": { "0x539": { diff --git a/test/e2e/fixtures/imported-account/state.json b/test/e2e/fixtures/imported-account/state.json index 449f6018b..c6e2f4a8e 100644 --- a/test/e2e/fixtures/imported-account/state.json +++ b/test/e2e/fixtures/imported-account/state.json @@ -91,361 +91,6 @@ } ] }, - "TokenListController": { - "tokenList": { - "0xbbbbca6a901c926f240b89eacb641d8aec7aeafd": { - "address": "0xbbbbca6a901c926f240b89eacb641d8aec7aeafd", - "symbol": "LRC", - "decimals": 18, - "name": "Loopring", - "iconUrl": "", - "aggregators": [ - "airswapLight", - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 12 - }, - "0x04fa0d235c4abf4bcf4787af4cf447de572ef828": { - "address": "0x04fa0d235c4abf4bcf4787af4cf447de572ef828", - "symbol": "UMA", - "decimals": 18, - "name": "UMA", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2": { - "address": "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2", - "symbol": "SUSHI", - "decimals": 18, - "name": "SushiSwap", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - "0xd533a949740bb3306d119cc777fa900ba034cd52": { - "address": "0xd533a949740bb3306d119cc777fa900ba034cd52", - "symbol": "CRV", - "decimals": 18, - "name": "Curve DAO Token", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - "0xc00e94cb662c3520282e6f5717214004a7f26888": { - "address": "0xc00e94cb662c3520282e6f5717214004a7f26888", - "symbol": "COMP", - "decimals": 18, - "name": "Compound", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - "0xba100000625a3754423978a60c9317c58a424e3d": { - "address": "0xba100000625a3754423978a60c9317c58a424e3d", - "symbol": "BAL", - "decimals": 18, - "name": "Balancer", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0": { - "address": "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0", - "symbol": "MATIC", - "decimals": 18, - "name": "Polygon", - "iconUrl": "", - "aggregators": [ - "airswapLight", - "bancor", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - "0x0d8775f648430679a709e98d2b0cb6250d2887ef": { - "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", - "symbol": "BAT", - "decimals": 18, - "name": "Basic Attention Tok", - "iconUrl": "", - "aggregators": [ - "airswapLight", - "bancor", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - } - }, - "tokensChainsCache": { - "1": { - "timestamp": 1628769574961, - "data": [ - { - "address": "0xbbbbca6a901c926f240b89eacb641d8aec7aeafd", - "symbol": "LRC", - "decimals": 18, - "name": "Loopring", - "iconUrl": "", - "aggregators": [ - "airswapLight", - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 12 - }, - { - "address": "0x04fa0d235c4abf4bcf4787af4cf447de572ef828", - "symbol": "UMA", - "decimals": 18, - "name": "UMA", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - { - "address": "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2", - "symbol": "SUSHI", - "decimals": 18, - "name": "SushiSwap", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - { - "address": "0xd533a949740bb3306d119cc777fa900ba034cd52", - "symbol": "CRV", - "decimals": 18, - "name": "Curve DAO Token", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - { - "address": "0xc00e94cb662c3520282e6f5717214004a7f26888", - "symbol": "COMP", - "decimals": 18, - "name": "Compound", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - { - "address": "0xba100000625a3754423978a60c9317c58a424e3d", - "symbol": "BAL", - "decimals": 18, - "name": "Balancer", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - { - "address": "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0", - "symbol": "MATIC", - "decimals": 18, - "name": "Polygon", - "iconUrl": "", - "aggregators": [ - "airswapLight", - "bancor", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - { - "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", - "symbol": "BAT", - "decimals": 18, - "name": "Basic Attention Tok", - "iconUrl": "", - "aggregators": [ - "airswapLight", - "bancor", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - } - ] - }, - "3": { - "timestamp": 1628769543620 - }, - "1337": { - "timestamp": 1628769513476 - } - } - }, "PreferencesController": { "accountTokens": { "0x5cfe73b6021e818b776b421b1c4db2474086a7e1": { diff --git a/test/e2e/fixtures/navigate-transactions/state.json b/test/e2e/fixtures/navigate-transactions/state.json index 16aae0325..6760e0d4f 100644 --- a/test/e2e/fixtures/navigate-transactions/state.json +++ b/test/e2e/fixtures/navigate-transactions/state.json @@ -91,361 +91,6 @@ } ] }, - "TokenListController": { - "tokenList": { - "0xbbbbca6a901c926f240b89eacb641d8aec7aeafd": { - "address": "0xbbbbca6a901c926f240b89eacb641d8aec7aeafd", - "symbol": "LRC", - "decimals": 18, - "name": "Loopring", - "iconUrl": "", - "aggregators": [ - "airswapLight", - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 12 - }, - "0x04fa0d235c4abf4bcf4787af4cf447de572ef828": { - "address": "0x04fa0d235c4abf4bcf4787af4cf447de572ef828", - "symbol": "UMA", - "decimals": 18, - "name": "UMA", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2": { - "address": "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2", - "symbol": "SUSHI", - "decimals": 18, - "name": "SushiSwap", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - "0xd533a949740bb3306d119cc777fa900ba034cd52": { - "address": "0xd533a949740bb3306d119cc777fa900ba034cd52", - "symbol": "CRV", - "decimals": 18, - "name": "Curve DAO Token", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - "0xc00e94cb662c3520282e6f5717214004a7f26888": { - "address": "0xc00e94cb662c3520282e6f5717214004a7f26888", - "symbol": "COMP", - "decimals": 18, - "name": "Compound", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - "0xba100000625a3754423978a60c9317c58a424e3d": { - "address": "0xba100000625a3754423978a60c9317c58a424e3d", - "symbol": "BAL", - "decimals": 18, - "name": "Balancer", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0": { - "address": "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0", - "symbol": "MATIC", - "decimals": 18, - "name": "Polygon", - "iconUrl": "", - "aggregators": [ - "airswapLight", - "bancor", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - "0x0d8775f648430679a709e98d2b0cb6250d2887ef": { - "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", - "symbol": "BAT", - "decimals": 18, - "name": "Basic Attention Tok", - "iconUrl": "", - "aggregators": [ - "airswapLight", - "bancor", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - } - }, - "tokensChainsCache": { - "1": { - "timestamp": 1628769574961, - "data": [ - { - "address": "0xbbbbca6a901c926f240b89eacb641d8aec7aeafd", - "symbol": "LRC", - "decimals": 18, - "name": "Loopring", - "iconUrl": "", - "aggregators": [ - "airswapLight", - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 12 - }, - { - "address": "0x04fa0d235c4abf4bcf4787af4cf447de572ef828", - "symbol": "UMA", - "decimals": 18, - "name": "UMA", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - { - "address": "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2", - "symbol": "SUSHI", - "decimals": 18, - "name": "SushiSwap", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - { - "address": "0xd533a949740bb3306d119cc777fa900ba034cd52", - "symbol": "CRV", - "decimals": 18, - "name": "Curve DAO Token", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - { - "address": "0xc00e94cb662c3520282e6f5717214004a7f26888", - "symbol": "COMP", - "decimals": 18, - "name": "Compound", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - { - "address": "0xba100000625a3754423978a60c9317c58a424e3d", - "symbol": "BAL", - "decimals": 18, - "name": "Balancer", - "iconUrl": "", - "aggregators": [ - "bancor", - "cmc", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - { - "address": "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0", - "symbol": "MATIC", - "decimals": 18, - "name": "Polygon", - "iconUrl": "", - "aggregators": [ - "airswapLight", - "bancor", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - { - "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", - "symbol": "BAT", - "decimals": 18, - "name": "Basic Attention Tok", - "iconUrl": "", - "aggregators": [ - "airswapLight", - "bancor", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - } - ] - }, - "3": { - "timestamp": 1628769543620 - }, - "1337": { - "timestamp": 1628769513476 - } - } - }, "PreferencesController": { "accountTokens": { "0x5cfe73b6021e818b776b421b1c4db2474086a7e1": { diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index fa0d0b30f..1487567fb 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -57,6 +57,36 @@ async function setupMocking(server, testSpecificMock) { }; }); + await server + .forGet('https://token-api.metaswap.codefi.network/tokens/1337') + .thenCallback(() => { + return { + statusCode: 200, + json: [ + { + address: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', + symbol: 'BAT', + decimals: 18, + name: 'Basic Attention Token', + iconUrl: + 'https://assets.coingecko.com/coins/images/677/thumb/basic-attention-token.png?1547034427', + aggregators: [ + 'aave', + 'bancor', + 'coinGecko', + 'oneInch', + 'paraswap', + 'pmm', + 'zapper', + 'zerion', + 'zeroEx', + ], + occurrences: 9, + }, + ], + }; + }); + testSpecificMock(server); } From 1582efdc061870f0208c512b27c32e627aed3d75 Mon Sep 17 00:00:00 2001 From: PeterYinusa <53189696+PeterYinusa@users.noreply.github.com> Date: Mon, 4 Apr 2022 16:02:54 +0100 Subject: [PATCH 44/92] update chromedriver and chrome binary to v100 (#14337) --- .circleci/scripts/chrome-install.sh | 4 ++-- package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.circleci/scripts/chrome-install.sh b/.circleci/scripts/chrome-install.sh index e2cbd7bdc..8be51ad30 100755 --- a/.circleci/scripts/chrome-install.sh +++ b/.circleci/scripts/chrome-install.sh @@ -5,12 +5,12 @@ set -u set -o pipefail # To get the latest version, see -CHROME_VERSION='99.0.4844.51-1' +CHROME_VERSION='100.0.4896.60-1' CHROME_BINARY="google-chrome-stable_${CHROME_VERSION}_amd64.deb" CHROME_BINARY_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/${CHROME_BINARY}" # To retrieve this checksum, run the `wget` and `shasum` commands below -CHROME_BINARY_SHA512SUM='f0fa5c6c23d9e8373aafa14622ed4362cbf3a101691ce309864e4aa0030dc3bf7b8e7c8ce294c06106b26eb6b8dc0f2b80376bf2a49d703fc9f6597961b9432e' +CHROME_BINARY_SHA512SUM='d7a98777650e8218fef4acc8466d4ddf5e234b97fbc16c33f38f69f9ebfe7b6bb6827a90aad15ea729d7c1bacfa78488c5d5194b531f00f454302dd9c0957e4e' wget -O "${CHROME_BINARY}" -t 5 "${CHROME_BINARY_URL}" diff --git a/package.json b/package.json index 46acc0881..8e189f8fb 100644 --- a/package.json +++ b/package.json @@ -282,7 +282,7 @@ "browser-util-inspect": "^0.2.0", "browserify": "^16.5.1", "chalk": "^3.0.0", - "chromedriver": "^99.0.0", + "chromedriver": "^100.0.0", "concurrently": "^5.2.0", "copy-webpack-plugin": "^6.0.3", "cross-spawn": "^7.0.3", diff --git a/yarn.lock b/yarn.lock index bdd50daa9..c1e69d562 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8006,10 +8006,10 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -chromedriver@^99.0.0: - version "99.0.0" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-99.0.0.tgz#fbfcc7e74991dd50962e7dd456d78eaf49f56774" - integrity sha512-pyB+5LuyZdb7EBPL3i5D5yucZUD+SlkdiUtmpjaEnLd9zAXp+SvD/hP5xF4l/ZmWvUo/1ZLxAI1YBdhazGTpgA== +chromedriver@^100.0.0: + version "100.0.0" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-100.0.0.tgz#1b4bf5c89cea12c79f53bc94d8f5bb5aa79ed7be" + integrity sha512-oLfB0IgFEGY9qYpFQO/BNSXbPw7bgfJUN5VX8Okps9W2qNT4IqKh5hDwKWtpUIQNI6K3ToWe2/J5NdpurTY02g== dependencies: "@testim/chrome-version" "^1.1.2" axios "^0.24.0" From 4bcb48df53e3a6f723bdcec1a4b6eac136201219 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 4 Apr 2022 12:37:01 -0230 Subject: [PATCH 45/92] Replace `ReadOnlyInput` (#14330) Replace the last two uses of the `ReadOnlyInput` with a div. The idea of using an input for read-only data is silly. We can just put it in the DOM directly instead. --- .../export-private-key-modal.component.js | 19 ++++------ .../export-private-key-modal/index.scss | 30 +++++---------- ui/components/ui/readonly-input/index.js | 1 - ui/components/ui/readonly-input/index.scss | 8 ---- .../ui/readonly-input/readonly-input.js | 38 ------------------- ui/components/ui/ui-components.scss | 1 - 6 files changed, 17 insertions(+), 80 deletions(-) delete mode 100644 ui/components/ui/readonly-input/index.js delete mode 100644 ui/components/ui/readonly-input/index.scss delete mode 100644 ui/components/ui/readonly-input/readonly-input.js diff --git a/ui/components/app/modals/export-private-key-modal/export-private-key-modal.component.js b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.component.js index ae378f3ce..2b5cb0acb 100644 --- a/ui/components/app/modals/export-private-key-modal/export-private-key-modal.component.js +++ b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.component.js @@ -4,7 +4,6 @@ import React, { Component } from 'react'; import { stripHexPrefix } from 'ethereumjs-util'; import copyToClipboard from 'copy-to-clipboard'; -import ReadOnlyInput from '../../../ui/readonly-input'; import Button from '../../../ui/button'; import AccountModalContainer from '../account-modal-container'; import { toChecksumHexAddress } from '../../../../../shared/modules/hexstring-utils'; @@ -78,13 +77,12 @@ export default class ExportPrivateKeyModal extends Component { } return ( - copyToClipboard(plainKey)} - /> + > + {plainKey} +
); } @@ -147,10 +145,9 @@ export default class ExportPrivateKeyModal extends Component { backButtonAction={() => showAccountDetailModal()} > {name} - +
+ {toChecksumHexAddress(address)} +
{this.context.t('showPrivateKeys')} diff --git a/ui/components/app/modals/export-private-key-modal/index.scss b/ui/components/app/modals/export-private-key-modal/index.scss index 3734d04a4..46e12a102 100644 --- a/ui/components/app/modals/export-private-key-modal/index.scss +++ b/ui/components/app/modals/export-private-key-modal/index.scss @@ -65,24 +65,17 @@ margin-top: 18px; } - &__password-display-wrapper { + &__private-key-display { + @include Paragraph; + height: 80px; width: 291px; border: 1px solid var(--color-border-default); border-radius: 2px; - } - - &__password-display-textarea { - @include Paragraph; - color: var(--color-error-default); - background-color: var(--color-background-default); - border: none; - height: 75px; - width: 100%; - overflow: hidden; - resize: none; padding: 9px 13px 8px; + overflow: hidden; + overflow-wrap: break-word; } &__buttons { @@ -104,18 +97,13 @@ } .ellip-address-wrapper { - display: flex; - justify-content: center; border: 1px solid var(--color-border-default); padding: 5px 10px; margin-top: 7px; - width: 286px; - - input { - background: var(--color-background-default); - color: var(--color-text-default); - border: 0; - } + max-width: 286px; + direction: ltr; + overflow: hidden; + text-overflow: ellipsis; } } diff --git a/ui/components/ui/readonly-input/index.js b/ui/components/ui/readonly-input/index.js deleted file mode 100644 index f944d6a89..000000000 --- a/ui/components/ui/readonly-input/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './readonly-input'; diff --git a/ui/components/ui/readonly-input/index.scss b/ui/components/ui/readonly-input/index.scss deleted file mode 100644 index 9eff60bbb..000000000 --- a/ui/components/ui/readonly-input/index.scss +++ /dev/null @@ -1,8 +0,0 @@ -.readonly-input { - &__input { - direction: ltr; - overflow: hidden; - text-overflow: ellipsis; - width: 100%; - } -} diff --git a/ui/components/ui/readonly-input/readonly-input.js b/ui/components/ui/readonly-input/readonly-input.js deleted file mode 100644 index 30fbce0e2..000000000 --- a/ui/components/ui/readonly-input/readonly-input.js +++ /dev/null @@ -1,38 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import classnames from 'classnames'; - -export default function ReadOnlyInput(props) { - const { - wrapperClass = '', - inputClass = '', - value, - textarea, - onClick, - autoFocus = false, - } = props; - - const InputType = textarea ? 'textarea' : 'input'; - - return ( -
- event.target.select()} - onClick={onClick} - autoFocus={autoFocus} - /> -
- ); -} - -ReadOnlyInput.propTypes = { - wrapperClass: PropTypes.string, - inputClass: PropTypes.string, - value: PropTypes.string, - textarea: PropTypes.bool, - onClick: PropTypes.func, - autoFocus: PropTypes.bool, -}; diff --git a/ui/components/ui/ui-components.scss b/ui/components/ui/ui-components.scss index ff57d3769..52c0a8e95 100644 --- a/ui/components/ui/ui-components.scss +++ b/ui/components/ui/ui-components.scss @@ -40,7 +40,6 @@ @import 'pulse-loader/index'; @import 'qr-code/index'; @import 'radio-group/index'; -@import 'readonly-input/index'; @import 'sender-to-recipient/index'; @import 'show-hide-toggle/index.scss'; @import 'snackbar/index'; From 78828f1b6e3bb135492d933a319b6e7e4b305f1a Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Mon, 4 Apr 2022 18:27:36 +0200 Subject: [PATCH 46/92] Add e2e testcase for Settings Search functionality (#14259) * Settings Search e2e testcase * Remove unnecessary variable * Include assertations for correct item urls * Remove extra assertions * Change assertion of url for page main header * Remove un-used keys for urls * Fix variable scope --- test/e2e/tests/settings-search.spec.js | 265 +++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 test/e2e/tests/settings-search.spec.js diff --git a/test/e2e/tests/settings-search.spec.js b/test/e2e/tests/settings-search.spec.js new file mode 100644 index 000000000..7137cb4ca --- /dev/null +++ b/test/e2e/tests/settings-search.spec.js @@ -0,0 +1,265 @@ +const { strict: assert } = require('assert'); +const { convertToHexValue, withFixtures } = require('../helpers'); + +describe('Settings Search', function () { + const ganacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: convertToHexValue(25000000000000000000), + }, + ], + }; + const settingsSearch = { + general: 'Primary Currency', + advanced: 'State Logs', + contacts: 'Contacts', + security: 'Reveal Secret', + alerts: 'Browsing a website', + networks: 'Ethereum Mainnet', + experimental: 'Token Detection', + about: 'Terms of Use', + }; + + it('should find element inside the General tab', async function () { + await withFixtures( + { + dapp: true, + fixtures: 'imported-account', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await driver.clickElement('.account-menu__icon'); + await driver.clickElement({ text: 'Settings', tag: 'div' }); + await driver.fill('#search-settings', settingsSearch.general); + + await driver.clickElement({ text: 'General', tag: 'span' }); + assert.equal( + await driver.isElementPresent({ text: 'General', tag: 'div' }), + true, + 'Item does not redirect to correct page', + ); + }, + ); + }); + it('should find element inside the Advanced tab', async function () { + await withFixtures( + { + dapp: true, + fixtures: 'imported-account', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await driver.clickElement('.account-menu__icon'); + await driver.clickElement({ text: 'Settings', tag: 'div' }); + await driver.fill('#search-settings', settingsSearch.advanced); + + // Check if element redirects to the correct page + await driver.clickElement({ text: 'Advanced', tag: 'span' }); + assert.equal( + await driver.isElementPresent({ text: 'Advanced', tag: 'div' }), + true, + 'Item does not redirect to correct page', + ); + }, + ); + }); + it('should find element inside the Contacts tab', async function () { + await withFixtures( + { + dapp: true, + fixtures: 'imported-account', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await driver.clickElement('.account-menu__icon'); + await driver.clickElement({ text: 'Settings', tag: 'div' }); + await driver.fill('#search-settings', settingsSearch.contacts); + + // Check if element redirects to the correct page + await driver.clickElement({ text: 'Contacts', tag: 'span' }); + assert.equal( + await driver.isElementPresent({ text: 'Contacts', tag: 'div' }), + true, + 'Item does not redirect to correct page', + ); + }, + ); + }); + it('should find element inside the Security tab', async function () { + await withFixtures( + { + dapp: true, + fixtures: 'imported-account', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await driver.clickElement('.account-menu__icon'); + await driver.clickElement({ text: 'Settings', tag: 'div' }); + await driver.fill('#search-settings', settingsSearch.security); + + // Check if element redirects to the correct page + await driver.clickElement({ text: 'Security', tag: 'span' }); + assert.equal( + await driver.isElementPresent({ text: 'Security', tag: 'div' }), + true, + 'Item does not redirect to correct page', + ); + }, + ); + }); + it('should find element inside the Alerts tab', async function () { + await withFixtures( + { + dapp: true, + fixtures: 'imported-account', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await driver.clickElement('.account-menu__icon'); + await driver.clickElement({ text: 'Settings', tag: 'div' }); + await driver.fill('#search-settings', settingsSearch.alerts); + + // Check if element redirects to the correct page + await driver.clickElement({ text: 'Alerts', tag: 'span' }); + assert.equal( + await driver.isElementPresent({ text: 'Alerts', tag: 'div' }), + true, + 'Item does not redirect to correct page', + ); + }, + ); + }); + it('should find element inside the Networks tab', async function () { + await withFixtures( + { + dapp: true, + fixtures: 'imported-account', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await driver.clickElement('.account-menu__icon'); + await driver.clickElement({ text: 'Settings', tag: 'div' }); + await driver.fill('#search-settings', settingsSearch.networks); + + // Check if element redirects to the correct page + await driver.clickElement({ text: 'Networks', tag: 'span' }); + assert.equal( + await driver.isElementPresent({ text: 'Networks', tag: 'div' }), + true, + 'Item does not redirect to correct page', + ); + }, + ); + }); + it('should find element inside the Experimental tab', async function () { + await withFixtures( + { + dapp: true, + fixtures: 'imported-account', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await driver.clickElement('.account-menu__icon'); + await driver.clickElement({ text: 'Settings', tag: 'div' }); + await driver.fill('#search-settings', settingsSearch.experimental); + + // Check if element redirects to the correct page + await driver.clickElement({ text: 'Experimental', tag: 'span' }); + assert.equal( + await driver.isElementPresent({ text: 'Experimental', tag: 'div' }), + true, + 'Item does not redirect to correct page', + ); + }, + ); + }); + it('should find element inside the About tab', async function () { + await withFixtures( + { + dapp: true, + fixtures: 'imported-account', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await driver.clickElement('.account-menu__icon'); + await driver.clickElement({ text: 'Settings', tag: 'div' }); + await driver.fill('#search-settings', settingsSearch.about); + + // Check if element redirects to the correct page + await driver.clickElement({ text: 'About', tag: 'span' }); + assert.equal( + await driver.isElementPresent({ text: 'About', tag: 'div' }), + true, + 'Item does not redirect to correct page', + ); + }, + ); + }); + it('should display "Element not found" for a non-existing element', async function () { + await withFixtures( + { + dapp: true, + fixtures: 'imported-account', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await driver.clickElement('.account-menu__icon'); + await driver.clickElement({ text: 'Settings', tag: 'div' }); + await driver.fill('#search-settings', 'Lorem ipsum'); + + const found = await driver.isElementPresent({ + text: 'No matching results found', + tag: 'span', + }); + assert.equal(found, true, 'Non existent element was found'); + }, + ); + }); +}); From 7e7b97d7c9eeb42096ec707f178f9d0997aff326 Mon Sep 17 00:00:00 2001 From: ryanml Date: Mon, 4 Apr 2022 10:36:56 -0700 Subject: [PATCH 47/92] Improving identicon settings accessibility (#13760) * Improving identicon settings accessibility * Make settings keyboard accessible --- ui/pages/settings/index.scss | 2 ++ .../settings-tab/settings-tab.component.js | 20 +++++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ui/pages/settings/index.scss b/ui/pages/settings/index.scss index bea6e6e6e..7696fce54 100644 --- a/ui/pages/settings/index.scss +++ b/ui/pages/settings/index.scss @@ -298,6 +298,8 @@ flex-direction: row; align-items: center; justify-content: space-between; + background: unset; + border: 1px solid transparent; &__icon { &--active { diff --git a/ui/pages/settings/settings-tab/settings-tab.component.js b/ui/pages/settings/settings-tab/settings-tab.component.js index 8a2654cc2..aacff5327 100644 --- a/ui/pages/settings/settings-tab/settings-tab.component.js +++ b/ui/pages/settings/settings-tab/settings-tab.component.js @@ -200,16 +200,18 @@ export default class SettingsTab extends PureComponent { {t('jazzAndBlockies')}
-
+ +
From 0556feb14295e8bcb78bbabbff5e35c275b4604f Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Mon, 4 Apr 2022 19:40:24 +0200 Subject: [PATCH 48/92] Add snap version to details page (#14110) * Start to add snap version to details page * Show install date * Clean up * Address PR comments --- app/_locales/en/messages.json | 4 ++++ ui/pages/settings/flask/view-snap/index.scss | 17 +++++++++++-- .../settings/flask/view-snap/view-snap.js | 24 +++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 4ae002665..2da5b1ebe 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2895,6 +2895,10 @@ "message": "$1 snap has access to:", "description": "$1 represents the name of the snap" }, + "snapAdded": { + "message": "Added on $1 from $2", + "description": "$1 represents the date the snap was installed, $2 represents which origin installed the snap." + }, "snapError": { "message": "Snap Error: '$1'. Error Code: '$2'", "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." diff --git a/ui/pages/settings/flask/view-snap/index.scss b/ui/pages/settings/flask/view-snap/index.scss index eec82c196..a57304df6 100644 --- a/ui/pages/settings/flask/view-snap/index.scss +++ b/ui/pages/settings/flask/view-snap/index.scss @@ -8,14 +8,14 @@ &__subheader { padding: 16px 4px; border-bottom: 1px solid var(--color-border-muted); - margin-right: 24px; + margin-inline-end: 24px; height: 72px; align-items: center; display: flex; flex-flow: row nowrap; @media screen and (max-width: $break-small) { - margin-right: 0; + margin-inline-end: 0; padding: 0 0 16px; flex-direction: column; align-items: center; @@ -24,6 +24,19 @@ } } + &__install-details { + border-bottom: 1px solid var(--color-border-muted); + margin-inline-end: 24px; + + @media screen and (max-width: $break-small) { + margin-inline-end: 0; + } + } + + &__version { + font-family: monospace; + } + &__title { @media screen and (max-width: $break-small) { padding-bottom: 16px; diff --git a/ui/pages/settings/flask/view-snap/view-snap.js b/ui/pages/settings/flask/view-snap/view-snap.js index 4c39a86c2..139add018 100644 --- a/ui/pages/settings/flask/view-snap/view-snap.js +++ b/ui/pages/settings/flask/view-snap/view-snap.js @@ -25,6 +25,7 @@ import { removePermissionsFor, } from '../../../../store/actions'; import { getSnaps, getSubjectsWithPermission } from '../../../../selectors'; +import { formatDate } from '../../../../helpers/utils/util'; function ViewSnap() { const t = useI18nContext(); @@ -71,6 +72,10 @@ function ViewSnap() { if (!snap) { return null; } + + const versionHistory = snap.versionHistory ?? []; + const [firstInstall] = versionHistory; + return (
@@ -100,6 +105,25 @@ function ViewSnap() {
+ + {firstInstall && ( + + {t('snapAdded', [ + formatDate(firstInstall.date, 'MMMM d, y'), + firstInstall.origin, + ])} + + )} + + {t('shorthandVersion', [snap.version])} + + Date: Mon, 4 Apr 2022 16:44:32 -0230 Subject: [PATCH 49/92] Restore `version` missing from certain build steps (#14344) Certain build steps accidentally omitted the `version` variable. It has now been restored to all steps, ensuring that all environment variables are correctly injected into all bundles. A check has been added to the Sentry setup module to ensure the release is not omitted in the future. --- app/scripts/lib/setupSentry.js | 7 ++++--- development/build/scripts.js | 13 ++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 052be66ce..7332041bb 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -68,9 +68,9 @@ export const SENTRY_STATE = { }; export default function setupSentry({ release, getState }) { - let sentryTarget; - - if (METAMASK_DEBUG) { + if (!release) { + throw new Error('Missing release'); + } else if (METAMASK_DEBUG) { return undefined; } @@ -79,6 +79,7 @@ export default function setupSentry({ release, getState }) { ? METAMASK_ENVIRONMENT : `${METAMASK_ENVIRONMENT}-${METAMASK_BUILD_TYPE}`; + let sentryTarget; if (METAMASK_ENVIRONMENT === 'production') { if (!process.env.SENTRY_DSN) { throw new Error( diff --git a/development/build/scripts.js b/development/build/scripts.js index d2c369c52..f942c4c1b 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -269,6 +269,7 @@ function createScriptTasks({ testing, policyOnly, shouldLintFenceFiles, + version, }); } @@ -285,6 +286,7 @@ function createScriptTasks({ testing, policyOnly, shouldLintFenceFiles, + version, }); } @@ -301,6 +303,7 @@ function createScriptTasks({ testing, policyOnly, shouldLintFenceFiles, + version, }); } @@ -320,6 +323,7 @@ function createScriptTasks({ policyOnly, shouldLintFenceFiles, testing, + version, }), createNormalBundle({ buildType, @@ -332,6 +336,7 @@ function createScriptTasks({ policyOnly, shouldLintFenceFiles, testing, + version, }), ); } @@ -532,6 +537,7 @@ function createNormalBundle({ modulesToExpose, shouldLintFenceFiles, testing, + version, }) { return async function () { // create bundler setup and apply defaults @@ -543,7 +549,12 @@ function createNormalBundle({ const reloadOnChange = Boolean(devMode); const minify = Boolean(devMode) === false; - const envVars = getEnvironmentVariables({ buildType, devMode, testing }); + const envVars = getEnvironmentVariables({ + buildType, + devMode, + testing, + version, + }); setupBundlerDefaults(buildConfiguration, { buildType, devMode, From d6df3700f7356772bdbb689c389ff1ad4ea9e4ef Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Mon, 4 Apr 2022 14:26:13 -0500 Subject: [PATCH 50/92] track rpc method usage (#14269) --- .../lib/createRPCMethodTrackingMiddleware.js | 86 +++++++++++++++++++ app/scripts/metamask-controller.js | 12 +++ shared/constants/metametrics.js | 7 ++ 3 files changed, 105 insertions(+) create mode 100644 app/scripts/lib/createRPCMethodTrackingMiddleware.js diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.js new file mode 100644 index 000000000..0b47c3462 --- /dev/null +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.js @@ -0,0 +1,86 @@ +import { EVENT_NAMES } from '../../../shared/constants/metametrics'; +import { SECOND } from '../../../shared/constants/time'; + +const USER_PROMPTED_EVENT_NAME_MAP = { + eth_signTypedData_v4: EVENT_NAMES.SIGNATURE_REQUESTED, + eth_signTypedData_v3: EVENT_NAMES.SIGNATURE_REQUESTED, + eth_signTypedData: EVENT_NAMES.SIGNATURE_REQUESTED, + eth_personal_sign: EVENT_NAMES.SIGNATURE_REQUESTED, + eth_sign: EVENT_NAMES.SIGNATURE_REQUESTED, + eth_getEncryptionPublicKey: EVENT_NAMES.ENCRYPTION_PUBLIC_KEY_REQUESTED, + eth_decrypt: EVENT_NAMES.DECRYPTION_REQUESTED, + wallet_requestPermissions: EVENT_NAMES.PERMISSIONS_REQUESTED, + eth_requestAccounts: EVENT_NAMES.PERMISSIONS_REQUESTED, +}; + +const samplingTimeouts = {}; + +/** + * Returns a middleware that tracks inpage_provider usage using sampling for + * each type of event except those that require user interaction, such as + * signature requests + * + * @param {object} opts - options for the rpc method tracking middleware + * @param {Function} opts.trackEvent - trackEvent method from MetaMetricsController + * @param {Function} opts.getMetricsState - get the state of MetaMetricsController + * @returns {Function} + */ +export default function createRPCMethodTrackingMiddleware({ + trackEvent, + getMetricsState, +}) { + return function rpcMethodTrackingMiddleware( + /** @type {any} */ req, + /** @type {any} */ res, + /** @type {Function} */ next, + ) { + const startTime = Date.now(); + const { origin } = req; + + next((callback) => { + const endTime = Date.now(); + if (!getMetricsState().participateInMetaMetrics) { + return callback(); + } + if (USER_PROMPTED_EVENT_NAME_MAP[req.method]) { + const userRejected = res.error?.code === 4001; + trackEvent({ + event: USER_PROMPTED_EVENT_NAME_MAP[req.method], + category: 'inpage_provider', + referrer: { + url: origin, + }, + properties: { + method: req.method, + status: userRejected ? 'rejected' : 'approved', + error_code: res.error?.code, + error_message: res.error?.message, + has_result: typeof res.result !== 'undefined', + duration: endTime - startTime, + }, + }); + } else if (typeof samplingTimeouts[req.method] === 'undefined') { + trackEvent({ + event: 'Provider Method Called', + category: 'inpage_provider', + referrer: { + url: origin, + }, + properties: { + method: req.method, + error_code: res.error?.code, + error_message: res.error?.message, + has_result: typeof res.result !== 'undefined', + duration: endTime - startTime, + }, + }); + // Only record one call to this method every ten seconds to avoid + // overloading network requests. + samplingTimeouts[req.method] = setTimeout(() => { + delete samplingTimeouts[req.method]; + }, SECOND * 10); + } + return callback(); + }); + }; +} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 9f5311d05..f615d0a51 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -137,6 +137,7 @@ import { buildSnapRestrictedMethodSpecifications, ///: END:ONLY_INCLUDE_IN } from './controllers/permissions'; +import createRPCMethodTrackingMiddleware from './lib/createRPCMethodTrackingMiddleware'; ///: BEGIN:ONLY_INCLUDE_IN(flask) import { getPlatform } from './lib/util'; @@ -3330,6 +3331,17 @@ export default class MetamaskController extends EventEmitter { engine.push(createLoggerMiddleware({ origin })); engine.push(this.permissionLogController.createMiddleware()); + engine.push( + createRPCMethodTrackingMiddleware({ + trackEvent: this.metaMetricsController.trackEvent.bind( + this.metaMetricsController, + ), + getMetricsState: this.metaMetricsController.store.getState.bind( + this.metaMetricsController.store, + ), + }), + ); + // onboarding if (subjectType === SUBJECT_TYPES.WEBSITE) { engine.push( diff --git a/shared/constants/metametrics.js b/shared/constants/metametrics.js index 6639fb250..ffcfc4012 100644 --- a/shared/constants/metametrics.js +++ b/shared/constants/metametrics.js @@ -213,3 +213,10 @@ export const METAMETRICS_BACKGROUND_PAGE_OBJECT = { export const REJECT_NOTFICIATION_CLOSE = 'Cancel Via Notification Close'; export const REJECT_NOTFICIATION_CLOSE_SIG = 'Cancel Sig Request Via Notification Close'; + +export const EVENT_NAMES = { + SIGNATURE_REQUESTED: 'Signature Requested', + ENCRYPTION_PUBLIC_KEY_REQUESTED: 'Encryption Public Key Requested', + DECRYPTION_REQUESTED: 'Decryption Requested', + PERMISSIONS_REQUESTED: 'Permissions Requested', +}; From fc4ae4ef6110907193503fcd8044a272c211ad74 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Tue, 5 Apr 2022 11:06:58 -0230 Subject: [PATCH 51/92] Fix the close button in the speed up and cancel popovers (#14356) --- .../app/cancel-speedup-popover/cancel-speedup-popover.js | 2 +- .../app/edit-gas-fee-popover/edit-gas-fee-popover.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.js b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.js index 9bcadbc00..df1cf472f 100644 --- a/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.js +++ b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.js @@ -96,7 +96,7 @@ const CancelSpeedupPopover = () => { : `🚀${t('speedUp')}`} } - onClose={() => closeModal('cancelSpeedUpTransaction')} + onClose={() => closeModal(['cancelSpeedUpTransaction'])} className="cancel-speedup-popover" > diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.js b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.js index 850eb17ed..0eade1419 100644 --- a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.js +++ b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.js @@ -44,7 +44,9 @@ const EditGasFeePopover = () => { closeModal('editGasFee')} + onBack={ + openModalCount === 1 ? undefined : () => closeModal(['editGasFee']) + } onClose={closeAllModals} className="edit-gas-fee-popover" > From e4bf3400bd1fc9e2fe9b6ea572f3c460b4b0111f Mon Sep 17 00:00:00 2001 From: Daniel <80175477+dan437@users.noreply.github.com> Date: Tue, 5 Apr 2022 16:58:47 +0200 Subject: [PATCH 52/92] stx-controller-v1.10.0 (#14352) * stx-controller-v1.10.0 * yarn yarn-deduplicate --- package.json | 2 +- yarn.lock | 122 ++++++++++++++++++++++++++------------------------- 2 files changed, 63 insertions(+), 61 deletions(-) diff --git a/package.json b/package.json index 8e189f8fb..f676a7259 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "@metamask/providers": "^8.1.1", "@metamask/rpc-methods": "^0.10.7", "@metamask/slip44": "^2.0.0", - "@metamask/smart-transactions-controller": "^1.9.1", + "@metamask/smart-transactions-controller": "^1.10.0", "@metamask/snap-controllers": "^0.10.7", "@ngraveio/bc-ur": "^1.1.6", "@popperjs/core": "^2.4.0", diff --git a/yarn.lock b/yarn.lock index c1e69d562..8bcfaa760 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2404,6 +2404,17 @@ hdkey "^2.0.1" uuid "^8.3.2" +"@keystonehq/base-eth-keyring@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@keystonehq/base-eth-keyring/-/base-eth-keyring-0.4.0.tgz#7667d2b6e38fc90553ce934c0c60c89329315b92" + integrity sha512-CDlRNGdrHDHtBS0pAdrsjNNbyi7tn7mGrwmgiGQ6F8rhYXDZ/TcvYV1AXlzCe0eFyjPdMGdl+PgZRwBpVRtpQQ== + dependencies: + "@ethereumjs/tx" "3.0.0" + "@keystonehq/bc-ur-registry-eth" "^0.9.0" + ethereumjs-util "^7.0.8" + hdkey "^2.0.1" + uuid "^8.3.2" + "@keystonehq/bc-ur-registry-eth@^0.6.8": version "0.6.13" resolved "https://registry.yarnpkg.com/@keystonehq/bc-ur-registry-eth/-/bc-ur-registry-eth-0.6.13.tgz#c1680930b1d3fed14857336bd4fb47a484dfac32" @@ -2424,6 +2435,16 @@ hdkey "^2.0.1" uuid "^8.3.2" +"@keystonehq/bc-ur-registry-eth@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@keystonehq/bc-ur-registry-eth/-/bc-ur-registry-eth-0.9.0.tgz#607428945029a06ec17ce3288caf53a0cbd8cc22" + integrity sha512-OVRT8Op+ZlOU9EBMxPBtQLrQZKzsV3DlfLq8P1T+Dq7WmGQNsRmQPchgju9qOlIIvmuAKaKdGXNN9W2qpTBAfA== + dependencies: + "@keystonehq/bc-ur-registry" "^0.5.0-alpha.5" + ethereumjs-util "^7.0.8" + hdkey "^2.0.1" + uuid "^8.3.2" + "@keystonehq/bc-ur-registry@^0.4.4": version "0.4.4" resolved "https://registry.yarnpkg.com/@keystonehq/bc-ur-registry/-/bc-ur-registry-0.4.4.tgz#3073fdd4b33cdcbd04526a313a7685891a4b4583" @@ -2433,6 +2454,15 @@ base58check "^2.0.0" tslib "^2.3.0" +"@keystonehq/bc-ur-registry@^0.5.0-alpha.5": + version "0.5.0-alpha.5" + resolved "https://registry.yarnpkg.com/@keystonehq/bc-ur-registry/-/bc-ur-registry-0.5.0-alpha.5.tgz#3d1a7eab980e8445c1596cdde704215c96d6b88a" + integrity sha512-T80XI+c8pWnkq9ZbuadlhFq/+8o4TcHtq+LQsK1XfjkhBqH75tcwim0310gKxavOhaSoC1i8dSqAnrFpj+5dJw== + dependencies: + "@ngraveio/bc-ur" "^1.1.5" + base58check "^2.0.0" + tslib "^2.3.0" + "@keystonehq/metamask-airgapped-keyring@0.2.1": version "0.2.1" resolved "https://registry.yarnpkg.com/@keystonehq/metamask-airgapped-keyring/-/metamask-airgapped-keyring-0.2.1.tgz#d6a8dd75d97cf7911faa8c2a8b19a0168b74891e" @@ -2445,6 +2475,18 @@ rlp "^2.2.6" uuid "^8.3.2" +"@keystonehq/metamask-airgapped-keyring@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@keystonehq/metamask-airgapped-keyring/-/metamask-airgapped-keyring-0.3.0.tgz#3de02b268b28d9f2e2e728a10cad8cfc17870c3c" + integrity sha512-CkiQGRPYM8CBeb8GsrrsTXpdHACl9NnoeWGQDY7DXGiy3s6u7WQ6TXal7K+wAHdU4asBzTaK2SNPZ/eIvGiAfg== + dependencies: + "@ethereumjs/tx" "^3.3.0" + "@keystonehq/base-eth-keyring" "^0.4.0" + "@keystonehq/bc-ur-registry-eth" "^0.9.0" + "@metamask/obs-store" "^7.0.0" + rlp "^2.2.6" + uuid "^8.3.2" + "@lavamoat/allow-scripts@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@lavamoat/allow-scripts/-/allow-scripts-2.0.0.tgz#d2b84491961c8b2b80281112e19654beae9eb584" @@ -2676,46 +2718,10 @@ semver "^7.3.5" yargs "^17.0.1" -"@metamask/contract-metadata@^1.31.0": - version "1.31.0" - resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.31.0.tgz#9e3e46de7a955ea1ca61f7db20d9a17b5e91d3d0" - integrity sha512-4FBJkg/vDiYp/thIiZknxrJ0lfsj2eWIPenwlNZmoqOhoL4VqhK5eKWxi+EuGMvv9taP+QBRk6Key7wC1uL78A== - -"@metamask/controllers@^25.1.0": - version "25.1.0" - resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-25.1.0.tgz#2efee24a9a2b03ab2a2b0422c8f250931c269560" - integrity sha512-syndn2lIhtlACzaqjDrw23dJzw8pZ6en4Cr35C7B9RRS87EhahUqkPP73moAzLtvbyqtBlAUO1HHrqV3lw4E5g== - dependencies: - "@ethereumjs/common" "^2.3.1" - "@ethereumjs/tx" "^3.2.1" - "@metamask/contract-metadata" "^1.31.0" - "@metamask/metamask-eth-abis" "^2.1.0" - "@types/uuid" "^8.3.0" - abort-controller "^3.0.0" - async-mutex "^0.2.6" - babel-runtime "^6.26.0" - eth-ens-namehash "^2.0.8" - eth-json-rpc-infura "^5.1.0" - eth-keyring-controller "^6.2.1" - eth-method-registry "1.1.0" - eth-phishing-detect "^1.1.14" - eth-query "^2.1.2" - eth-rpc-errors "^4.0.0" - eth-sig-util "^3.0.0" - ethereumjs-util "^7.0.10" - ethereumjs-wallet "^1.0.1" - ethers "^5.4.1" - ethjs-unit "^0.1.6" - immer "^9.0.6" - isomorphic-fetch "^3.0.0" - jsonschema "^1.2.4" - multiformats "^9.5.2" - nanoid "^3.1.31" - punycode "^2.1.1" - single-call-balance-checker-abi "^1.0.0" - uuid "^8.3.2" - web3 "^0.20.7" - web3-provider-engine "^16.0.3" +"@metamask/contract-metadata@^1.31.0", "@metamask/contract-metadata@^1.33.0": + version "1.33.0" + resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.33.0.tgz#3f0501d5c6d9119ce09c1edb075fc0a8fed7d09c" + integrity sha512-sWfzsUe59UH2Y1A7czRjhPmYrWlg4UQDOUPdf+lY7kbXwYrlF/ZUvhQYajdgJVchv2yDzr+cFhWF7DmNb5NyTQ== "@metamask/controllers@^26.0.0": version "26.0.0" @@ -2757,14 +2763,15 @@ web3 "^0.20.7" web3-provider-engine "^16.0.3" -"@metamask/controllers@^27.0.0": - version "27.0.0" - resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-27.0.0.tgz#23fb24960880047635a7e0b226375b843f385ad1" - integrity sha512-ZMSniSWVJN6TGFwZv8o/N3jff0QKxW6/ZkhyE0Ikd6WB9WKt665OD/FgWbsJVBpzJ5fF4N7pziYULbVsag8pVQ== +"@metamask/controllers@^27.0.0", "@metamask/controllers@^27.1.1": + version "27.1.1" + resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-27.1.1.tgz#b3288bfd05e381e9e32ed60b68a09b2855db1140" + integrity sha512-RzQ4zKsqmieYqAiVsIIazLTo9GYMcm9fDhYPJklP1M+bzm1k49GRFnZEfru3w/dPVY+wWgcDo/0ZWlOILbu3hg== dependencies: "@ethereumjs/common" "^2.3.1" "@ethereumjs/tx" "^3.2.1" - "@metamask/contract-metadata" "^1.31.0" + "@keystonehq/metamask-airgapped-keyring" "^0.3.0" + "@metamask/contract-metadata" "^1.33.0" "@metamask/metamask-eth-abis" "3.0.0" "@metamask/types" "^1.1.0" "@types/uuid" "^8.3.0" @@ -2776,7 +2783,7 @@ eth-json-rpc-infura "^5.1.0" eth-keyring-controller "^6.2.1" eth-method-registry "1.1.0" - eth-phishing-detect "^1.1.14" + eth-phishing-detect "^1.1.16" eth-query "^2.1.2" eth-rpc-errors "^4.0.0" eth-sig-util "^3.0.0" @@ -2934,11 +2941,6 @@ resolved "https://registry.yarnpkg.com/@metamask/metamask-eth-abis/-/metamask-eth-abis-3.0.0.tgz#eccc0746b3ab1ab63000444403819c16e88b5272" integrity sha512-YtIl4e1VzqwwHGafuLIVPqbcWWWqQ0Ezo8/Ci5m5OGllqE2oTTx9iVHdUmXNkgCVD37SBfwn/fm/S1IGkM8BQA== -"@metamask/metamask-eth-abis@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@metamask/metamask-eth-abis/-/metamask-eth-abis-2.1.0.tgz#316c2e72373506f1a0120b76e432760a27eb6806" - integrity sha512-T8LBEB0PQo0N1tZQKZ2K8BGmv+IDLcXkzt8Pn7x0YnwZD6YpCIvKqYM3iy2fJ6wFXeCvRKqpn4K6EqwnkSJAbQ== - "@metamask/object-multiplex@^1.1.0", "@metamask/object-multiplex@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@metamask/object-multiplex/-/object-multiplex-1.2.0.tgz#38fc15c142f61939391e1b9a8eed679696c7e4f4" @@ -3011,12 +3013,12 @@ resolved "https://registry.yarnpkg.com/@metamask/slip44/-/slip44-2.0.0.tgz#1b646a1418af341d5ea979c28015a817ff23af33" integrity sha512-eRomm783ti/1b/TlNnlTCUkYRuTaMYkeTAG0z2rt/WyT8UzxY+8+v/kbl9vk5qhDHeclzBrd9gbqLnLU1kh+Ow== -"@metamask/smart-transactions-controller@^1.9.1": - version "1.9.1" - resolved "https://registry.yarnpkg.com/@metamask/smart-transactions-controller/-/smart-transactions-controller-1.9.1.tgz#f9fa168b33cc23c2238c23eed29475f16afafdd0" - integrity sha512-Vq6HU+l6WSXTCTWazsFwSDNm5DtX6SWuqf3qkMWvollnSduExu2q1XrCIrtsDg7W69NO0XNYL3R13w+ZaNhjzA== +"@metamask/smart-transactions-controller@^1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@metamask/smart-transactions-controller/-/smart-transactions-controller-1.10.0.tgz#230f611eaf9eefc41bac0e7af78101a215c6acba" + integrity sha512-Bx2zT7UJJF2f11yANpC3OYCXYt2gpdqXj+RC4hnf18CELeF9Sp52xwQEkO6ig+3isrj6NsyVVmoo5PRcrU++cA== dependencies: - "@metamask/controllers" "^25.1.0" + "@metamask/controllers" "^27.1.1" "@types/lodash" "^4.14.176" bignumber.js "^9.0.1" ethers "^5.5.1" @@ -11277,10 +11279,10 @@ eth-method-registry@^2.0.0: dependencies: ethjs "^0.4.0" -eth-phishing-detect@^1.1.14: - version "1.1.15" - resolved "https://registry.yarnpkg.com/eth-phishing-detect/-/eth-phishing-detect-1.1.15.tgz#c42e1aad6cd1c5eeee41c6bf932dcfd0e523d499" - integrity sha512-RVNSGMVIuO6VZ1Uv4v8dljjj0ephW+APVAU5QL5mBu3VEqfBluPMNb6jw66kxYrIFrSNalnb/pMeDpAA+W3cvg== +eth-phishing-detect@^1.1.14, eth-phishing-detect@^1.1.16: + version "1.1.16" + resolved "https://registry.yarnpkg.com/eth-phishing-detect/-/eth-phishing-detect-1.1.16.tgz#637158d5774819e1a861f6d169e6d77d076a47fd" + integrity sha512-/o9arK5qFOKVdfZK9hJVAQP0eKXjAvImIKNBMfF9Nj1HGicD3wfsVuXDu1OHrxuEi6+4kYtD9wyAn/3G7g7VrA== dependencies: fast-levenshtein "^2.0.6" From 2cd1472e3a975a6fe8971842f7c5f421856adc8e Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Tue, 5 Apr 2022 10:48:03 -0700 Subject: [PATCH 53/92] Fix imported component name and change size proptype type to string (#14237) * Fix imported component name and change size proptype type to string * Change IconTokenSearch size prop back to number and change usage to number --- .../token-list-placeholder.component.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/pages/import-token/token-list/token-list-placeholder/token-list-placeholder.component.js b/ui/pages/import-token/token-list/token-list-placeholder/token-list-placeholder.component.js index 3ec1f8b8e..602fb2d8e 100644 --- a/ui/pages/import-token/token-list/token-list-placeholder/token-list-placeholder.component.js +++ b/ui/pages/import-token/token-list/token-list-placeholder/token-list-placeholder.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Button from '../../../../components/ui/button'; -import IconSearch from '../../../../components/ui/icon/icon-token-search'; +import IconTokenSearch from '../../../../components/ui/icon/icon-token-search'; export default class TokenListPlaceholder extends Component { static contextTypes = { @@ -11,7 +11,7 @@ export default class TokenListPlaceholder extends Component { render() { return (
- +
{this.context.t('addAcquiredTokens')}
From d8fd5f68ce654cc3fb0c45fb9c23674db616229d Mon Sep 17 00:00:00 2001 From: David Walsh Date: Tue, 5 Apr 2022 13:51:05 -0500 Subject: [PATCH 54/92] Dark Mode: Fix Actionable Messages (#14361) --- ui/components/ui/actionable-message/index.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/components/ui/actionable-message/index.scss b/ui/components/ui/actionable-message/index.scss index e9f3e7f1f..3ab7cb146 100644 --- a/ui/components/ui/actionable-message/index.scss +++ b/ui/components/ui/actionable-message/index.scss @@ -108,7 +108,7 @@ text-decoration: underline; } - button { + .actionable-message__actions button { background: var(--color-warning-default); color: var(--color-warning-inverse); } @@ -126,7 +126,7 @@ text-align: left; } - button { + .actionable-message__actions button { background: var(--color-error-default); color: var(--color-error-inverse); } @@ -139,7 +139,7 @@ background: var(--color-success-muted); } - button { + .actionable-message__actions button { background: var(--color-success-default); color: var(--color-success-inverse); } From af963d7f7b127923661ff100536c5032c0579ba0 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Tue, 5 Apr 2022 14:55:00 -0500 Subject: [PATCH 55/92] Dark Mode: What's New Announcement (#14346) --- app/_locales/en/messages.json | 9 +++++++++ app/images/darkmode-banner.png | Bin 0 -> 22646 bytes shared/notifications/index.js | 17 +++++++++++++++++ test/e2e/fixtures/address-entry/state.json | 3 +++ test/e2e/fixtures/connected-state/state.json | 3 +++ test/e2e/fixtures/custom-rpc/state.json | 3 +++ test/e2e/fixtures/custom-token/state.json | 3 +++ test/e2e/fixtures/eip-1559-v2-dapp/state.json | 3 +++ test/e2e/fixtures/eip-1559-v2/state.json | 3 +++ test/e2e/fixtures/import-ui/state.json | 3 +++ test/e2e/fixtures/imported-account/state.json | 3 +++ test/e2e/fixtures/localization/state.json | 3 +++ test/e2e/fixtures/metrics-enabled/state.json | 3 +++ .../fixtures/navigate-transactions/state.json | 3 +++ test/e2e/fixtures/onboarding/state.json | 7 +++++++ test/e2e/fixtures/send-edit-v2/state.json | 3 +++ test/e2e/fixtures/send-edit/state.json | 3 +++ test/e2e/fixtures/threebox-enabled/state.json | 3 +++ .../app/whats-new-popup/whats-new-popup.js | 5 +++++ ui/selectors/selectors.js | 1 + 20 files changed, 81 insertions(+) create mode 100644 app/images/darkmode-banner.png diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 2da5b1ebe..73fabb021 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2131,6 +2131,15 @@ "notifications11Title": { "message": "Scam and security risks" }, + "notifications12ActionText": { + "message": "Enable dark mode" + }, + "notifications12Description": { + "message": "Dark Mode will be enabled for new users depending on their system preferences. For existing users, enable Dark Mode manually under Settings -> Experimental." + }, + "notifications12Title": { + "message": "Wen dark mode? Now dark mode! 🕶️🦊" + }, "notifications1Description": { "message": "MetaMask Mobile users can now swap tokens inside their mobile wallet. Scan the QR code to get the mobile app and start swapping.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." diff --git a/app/images/darkmode-banner.png b/app/images/darkmode-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..7f72b312828ad37092980bc88b64e4e1d4398707 GIT binary patch literal 22646 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 { @@ -165,5 +173,14 @@ export const getTranslatedUINoficiations = (t, locale) => { new Date(UI_NOTIFICATIONS[11].date), ), }, + 12: { + ...UI_NOTIFICATIONS[12], + title: t('notifications12Title'), + description: t('notifications12Description'), + actionText: t('notifications12ActionText'), + date: new Intl.DateTimeFormat(formattedLocale).format( + new Date(UI_NOTIFICATIONS[12].date), + ), + }, }; }; diff --git a/test/e2e/fixtures/address-entry/state.json b/test/e2e/fixtures/address-entry/state.json index 59da933e5..96b17cdb1 100644 --- a/test/e2e/fixtures/address-entry/state.json +++ b/test/e2e/fixtures/address-entry/state.json @@ -66,6 +66,9 @@ }, "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/test/e2e/fixtures/connected-state/state.json b/test/e2e/fixtures/connected-state/state.json index ccc9ab82d..4d48df472 100644 --- a/test/e2e/fixtures/connected-state/state.json +++ b/test/e2e/fixtures/connected-state/state.json @@ -56,6 +56,9 @@ }, "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/test/e2e/fixtures/custom-rpc/state.json b/test/e2e/fixtures/custom-rpc/state.json index 43319cd60..222eb0159 100644 --- a/test/e2e/fixtures/custom-rpc/state.json +++ b/test/e2e/fixtures/custom-rpc/state.json @@ -52,6 +52,9 @@ }, "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/test/e2e/fixtures/custom-token/state.json b/test/e2e/fixtures/custom-token/state.json index da6098fcf..ae1fcd1b4 100644 --- a/test/e2e/fixtures/custom-token/state.json +++ b/test/e2e/fixtures/custom-token/state.json @@ -70,6 +70,9 @@ "notifications": { "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/test/e2e/fixtures/eip-1559-v2-dapp/state.json b/test/e2e/fixtures/eip-1559-v2-dapp/state.json index 3e29d737e..839484240 100644 --- a/test/e2e/fixtures/eip-1559-v2-dapp/state.json +++ b/test/e2e/fixtures/eip-1559-v2-dapp/state.json @@ -53,6 +53,9 @@ }, "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/test/e2e/fixtures/eip-1559-v2/state.json b/test/e2e/fixtures/eip-1559-v2/state.json index 452c896c3..0c79d8c61 100644 --- a/test/e2e/fixtures/eip-1559-v2/state.json +++ b/test/e2e/fixtures/eip-1559-v2/state.json @@ -53,6 +53,9 @@ }, "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/test/e2e/fixtures/import-ui/state.json b/test/e2e/fixtures/import-ui/state.json index 75aa4e7da..20c6bf87c 100644 --- a/test/e2e/fixtures/import-ui/state.json +++ b/test/e2e/fixtures/import-ui/state.json @@ -107,6 +107,9 @@ }, "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/test/e2e/fixtures/imported-account/state.json b/test/e2e/fixtures/imported-account/state.json index c6e2f4a8e..cb5c96950 100644 --- a/test/e2e/fixtures/imported-account/state.json +++ b/test/e2e/fixtures/imported-account/state.json @@ -52,6 +52,9 @@ }, "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/test/e2e/fixtures/localization/state.json b/test/e2e/fixtures/localization/state.json index 952a3a502..4b32460b5 100644 --- a/test/e2e/fixtures/localization/state.json +++ b/test/e2e/fixtures/localization/state.json @@ -52,6 +52,9 @@ }, "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/test/e2e/fixtures/metrics-enabled/state.json b/test/e2e/fixtures/metrics-enabled/state.json index 3b272611e..43b345204 100644 --- a/test/e2e/fixtures/metrics-enabled/state.json +++ b/test/e2e/fixtures/metrics-enabled/state.json @@ -56,6 +56,9 @@ }, "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/test/e2e/fixtures/navigate-transactions/state.json b/test/e2e/fixtures/navigate-transactions/state.json index 6760e0d4f..963a846b4 100644 --- a/test/e2e/fixtures/navigate-transactions/state.json +++ b/test/e2e/fixtures/navigate-transactions/state.json @@ -52,6 +52,9 @@ }, "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/test/e2e/fixtures/onboarding/state.json b/test/e2e/fixtures/onboarding/state.json index b0fbccd9a..6a4c94b96 100644 --- a/test/e2e/fixtures/onboarding/state.json +++ b/test/e2e/fixtures/onboarding/state.json @@ -26,6 +26,13 @@ }, "network": "1337" }, + "NotificationController": { + "notifications": { + "12": { + "isShown": true + } + } + }, "CurrencyController": { "conversionDate": 1617927806.941, "conversionRate": 2084.64, diff --git a/test/e2e/fixtures/send-edit-v2/state.json b/test/e2e/fixtures/send-edit-v2/state.json index fe1a4390c..9de073c00 100644 --- a/test/e2e/fixtures/send-edit-v2/state.json +++ b/test/e2e/fixtures/send-edit-v2/state.json @@ -53,6 +53,9 @@ }, "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/test/e2e/fixtures/send-edit/state.json b/test/e2e/fixtures/send-edit/state.json index f538e00bf..1272fbd04 100644 --- a/test/e2e/fixtures/send-edit/state.json +++ b/test/e2e/fixtures/send-edit/state.json @@ -53,6 +53,9 @@ }, "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/test/e2e/fixtures/threebox-enabled/state.json b/test/e2e/fixtures/threebox-enabled/state.json index 565e8a122..78b6f1226 100644 --- a/test/e2e/fixtures/threebox-enabled/state.json +++ b/test/e2e/fixtures/threebox-enabled/state.json @@ -63,6 +63,9 @@ }, "8": { "isShown": true + }, + "12": { + "isShown": true } } }, diff --git a/ui/components/app/whats-new-popup/whats-new-popup.js b/ui/components/app/whats-new-popup/whats-new-popup.js index bba492040..4d47ee262 100644 --- a/ui/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/components/app/whats-new-popup/whats-new-popup.js @@ -15,6 +15,7 @@ import { getSortedNotificationsToShow } from '../../../selectors'; import { BUILD_QUOTE_ROUTE, ADVANCED_ROUTE, + EXPERIMENTAL_ROUTE, } from '../../../helpers/constants/routes'; import { TYPOGRAPHY } from '../../../helpers/constants/design-system'; @@ -49,6 +50,10 @@ function getActionFunctionById(id, history) { updateViewedNotifications({ 10: true }); history.push(`${ADVANCED_ROUTE}#token-description`); }, + 12: () => { + updateViewedNotifications({ 12: true }); + history.push(EXPERIMENTAL_ROUTE); + }, }; return actionFunctions[id]; diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 270315831..4a232452e 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -728,6 +728,7 @@ function getAllowedNotificationIds(state) { 9: getIsMainnet(state), 10: Boolean(process.env.TOKEN_DETECTION_V2), 11: Boolean(process.env.TOKEN_DETECTION_V2), + 12: true, }; } From 582f6168f9860a1f7a5dbf950b53d8b8c471312d Mon Sep 17 00:00:00 2001 From: Ariella Vu <20778143+digiwand@users.noreply.github.com> Date: Tue, 5 Apr 2022 20:22:41 -0300 Subject: [PATCH 56/92] Settings:TabBar: Fix text wrap style (#14348) * TabBar: fix text wrap style * TabBar: adjust icon alignment --- ui/components/app/tab-bar/index.scss | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ui/components/app/tab-bar/index.scss b/ui/components/app/tab-bar/index.scss index 36b575e31..3a8cd46b8 100644 --- a/ui/components/app/tab-bar/index.scss +++ b/ui/components/app/tab-bar/index.scss @@ -38,7 +38,6 @@ &__content { padding: 12px 18px; display: flex; - flex-flow: row wrap; align-items: center; position: relative; @@ -64,8 +63,14 @@ } &__icon { - margin-inline-end: 16px; display: flex; + justify-content: center; + margin-inline-end: 16px; + flex: 0 0 18px; + + @media screen and (min-width: $break-large) { + flex: 0 0 14px; + } } } From b84e70895b64700df2f361c700ed4e2525ce06a5 Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Wed, 6 Apr 2022 10:05:45 +0200 Subject: [PATCH 57/92] Feature Flag Swap API mock (#14353) * Feature Flag Swap API mock * Reorganize API calls in alphabetical order --- test/e2e/mock-e2e.js | 61 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index 1487567fb..478a5b959 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -1,6 +1,12 @@ async function setupMocking(server, testSpecificMock) { await server.forAnyRequest().thenPassThrough(); + await server.forPost('https://api.segment.io/v1/batch').thenCallback(() => { + return { + statusCode: 200, + }; + }); + await server .forGet('https://gas-api.metaswap.codefi.network/networks/1/gasPrices') .thenCallback(() => { @@ -14,12 +20,6 @@ async function setupMocking(server, testSpecificMock) { }; }); - await server.forPost('https://api.segment.io/v1/batch').thenCallback(() => { - return { - statusCode: 200, - }; - }); - await server .forGet( 'https://gas-api.metaswap.codefi.network/networks/1/suggestedGasFees', @@ -57,6 +57,55 @@ async function setupMocking(server, testSpecificMock) { }; }); + await server + .forGet('https://swap.metaswap.codefi.network/featureFlags') + .thenCallback(() => { + return { + statusCode: 200, + json: [ + { + ethereum: { + mobile_active: true, + extension_active: true, + fallback_to_v1: false, + mobileActive: true, + extensionActive: true, + }, + bsc: { + mobile_active: true, + extension_active: true, + fallback_to_v1: false, + mobileActive: true, + extensionActive: true, + }, + polygon: { + mobile_active: true, + extension_active: true, + fallback_to_v1: false, + mobileActive: true, + extensionActive: true, + }, + avalanche: { + mobile_active: true, + extension_active: true, + fallback_to_v1: false, + mobileActive: true, + extensionActive: true, + }, + smart_transactions: { + mobile_active: false, + extension_active: false, + }, + smartTransactions: { + mobileActive: false, + extensionActive: false, + }, + updated_at: '2022-03-17T15:54:00.360Z', + }, + ], + }; + }); + await server .forGet('https://token-api.metaswap.codefi.network/tokens/1337') .thenCallback(() => { From 7e059550a28b7d502676b467f45a46eac9381f03 Mon Sep 17 00:00:00 2001 From: Ariella Vu <20778143+digiwand@users.noreply.github.com> Date: Wed, 6 Apr 2022 11:00:52 -0300 Subject: [PATCH 58/92] TabBar: prevent text from wrapping early (#14368) --- ui/components/app/tab-bar/index.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/components/app/tab-bar/index.scss b/ui/components/app/tab-bar/index.scss index 3a8cd46b8..4329b5b5c 100644 --- a/ui/components/app/tab-bar/index.scss +++ b/ui/components/app/tab-bar/index.scss @@ -38,6 +38,7 @@ &__content { padding: 12px 18px; display: flex; + flex: 1 1 auto; align-items: center; position: relative; From a7a16460024888a070dfd23b09212318e5f9d294 Mon Sep 17 00:00:00 2001 From: ricky Date: Wed, 6 Apr 2022 12:37:08 -0400 Subject: [PATCH 59/92] Remove `base_url` from crowdin.yml (#14364) Remove `base_url` since we're moving away from Crowdin Enterprise --- crowdin.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/crowdin.yml b/crowdin.yml index 60bc9c2fc..25f294025 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,7 +1,6 @@ "project_id_env": CROWDIN_PROJECT_ID "api_token_env": CROWDIN_PERSONAL_TOKEN "base_path" : "." -"base_url" : "https://metamask.crowdin.com" "preserve_hierarchy": true From 07231e42b23c4c2e1c2c3634235412ba67267318 Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Wed, 6 Apr 2022 13:59:16 -0400 Subject: [PATCH 60/92] Update token detection logic to only control auto-detection (#14251) --- .../ui/identicon/identicon.component.js | 22 ++++++++---- .../nickname-popover.component.js | 6 ++++ .../update-nickname-popover.js | 7 ++++ ui/helpers/utils/icon-factory.js | 29 +++++++++------ ui/hooks/useAddressDetails.js | 26 ++++++++------ ui/hooks/useTokensToSearch.js | 36 ++++++++++++------- .../confirm-transaction-base.container.js | 19 +++++----- .../token-list/token-list.component.js | 12 ++++--- ui/pages/token-details/token-details-page.js | 7 ++-- 9 files changed, 108 insertions(+), 56 deletions(-) diff --git a/ui/components/ui/identicon/identicon.component.js b/ui/components/ui/identicon/identicon.component.js index 8174ab38e..b7aa747ab 100644 --- a/ui/components/ui/identicon/identicon.component.js +++ b/ui/components/ui/identicon/identicon.component.js @@ -151,14 +151,22 @@ export default class Identicon extends PureComponent { } if (address) { - // token from dynamic api list is fetched when useTokenDetection is true - // And since the token.address from allTokens is checksumaddress - // tokenAddress have to be changed to lowercase when we are using dynamic list - const tokenAddress = useTokenDetection ? address.toLowerCase() : address; - if (tokenAddress && tokenList[tokenAddress]?.iconUrl) { - return this.renderJazzicon(); + if (process.env.TOKEN_DETECTION_V2) { + if (tokenList[address.toLowerCase()]?.iconUrl) { + return this.renderJazzicon(); + } + } else { + /** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */ + // token from dynamic api list is fetched when useTokenDetection is true + // And since the token.address from allTokens is checksumaddress + // tokenAddress have to be changed to lowercase when we are using dynamic list + const tokenAddress = useTokenDetection + ? address.toLowerCase() + : address; + if (tokenAddress && tokenList[tokenAddress]?.iconUrl) { + return this.renderJazzicon(); + } } - return (
@@ -31,6 +35,8 @@ const NicknamePopover = ({ address={address} diameter={36} className="nickname-popover__identicon" + useTokenDetection={useTokenDetection} + tokenList={tokenList} />
{nickname || shortenAddress(address)} diff --git a/ui/components/ui/update-nickname-popover/update-nickname-popover.js b/ui/components/ui/update-nickname-popover/update-nickname-popover.js index 2713d7b70..3396d4fdc 100644 --- a/ui/components/ui/update-nickname-popover/update-nickname-popover.js +++ b/ui/components/ui/update-nickname-popover/update-nickname-popover.js @@ -1,4 +1,5 @@ import React, { useCallback, useContext, useState } from 'react'; +import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import Popover from '../popover'; @@ -8,6 +9,7 @@ import TextField from '../text-field'; import { I18nContext } from '../../../contexts/i18n'; import Identicon from '../identicon/identicon.component'; +import { getUseTokenDetection, getTokenList } from '../../../selectors'; export default function UpdateNicknamePopover({ nickname, @@ -42,6 +44,9 @@ export default function UpdateNicknamePopover({ onClose(); }; + const useTokenDetection = useSelector(getUseTokenDetection); + const tokenList = useSelector(getTokenList); + return (
{Object.entries(alertConfig).map( - ([alertId, { title, description }], index) => ( + ([alertId, { title, description }], _) => ( ), )} diff --git a/ui/pages/settings/contact-list-tab/contact-list-tab.component.js b/ui/pages/settings/contact-list-tab/contact-list-tab.component.js index 04ee9a88a..4c5080255 100644 --- a/ui/pages/settings/contact-list-tab/contact-list-tab.component.js +++ b/ui/pages/settings/contact-list-tab/contact-list-tab.component.js @@ -8,7 +8,7 @@ import { } from '../../../helpers/constants/routes'; import Button from '../../../components/ui/button'; import { - getSettingsSectionNumber, + getNumberOfSettingsInSection, handleSettingsRefs, } from '../../../helpers/utils/settings-search'; import EditContact from './edit-contact'; @@ -32,7 +32,7 @@ export default class ContactListTab extends Component { }; settingsRefs = Array( - getSettingsSectionNumber(this.context.t, this.context.t('contacts')), + getNumberOfSettingsInSection(this.context.t, this.context.t('contacts')), ) .fill(undefined) .map(() => { diff --git a/ui/pages/settings/experimental-tab/experimental-tab.component.js b/ui/pages/settings/experimental-tab/experimental-tab.component.js index fd91e01ab..e8efda0f4 100644 --- a/ui/pages/settings/experimental-tab/experimental-tab.component.js +++ b/ui/pages/settings/experimental-tab/experimental-tab.component.js @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import ToggleButton from '../../../components/ui/toggle-button'; import { - getSettingsSectionNumber, + getNumberOfSettingsInSection, handleSettingsRefs, } from '../../../helpers/utils/settings-search'; import Dropdown from '../../../components/ui/dropdown'; @@ -29,7 +29,10 @@ export default class ExperimentalTab extends PureComponent { }; settingsRefs = Array( - getSettingsSectionNumber(this.context.t, this.context.t('experimental')), + getNumberOfSettingsInSection( + this.context.t, + this.context.t('experimental'), + ), ) .fill(undefined) .map(() => { diff --git a/ui/pages/settings/flask/snaps-list-tab/snap-list-tab.js b/ui/pages/settings/flask/snaps-list-tab/snap-list-tab.js index e030ddbf1..d0c4f3306 100644 --- a/ui/pages/settings/flask/snaps-list-tab/snap-list-tab.js +++ b/ui/pages/settings/flask/snaps-list-tab/snap-list-tab.js @@ -1,4 +1,4 @@ -import React, { useMemo, useEffect } from 'react'; +import React, { useRef, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import SnapSettingsCard from '../../../../components/app/flask/snap-settings-card'; @@ -22,7 +22,7 @@ const SnapListTab = () => { const history = useHistory(); const dispatch = useDispatch(); const snaps = useSelector(getSnaps); - const settingsRef = useMemo(() => [React.createRef()], []); + const settingsRef = useRef(); const onClick = (snap) => { const route = `${SNAPS_VIEW_ROUTE}/${window.btoa( unescape(encodeURIComponent(snap.id)), @@ -39,7 +39,7 @@ const SnapListTab = () => { useEffect(() => { handleSettingsRefs(t, t('snaps'), settingsRef); - }, [t, settingsRef]); + }, [settingsRef, t]); return (
diff --git a/ui/pages/settings/info-tab/info-tab.component.js b/ui/pages/settings/info-tab/info-tab.component.js index 80ef996ad..1ee949c87 100644 --- a/ui/pages/settings/info-tab/info-tab.component.js +++ b/ui/pages/settings/info-tab/info-tab.component.js @@ -7,7 +7,7 @@ import { } from '../../../helpers/constants/common'; import { isBeta } from '../../../helpers/utils/build-types'; import { - getSettingsSectionNumber, + getNumberOfSettingsInSection, handleSettingsRefs, } from '../../../helpers/utils/settings-search'; @@ -21,7 +21,7 @@ export default class InfoTab extends PureComponent { }; settingsRefs = Array( - getSettingsSectionNumber(this.context.t, this.context.t('about')), + getNumberOfSettingsInSection(this.context.t, this.context.t('about')), ) .fill(undefined) .map(() => { diff --git a/ui/pages/settings/networks-tab/networks-list-item/networks-list-item.js b/ui/pages/settings/networks-tab/networks-list-item/networks-list-item.js index 5ee11bf71..5f41df97c 100644 --- a/ui/pages/settings/networks-tab/networks-list-item/networks-list-item.js +++ b/ui/pages/settings/networks-tab/networks-list-item/networks-list-item.js @@ -18,13 +18,12 @@ import { getProvider } from '../../../../selectors'; import Identicon from '../../../../components/ui/identicon'; import UrlIcon from '../../../../components/ui/url-icon'; -import { handleHooksSettingsRefs } from '../../../../helpers/utils/settings-search'; +import { handleSettingsRefs } from '../../../../helpers/utils/settings-search'; const NetworksListItem = ({ network, networkIsSelected, selectedRpcUrl, - networkIndex, setSearchQuery, setSearchedNetworks, }) => { @@ -56,8 +55,8 @@ const NetworksListItem = ({ const settingsRefs = useRef(); useEffect(() => { - handleHooksSettingsRefs(t, t('networks'), settingsRefs, networkIndex); - }, [networkIndex, settingsRefs, t]); + handleSettingsRefs(t, t('networks'), settingsRefs); + }, [settingsRefs, t]); return (
- {searchedNetworksToRenderThatAreNotTestNetworks.map((network, index) => ( + {searchedNetworksToRenderThatAreNotTestNetworks.map((network, _) => ( @@ -74,13 +73,12 @@ const NetworksList = ({ {t('testNetworks')} )} - {searchedNetworksToRenderThatAreTestNetworks.map((network, index) => ( + {searchedNetworksToRenderThatAreTestNetworks.map((network, _) => ( diff --git a/ui/pages/settings/security-tab/security-tab.component.js b/ui/pages/settings/security-tab/security-tab.component.js index f89c9bb1f..d356e1138 100644 --- a/ui/pages/settings/security-tab/security-tab.component.js +++ b/ui/pages/settings/security-tab/security-tab.component.js @@ -4,7 +4,7 @@ import ToggleButton from '../../../components/ui/toggle-button'; import { REVEAL_SEED_ROUTE } from '../../../helpers/constants/routes'; import Button from '../../../components/ui/button'; import { - getSettingsSectionNumber, + getNumberOfSettingsInSection, handleSettingsRefs, } from '../../../helpers/utils/settings-search'; @@ -26,7 +26,7 @@ export default class SecurityTab extends PureComponent { }; settingsRefs = Array( - getSettingsSectionNumber( + getNumberOfSettingsInSection( this.context.t, this.context.t('securityAndPrivacy'), ), diff --git a/ui/pages/settings/settings-search-list/settings-search-list.js b/ui/pages/settings/settings-search-list/settings-search-list.js index 911871959..171e7ec5d 100644 --- a/ui/pages/settings/settings-search-list/settings-search-list.js +++ b/ui/pages/settings/settings-search-list/settings-search-list.js @@ -13,10 +13,10 @@ export default function SettingsSearchList({ results, onClickSetting }) { return (
{results.slice(0, 5).map((result) => { - const { icon, tab, section, id } = result; + const { icon, tabMessage, sectionMessage, route } = result; return ( - Boolean(icon || tab || section) && ( -
+ Boolean(icon || tabMessage || sectionMessage) && ( +
onClickSetting(result)} @@ -29,32 +29,33 @@ export default function SettingsSearchList({ results, onClickSetting }) { /> - {tab} + {tabMessage(t)} - {section} + {sectionMessage(t)}
diff --git a/ui/pages/settings/settings-search/settings-search.js b/ui/pages/settings/settings-search/settings-search.js index 044f20a96..c00f2b563 100644 --- a/ui/pages/settings/settings-search/settings-search.js +++ b/ui/pages/settings/settings-search/settings-search.js @@ -27,12 +27,15 @@ export default function SettingsSearch({ distance: 100, maxPatternLength: 32, minMatchCharLength: 1, - keys: ['tab', 'section', 'description'], + keys: ['tabMessage', 'sectionMessage', 'descriptionMessage'], + getFn: (routeObject, path) => routeObject[path](t), }); - // eslint-disable-next-line no-shadow const handleSearch = (_searchQuery) => { - const sanitizedSearchQuery = _searchQuery.replace(/[^A-z0-9\s]|[\\]/gu, ''); + const sanitizedSearchQuery = _searchQuery.replace( + /[^A-z0-9\s&]|[\\]/gu, + '', + ); setSearchQuery(sanitizedSearchQuery); if (sanitizedSearchQuery === '') { setSearchIconColor('var(--color-icon-muted)'); diff --git a/ui/pages/settings/settings-tab/settings-tab.component.js b/ui/pages/settings/settings-tab/settings-tab.component.js index aacff5327..982447077 100644 --- a/ui/pages/settings/settings-tab/settings-tab.component.js +++ b/ui/pages/settings/settings-tab/settings-tab.component.js @@ -11,7 +11,7 @@ import BlockieIdenticon from '../../../components/ui/identicon/blockieIdenticon' import Typography from '../../../components/ui/typography'; import { - getSettingsSectionNumber, + getNumberOfSettingsInSection, handleSettingsRefs, } from '../../../helpers/utils/settings-search'; @@ -59,7 +59,7 @@ export default class SettingsTab extends PureComponent { }; settingsRefs = Array( - getSettingsSectionNumber(this.context.t, this.context.t('general')), + getNumberOfSettingsInSection(this.context.t, this.context.t('general')), ) .fill(undefined) .map(() => { diff --git a/ui/pages/settings/settings.component.js b/ui/pages/settings/settings.component.js index 391ed082c..dc9bb1d31 100644 --- a/ui/pages/settings/settings.component.js +++ b/ui/pages/settings/settings.component.js @@ -24,8 +24,8 @@ import { EXPERIMENTAL_ROUTE, ADD_NETWORK_ROUTE, } from '../../helpers/constants/routes'; -import { getSettingsRoutes } from '../../helpers/utils/settings-search'; +import { getSettingsRoutes } from '../../helpers/utils/settings-search'; import SettingsTab from './settings-tab'; import AlertsTab from './alerts-tab'; import NetworksTab from './networks-tab'; @@ -105,7 +105,6 @@ class SettingsPage extends PureComponent { } = this.props; const { searchResults, isSearchList, searchText } = this.state; - const { t } = this.context; return (
{isSearchList && searchText.length >= 3 && ( this.handleClickSetting(setting)} /> From 9ce966ba1df7690292632a4f22b6f45f94bcf5ac Mon Sep 17 00:00:00 2001 From: George Marshall Date: Thu, 7 Apr 2022 08:14:22 -0700 Subject: [PATCH 62/92] =?UTF-8?q?Storybook=20housekeeping=20=F0=9F=A7=B9?= =?UTF-8?q?=20=20(#14331)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Removing argTypes that are related to static props and don't have anything to do with the component. Also some general house keeping * updates --- .../app/app-header/app-header.stories.js | 24 ---------------- .../confirm-remove-account.stories.js | 16 ----------- .../export-private-key-modal.stories.js | 28 +------------------ ...er-preferenced-currency-display.stories.js | 3 ++ .../reveal-seed-phrase.stories.js | 2 +- .../redirect/permissions-redirect.stories.js | 2 +- .../smart-transaction-status.stories.js | 2 +- 7 files changed, 7 insertions(+), 70 deletions(-) diff --git a/ui/components/app/app-header/app-header.stories.js b/ui/components/app/app-header/app-header.stories.js index f783fd3e2..989e2997a 100644 --- a/ui/components/app/app-header/app-header.stories.js +++ b/ui/components/app/app-header/app-header.stories.js @@ -5,27 +5,6 @@ export default { title: 'Components/App/AppHeader', id: __filename, argTypes: { - history: { - control: 'object', - }, - networkDropdownOpen: { - control: 'boolean', - }, - showNetworkDropdown: { - action: 'showNetworkDropdown', - }, - hideNetworkDropdown: { - action: 'hideNetworkDropdown', - }, - toggleAccountMenu: { - action: 'toggleAccountMenu', - }, - selectedAddress: { - control: 'text', - }, - isUnlocked: { - control: 'boolean', - }, hideNetworkIndicator: { control: 'boolean', }, @@ -35,9 +14,6 @@ export default { disableNetworkIndicator: { control: 'boolean', }, - isAccountMenuOpen: { - control: 'boolean', - }, onClick: { action: 'onClick', }, diff --git a/ui/components/app/modals/confirm-remove-account/confirm-remove-account.stories.js b/ui/components/app/modals/confirm-remove-account/confirm-remove-account.stories.js index 7bd6779aa..aee9bfc3e 100644 --- a/ui/components/app/modals/confirm-remove-account/confirm-remove-account.stories.js +++ b/ui/components/app/modals/confirm-remove-account/confirm-remove-account.stories.js @@ -6,30 +6,14 @@ export default { id: __filename, component: ConfirmRemoveAccount, argTypes: { - hideModal: { - action: 'hideModal', - }, - removeAccount: { - action: 'removeAccount', - }, identity: { control: 'object', }, - chainId: { - control: 'text', - }, - rpcPrefs: { - control: 'object', - }, }, args: { identity: { control: 'object', }, - chainId: 'chainId', - rpcPrefs: { - control: 'object', - }, }, }; diff --git a/ui/components/app/modals/export-private-key-modal/export-private-key-modal.stories.js b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.stories.js index 74901533b..60a947f41 100644 --- a/ui/components/app/modals/export-private-key-modal/export-private-key-modal.stories.js +++ b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.stories.js @@ -4,34 +4,8 @@ import ExportPrivateKeyModal from '.'; export default { title: 'Components/App/Modals/ExportPrivateKeyModal', id: __filename, - argTypes: { - exportAccount: { - action: 'exportAccount', - }, - selectedIdentity: { - control: 'object', - }, - warning: { - control: 'node', - }, - showAccountDetailModal: { - action: 'showAccountDetailModal', - }, - hideModal: { - action: 'hideModal', - }, - hideWarning: { - action: 'hideWarning', - }, - clearAccountDetails: { - action: 'clearAccountDetails', - }, - previousModalState: { - control: 'text', - }, - }, }; -export const DefaultStory = (args) => ; +export const DefaultStory = () => ; DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.stories.js b/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.stories.js index 0fcdf1f9f..663ec82cf 100644 --- a/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.stories.js +++ b/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.stories.js @@ -47,6 +47,9 @@ export default { fiatNumberOfDecimals: { control: 'number', }, + showFiat: { + control: 'boolean', + }, }, args: { type: ETH, diff --git a/ui/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.stories.js b/ui/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.stories.js index 32c7f4136..1176ef6a1 100644 --- a/ui/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.stories.js +++ b/ui/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.stories.js @@ -2,7 +2,7 @@ import React from 'react'; import RevealSeedPhrase from '.'; export default { - title: 'Components/Pages/FirstTimeFlow/RevealSeedPhrase', + title: 'Pages/FirstTimeFlow/RevealSeedPhrase', id: __filename, }; diff --git a/ui/pages/permissions-connect/redirect/permissions-redirect.stories.js b/ui/pages/permissions-connect/redirect/permissions-redirect.stories.js index ed952082d..6624d946c 100644 --- a/ui/pages/permissions-connect/redirect/permissions-redirect.stories.js +++ b/ui/pages/permissions-connect/redirect/permissions-redirect.stories.js @@ -2,7 +2,7 @@ import React from 'react'; import PermissionsRedirect from '.'; export default { - title: 'Components/Pages/PermissionsConnect/Redirect/PermissionsRedirect', + title: 'Pages/PermissionsConnect/Redirect/PermissionsRedirect', id: __filename, argTypes: { subjectMetadata: { diff --git a/ui/pages/swaps/smart-transaction-status/smart-transaction-status.stories.js b/ui/pages/swaps/smart-transaction-status/smart-transaction-status.stories.js index ce93a3717..7a5c363d7 100644 --- a/ui/pages/swaps/smart-transaction-status/smart-transaction-status.stories.js +++ b/ui/pages/swaps/smart-transaction-status/smart-transaction-status.stories.js @@ -2,7 +2,7 @@ import React from 'react'; import SmartTransactionStatus from './smart-transaction-status'; export default { - title: 'SmartTransactionStatus', + title: 'Pages/Swaps/SmartTransactionStatus', }; export const SmartTransactionStatusComponent = () => { From 553e11d555087db96dcf4e3a3b65634f00db666e Mon Sep 17 00:00:00 2001 From: PeterYinusa <53189696+PeterYinusa@users.noreply.github.com> Date: Thu, 7 Apr 2022 16:49:30 +0100 Subject: [PATCH 63/92] Update ganache (#14375) * Update ganache * dedup --- package.json | 2 +- yarn.lock | 34 ++++++++++++++++------------------ 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index e696ae9d9..e8f77be0e 100644 --- a/package.json +++ b/package.json @@ -309,7 +309,7 @@ "fancy-log": "^1.3.3", "fast-glob": "^3.2.2", "fs-extra": "^8.1.0", - "ganache": "^v7.0.0-rc.0", + "ganache": "^v7.0.4", "geckodriver": "^1.21.0", "globby": "^11.0.4", "gulp": "^4.0.2", diff --git a/yarn.lock b/yarn.lock index 8bcfaa760..76cd3b2cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6885,9 +6885,9 @@ bn.js@^1.0.0: integrity sha1-DbTL+W+PI7dC9by50ap6mZSgXoM= bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.0, bn.js@^4.11.6, bn.js@^4.11.7, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.4.0: - version "4.11.9" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" - integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== bo-selector@0.0.10: version "0.0.10" @@ -7749,11 +7749,9 @@ cashaddrjs@0.4.4: big-integer "1.6.36" catering@^2.0.0, catering@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.0.tgz#1354f5e8e231a5b80309302bb23b40624d3212c5" - integrity sha512-M5imwzQn6y+ODBfgi+cfgZv2hIUI6oYU/0f35Mdb1ujGeqeoI5tOnl9Q13DTH7LW+7er+NYq8stNOKZD/Z3U/A== - dependencies: - queue-tick "^1.0.0" + version "2.1.1" + resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" + integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w== cbor-sync@^1.0.4: version "1.0.4" @@ -13000,10 +12998,10 @@ fuse.js@^3.2.0, fuse.js@^3.6.1: resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.6.1.tgz#7de85fdd6e1b3377c23ce010892656385fd9b10c" integrity sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw== -ganache@^v7.0.0-rc.0: - version "7.0.0-rc.0" - resolved "https://registry.yarnpkg.com/ganache/-/ganache-7.0.0-rc.0.tgz#250a588ba23a98ec2ed39b192733d8b9ec964e6f" - integrity sha512-4VNB0rhUJBTq3aGuQm4I/PHk/DkiJWfv/odguY40/G4BTgefhksK2miy615LM791iUKeNYnllBuOmd17xQhH/w== +ganache@^v7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/ganache/-/ganache-7.0.4.tgz#a1ec33f8e264a7691397dc9efdc4b9f85c8119e4" + integrity sha512-/ur2WNQoCwmobZb/TcpMgJlJy4TLtCUMtnCIldChAn5LleeAETaHB80knGqOgGc3ZS+ksokSqmQHuHqMAmlRkg== dependencies: "@trufflesuite/bigint-buffer" "1.1.9" emittery "0.10.0" @@ -19828,11 +19826,16 @@ node-forge@^0.7.1, node-forge@^0.7.5, node-forge@^1.2.1, node-forge@^1.3.0, node resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.0.tgz#37a874ea723855f37db091e6c186e5b67a01d4b2" integrity sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA== -node-gyp-build@4.3.0, node-gyp-build@^4.2.0, node-gyp-build@^4.2.2, node-gyp-build@^4.2.3, node-gyp-build@^4.3.0: +node-gyp-build@4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== +node-gyp-build@^4.2.0, node-gyp-build@^4.2.2, node-gyp-build@^4.2.3, node-gyp-build@^4.3.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.4.0.tgz#42e99687ce87ddeaf3a10b99dc06abc11021f3f4" + integrity sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ== + node-gyp-build@~3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.8.0.tgz#0f57efeb1971f404dfcbfab975c284de7c70f14a" @@ -22657,11 +22660,6 @@ queue-microtask@^1.2.3: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -queue-tick@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.0.tgz#011104793a3309ae86bfeddd54e251dc94a36725" - integrity sha512-ULWhjjE8BmiICGn3G8+1L9wFpERNxkf8ysxkAer4+TFdRefDaXOCV5m92aMB9FtBVmn/8sETXLXY6BfW7hyaWQ== - quick-format-unescaped@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-3.0.2.tgz#0137e94d8fb37ffeb70040535111c378e75396fb" From 89ce0cee85b278d0934197c00f20f187d8c1be6c Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Thu, 7 Apr 2022 13:11:42 -0400 Subject: [PATCH 64/92] Updating info ActionableMessage type to default (#14385) --- ui/pages/import-token/import-token.component.js | 2 +- ui/pages/import-token/import-token.container.js | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ui/pages/import-token/import-token.component.js b/ui/pages/import-token/import-token.component.js index c12db1b74..4dcd97a67 100644 --- a/ui/pages/import-token/import-token.component.js +++ b/ui/pages/import-token/import-token.component.js @@ -419,7 +419,7 @@ class ImportToken extends Component {
{TOKEN_DETECTION_V2 ? ( { selectedAddress, }, } = state; + + const tokenDetectionV2Supported = + process.env.TOKEN_DETECTION_V2 && getIsTokenDetectionSupported(state); const showSearchTab = - getIsTokenDetectionSupported(state) || process.env.IN_TEST; + getIsMainnet(state) || + tokenDetectionV2Supported || + Boolean(process.env.IN_TEST); + return { identities, mostRecentOverviewPage: getMostRecentOverviewPage(state), From 2b134a0c7f831b5d88c3113debb38b59943e53f6 Mon Sep 17 00:00:00 2001 From: ricky Date: Thu, 7 Apr 2022 14:28:03 -0400 Subject: [PATCH 65/92] Update `crowdin/github-action` (#14381) * Use crowdin/github-action@1.4.7 * use SHA --- .github/workflows/crowdin_action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/crowdin_action.yml b/.github/workflows/crowdin_action.yml index b07ac651a..9ddc21705 100644 --- a/.github/workflows/crowdin_action.yml +++ b/.github/workflows/crowdin_action.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v2 - name: crowdin action - uses: crowdin/github-action@d0622816ed4f4744db27d04374b2cef6867f7bed + uses: crowdin/github-action@9237b4cb361788dfce63feb2e2f15c09e2fe7415 with: upload_translations: true download_translations: true From 14a5dda24fe8dd2a68e8f11d5111526d1909a9a5 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Thu, 7 Apr 2022 22:26:35 +0200 Subject: [PATCH 66/92] Dark Mode : Fix connected account permission icon color (#14406) --- ui/components/app/connected-accounts-permissions/index.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/components/app/connected-accounts-permissions/index.scss b/ui/components/app/connected-accounts-permissions/index.scss index 06a2fcad6..c3a114f39 100644 --- a/ui/components/app/connected-accounts-permissions/index.scss +++ b/ui/components/app/connected-accounts-permissions/index.scss @@ -28,6 +28,7 @@ background: none; padding: 0; margin-left: 8px; + color: var(--color-icon-default); } } From 6db1be738d7782d93cdb8049f799f50ed85bc062 Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Fri, 8 Apr 2022 16:54:43 +0200 Subject: [PATCH 67/92] E2e infura requests (#14394) * Redirect infura requests to localhost while e2e * Change requests from Infura to localhost (ganache) * Included blacklisted hosts * Fix behaviour for all urls * Added a couple of explorers and reorg * Remove repeated line * Lint fix * Removed other services aside from infura * Includes changed for 'ends with' * Fix security handling of host by including listed of arrays --- test/e2e/mock-e2e.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index 478a5b959..a6d5d6518 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -1,5 +1,23 @@ +const blacklistedHosts = [ + 'goerli.infura.io', + 'kovan.infura.io', + 'mainnet.infura.io', + 'rinkeby.infura.io', + 'ropsten.infura.io', +]; + async function setupMocking(server, testSpecificMock) { - await server.forAnyRequest().thenPassThrough(); + await server.forAnyRequest().thenPassThrough({ + beforeRequest: (req) => { + const { host } = req.headers; + if (blacklistedHosts.includes(host)) { + return { + url: 'http://localhost:8545', + }; + } + return {}; + }, + }); await server.forPost('https://api.segment.io/v1/batch').thenCallback(() => { return { From 637a960d3c1126557656f9d68483f4db41bf73d9 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Fri, 8 Apr 2022 18:21:41 +0200 Subject: [PATCH 68/92] Use correct casing for "learn more" (#14412) --- .../confirmation/templates/flask/snap-confirm/snap-confirm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/pages/confirmation/templates/flask/snap-confirm/snap-confirm.js b/ui/pages/confirmation/templates/flask/snap-confirm/snap-confirm.js index 4a3a2268c..b6a2dcfbe 100644 --- a/ui/pages/confirmation/templates/flask/snap-confirm/snap-confirm.js +++ b/ui/pages/confirmation/templates/flask/snap-confirm/snap-confirm.js @@ -73,7 +73,7 @@ function getValues(pendingApproval, t, actions) { }, { element: 'a', - children: t('learnMore'), + children: t('learnMoreUpperCase'), key: 'learnMore-a-href', props: { href: From 9bd391a35fa653c1285b428795ae718bfa3546eb Mon Sep 17 00:00:00 2001 From: Olusegun Akintayo Date: Sun, 10 Apr 2022 11:42:00 +0400 Subject: [PATCH 69/92] User trait - number of nft collections (#14377) * Add new user trait for 'Number of NFT collections'. Signed-off-by: Akintayo A. Olusegun FIx JS DOC Signed-off-by: Akintayo A. Olusegun Arrange TRAITS in alphabetical order Signed-off-by: Akintayo A. Olusegun Unit Tests for allCollectibles traits tracking. Signed-off-by: Akintayo A. Olusegun change cid to chainId Signed-off-by: Akintayo A. Olusegun * invert condition Signed-off-by: Akintayo A. Olusegun * jsdoc - alphabetical order Signed-off-by: Akintayo A. Olusegun * change {string} to the literal {number_of_nft_collections} Signed-off-by: Akintayo A. Olusegun * Optimize _getNumberOfNFTs Signed-off-by: Akintayo A. Olusegun --- app/scripts/controllers/metametrics.js | 22 +++++++++++++++ app/scripts/controllers/metametrics.test.js | 31 +++++++++++++++++++-- shared/constants/metametrics.js | 7 +++-- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index a3fd4eead..4cc3ed69e 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -540,6 +540,9 @@ export default class MetaMetricsController { (rpc) => rpc.chainId, ), [TRAITS.THREE_BOX_ENABLED]: metamaskState.threeBoxSyncingAllowed, + [TRAITS.NUMBER_OF_NFT_COLLECTIONS]: this._getNumberOfNFtCollection( + metamaskState, + ), }; if (!this.previousTraits) { @@ -581,6 +584,25 @@ export default class MetaMetricsController { }, {}); } + /** + * + * @param {object} metamaskState + * @returns number of unique collectible addresses + */ + _getNumberOfNFtCollection(metamaskState) { + const { allCollectibles } = metamaskState; + if (!allCollectibles) { + return 0; + } + + const allAddresses = Object.values(allCollectibles) + .flatMap((chainCollectibles) => Object.values(chainCollectibles)) + .flat() + .map((collectible) => collectible.address); + const unique = [...new Set(allAddresses)]; + return unique.length; + } + /** * Calls segment.identify with given user traits * diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 9d5819f2d..81ea8e0ed 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -619,14 +619,41 @@ describe('MetaMetricsController', function () { ], ledgerTransportType: 'web-hid', identities: [{}, {}], + allCollectibles: { + '0xac706cE8A9BF27Afecf080fB298d0ee13cfb978A': { + 56: [ + { + address: '0xd2cea331e5f5d8ee9fb1055c297795937645de91', + tokenId: '100', + }, + { + address: '0xd2cea331e5f5d8ee9fb1055c297795937645de91', + tokenId: '101', + }, + { + address: '0x7488d2ce5deb26db021285b50b661d655eb3d3d9', + tokenId: '99', + }, + ], + }, + '0xe04AB39684A24D8D4124b114F3bd6FBEB779cacA': { + 69: [ + { + address: '0x63d646bc7380562376d5de205123a57b1718184d', + tokenId: '14', + }, + ], + }, + }, threeBoxSyncingAllowed: false, }); assert.deepEqual(traits, { - [TRAITS.THREE_BOX_ENABLED]: false, [TRAITS.LEDGER_CONNECTION_TYPE]: 'web-hid', - [TRAITS.NUMBER_OF_ACCOUNTS]: 2, [TRAITS.NETWORKS_ADDED]: [MAINNET_CHAIN_ID, ROPSTEN_CHAIN_ID], + [TRAITS.NUMBER_OF_ACCOUNTS]: 2, + [TRAITS.NUMBER_OF_NFT_COLLECTIONS]: 3, + [TRAITS.THREE_BOX_ENABLED]: false, }); }); diff --git a/shared/constants/metametrics.js b/shared/constants/metametrics.js index ffcfc4012..8291f4860 100644 --- a/shared/constants/metametrics.js +++ b/shared/constants/metametrics.js @@ -162,6 +162,8 @@ * identify the networks_added trait * @property {string} [NUMBER_OF_ACCOUNTS] - when identities change, we * identify the new number_of_accounts trait + * @property {number_of_nft_collections} [NUMBER_OF_NFT_COLLECTIONS] - user trait for number of + * unique NFT addresses * @property {string} [THREE_BOX_ENABLED] - when 3box feature is toggled we * identify the 3box_enabled trait */ @@ -173,9 +175,10 @@ export const TRAITS = { LEDGER_CONNECTION_TYPE: 'ledger_connection_type', - THREE_BOX_ENABLED: 'three_box_enabled', - NUMBER_OF_ACCOUNTS: 'number_of_accounts', NETWORKS_ADDED: 'networks_added', + NUMBER_OF_ACCOUNTS: 'number_of_accounts', + NUMBER_OF_NFT_COLLECTIONS: 'number_of_nft_collections', + THREE_BOX_ENABLED: 'three_box_enabled', }; // Mixpanel converts the zero address value to a truly anonymous event, which From d642dbc1118bc990cd87f92bea6164344ae949fe Mon Sep 17 00:00:00 2001 From: gholmes86 Date: Tue, 12 Apr 2022 04:57:18 -0500 Subject: [PATCH 70/92] e2e test for swaps (#14393) * chore: adding swap automation test * chore:updating test for error --- test/e2e/tests/swap-eth.spec.js | 94 +++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 test/e2e/tests/swap-eth.spec.js diff --git a/test/e2e/tests/swap-eth.spec.js b/test/e2e/tests/swap-eth.spec.js new file mode 100644 index 000000000..5e26b00e0 --- /dev/null +++ b/test/e2e/tests/swap-eth.spec.js @@ -0,0 +1,94 @@ +const { strict: assert } = require('assert'); +const { withFixtures } = require('../helpers'); + +describe('Swap Eth for another Token', function () { + const ganacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: 25000000000000000000, + }, + ], + }; + it('Completes a Swap between Eth and Matic', async function () { + await withFixtures( + { + fixtures: 'imported-account', + ganacheOptions, + title: this.test.title, + failOnConsoleError: false, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + await driver.clickElement( + '.wallet-overview__buttons .icon-button:nth-child(3)', + ); + await driver.clickElement( + '[class*="dropdown-search-list"] + div[class*="MuiFormControl-root MuiTextField-root"]', + ); + await driver.fill('input[placeholder*="0"]', '2'); + await driver.clickElement( + '[class*="dropdown-search-list"] + div[class*="MuiFormControl-root MuiTextField-root"]', + ); + await driver.clickElement( + '[class="dropdown-search-list__closed-primary-label dropdown-search-list__select-default"]', + ); + await driver.clickElement('[placeholder="Search for a token"]'); + await driver.clickElement('[placeholder="Search for a token"]'); + await driver.fill('[placeholder="Search for a token"]', 'DAI'); + await driver.waitForSelector( + '[class="searchable-item-list__primary-label"]', + ); + await driver.clickElement( + '[class="searchable-item-list__primary-label"]', + ); + await driver.clickElement({ text: 'Review Swap', tag: 'button' }); + await driver.waitForSelector('[class*="box--align-items-center"]'); + const estimatedEth = await driver.waitForSelector({ + css: '[class*="box--align-items-center"]', + text: 'Estimated gas fee', + }); + assert.equal(await estimatedEth.getText(), 'Estimated gas fee'); + await driver.waitForSelector( + '[class="exchange-rate-display main-quote-summary__exchange-rate-display"]', + ); + await driver.waitForSelector( + '[class="fee-card__info-tooltip-container"]', + ); + await driver.waitForSelector({ + css: '[class="countdown-timer__time"]', + text: '0:24', + }); + await driver.clickElement({ text: 'Swap', tag: 'button' }); + const sucessfulTransactionMessage = await driver.waitForSelector({ + css: '[class="awaiting-swap__header"]', + text: 'Transaction complete', + }); + assert.equal( + await sucessfulTransactionMessage.getText(), + 'Transaction complete', + ); + const sucessfulTransactionToken = await driver.waitForSelector({ + css: '[class="awaiting-swap__amount-and-symbol"]', + text: 'DAI', + }); + assert.equal(await sucessfulTransactionToken.getText(), 'DAI'); + await driver.clickElement({ text: 'Close', tag: 'button' }); + await driver.clickElement('[data-testid="home__activity-tab"]'); + const swaptotal = await driver.waitForSelector({ + css: '[class="transaction-list-item__primary-currency"]', + text: '-2 TESTETH', + }); + assert.equal(await swaptotal.getText(), '-2 TESTETH'); + const swaptotaltext = await driver.waitForSelector({ + css: '[class="list-item__title"]', + text: 'Swap TESTETH to DAI', + }); + assert.equal(await swaptotaltext.getText(), 'Swap TESTETH to DAI'); + }, + ); + }); +}); From 8eb8fc15910b8aaf8a7e152135bcd628c189698a Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Tue, 12 Apr 2022 18:33:16 +0200 Subject: [PATCH 71/92] Remove unused function (#14423) * Remove unused function * Removed fetch mocks file as it's no longer used --- test/data/fetch-mocks.json | 73 ------------------------------------- test/e2e/webdriver/index.js | 49 ------------------------- 2 files changed, 122 deletions(-) delete mode 100644 test/data/fetch-mocks.json diff --git a/test/data/fetch-mocks.json b/test/data/fetch-mocks.json deleted file mode 100644 index f9b2885f2..000000000 --- a/test/data/fetch-mocks.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "gasPricesBasic": { - "average": 85, - "fast": 200, - "safeLow": 80 - }, - "metametrics": { - "mockMetaMetricsResponse": true - }, - "swaps": { - "featureFlags": { - "bsc": { - "mobile_active": false, - "extension_active": true, - "fallback_to_v1": true - }, - "ethereum": { - "mobile_active": false, - "extension_active": true, - "fallback_to_v1": true - }, - "polygon": { - "mobile_active": false, - "extension_active": true, - "fallback_to_v1": false - } - } - }, - "tokenList": { - "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0": { - "address": "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0", - "symbol": "MATIC", - "decimals": 18, - "name": "Polygon", - "iconUrl": "https://raw.githubusercontent.com/MetaMask/eth-contract-metadata/master/images/matic-network-logo.svg", - "aggregators": [ - "airswapLight", - "bancor", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - }, - "0x0d8775f648430679a709e98d2b0cb6250d2887ef": { - "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", - "symbol": "BAT", - "decimals": 18, - "name": "Basic Attention Tok", - "iconUrl": "https://s3.amazonaws.com/airswap-token-images/BAT.png", - "aggregators": [ - "airswapLight", - "bancor", - "coinGecko", - "kleros", - "oneInch", - "paraswap", - "pmm", - "totle", - "zapper", - "zerion", - "zeroEx" - ], - "occurrences": 11 - } - } -} diff --git a/test/e2e/webdriver/index.js b/test/e2e/webdriver/index.js index 9753c03fe..13fcbb29f 100644 --- a/test/e2e/webdriver/index.js +++ b/test/e2e/webdriver/index.js @@ -1,5 +1,4 @@ const { Browser } = require('selenium-webdriver'); -const fetchMockResponses = require('../../data/fetch-mocks.json'); const Driver = require('./driver'); const ChromeDriver = require('./chrome'); const FirefoxDriver = require('./firefox'); @@ -12,7 +11,6 @@ async function buildWebDriver({ responsive, port, type } = {}) { extensionId, extensionUrl, } = await buildBrowserWebDriver(browser, { responsive, port, type }); - await setupFetchMocking(seleniumDriver); const driver = new Driver(seleniumDriver, browser, extensionUrl); return { @@ -35,53 +33,6 @@ async function buildBrowserWebDriver(browser, webDriverOptions) { } } -async function setupFetchMocking(driver) { - // define fetchMocking script, to be evaluated in the browser - function fetchMocking(mockResponses) { - window.origFetch = window.fetch.bind(window); - window.fetch = async (...args) => { - const url = args[0]; - // api.metaswap.codefi.network/gasPrices - if ( - url.match(/^http(s)?:\/\/api\.metaswap\.codefi\.network\/gasPrices/u) - ) { - return { json: async () => clone(mockResponses.gasPricesBasic) }; - } else if (url.match(/chromeextensionmm/u)) { - return { json: async () => clone(mockResponses.metametrics) }; - } else if (url.match(/^https:\/\/(swap\.metaswap\.codefi\.network)/u)) { - if (url.match(/featureFlags$/u)) { - return { json: async () => clone(mockResponses.swaps.featureFlags) }; - } - } else if ( - url.match(/^https:\/\/(token-api\.airswap-prod\.codefi\.network)/u) - ) { - if (url.match(/tokens\/1337$/u)) { - return { json: async () => clone(mockResponses.tokenList) }; - } - } - return window.origFetch(...args); - }; - if (window.chrome && window.chrome.webRequest) { - window.chrome.webRequest.onBeforeRequest.addListener( - cancelInfuraRequest, - { urls: ['https://*.infura.io/*'] }, - ['blocking'], - ); - } - function cancelInfuraRequest(requestDetails) { - console.log(`fetchMocking - Canceling request: "${requestDetails.url}"`); - return { cancel: true }; - } - function clone(obj) { - return JSON.parse(JSON.stringify(obj)); - } - } - // fetchMockResponses are parsed last minute to ensure that objects are uniquely instantiated - const fetchMockResponsesJson = JSON.stringify(fetchMockResponses); - // eval the fetchMocking script in the browser - await driver.executeScript(`(${fetchMocking})(${fetchMockResponsesJson})`); -} - module.exports = { buildWebDriver, }; From 4b3977044fba6c311ebdccc48cc21697c1853b9a Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Wed, 13 Apr 2022 14:18:35 +0200 Subject: [PATCH 72/92] Swap : Fix undefined icon url (#14411) --- ui/hooks/useTokensToSearch.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ui/hooks/useTokensToSearch.js b/ui/hooks/useTokensToSearch.js index 55cadd863..7ecf4b3a8 100644 --- a/ui/hooks/useTokensToSearch.js +++ b/ui/hooks/useTokensToSearch.js @@ -61,19 +61,19 @@ export function getRenderableTokenData( symbol, false, ) || ''; - let tokenAddress; - let tokenIconUrl; - if (process.env.TOKEN_DETECTION_V2) { - tokenAddress = address?.toLowerCase(); - tokenIconUrl = tokenList[tokenAddress]?.iconUrl; - } else { - // token from dynamic api list is fetched when useTokenDetection is true - // And since the token.address from allTokens is checksumaddress - // token Address have to be changed to lowercase when we are using dynamic list - tokenAddress = useTokenDetection ? address?.toLowerCase() : address; - tokenIconUrl = useTokenDetection - ? tokenList[tokenAddress]?.iconUrl - : `images/contract/${tokenList[tokenAddress].iconUrl}`; + + // token from dynamic api list is fetched when useTokenDetection is true + // And since the token.address from allTokens is checksumaddress + // token Address have to be changed to lowercase when we are using dynamic list + const tokenAddress = + useTokenDetection || process.env.TOKEN_DETECTION_V2 + ? address?.toLowerCase() + : address; + + let tokenIconUrl = tokenList[tokenAddress]?.iconUrl; + + if (!process.env.TOKEN_DETECTION_V2 && !useTokenDetection && tokenIconUrl) { + tokenIconUrl = `images/contract/${tokenIconUrl}`; } const usedIconUrl = iconUrl || tokenIconUrl || token?.image; return { From e768119dd0bc398477143988a5da582c2c22059f Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Wed, 13 Apr 2022 12:30:10 -0230 Subject: [PATCH 73/92] Remove duplicate gas simulation error messages (#14409) * Remove duplicate gas simulation error messages * Fixing locales * Fixing unit tests * Fixing e2e Co-authored-by: ryanml --- app/_locales/de/messages.json | 6 --- app/_locales/el/messages.json | 6 --- app/_locales/en/messages.json | 6 --- app/_locales/es_419/messages.json | 6 --- app/_locales/fr/messages.json | 6 --- app/_locales/hi/messages.json | 6 --- app/_locales/id/messages.json | 6 --- app/_locales/ja/messages.json | 6 --- app/_locales/ko/messages.json | 6 --- app/_locales/pt_BR/messages.json | 6 --- app/_locales/ru/messages.json | 6 --- app/_locales/tl/messages.json | 6 --- app/_locales/tr/messages.json | 6 --- app/_locales/vi/messages.json | 6 --- app/_locales/zh_CN/messages.json | 6 --- test/e2e/tests/failing-contract.spec.js | 7 ++- ...onfirm-page-container-content.component.js | 23 ---------- ...m-page-container-content.component.test.js | 43 ++----------------- .../confirm-transaction-base.component.js | 27 ++++++------ 19 files changed, 23 insertions(+), 167 deletions(-) diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index a4ee3e2a1..c20aa22c5 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -2459,9 +2459,6 @@ "signed": { "message": "Unterschrieben" }, - "simulationErrorMessage": { - "message": "Diese Transaktion wird voraussichtlich fehlschlagen. Der Versuch, sie auszuführen, wird voraussichtlich teuer aber fehlschlagen und wird nicht empfohlen." - }, "simulationErrorMessageV2": { "message": "Wir konnten das Gas nicht schätzen. Es könnte einen Fehler im Vertrag geben und diese Transaktion könnte fehlschlagen." }, @@ -3130,9 +3127,6 @@ "tryAgain": { "message": "Erneut versuchen" }, - "tryAnywayOption": { - "message": "Ich werde es trotzdem versuchen" - }, "turnOnTokenDetection": { "message": "Erweiterte Token-Erkennung aktivieren" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 0b14618d9..f95600a02 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -2462,9 +2462,6 @@ "signed": { "message": "Συνδεδεμένος" }, - "simulationErrorMessage": { - "message": "Αυτή η συναλλαγή αναμένεται να αποτύχει. Η προσπάθεια εκτέλεσης αναμένεται να είναι δαπανηρή αλλά να αποτύχει και δεν συνιστάται." - }, "simulationErrorMessageV2": { "message": "Δεν ήμασταν σε θέση να εκτιμήσουμε το τέλος συναλλαγής. Μπορεί να υπάρχει σφάλμα στο συμβόλαιο και αυτή η συναλλαγή μπορεί να αποτύχει." }, @@ -3130,9 +3127,6 @@ "tryAgain": { "message": "Δοκιμάστε ξανά" }, - "tryAnywayOption": { - "message": "Θα προσπαθήσω ούτως ή άλλως" - }, "turnOnTokenDetection": { "message": "Ενεργοποιήστε την ενισχυμένη ανίχνευση token" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 73fabb021..f1d6f38cf 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2879,9 +2879,6 @@ "signed": { "message": "Signed" }, - "simulationErrorMessage": { - "message": "This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended." - }, "simulationErrorMessageV2": { "message": "We were not able to estimate gas. There might be an error in the contract and this transaction may fail." }, @@ -3757,9 +3754,6 @@ "tryAgain": { "message": "Try again" }, - "tryAnywayOption": { - "message": "I will try anyway" - }, "turnOnTokenDetection": { "message": "Turn on enhanced token detection" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index fdcc7c46f..a2cd398ed 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -2514,9 +2514,6 @@ "signed": { "message": "Firmado" }, - "simulationErrorMessage": { - "message": "Se anticipa que esta transacción falle. En caso de intentar ejecutarla, se anticipa que eso sea costoso y a la vez falle, y por lo tanto no se recomienda." - }, "simulationErrorMessageV2": { "message": "No pudimos estimar el gas. Podría haber un error en el contrato y esta transacción podría fallar." }, @@ -3188,9 +3185,6 @@ "tryAgain": { "message": "Vuelva a intentarlo" }, - "tryAnywayOption": { - "message": "Lo intentaré de todos modos" - }, "turnOnTokenDetection": { "message": "Activar la detección mejorada de tokens" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 9933807df..20c298f3a 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -2462,9 +2462,6 @@ "signed": { "message": "Signé" }, - "simulationErrorMessage": { - "message": "Cette transaction devrait échouer. Si vous choisissez de l’exécuter, cela devrait à la fois être coûteux et voué à l’échec. Ce n’est donc pas recommandé." - }, "simulationErrorMessageV2": { "message": "Nous n’avons pas pu estimer le prix de carburant. Par conséquent, il se peut qu’il y ait une erreur dans le contrat et que cette transaction échoue." }, @@ -3130,9 +3127,6 @@ "tryAgain": { "message": "Réessayez" }, - "tryAnywayOption": { - "message": "Je veux quand même essayer" - }, "turnOnTokenDetection": { "message": "Activer la détection améliorée des jetons" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index cf4f05860..61abe3972 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -2462,9 +2462,6 @@ "signed": { "message": "हस्ताक्षर किया गया" }, - "simulationErrorMessage": { - "message": "यह लेन-देन विफल होने की उम्मीद है। इसे निष्पादित करने के प्रयास के महंगा होने की उम्मीद है लेकिन विफल है, और इसकी अनुशंसा नहीं की जाती है।" - }, "simulationErrorMessageV2": { "message": "हम गैस का अनुमान नहीं लगा पाए। अनुबंध में कोई त्रुटि हो सकती है और यह लेन-देन विफल हो सकता है।" }, @@ -3130,9 +3127,6 @@ "tryAgain": { "message": "पुनः प्रयास करें" }, - "tryAnywayOption": { - "message": "मैं फिर भी कोशिश करूंगा" - }, "turnOnTokenDetection": { "message": "उन्नत टोकन डिटेक्शन चालू करें" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 287ef03e7..b913d62a1 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -2462,9 +2462,6 @@ "signed": { "message": "Ditandatangani" }, - "simulationErrorMessage": { - "message": "Transaksi ini diperkirakan akan gagal. Pelaksanaannya diperkirakan akan mahal dan berpotensi gagal, dan tidak direkomendasikan." - }, "simulationErrorMessageV2": { "message": "Kami tidak dapat memperkirakan gas. Tampaknya ada kesalahan dalam kontrak dan transaksi ini berpotensi gagal." }, @@ -3130,9 +3127,6 @@ "tryAgain": { "message": "Coba lagi" }, - "tryAnywayOption": { - "message": "Saya akan tetap mencobanya" - }, "turnOnTokenDetection": { "message": "Nyalakan deteksi token yang ditingkatkan" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 2407e8cc4..7f7f5427e 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -2462,9 +2462,6 @@ "signed": { "message": "署名が完了しました" }, - "simulationErrorMessage": { - "message": "このトランザクションは失敗する見込みです。実行しようとすると多くのコストがかかるうえに失敗する可能性が高いため、お勧めしません。" - }, "simulationErrorMessageV2": { "message": "ガス代を見積もれませんでした。コントラクトにエラーがある可能性があり、このトランザクションは失敗するかもしれません。" }, @@ -3130,9 +3127,6 @@ "tryAgain": { "message": "再試行" }, - "tryAnywayOption": { - "message": "とにかく試してみる" - }, "turnOnTokenDetection": { "message": "強化されたトークン検出をオンにする" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 20340caee..d331f4dab 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -2462,9 +2462,6 @@ "signed": { "message": "서명완료" }, - "simulationErrorMessage": { - "message": "이 거래는 실패할 것으로 예상됩니다. 실행하려면 비용이 많이 들지만 실패할 것으로 예상되어 권장하지 않습니다." - }, "simulationErrorMessageV2": { "message": "가스 요금을 추정할 수 없었습니다. 계약에 오류가 있을 수 있으며 이 거래가 실패할 수 있습니다." }, @@ -3130,9 +3127,6 @@ "tryAgain": { "message": "다시 시도" }, - "tryAnywayOption": { - "message": "계속 시도" - }, "turnOnTokenDetection": { "message": "향상된 토큰 감지 켜기" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index 911e931ed..e6144d85c 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -2498,9 +2498,6 @@ "signed": { "message": "Assinado" }, - "simulationErrorMessage": { - "message": "É esperado que essa transação falhe. Como é esperado que sua tentativa de execução seja dispendiosa e falhe, ela não é recomendada." - }, "simulationErrorMessageV2": { "message": "Não conseguimos estimar o preço do gás. Pode haver um erro no contrato, e essa transação poderá falhar." }, @@ -3172,9 +3169,6 @@ "tryAgain": { "message": "Tente novamente" }, - "tryAnywayOption": { - "message": "Vou tentar mesmo assim" - }, "turnOnTokenDetection": { "message": "Ativar detecção avançada de token" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 9acd6789c..47b662403 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -2462,9 +2462,6 @@ "signed": { "message": "Подписано" }, - "simulationErrorMessage": { - "message": "Ожидается, что эта транзакция завершится неудачно. Предполагается, что попытка выполнить ее будет дорогостоящей, но потерпит неудачу, и поэтому ее не рекомендуется выполнять." - }, "simulationErrorMessageV2": { "message": "Мы не смогли оценить размер платы за газ. В контракте может быть ошибка, и эта транзакция может завершиться неудачно." }, @@ -3130,9 +3127,6 @@ "tryAgain": { "message": "Попробуйте еще раз" }, - "tryAnywayOption": { - "message": "Я все равно попробую" - }, "turnOnTokenDetection": { "message": "Включите расширенное обнаружение токенов" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 7c4160138..89e3c8c93 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -2462,9 +2462,6 @@ "signed": { "message": "Nilagdaan" }, - "simulationErrorMessage": { - "message": "Inaasahang mabibigo ang transaksyong ito. Ang pagsisikap na isagawa ito ay inaasahang magastos ngunit nabigo, at hindi inirerekomenda." - }, "simulationErrorMessageV2": { "message": "Hindi namin nagawang tantyahin ang gas. Maaaring may error sa kontrata at maaaring mabigo ang transaksyong ito." }, @@ -3130,9 +3127,6 @@ "tryAgain": { "message": "Subukan ulit" }, - "tryAnywayOption": { - "message": "Susubukan ko pa rin" - }, "turnOnTokenDetection": { "message": "I-on ang pinahusay na pag-detect ng token" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 136d444dc..c581cb36d 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -2462,9 +2462,6 @@ "signed": { "message": "İmzalandı" }, - "simulationErrorMessage": { - "message": "Bu işlemin başarısız olması bekleniyor. Gerçekleştirmeye çalışıldığında maliyetli olması ancak başarısız olması bekleniyor ve önerilmiyor." - }, "simulationErrorMessageV2": { "message": "Gaz tahmini yapamadık. Sözleşmede bir hata olabilir ve bu işlem başarısız olabilir." }, @@ -3130,9 +3127,6 @@ "tryAgain": { "message": "Tekrar dene" }, - "tryAnywayOption": { - "message": "Yine de deneyeceğim" - }, "turnOnTokenDetection": { "message": "Gelişmiş token algılamayı açın" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index ddce0ae1e..440cf0df8 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -2462,9 +2462,6 @@ "signed": { "message": "Đã ký" }, - "simulationErrorMessage": { - "message": "Giao dịch này có khả năng thất bại. Việc cố gắng thực hiện sẽ được cho là gây tốn kém nhưng lại không thành công và không được khuyến khích." - }, "simulationErrorMessageV2": { "message": "Chúng tôi không thể ước tính gas. Có thể đã xảy ra lỗi trong hợp đồng và giao dịch này có thể thất bại." }, @@ -3130,9 +3127,6 @@ "tryAgain": { "message": "Thử lại" }, - "tryAnywayOption": { - "message": "Tôi vẫn sẽ thử" - }, "turnOnTokenDetection": { "message": "Bật phát hiện token nâng cao" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 24bd792bf..bde50eec9 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -2462,9 +2462,6 @@ "signed": { "message": "已签名" }, - "simulationErrorMessage": { - "message": "此交易预计将失败。尝试执行该交易预计将是昂贵的,但失败,不推荐。" - }, "simulationErrorMessageV2": { "message": "我们无法估计燃料。合同中可能有一个错误,这笔交易可能失败。" }, @@ -3130,9 +3127,6 @@ "tryAgain": { "message": "重试" }, - "tryAnywayOption": { - "message": "我仍然会试" - }, "turnOnTokenDetection": { "message": "打开增强代币检测" }, diff --git a/test/e2e/tests/failing-contract.spec.js b/test/e2e/tests/failing-contract.spec.js index f95fba9e4..603d0641e 100644 --- a/test/e2e/tests/failing-contract.spec.js +++ b/test/e2e/tests/failing-contract.spec.js @@ -76,7 +76,7 @@ describe('Failing contract interaction ', function () { // display warning when transaction is expected to fail const warningText = - 'This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.'; + 'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.'; const warning = await driver.findElement( '.actionable-message__message', ); @@ -87,7 +87,10 @@ describe('Failing contract interaction ', function () { assert.equal(await confirmButton.isEnabled(), false); // dismiss warning and confirm the transaction - await driver.clickElement({ text: 'I will try anyway', tag: 'button' }); + await driver.clickElement({ + text: 'I want to proceed anyway', + tag: 'button', + }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.waitUntilXWindowHandles(2); await driver.switchToWindow(extension); diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index 641a13f03..c10d0219a 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -25,7 +25,6 @@ export default class ConfirmPageContainerContent extends Component { detailsComponent: PropTypes.node, errorKey: PropTypes.string, errorMessage: PropTypes.string, - hasSimulationError: PropTypes.bool, hideSubtitle: PropTypes.bool, tokenAddress: PropTypes.string, nonce: PropTypes.string, @@ -41,10 +40,8 @@ export default class ConfirmPageContainerContent extends Component { onCancel: PropTypes.func, cancelText: PropTypes.string, onSubmit: PropTypes.func, - setUserAcknowledgedGasMissing: PropTypes.func, submitText: PropTypes.string, disabled: PropTypes.bool, - hideUserAcknowledgedGasMissing: PropTypes.bool, unapprovedTxCount: PropTypes.number, rejectNText: PropTypes.string, hideTitle: PropTypes.bool, @@ -100,7 +97,6 @@ export default class ConfirmPageContainerContent extends Component { action, errorKey, errorMessage, - hasSimulationError, title, image, titleComponent, @@ -122,8 +118,6 @@ export default class ConfirmPageContainerContent extends Component { origin, ethGasPriceWarning, hideTitle, - setUserAcknowledgedGasMissing, - hideUserAcknowledgedGasMissing, supportsEIP1559V2, hasTopBorder, currentTransaction, @@ -135,17 +129,10 @@ export default class ConfirmPageContainerContent extends Component { isBuyableChain, } = this.props; - const primaryAction = hideUserAcknowledgedGasMissing - ? null - : { - label: this.context.t('tryAnywayOption'), - onClick: setUserAcknowledgedGasMissing, - }; const { t } = this.context; const showInsuffienctFundsError = supportsEIP1559V2 && - !hasSimulationError && (errorKey || errorMessage) && errorKey === INSUFFICIENT_FUNDS_ERROR_KEY; @@ -159,15 +146,6 @@ export default class ConfirmPageContainerContent extends Component { {ethGasPriceWarning && ( )} - {hasSimulationError && ( -
- -
- )} {this.renderContent()} {!supportsEIP1559V2 && - !hasSimulationError && (errorKey || errorMessage) && currentTransaction.type !== TRANSACTION_TYPES.SIMPLE_SEND && (
diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js index 89c855a71..aec9b8ca2 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js @@ -39,7 +39,6 @@ describe('Confirm Page Container Content', () => { action: ' Withdraw Stake', errorMessage: null, errorKey: null, - hasSimulationError: true, onCancelAll: mockOnCancelAll, onCancel: mockOnCancel, cancelText: 'Reject', @@ -52,38 +51,7 @@ describe('Confirm Page Container Content', () => { }; }); - it('render ConfirmPageContainer component with simulation error', async () => { - const { queryByText, getByText } = renderWithProvider( - , - store, - ); - - expect( - queryByText('Transaction Error. Exception thrown in contract code.'), - ).not.toBeInTheDocument(); - expect( - queryByText( - 'This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.', - ), - ).toBeInTheDocument(); - expect(queryByText('I will try anyway')).toBeInTheDocument(); - - const confirmButton = getByText('Confirm'); - expect(getByText('Confirm').closest('button')).toBeDisabled(); - fireEvent.click(confirmButton); - expect(props.onSubmit).toHaveBeenCalledTimes(0); - - const iWillTryButton = getByText('I will try anyway'); - fireEvent.click(iWillTryButton); - expect(props.setUserAcknowledgedGasMissing).toHaveBeenCalledTimes(1); - - const cancelButton = getByText('Reject'); - fireEvent.click(cancelButton); - expect(props.onCancel).toHaveBeenCalledTimes(1); - }); - it('render ConfirmPageContainer component with another error', async () => { - props.hasSimulationError = false; props.disabled = true; props.errorKey = TRANSACTION_ERROR_KEY; props.currentTransaction = { @@ -96,10 +64,10 @@ describe('Confirm Page Container Content', () => { expect( queryByText( - 'This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.', + 'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.', ), ).not.toBeInTheDocument(); - expect(queryByText('I will try anyway')).not.toBeInTheDocument(); + expect(queryByText('I want to proceed anyway')).not.toBeInTheDocument(); expect(getByText('Confirm').closest('button')).toBeDisabled(); expect( getByText('Transaction Error. Exception thrown in contract code.'), @@ -111,7 +79,6 @@ describe('Confirm Page Container Content', () => { }); it('render ConfirmPageContainer component with no errors', async () => { - props.hasSimulationError = false; props.disabled = false; const { queryByText, getByText } = renderWithProvider( , @@ -120,13 +87,13 @@ describe('Confirm Page Container Content', () => { expect( queryByText( - 'This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.', + 'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.', ), ).not.toBeInTheDocument(); expect( queryByText('Transaction Error. Exception thrown in contract code.'), ).not.toBeInTheDocument(); - expect(queryByText('I will try anyway')).not.toBeInTheDocument(); + expect(queryByText('I want to proceed anyway')).not.toBeInTheDocument(); const confirmButton = getByText('Confirm'); fireEvent.click(confirmButton); @@ -138,7 +105,6 @@ describe('Confirm Page Container Content', () => { }); it('render contract address name from addressBook in title for contract', async () => { - props.hasSimulationError = false; props.disabled = false; props.toAddress = '0x06195827297c7A80a443b6894d3BDB8824b43896'; props.transactionType = TRANSACTION_TYPES.CONTRACT_INTERACTION; @@ -151,7 +117,6 @@ describe('Confirm Page Container Content', () => { }); it('render simple title without address name for simple send', async () => { - props.hasSimulationError = false; props.disabled = false; props.toAddress = '0x06195827297c7A80a443b6894d3BDB8824b43896'; props.transactionType = TRANSACTION_TYPES.SIMPLE_SEND; diff --git a/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js index 097aa7f25..c008bc8cc 100644 --- a/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -560,13 +560,18 @@ export default class ConfirmTransactionBase extends Component { const simulationFailureWarning = () => (
this.setUserAcknowledgedGasMissing(), - }} - message={this.context.t('simulationErrorMessage')} - roundedButtons + primaryActionV2={ + userAcknowledgedGasMissing === true + ? undefined + : { + label: t('proceedWithTransaction'), + onClick: () => this.setUserAcknowledgedGasMissing(), + } + } />
); @@ -593,7 +598,9 @@ export default class ConfirmTransactionBase extends Component { : () => this.handleEditGas() } rows={[ - renderSimulationFailureWarning && simulationFailureWarning(), + renderSimulationFailureWarning && + !this.supportsEIP1559V2 && + simulationFailureWarning(), !renderSimulationFailureWarning && !isMultiLayerFeeNetwork && renderGasDetailsItem(), @@ -1034,10 +1041,6 @@ export default class ConfirmTransactionBase extends Component { requestsWaitingText, } = this.getNavigateTxData(); - const isDisabled = () => { - return userAcknowledgedGasMissing ? false : !valid; - }; - let functionType; if (txData.type === TRANSACTION_TYPES.CONTRACT_INTERACTION) { functionType = getMethodName(name); @@ -1089,7 +1092,6 @@ export default class ConfirmTransactionBase extends Component { lastTx={lastTx} ofText={ofText} requestsWaitingText={requestsWaitingText} - hideUserAcknowledgedGasMissing={!isDisabled()} disabled={ renderSimulationFailureWarning || !valid || @@ -1101,7 +1103,6 @@ export default class ConfirmTransactionBase extends Component { onCancelAll={() => this.handleCancelAll()} onCancel={() => this.handleCancel()} onSubmit={() => this.handleSubmit()} - setUserAcknowledgedGasMissing={this.setUserAcknowledgedGasMissing} hideSenderToRecipient={hideSenderToRecipient} origin={txData.origin} ethGasPriceWarning={ethGasPriceWarning} From dc3f14ffbbfafdeae882a285ef5f666c6004f489 Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Wed, 13 Apr 2022 17:26:26 +0200 Subject: [PATCH 74/92] E2e settings search msgs (#14386) * Improve error messages with template literals * Lint fix * Changed variable let for const and scope --- test/e2e/tests/settings-search.spec.js | 56 +++++++++++++++----------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/test/e2e/tests/settings-search.spec.js b/test/e2e/tests/settings-search.spec.js index 7137cb4ca..d817acb1c 100644 --- a/test/e2e/tests/settings-search.spec.js +++ b/test/e2e/tests/settings-search.spec.js @@ -39,11 +39,12 @@ describe('Settings Search', function () { await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.fill('#search-settings', settingsSearch.general); - await driver.clickElement({ text: 'General', tag: 'span' }); + const page = 'General'; + await driver.clickElement({ text: page, tag: 'span' }); assert.equal( - await driver.isElementPresent({ text: 'General', tag: 'div' }), + await driver.isElementPresent({ text: page, tag: 'div' }), true, - 'Item does not redirect to correct page', + `${settingsSearch.general} item does not redirect to ${page} view`, ); }, ); @@ -66,11 +67,12 @@ describe('Settings Search', function () { await driver.fill('#search-settings', settingsSearch.advanced); // Check if element redirects to the correct page - await driver.clickElement({ text: 'Advanced', tag: 'span' }); + const page = 'Advanced'; + await driver.clickElement({ text: page, tag: 'span' }); assert.equal( - await driver.isElementPresent({ text: 'Advanced', tag: 'div' }), + await driver.isElementPresent({ text: page, tag: 'div' }), true, - 'Item does not redirect to correct page', + `${settingsSearch.advanced} item does not redirect to ${page} view`, ); }, ); @@ -93,11 +95,12 @@ describe('Settings Search', function () { await driver.fill('#search-settings', settingsSearch.contacts); // Check if element redirects to the correct page - await driver.clickElement({ text: 'Contacts', tag: 'span' }); + const page = 'Contacts'; + await driver.clickElement({ text: page, tag: 'span' }); assert.equal( - await driver.isElementPresent({ text: 'Contacts', tag: 'div' }), + await driver.isElementPresent({ text: page, tag: 'div' }), true, - 'Item does not redirect to correct page', + `${settingsSearch.contacts} item does not redirect to ${page} view`, ); }, ); @@ -120,11 +123,12 @@ describe('Settings Search', function () { await driver.fill('#search-settings', settingsSearch.security); // Check if element redirects to the correct page - await driver.clickElement({ text: 'Security', tag: 'span' }); + const page = 'Security'; + await driver.clickElement({ text: page, tag: 'span' }); assert.equal( - await driver.isElementPresent({ text: 'Security', tag: 'div' }), + await driver.isElementPresent({ text: page, tag: 'div' }), true, - 'Item does not redirect to correct page', + `${settingsSearch.security} item does not redirect to ${page} view`, ); }, ); @@ -147,11 +151,12 @@ describe('Settings Search', function () { await driver.fill('#search-settings', settingsSearch.alerts); // Check if element redirects to the correct page - await driver.clickElement({ text: 'Alerts', tag: 'span' }); + const page = 'Alerts'; + await driver.clickElement({ text: page, tag: 'span' }); assert.equal( - await driver.isElementPresent({ text: 'Alerts', tag: 'div' }), + await driver.isElementPresent({ text: page, tag: 'div' }), true, - 'Item does not redirect to correct page', + `${settingsSearch.alerts} item does not redirect to ${page} view`, ); }, ); @@ -174,11 +179,12 @@ describe('Settings Search', function () { await driver.fill('#search-settings', settingsSearch.networks); // Check if element redirects to the correct page - await driver.clickElement({ text: 'Networks', tag: 'span' }); + const page = 'Networks'; + await driver.clickElement({ text: page, tag: 'span' }); assert.equal( - await driver.isElementPresent({ text: 'Networks', tag: 'div' }), + await driver.isElementPresent({ text: page, tag: 'div' }), true, - 'Item does not redirect to correct page', + `${settingsSearch.networks} item does not redirect to ${page} view`, ); }, ); @@ -201,11 +207,12 @@ describe('Settings Search', function () { await driver.fill('#search-settings', settingsSearch.experimental); // Check if element redirects to the correct page - await driver.clickElement({ text: 'Experimental', tag: 'span' }); + const page = 'Experimental'; + await driver.clickElement({ text: page, tag: 'span' }); assert.equal( - await driver.isElementPresent({ text: 'Experimental', tag: 'div' }), + await driver.isElementPresent({ text: page, tag: 'div' }), true, - 'Item does not redirect to correct page', + `${settingsSearch.experimental} item not redirect to ${page} view`, ); }, ); @@ -228,11 +235,12 @@ describe('Settings Search', function () { await driver.fill('#search-settings', settingsSearch.about); // Check if element redirects to the correct page - await driver.clickElement({ text: 'About', tag: 'span' }); + const page = 'About'; + await driver.clickElement({ text: page, tag: 'span' }); assert.equal( - await driver.isElementPresent({ text: 'About', tag: 'div' }), + await driver.isElementPresent({ text: page, tag: 'div' }), true, - 'Item does not redirect to correct page', + `${settingsSearch.about} item does not redirect to ${page} view`, ); }, ); From 455d4a9825aeb7d6eeb91daef80aee147e385b1c Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:23:41 -0400 Subject: [PATCH 75/92] Update DetectTokenController for token detection V2 (#14216) --- app/_locales/en/messages.json | 4 + app/scripts/controllers/detect-tokens.js | 107 ++++++++++--- app/scripts/metamask-controller.js | 150 +++++++++++++----- shared/modules/network.utils.js | 26 ++- ui/components/app/app-components.scss | 1 + .../detected-tokens-link.js | 36 +++++ .../detetcted-tokens-link/index.scss | 5 + ui/selectors/selectors.js | 8 +- ui/store/actions.js | 59 +++++++ 9 files changed, 321 insertions(+), 75 deletions(-) create mode 100644 ui/components/app/asset-list/detetcted-tokens-link/detected-tokens-link.js create mode 100644 ui/components/app/asset-list/detetcted-tokens-link/index.scss diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index f1d6f38cf..2dce6fb82 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2229,6 +2229,10 @@ "notifications9Title": { "message": "👓 We are making transactions easier to read." }, + "numberOfNewTokensDetected": { + "message": "$1 new tokens found in this account", + "description": "$1 is the number of new tokens detected" + }, "ofTextNofM": { "message": "of" }, diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index c98c1683c..40ccfb5f0 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -4,6 +4,7 @@ import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi'; import { SINGLE_CALL_BALANCES_ADDRESS } from '../constants/contracts'; import { MINUTE } from '../../../shared/constants/time'; import { MAINNET_CHAIN_ID } from '../../../shared/constants/network'; +import { isTokenDetectionEnabledForNetwork } from '../../../shared/modules/network.utils'; import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; // By default, poll every 3 minutes @@ -24,6 +25,7 @@ export default class DetectTokensController { * @param config.keyringMemStore * @param config.tokenList * @param config.tokensController + * @param config.assetsContractController */ constructor({ interval = DEFAULT_INTERVAL, @@ -32,7 +34,9 @@ export default class DetectTokensController { keyringMemStore, tokenList, tokensController, + assetsContractController = null, } = {}) { + this.assetsContractController = assetsContractController; this.tokensController = tokensController; this.preferences = preferences; this.interval = interval; @@ -44,6 +48,9 @@ export default class DetectTokensController { return token.address; }); this.hiddenTokens = this.tokensController?.state.ignoredTokens; + this.detectedTokens = process.env.TOKEN_DETECTION_V2 + ? this.tokensController?.state.detectedTokens + : []; preferences?.store.subscribe(({ selectedAddress, useTokenDetection }) => { if ( @@ -55,14 +62,24 @@ export default class DetectTokensController { this.restartTokenDetection(); } }); - tokensController?.subscribe(({ tokens = [], ignoredTokens = [] }) => { - this.tokenAddresses = tokens.map((token) => { - return token.address; - }); - this.hiddenTokens = ignoredTokens; - }); + tokensController?.subscribe( + ({ tokens = [], ignoredTokens = [], detectedTokens = [] }) => { + this.tokenAddresses = tokens.map((token) => { + return token.address; + }); + this.hiddenTokens = ignoredTokens; + this.detectedTokens = process.env.TOKEN_DETECTION_V2 + ? detectedTokens + : []; + }, + ); } + /** + * TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up + * + * @param tokens + */ async _getTokenBalances(tokens) { const ethContract = this.web3.eth .contract(SINGLE_CALL_BALANCES_ABI) @@ -84,14 +101,23 @@ export default class DetectTokensController { if (!this.isActive) { return; } - + if ( + process.env.TOKEN_DETECTION_V2 && + (!this.useTokenDetection || + !isTokenDetectionEnabledForNetwork( + this._network.store.getState().provider.chainId, + )) + ) { + return; + } const { tokenList } = this._tokenList.state; // since the token detection is currently enabled only on Mainnet // we can use the chainId check to ensure token detection is not triggered for any other network // but once the balance check contract for other networks are deploayed and ready to use, we need to update this check. if ( - this._network.store.getState().provider.chainId !== MAINNET_CHAIN_ID || - Object.keys(tokenList).length === 0 + !process.env.TOKEN_DETECTION_V2 && + (this._network.store.getState().provider.chainId !== MAINNET_CHAIN_ID || + Object.keys(tokenList).length === 0) ) { return; } @@ -105,6 +131,9 @@ export default class DetectTokensController { ) && !this.hiddenTokens.find((address) => isEqualCaseInsensitive(address, tokenAddress), + ) && + !this.detectedTokens.find(({ address }) => + isEqualCaseInsensitive(address, tokenAddress), ) ) { tokensToDetect.push(tokenAddress); @@ -117,7 +146,12 @@ export default class DetectTokensController { for (const tokensSlice of sliceOfTokensToDetect) { let result; try { - result = await this._getTokenBalances(tokensSlice); + result = process.env.TOKEN_DETECTION_V2 + ? await this.assetsContractController.getBalancesInSingleCall( + this.selectedAddress, + tokensSlice, + ) + : await this._getTokenBalances(tokensSlice); } catch (error) { warn( `MetaMask - DetectTokensController single call balance fetch failed`, @@ -126,20 +160,45 @@ export default class DetectTokensController { return; } - const tokensWithBalance = tokensSlice.filter((_, index) => { - const balance = result[index]; - return balance && !balance.isZero(); - }); - - await Promise.all( - tokensWithBalance.map((tokenAddress) => { - return this.tokensController.addToken( - tokenAddress, - tokenList[tokenAddress].symbol, - tokenList[tokenAddress].decimals, - ); - }), - ); + let tokensWithBalance = []; + if (process.env.TOKEN_DETECTION_V2) { + if (result) { + const nonZeroTokenAddresses = Object.keys(result); + for (const nonZeroTokenAddress of nonZeroTokenAddresses) { + const { + address, + symbol, + decimals, + iconUrl, + aggregators, + } = tokenList[nonZeroTokenAddress]; + tokensWithBalance.push({ + address, + symbol, + decimals, + image: iconUrl, + aggregators, + }); + } + if (tokensWithBalance.length > 0) { + await this.tokensController.addDetectedTokens(tokensWithBalance); + } + } + } else { + tokensWithBalance = tokensSlice.filter((_, index) => { + const balance = result[index]; + return balance && !balance.isZero(); + }); + await Promise.all( + tokensWithBalance.map((tokenAddress) => { + return this.tokensController.addToken( + tokenAddress, + tokenList[tokenAddress].symbol, + tokenList[tokenAddress].decimals, + ); + }), + ); + } } } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index f615d0a51..4bdc59847 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -238,16 +238,35 @@ export default class MetamaskController extends EventEmitter { config: { provider: this.provider }, state: initState.TokensController, }); - - this.assetsContractController = new AssetsContractController( - { - onPreferencesStateChange: (listener) => - this.preferencesController.store.subscribe(listener), - }, - { - provider: this.provider, - }, - ); + process.env.TOKEN_DETECTION_V2 + ? (this.assetsContractController = new AssetsContractController({ + onPreferencesStateChange: (listener) => + this.preferencesController.store.subscribe(listener), + onNetworkStateChange: (cb) => + this.networkController.store.subscribe((networkState) => { + const modifiedNetworkState = { + ...networkState, + provider: { + ...networkState.provider, + chainId: hexToDecimal(networkState.provider.chainId), + }, + }; + return cb(modifiedNetworkState); + }), + config: { + provider: this.provider, + }, + state: initState.AssetsContractController, + })) + : (this.assetsContractController = new AssetsContractController( + { + onPreferencesStateChange: (listener) => + this.preferencesController.store.subscribe(listener), + }, + { + provider: this.provider, + }, + )); this.collectiblesController = new CollectiblesController( { @@ -388,33 +407,50 @@ export default class MetamaskController extends EventEmitter { const tokenListMessenger = this.controllerMessenger.getRestricted({ name: 'TokenListController', }); - this.tokenListController = new TokenListController({ - chainId: hexToDecimal(this.networkController.getCurrentChainId()), - useStaticTokenList: !this.preferencesController.store.getState() - .useTokenDetection, - onNetworkStateChange: (cb) => - this.networkController.store.subscribe((networkState) => { - const modifiedNetworkState = { - ...networkState, - provider: { - ...networkState.provider, - chainId: hexToDecimal(networkState.provider.chainId), - }, - }; - return cb(modifiedNetworkState); - }), - onPreferencesStateChange: (cb) => - this.preferencesController.store.subscribe((preferencesState) => { - const modifiedPreferencesState = { - ...preferencesState, - useStaticTokenList: !this.preferencesController.store.getState() - .useTokenDetection, - }; - return cb(modifiedPreferencesState); - }), - messenger: tokenListMessenger, - state: initState.TokenListController, - }); + process.env.TOKEN_DETECTION_V2 + ? (this.tokenListController = new TokenListController({ + chainId: hexToDecimal(this.networkController.getCurrentChainId()), + onNetworkStateChange: (cb) => + this.networkController.store.subscribe((networkState) => { + const modifiedNetworkState = { + ...networkState, + provider: { + ...networkState.provider, + chainId: hexToDecimal(networkState.provider.chainId), + }, + }; + return cb(modifiedNetworkState); + }), + messenger: tokenListMessenger, + state: initState.TokenListController, + })) + : (this.tokenListController = new TokenListController({ + chainId: hexToDecimal(this.networkController.getCurrentChainId()), + useStaticTokenList: !this.preferencesController.store.getState() + .useTokenDetection, + onNetworkStateChange: (cb) => + this.networkController.store.subscribe((networkState) => { + const modifiedNetworkState = { + ...networkState, + provider: { + ...networkState.provider, + chainId: hexToDecimal(networkState.provider.chainId), + }, + }; + return cb(modifiedNetworkState); + }), + onPreferencesStateChange: (cb) => + this.preferencesController.store.subscribe((preferencesState) => { + const modifiedPreferencesState = { + ...preferencesState, + useStaticTokenList: !this.preferencesController.store.getState() + .useTokenDetection, + }; + return cb(modifiedPreferencesState); + }), + messenger: tokenListMessenger, + state: initState.TokenListController, + })); this.phishingController = new PhishingController(); @@ -665,13 +701,22 @@ export default class MetamaskController extends EventEmitter { }); ///: END:ONLY_INCLUDE_IN - this.detectTokensController = new DetectTokensController({ - preferences: this.preferencesController, - tokensController: this.tokensController, - network: this.networkController, - keyringMemStore: this.keyringController.memStore, - tokenList: this.tokenListController, - }); + process.env.TOKEN_DETECTION_V2 + ? (this.detectTokensController = new DetectTokensController({ + preferences: this.preferencesController, + tokensController: this.tokensController, + assetsContractController: this.assetsContractController, + network: this.networkController, + keyringMemStore: this.keyringController.memStore, + tokenList: this.tokenListController, + })) + : (this.detectTokensController = new DetectTokensController({ + preferences: this.preferencesController, + tokensController: this.tokensController, + network: this.networkController, + keyringMemStore: this.keyringController.memStore, + tokenList: this.tokenListController, + })); this.addressBookController = new AddressBookController( undefined, @@ -1337,6 +1382,7 @@ export default class MetamaskController extends EventEmitter { tokensController, smartTransactionsController, txController, + assetsContractController, } = this; return { @@ -1817,6 +1863,22 @@ export default class MetamaskController extends EventEmitter { collectibleDetectionController, ) : null, + + /** Token Detection V2 */ + addDetectedTokens: process.env.TOKEN_DETECTION_V2 + ? tokensController.addDetectedTokens.bind(tokensController) + : null, + importTokens: process.env.TOKEN_DETECTION_V2 + ? tokensController.importTokens.bind(tokensController) + : null, + ignoreTokens: process.env.TOKEN_DETECTION_V2 + ? tokensController.ignoreTokens.bind(tokensController) + : null, + getBalancesInSingleCall: process.env.TOKEN_DETECTION_V2 + ? assetsContractController.getBalancesInSingleCall.bind( + assetsContractController, + ) + : null, }; } diff --git a/shared/modules/network.utils.js b/shared/modules/network.utils.js index 7d72d18f7..eaa7148a3 100644 --- a/shared/modules/network.utils.js +++ b/shared/modules/network.utils.js @@ -1,4 +1,10 @@ -import { MAX_SAFE_CHAIN_ID } from '../constants/network'; +import { + MAX_SAFE_CHAIN_ID, + BSC_CHAIN_ID, + POLYGON_CHAIN_ID, + AVALANCHE_CHAIN_ID, + MAINNET_CHAIN_ID, +} from '../constants/network'; /** * Checks whether the given number primitive chain ID is safe. @@ -28,3 +34,21 @@ export function isPrefixedFormattedHexString(value) { } return /^0x[1-9a-f]+[0-9a-f]*$/iu.test(value); } + +/** + * Check if token detection is enabled for certain networks + * + * @param chainId - ChainID of network + * @returns Whether the current network supports token detection + */ +export function isTokenDetectionEnabledForNetwork(chainId) { + switch (chainId) { + case MAINNET_CHAIN_ID: + case BSC_CHAIN_ID: + case POLYGON_CHAIN_ID: + case AVALANCHE_CHAIN_ID: + return true; + default: + return false; + } +} diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 0493d234a..aac5586b8 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -84,6 +84,7 @@ @import 'advanced-gas-fee-popover/advanced-gas-fee-input-subtext/index'; @import 'advanced-gas-fee-popover/advanced-gas-fee-defaults/index'; @import 'currency-input/index'; +@import 'asset-list/detetcted-tokens-link/index'; @import 'detected-token/detected-token-address/index'; @import 'detected-token/detected-token-aggregators/index'; @import 'detected-token/detected-token-values/index'; diff --git a/ui/components/app/asset-list/detetcted-tokens-link/detected-tokens-link.js b/ui/components/app/asset-list/detetcted-tokens-link/detected-tokens-link.js new file mode 100644 index 000000000..a1aa3fb41 --- /dev/null +++ b/ui/components/app/asset-list/detetcted-tokens-link/detected-tokens-link.js @@ -0,0 +1,36 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import Box from '../../../ui/box/box'; +import Button from '../../../ui/button'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { getDetectedTokensInCurrentNetwork } from '../../../../selectors'; + +const DetectedTokensLink = ({ className = '', onClick }) => { + const t = useI18nContext(); + const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork); + + return ( + + + + ); +}; + +DetectedTokensLink.propTypes = { + onClick: PropTypes.func.isRequired, + className: PropTypes.string, +}; + +export default DetectedTokensLink; diff --git a/ui/components/app/asset-list/detetcted-tokens-link/index.scss b/ui/components/app/asset-list/detetcted-tokens-link/index.scss new file mode 100644 index 000000000..01b5da1ff --- /dev/null +++ b/ui/components/app/asset-list/detetcted-tokens-link/index.scss @@ -0,0 +1,5 @@ +.detected-tokens-link { + & &__link { + @include H6; + } +} diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 4a232452e..ef880038e 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -946,10 +946,6 @@ export function getIsTokenDetectionSupported(state) { ].includes(chainId); } -export function getTokenDetectionNoticeDismissed(state) { - return state.metamask.tokenDetectionNoticeDismissed; -} - -export function getTokenDetectionWarningDismissed(state) { - return state.metamask.tokenDetectionWarningDismissed; +export function getDetectedTokensInCurrentNetwork(state) { + return state.metamask.detectedTokens; } diff --git a/ui/store/actions.js b/ui/store/actions.js index a157761da..df5411c52 100644 --- a/ui/store/actions.js +++ b/ui/store/actions.js @@ -1419,6 +1419,65 @@ export function addToken( } }; } +/** + * To add detected tokens to state + * + * @param newDetectedTokens + */ +export function addDetectedTokens(newDetectedTokens) { + return async (dispatch) => { + try { + await promisifiedBackground.addDetectedTokens(newDetectedTokens); + } catch (error) { + log.error(error); + } finally { + await forceUpdateMetamaskState(dispatch); + } + }; +} + +/** + * To add the tokens user selected to state + * + * @param tokensToImport + */ +export function importTokens(tokensToImport) { + return async (dispatch) => { + try { + await promisifiedBackground.importTokens(tokensToImport); + } catch (error) { + log.error(error); + } finally { + await forceUpdateMetamaskState(dispatch); + } + }; +} + +/** + * To add ignored tokens to state + * + * @param tokensToIgnore + */ +export function ignoreTokens(tokensToIgnore) { + return async (dispatch) => { + try { + await promisifiedBackground.ignoreTokens(tokensToIgnore); + } catch (error) { + log.error(error); + } finally { + await forceUpdateMetamaskState(dispatch); + } + }; +} + +/** + * To fetch the ERC20 tokens with non-zero balance in a single call + * + * @param tokens + */ +export async function getBalancesInSingleCall(tokens) { + return await promisifiedBackground.getBalancesInSingleCall(tokens); +} export function addCollectible(address, tokenID, dontShowLoadingIndicator) { return async (dispatch) => { From 922ebd57cbd4aa72c5872b89730d61972b939efc Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Wed, 13 Apr 2022 11:46:49 -0500 Subject: [PATCH 76/92] track address book usage (#14382) * track address book usage * Update app/scripts/controllers/metametrics.js Co-authored-by: Ariella Vu <20778143+digiwand@users.noreply.github.com> * Update shared/constants/metametrics.js Co-authored-by: Ariella Vu <20778143+digiwand@users.noreply.github.com> Co-authored-by: Ariella Vu <20778143+digiwand@users.noreply.github.com> --- app/scripts/controllers/metametrics.js | 16 +++++++- app/scripts/controllers/metametrics.test.js | 22 +++++++++++ shared/constants/metametrics.js | 41 ++++++++++++++++----- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 4cc3ed69e..65fa46c83 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -1,4 +1,4 @@ -import { isEqual, merge, omit, omitBy, pickBy } from 'lodash'; +import { isEqual, merge, omit, omitBy, pickBy, size, sum } from 'lodash'; import { ObservableStore } from '@metamask/obs-store'; import { bufferToHex, keccak } from 'ethereumjs-util'; import { generateUUID } from 'pubnub'; @@ -31,6 +31,7 @@ const exceptionsToFilter = { * @typedef {import('../../../shared/constants/metametrics').MetaMetricsPagePayload} MetaMetricsPagePayload * @typedef {import('../../../shared/constants/metametrics').MetaMetricsPageOptions} MetaMetricsPageOptions * @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventFragment} MetaMetricsEventFragment + * @typedef {import('../../../shared/constants/metametrics').MetaMetricsTraits} MetaMetricsTraits */ /** @@ -531,8 +532,21 @@ export default class MetaMetricsController { }; } + /** + * This method generates the MetaMetrics user traits object, omitting any + * traits that have not changed since the last invocation of this method. + * + * @param {object} metamaskState - Full metamask state object. + * @returns {MetaMetricsTraits | null} traits that have changed since last update + */ _buildUserTraitsObject(metamaskState) { + /** + * @type {MetaMetricsTraits} + */ const currentTraits = { + [TRAITS.ADDRESS_BOOK_ENTRIES]: sum( + Object.values(metamaskState.addressBook).map(size), + ), [TRAITS.LEDGER_CONNECTION_TYPE]: metamaskState.ledgerTransportType, [TRAITS.NUMBER_OF_ACCOUNTS]: Object.values(metamaskState.identities) .length, diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 81ea8e0ed..0cf82af71 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -646,9 +646,14 @@ describe('MetaMetricsController', function () { }, }, threeBoxSyncingAllowed: false, + addressBook: { + [MAINNET_CHAIN_ID]: [{ address: '0x' }], + [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + }, }); assert.deepEqual(traits, { + [TRAITS.ADDRESS_BOOK_ENTRIES]: 3, [TRAITS.LEDGER_CONNECTION_TYPE]: 'web-hid', [TRAITS.NETWORKS_ADDED]: [MAINNET_CHAIN_ID, ROPSTEN_CHAIN_ID], [TRAITS.NUMBER_OF_ACCOUNTS]: 2, @@ -667,6 +672,10 @@ describe('MetaMetricsController', function () { ledgerTransportType: 'web-hid', identities: [{}, {}], threeBoxSyncingAllowed: false, + addressBook: { + [MAINNET_CHAIN_ID]: [{ address: '0x' }], + [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + }, }); const updatedTraits = metaMetricsController._buildUserTraitsObject({ @@ -677,9 +686,14 @@ describe('MetaMetricsController', function () { ledgerTransportType: 'web-hid', identities: [{}, {}, {}], threeBoxSyncingAllowed: false, + addressBook: { + [MAINNET_CHAIN_ID]: [{ address: '0x' }, { address: '0x1' }], + [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + }, }); assert.deepEqual(updatedTraits, { + [TRAITS.ADDRESS_BOOK_ENTRIES]: 4, [TRAITS.NUMBER_OF_ACCOUNTS]: 3, }); }); @@ -694,6 +708,10 @@ describe('MetaMetricsController', function () { ledgerTransportType: 'web-hid', identities: [{}, {}], threeBoxSyncingAllowed: false, + addressBook: { + [MAINNET_CHAIN_ID]: [{ address: '0x' }], + [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + }, }); const updatedTraits = metaMetricsController._buildUserTraitsObject({ @@ -704,6 +722,10 @@ describe('MetaMetricsController', function () { ledgerTransportType: 'web-hid', identities: [{}, {}], threeBoxSyncingAllowed: false, + addressBook: { + [MAINNET_CHAIN_ID]: [{ address: '0x' }], + [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + }, }); assert.equal(updatedTraits, null); diff --git a/shared/constants/metametrics.js b/shared/constants/metametrics.js index 8291f4860..28c6076f3 100644 --- a/shared/constants/metametrics.js +++ b/shared/constants/metametrics.js @@ -156,16 +156,20 @@ /** * @typedef {Object} Traits - * @property {string} [LEDGER_CONNECTION_TYPE] - when ledger live connnection - * type is changed we identify the ledger_connection_type trait - * @property {string} [NETWORKS_ADDED] - when user modifies networks we - * identify the networks_added trait - * @property {string} [NUMBER_OF_ACCOUNTS] - when identities change, we - * identify the new number_of_accounts trait - * @property {number_of_nft_collections} [NUMBER_OF_NFT_COLLECTIONS] - user trait for number of - * unique NFT addresses - * @property {string} [THREE_BOX_ENABLED] - when 3box feature is toggled we - * identify the 3box_enabled trait + * @property {'address_book_entries'} ADDRESS_BOOK_ENTRIES - When the user + * adds or modifies addresses in address book the address_book_entries trait + * is identified. + * @property {'ledger_connection_type'} LEDGER_CONNECTION_TYPE - when ledger + * live connnection type is changed we identify the ledger_connection_type + * trait + * @property {'networks_added'} NETWORKS_ADDED - when user modifies networks + * we identify the networks_added trait + * @property {'number_of_accounts'} NUMBER_OF_ACCOUNTS - when identities + * change, we identify the new number_of_accounts trait + * @property {'number_of_nft_collections'} NUMBER_OF_NFT_COLLECTIONS - user + * trait for number of unique NFT addresses + * @property {'three_box_enabled'} THREE_BOX_ENABLED - when 3box feature is + * toggled we identify the 3box_enabled trait */ /** @@ -174,6 +178,7 @@ */ export const TRAITS = { + ADDRESS_BOOK_ENTRIES: 'address_book_entries', LEDGER_CONNECTION_TYPE: 'ledger_connection_type', NETWORKS_ADDED: 'networks_added', NUMBER_OF_ACCOUNTS: 'number_of_accounts', @@ -181,6 +186,22 @@ export const TRAITS = { THREE_BOX_ENABLED: 'three_box_enabled', }; +/** + * @typedef {Object} MetaMetricsTraits + * @property {number} [address_book_entries] - The number of entries in the + * user's address book. + * @property {'ledgerLive' | 'webhid' | 'u2f'} [ledger_connection_type] - the + * type of ledger connection set by user preference. + * @property {Array} [networks_added] - An array consisting of chainIds + * that indicate the networks a user has added to their MetaMask. + * @property {number} [number_of_accounts] - A number representing the number + * of identities(accounts) added to the user's MetaMask. + * @property {number} [number_of_nft_collections] - A number representing the + * amount of different NFT collections the user possesses an NFT from. + * @property {boolean} [three_box_enabled] - does the user have 3box sync + * enabled? + */ + // Mixpanel converts the zero address value to a truly anonymous event, which // speeds up reporting export const METAMETRICS_ANONYMOUS_ID = '0x0000000000000000'; From 0f4417684b238df81adf23e77fd65cf69c23eeb2 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Apr 2022 17:28:03 -1000 Subject: [PATCH 77/92] unblock ci: update vuln deps + fix npm registry for snaps firefox (#14437) * dep-audit-fix for async@2.6.3 * deps - update async for vuln fix and remove patch * deps/ci-yarn-audit - remove outdated ignored vulns * deps/lock - deduplicate async@2 * deps/lock - deduplicate async@2 + remove indirectly used dep * Use regular NPM registry for snaps on Firefox (#14439) * Use regular NPM registry for snaps on FF * Fix linting * Update app/scripts/metamask-controller.js Co-authored-by: Shane Co-authored-by: kumavis Co-authored-by: Shane Co-authored-by: Frederik Bolding Co-authored-by: Shane --- .circleci/scripts/yarn-audit.sh | 2 +- app/scripts/metamask-controller.js | 8 -------- yarn.lock | 6 +++--- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/.circleci/scripts/yarn-audit.sh b/.circleci/scripts/yarn-audit.sh index 30efcf2d7..f9e83e654 100755 --- a/.circleci/scripts/yarn-audit.sh +++ b/.circleci/scripts/yarn-audit.sh @@ -7,7 +7,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,GHSA-93q8-gq69-wqmw,GHSA-257v-vj4p-3w2h,GHSA-qrpm-p2h7-hrv2 +yarn run improved-yarn-audit --ignore-dev-deps --min-severity moderate --exclude GHSA-93q8-gq69-wqmw,GHSA-257v-vj4p-3w2h,GHSA-fwr7-v2mv-hh25 audit_status="$?" # Use a bitmask to ignore INFO and LOW severity audit results diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 4bdc59847..159ace8ea 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -77,7 +77,6 @@ import { MILLISECOND } from '../../shared/constants/time'; import { ///: BEGIN:ONLY_INCLUDE_IN(flask) MESSAGE_TYPE, - PLATFORM_FIREFOX, ///: END:ONLY_INCLUDE_IN POLLING_TOKEN_ENVIRONMENT_TYPES, SUBJECT_TYPES, @@ -139,10 +138,6 @@ import { } from './controllers/permissions'; import createRPCMethodTrackingMiddleware from './lib/createRPCMethodTrackingMiddleware'; -///: BEGIN:ONLY_INCLUDE_IN(flask) -import { getPlatform } from './lib/util'; -///: END:ONLY_INCLUDE_IN - export const METAMASK_CONTROLLER_EVENTS = { // Fired after state changes that impact the extension badge (unapproved msg count) // The process of updating the badge happens in app/scripts/background.js. @@ -657,10 +652,7 @@ export default class MetamaskController extends EventEmitter { ], }); - const usingFirefox = getPlatform() === PLATFORM_FIREFOX; - this.snapController = new SnapController({ - npmRegistryUrl: usingFirefox ? 'https://registry.npmjs.cf/' : undefined, endowmentPermissionNames: Object.values(EndowmentPermissions), terminateAllSnaps: this.workerController.terminateAllSnaps.bind( this.workerController, diff --git a/yarn.lock b/yarn.lock index 76cd3b2cc..5a8f60d7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6200,9 +6200,9 @@ async@^1.4.2: integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= async@^2.0.1, async@^2.1.2, async@^2.1.4, async@^2.4.0, async@^2.5.0, async@^2.6.0, async@^2.6.1, async@^2.6.2, async@^2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== dependencies: lodash "^4.17.14" From 99461941f8892e50e4d4f3ac9229a892c850d235 Mon Sep 17 00:00:00 2001 From: PeterYinusa <53189696+PeterYinusa@users.noreply.github.com> Date: Thu, 14 Apr 2022 13:33:33 +0100 Subject: [PATCH 78/92] enable skipped test (#14378) --- test/e2e/tests/phishing-detection.spec.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/e2e/tests/phishing-detection.spec.js b/test/e2e/tests/phishing-detection.spec.js index 1e6095343..612125f6a 100644 --- a/test/e2e/tests/phishing-detection.spec.js +++ b/test/e2e/tests/phishing-detection.spec.js @@ -1,4 +1,3 @@ -/* eslint-disable mocha/no-skipped-tests */ const { strict: assert } = require('assert'); const { convertToHexValue, withFixtures } = require('../helpers'); @@ -30,7 +29,7 @@ describe('Phishing Detection', function () { }, ], }; - it.skip('should display the MetaMask Phishing Detection page', async function () { + it('should display the MetaMask Phishing Detection page', async function () { await withFixtures( { fixtures: 'imported-account', From 9403ee9c7adb6c6e2f0f3cd42ce9898529a81063 Mon Sep 17 00:00:00 2001 From: Daniel <80175477+dan437@users.noreply.github.com> Date: Thu, 14 Apr 2022 17:02:38 +0200 Subject: [PATCH 79/92] Disable Swaps on Rinkeby for production (#14372) * Disable Swaps in Rinkeby for production * Use arrays instead of objects for allowed chain ids * Trigger Build --- shared/constants/swaps.js | 20 ++++++++++++-------- ui/selectors/selectors.js | 8 ++++++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/shared/constants/swaps.js b/shared/constants/swaps.js index c9b183f7a..88cc47949 100644 --- a/shared/constants/swaps.js +++ b/shared/constants/swaps.js @@ -114,14 +114,18 @@ const RINKEBY_DEFAULT_BLOCK_EXPLORER_URL = 'https://rinkeby.etherscan.io/'; const POLYGON_DEFAULT_BLOCK_EXPLORER_URL = 'https://polygonscan.com/'; const AVALANCHE_DEFAULT_BLOCK_EXPLORER_URL = 'https://snowtrace.io/'; -export const ALLOWED_SWAPS_CHAIN_IDS = { - [MAINNET_CHAIN_ID]: true, - [SWAPS_TESTNET_CHAIN_ID]: true, - [BSC_CHAIN_ID]: true, - [POLYGON_CHAIN_ID]: true, - [RINKEBY_CHAIN_ID]: true, - [AVALANCHE_CHAIN_ID]: true, -}; +export const ALLOWED_PROD_SWAPS_CHAIN_IDS = [ + MAINNET_CHAIN_ID, + SWAPS_TESTNET_CHAIN_ID, + BSC_CHAIN_ID, + POLYGON_CHAIN_ID, + AVALANCHE_CHAIN_ID, +]; + +export const ALLOWED_DEV_SWAPS_CHAIN_IDS = [ + ...ALLOWED_PROD_SWAPS_CHAIN_IDS, + RINKEBY_CHAIN_ID, +]; export const ALLOWED_SMART_TRANSACTIONS_CHAIN_IDS = [ MAINNET_CHAIN_ID, diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index ef880038e..46600ce4a 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -37,7 +37,8 @@ import { TRUNCATED_NAME_CHAR_LIMIT } from '../../shared/constants/labels'; import { SWAPS_CHAINID_DEFAULT_TOKEN_MAP, - ALLOWED_SWAPS_CHAIN_IDS, + ALLOWED_PROD_SWAPS_CHAIN_IDS, + ALLOWED_DEV_SWAPS_CHAIN_IDS, } from '../../shared/constants/swaps'; import { shortenAddress, getAccountByAddress } from '../helpers/utils/util'; @@ -666,7 +667,10 @@ export function getSwapsDefaultToken(state) { export function getIsSwapsChain(state) { const chainId = getCurrentChainId(state); - return ALLOWED_SWAPS_CHAIN_IDS[chainId]; + const isProduction = process.env.METAMASK_ENVIRONMENT === 'production'; + return isProduction + ? ALLOWED_PROD_SWAPS_CHAIN_IDS.includes(chainId) + : ALLOWED_DEV_SWAPS_CHAIN_IDS.includes(chainId); } export function getIsBuyableChain(state) { From b7b041c43bbd34160115833ec01ba96714fe6015 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Thu, 14 Apr 2022 13:20:48 -0230 Subject: [PATCH 80/92] Fix speed-up/cancel: don't update existing transaction data (#14355) * Fix speed-up/cancel: don't update existing transaction data * Move retryTxMeta state management to useGasFeeInputs.js * Handle initial retryTxMeta set if no transaction is passed to useGasFeeInputs * Ensure previousGas is use on retry transaction if it is available in useGasFeeInputs * Remove update transaction mock and correctly test gas fee increase scenarios now that updateTransaction used in cancel-speedup is defined on the front end --- .../cancel-speedup-popover.test.js | 77 +++++++++++++++++-- ui/helpers/utils/gas.js | 18 ++++- ui/hooks/gasFeeInput/useGasFeeInputs.js | 24 +++++- .../gasFeeInput/useTransactionFunctions.js | 9 ++- 4 files changed, 116 insertions(+), 12 deletions(-) diff --git a/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js index 94a393c44..cd2984280 100644 --- a/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js +++ b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js @@ -1,5 +1,6 @@ import React from 'react'; import { act, screen } from '@testing-library/react'; +import BigNumber from 'bignumber.js'; import { EDIT_GAS_MODES, @@ -10,9 +11,47 @@ import mockEstimates from '../../../../test/data/mock-estimates.json'; import mockState from '../../../../test/data/mock-state.json'; import { GasFeeContextProvider } from '../../../contexts/gasFee'; import configureStore from '../../../store/store'; +import { + hexWEIToDecETH, + decGWEIToHexWEI, +} from '../../../helpers/utils/conversions.util'; import CancelSpeedupPopover from './cancel-speedup-popover'; +const MAXFEEPERGAS_ABOVE_MOCK_MEDIUM_HEX = '0x174876e800'; +const MAXGASCOST_ABOVE_MOCK_MEDIUM_BN = new BigNumber( + MAXFEEPERGAS_ABOVE_MOCK_MEDIUM_HEX, + 16, +).times(21000, 10); +const MAXGASCOST_ABOVE_MOCK_MEDIUM_BN_PLUS_TEN_PCT_HEX = MAXGASCOST_ABOVE_MOCK_MEDIUM_BN.times( + 1.1, + 10, +).toString(16); + +const EXPECTED_ETH_FEE_1 = hexWEIToDecETH( + MAXGASCOST_ABOVE_MOCK_MEDIUM_BN_PLUS_TEN_PCT_HEX, +); + +const MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_DEC_GWEI = + mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET].gasFeeEstimates.medium + .suggestedMaxFeePerGas; +const MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_BN_WEI = new BigNumber( + decGWEIToHexWEI(MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_DEC_GWEI), + 16, +); +const MAXFEEPERGAS_BELOW_MOCK_MEDIUM_HEX = MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_BN_WEI.div( + 10, + 10, +).toString(16); + +const EXPECTED_ETH_FEE_2 = hexWEIToDecETH( + MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_BN_WEI.times(21000, 10).toString(16), +); + +const MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_HEX_WEI = MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_BN_WEI.toString( + 16, +); + jest.mock('../../../store/actions', () => ({ disconnectGasFeeEstimatePoller: jest.fn(), getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()), @@ -21,7 +60,6 @@ jest.mock('../../../store/actions', () => ({ .mockImplementation(() => Promise.resolve()), addPollingTokenToAppState: jest.fn(), removePollingTokenFromAppState: jest.fn(), - updateTransaction: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }), updateTransactionGasFees: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }), updatePreviousGasParams: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }), createTransactionEventFragment: jest.fn(), @@ -34,7 +72,10 @@ jest.mock('../../../contexts/transaction-modal', () => ({ }), })); -const render = (props) => { +const render = ( + props, + maxFeePerGas = MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_HEX_WEI, +) => { const store = configureStore({ metamask: { ...mockState.metamask, @@ -56,7 +97,7 @@ const render = (props) => { userFeeLevel: 'tenPercentIncreased', txParams: { gas: '0x5208', - maxFeePerGas: '0x59682f10', + maxFeePerGas, maxPriorityFeePerGas: '0x59682f00', }, }} @@ -80,12 +121,32 @@ describe('CancelSpeedupPopover', () => { expect(screen.queryByText('🚀Speed Up')).toBeInTheDocument(); }); - it('should show correct gas values', async () => { + it('should show correct gas values, increased by 10%, when initial initial gas value is above estimated medium', async () => { await act(async () => - render({ - editGasMode: EDIT_GAS_MODES.SPEED_UP, - }), + render( + { + editGasMode: EDIT_GAS_MODES.SPEED_UP, + }, + MAXFEEPERGAS_ABOVE_MOCK_MEDIUM_HEX, + ), ); - expect(screen.queryAllByTitle('0.0000315 ETH').length).toBeGreaterThan(0); + expect( + screen.queryAllByTitle(`${EXPECTED_ETH_FEE_1} ETH`).length, + ).toBeGreaterThan(0); + }); + + it('should show correct gas values, set to the estimated medium, when initial initial gas value is below estimated medium', async () => { + await act(async () => + render( + { + editGasMode: EDIT_GAS_MODES.SPEED_UP, + }, + `0x${MAXFEEPERGAS_BELOW_MOCK_MEDIUM_HEX}`, + ), + ); + + expect( + screen.queryAllByTitle(`${EXPECTED_ETH_FEE_2} ETH`).length, + ).toBeGreaterThan(0); }); }); diff --git a/ui/helpers/utils/gas.js b/ui/helpers/utils/gas.js index 7fa72dd95..56ca8f253 100644 --- a/ui/helpers/utils/gas.js +++ b/ui/helpers/utils/gas.js @@ -1,7 +1,10 @@ import { constant, times, uniq, zip } from 'lodash'; import BigNumber from 'bignumber.js'; import { addHexPrefix } from 'ethereumjs-util'; -import { GAS_RECOMMENDATIONS } from '../../../shared/constants/gas'; +import { + GAS_RECOMMENDATIONS, + EDIT_GAS_MODES, +} from '../../../shared/constants/gas'; import { multiplyCurrencies } from '../../../shared/modules/conversion.utils'; import { bnGreaterThan, @@ -106,3 +109,16 @@ export function formatGasFeeOrFeeRange( return `${formattedRange} GWEI`; } + +/** + * Helper method for determining whether an edit gas mode is either a speed up or cancel transaction + * + * @param {string | undefined} editGasMode - One of 'speed-up', 'cancel', 'modify-in-place', or 'swaps' + * @returns boolean + */ +export function editGasModeIsSpeedUpOrCancel(editGasMode) { + return ( + editGasMode === EDIT_GAS_MODES.CANCEL || + editGasMode === EDIT_GAS_MODES.SPEED_UP + ); +} diff --git a/ui/hooks/gasFeeInput/useGasFeeInputs.js b/ui/hooks/gasFeeInput/useGasFeeInputs.js index 63f54753a..a53c0336b 100644 --- a/ui/hooks/gasFeeInput/useGasFeeInputs.js +++ b/ui/hooks/gasFeeInput/useGasFeeInputs.js @@ -17,6 +17,7 @@ import { hexToDecimal } from '../../helpers/utils/conversions.util'; import { isLegacyTransaction } from '../../helpers/utils/transactions.util'; import { useGasFeeEstimates } from '../useGasFeeEstimates'; +import { editGasModeIsSpeedUpOrCancel } from '../../helpers/utils/gas'; import { useGasFeeErrors } from './useGasFeeErrors'; import { useGasPriceInput } from './useGasPriceInput'; import { useMaxFeePerGasInput } from './useMaxFeePerGasInput'; @@ -81,7 +82,7 @@ import { useTransactionFunctions } from './useTransactionFunctions'; * * @param {EstimateLevel} [defaultEstimateToUse] - which estimate * level to default the 'estimateToUse' state variable to. - * @param {object} [transaction] + * @param {object} [_transaction] * @param {string} [minimumGasLimit] * @param {EDIT_GAS_MODES[keyof EDIT_GAS_MODES]} editGasMode * @returns {GasFeeInputReturnType & import( @@ -90,10 +91,28 @@ import { useTransactionFunctions } from './useTransactionFunctions'; */ export function useGasFeeInputs( defaultEstimateToUse = GAS_RECOMMENDATIONS.MEDIUM, - transaction, + _transaction, minimumGasLimit = '0x5208', editGasMode = EDIT_GAS_MODES.MODIFY_IN_PLACE, ) { + const initialRetryTxMeta = { + txParams: _transaction?.txParams, + id: _transaction?.id, + userFeeLevel: _transaction?.userFeeLevel, + originalGasEstimate: _transaction?.originalGasEstimate, + userEditedGasLimit: _transaction?.userEditedGasLimit, + }; + + if (_transaction?.previousGas) { + initialRetryTxMeta.previousGas = _transaction?.previousGas; + } + + const [retryTxMeta, setRetryTxMeta] = useState(initialRetryTxMeta); + + const transaction = editGasModeIsSpeedUpOrCancel(editGasMode) + ? retryTxMeta + : _transaction; + const eip1559V2Enabled = useSelector(getEIP1559V2Enabled); const supportsEIP1559 = @@ -272,6 +291,7 @@ export function useGasFeeInputs( maxPriorityFeePerGas, minimumGasLimit, transaction, + setRetryTxMeta, }); // When a user selects an estimate level, it will wipe out what they have diff --git a/ui/hooks/gasFeeInput/useTransactionFunctions.js b/ui/hooks/gasFeeInput/useTransactionFunctions.js index 616684386..f8b83e58b 100644 --- a/ui/hooks/gasFeeInput/useTransactionFunctions.js +++ b/ui/hooks/gasFeeInput/useTransactionFunctions.js @@ -6,7 +6,10 @@ import { decimalToHex, decGWEIToHexWEI, } from '../../helpers/utils/conversions.util'; -import { addTenPercentAndRound } from '../../helpers/utils/gas'; +import { + addTenPercentAndRound, + editGasModeIsSpeedUpOrCancel, +} from '../../helpers/utils/gas'; import { createCancelTransaction, createSpeedUpTransaction, @@ -24,6 +27,7 @@ export const useTransactionFunctions = ({ gasLimit: gasLimitValue, maxPriorityFeePerGas: maxPriorityFeePerGasValue, transaction, + setRetryTxMeta, }) => { const dispatch = useDispatch(); @@ -87,6 +91,8 @@ export const useTransactionFunctions = ({ updateSwapsUserFeeLevel(estimateUsed || PRIORITY_LEVELS.CUSTOM), ); dispatch(updateCustomSwapsEIP1559GasParams(newGasSettings)); + } else if (editGasModeIsSpeedUpOrCancel(editGasMode)) { + setRetryTxMeta(updatedTxMeta); } else { newGasSettings.userEditedGasLimit = updatedTxMeta.userEditedGasLimit; newGasSettings.userFeeLevel = updatedTxMeta.userFeeLevel; @@ -110,6 +116,7 @@ export const useTransactionFunctions = ({ getTxMeta, maxPriorityFeePerGasValue, transaction, + setRetryTxMeta, ], ); From 76615b7becba9332deae0aab222bd9801de9eb67 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Thu, 14 Apr 2022 19:30:19 +0200 Subject: [PATCH 81/92] Enhanced Gas Fee UI : Fix gas values overlaping with labels (#14392) --- .../app/edit-gas-fee-popover/edit-gas-tooltip/index.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-tooltip/index.scss b/ui/components/app/edit-gas-fee-popover/edit-gas-tooltip/index.scss index 63c1b7e98..92d067dec 100644 --- a/ui/components/app/edit-gas-fee-popover/edit-gas-tooltip/index.scss +++ b/ui/components/app/edit-gas-fee-popover/edit-gas-tooltip/index.scss @@ -26,11 +26,11 @@ &__label { white-space: nowrap; - width: 50%; + min-width: 50%; } &__value { - width: 50%; + min-width: 50%; } p { @@ -44,6 +44,7 @@ div { display: flex; flex-direction: row; + flex-wrap: wrap; text-align: left; } } From 0c52723885af4493037044b4f106353686fcc893 Mon Sep 17 00:00:00 2001 From: Ariella Vu <20778143+digiwand@users.noreply.github.com> Date: Thu, 14 Apr 2022 18:31:54 -0300 Subject: [PATCH 82/92] Update segment instantiation check. Only check if SEGMENT_WRITE_KEY exists (#14407) * segment: instantiate w/out SEGMENT_HOST check If SEGMENT_HOST is null, then the analytics-node library will usea defaulted host * Segment: rm IN_TEST check for instantiation --- app/scripts/lib/segment.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/app/scripts/lib/segment.js b/app/scripts/lib/segment.js index bb52c42d8..1b054333b 100644 --- a/app/scripts/lib/segment.js +++ b/app/scripts/lib/segment.js @@ -1,9 +1,6 @@ import Analytics from 'analytics-node'; import { SECOND } from '../../../shared/constants/time'; -const isDevEnvironment = Boolean( - process.env.METAMASK_DEBUG && !process.env.IN_TEST, -); const SEGMENT_WRITE_KEY = process.env.SEGMENT_WRITE_KEY ?? null; const SEGMENT_HOST = process.env.SEGMENT_HOST ?? null; @@ -81,11 +78,10 @@ export const createSegmentMock = (flushAt = SEGMENT_FLUSH_AT) => { return segmentMock; }; -export const segment = - !SEGMENT_WRITE_KEY || (isDevEnvironment && !SEGMENT_HOST) - ? createSegmentMock(SEGMENT_FLUSH_AT, SEGMENT_FLUSH_INTERVAL) - : new Analytics(SEGMENT_WRITE_KEY, { - host: SEGMENT_HOST, - flushAt: SEGMENT_FLUSH_AT, - flushInterval: SEGMENT_FLUSH_INTERVAL, - }); +export const segment = SEGMENT_WRITE_KEY + ? new Analytics(SEGMENT_WRITE_KEY, { + host: SEGMENT_HOST, + flushAt: SEGMENT_FLUSH_AT, + flushInterval: SEGMENT_FLUSH_INTERVAL, + }) + : createSegmentMock(SEGMENT_FLUSH_AT, SEGMENT_FLUSH_INTERVAL); From eed6ae2b8c3d3b00c4797dc00c04fe2026defdad Mon Sep 17 00:00:00 2001 From: Daniel <80175477+dan437@users.noreply.github.com> Date: Fri, 15 Apr 2022 14:57:58 +0200 Subject: [PATCH 83/92] Swaps: Sort "token_from" dropdown tokens by their fiat value first and "token_to" by top tokens (#14436) --- shared/constants/swaps.js | 5 +++++ ui/hooks/useTokensToSearch.js | 25 ++++++++++++++++++++++- ui/pages/swaps/build-quote/build-quote.js | 20 ++++++++++++------ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/shared/constants/swaps.js b/shared/constants/swaps.js index 88cc47949..10db41a2e 100644 --- a/shared/constants/swaps.js +++ b/shared/constants/swaps.js @@ -201,3 +201,8 @@ export const RINKEBY = 'rinkeby'; export const AVALANCHE = 'avalanche'; export const SWAPS_CLIENT_ID = 'extension'; + +export const TOKEN_BUCKET_PRIORITY = { + OWNED: 'owned', + TOP: 'top', +}; diff --git a/ui/hooks/useTokensToSearch.js b/ui/hooks/useTokensToSearch.js index 7ecf4b3a8..62ceda3c6 100644 --- a/ui/hooks/useTokensToSearch.js +++ b/ui/hooks/useTokensToSearch.js @@ -17,6 +17,7 @@ import { getConversionRate } from '../ducks/metamask/metamask'; import { getSwapsTokens } from '../ducks/swaps/swaps'; import { isSwapsDefaultTokenSymbol } from '../../shared/modules/swaps.utils'; import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils'; +import { TOKEN_BUCKET_PRIORITY } from '../../shared/constants/swaps'; import { useEqualityCheck } from './useEqualityCheck'; /** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */ @@ -96,6 +97,7 @@ export function useTokensToSearch({ usersTokens = [], topTokens = {}, shuffledTokensList, + tokenBucketPriority = TOKEN_BUCKET_PRIORITY.OWNED, }) { const chainId = useSelector(getCurrentChainId); const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual); @@ -164,7 +166,20 @@ export function useTokensToSearch({ tokenList, useTokenDetection, ); - if (memoizedTopTokens[token.address.toLowerCase()]) { + if (tokenBucketPriority === TOKEN_BUCKET_PRIORITY.OWNED) { + if ( + isSwapsDefaultTokenSymbol(renderableDataToken.symbol, chainId) || + usersTokensAddressMap[token.address.toLowerCase()] + ) { + tokensToSearchBuckets.owned.push(renderableDataToken); + } else if (memoizedTopTokens[token.address.toLowerCase()]) { + tokensToSearchBuckets.top[ + memoizedTopTokens[token.address.toLowerCase()].index + ] = renderableDataToken; + } else { + tokensToSearchBuckets.others.push(renderableDataToken); + } + } else if (memoizedTopTokens[token.address.toLowerCase()]) { tokensToSearchBuckets.top[ memoizedTopTokens[token.address.toLowerCase()].index ] = renderableDataToken; @@ -184,6 +199,13 @@ export function useTokensToSearch({ }, ); tokensToSearchBuckets.top = tokensToSearchBuckets.top.filter(Boolean); + if (tokenBucketPriority === TOKEN_BUCKET_PRIORITY.OWNED) { + return [ + ...tokensToSearchBuckets.owned, + ...tokensToSearchBuckets.top, + ...tokensToSearchBuckets.others, + ]; + } return [ ...tokensToSearchBuckets.top, ...tokensToSearchBuckets.owned, @@ -200,5 +222,6 @@ export function useTokensToSearch({ chainId, tokenList, useTokenDetection, + tokenBucketPriority, ]); } diff --git a/ui/pages/swaps/build-quote/build-quote.js b/ui/pages/swaps/build-quote/build-quote.js index a4331c146..2cd73d666 100644 --- a/ui/pages/swaps/build-quote/build-quote.js +++ b/ui/pages/swaps/build-quote/build-quote.js @@ -89,6 +89,7 @@ import { import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP, SWAPS_CHAINID_DEFAULT_TOKEN_MAP, + TOKEN_BUCKET_PRIORITY, } from '../../../../shared/constants/swaps'; import { @@ -214,13 +215,20 @@ export default function BuildQuote({ useTokenDetection, ); - const tokensToSearch = useTokensToSearch({ + const tokensToSearchSwapFrom = useTokensToSearch({ usersTokens: memoizedUsersTokens, topTokens: topAssets, shuffledTokensList, + tokenBucketPriority: TOKEN_BUCKET_PRIORITY.OWNED, + }); + const tokensToSearchSwapTo = useTokensToSearch({ + usersTokens: memoizedUsersTokens, + topTokens: topAssets, + shuffledTokensList, + tokenBucketPriority: TOKEN_BUCKET_PRIORITY.TOP, }); const selectedToToken = - tokensToSearch.find(({ address }) => + tokensToSearchSwapFrom.find(({ address }) => isEqualCaseInsensitive(address, toToken?.address), ) || toToken; const toTokenIsNotDefault = @@ -658,7 +666,7 @@ export default function BuildQuote({
{ onInputChange(value, fromTokenBalance); }} @@ -668,7 +676,7 @@ export default function BuildQuote({ maxListItems={30} loading={ loading && - (!tokensToSearch?.length || + (!tokensToSearchSwapFrom?.length || !topAssets || !Object.keys(topAssets).length) } @@ -729,7 +737,7 @@ export default function BuildQuote({
Date: Fri, 15 Apr 2022 10:39:55 -0300 Subject: [PATCH 84/92] MetaMetrics: Identify 'nft_autodetection_enabled' & 'opensea_api_enabled' (#14367) * MetaMetrics: alphabetize traits * MetaMetrics: identify nft_autodetection_enabled * MetaMetrics: identify opensea_api_enabled * metametrics.test: alphabetize traits * metametrics.test: add new traits - nft_autodetection_enabled - opensea_api_enabled * MetaMetrics: @property use string literals * MetaMetrics.test: update with openSeaEnabled & useCollectibleDetection * MetaMetrics: alphabetize * MetaMetrics: update MetaMetricsTraits @typedef - add nft_autodetection_enabled - add opensea_api_enabled --- app/scripts/controllers/metametrics.js | 8 ++- app/scripts/controllers/metametrics.test.js | 63 +++++++++++++-------- shared/constants/metametrics.js | 10 ++++ 3 files changed, 53 insertions(+), 28 deletions(-) diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 65fa46c83..16a001b7a 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -548,15 +548,17 @@ export default class MetaMetricsController { Object.values(metamaskState.addressBook).map(size), ), [TRAITS.LEDGER_CONNECTION_TYPE]: metamaskState.ledgerTransportType, - [TRAITS.NUMBER_OF_ACCOUNTS]: Object.values(metamaskState.identities) - .length, [TRAITS.NETWORKS_ADDED]: metamaskState.frequentRpcListDetail.map( (rpc) => rpc.chainId, ), - [TRAITS.THREE_BOX_ENABLED]: metamaskState.threeBoxSyncingAllowed, + [TRAITS.NFT_AUTODETECTION_ENABLED]: metamaskState.useCollectibleDetection, + [TRAITS.NUMBER_OF_ACCOUNTS]: Object.values(metamaskState.identities) + .length, [TRAITS.NUMBER_OF_NFT_COLLECTIONS]: this._getNumberOfNFtCollection( metamaskState, ), + [TRAITS.OPENSEA_API_ENABLED]: metamaskState.openSeaEnabled, + [TRAITS.THREE_BOX_ENABLED]: metamaskState.threeBoxSyncingAllowed, }; if (!this.previousTraits) { diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 0cf82af71..8943cd080 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -613,12 +613,10 @@ describe('MetaMetricsController', function () { it('should return full user traits object on first call', function () { const metaMetricsController = getMetaMetricsController(); const traits = metaMetricsController._buildUserTraitsObject({ - frequentRpcListDetail: [ - { chainId: MAINNET_CHAIN_ID }, - { chainId: ROPSTEN_CHAIN_ID }, - ], - ledgerTransportType: 'web-hid', - identities: [{}, {}], + addressBook: { + [MAINNET_CHAIN_ID]: [{ address: '0x' }], + [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + }, allCollectibles: { '0xac706cE8A9BF27Afecf080fB298d0ee13cfb978A': { 56: [ @@ -645,19 +643,25 @@ describe('MetaMetricsController', function () { ], }, }, + frequentRpcListDetail: [ + { chainId: MAINNET_CHAIN_ID }, + { chainId: ROPSTEN_CHAIN_ID }, + ], + identities: [{}, {}], + ledgerTransportType: 'web-hid', + openSeaEnabled: true, threeBoxSyncingAllowed: false, - addressBook: { - [MAINNET_CHAIN_ID]: [{ address: '0x' }], - [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], - }, + useCollectibleDetection: false, }); assert.deepEqual(traits, { [TRAITS.ADDRESS_BOOK_ENTRIES]: 3, [TRAITS.LEDGER_CONNECTION_TYPE]: 'web-hid', [TRAITS.NETWORKS_ADDED]: [MAINNET_CHAIN_ID, ROPSTEN_CHAIN_ID], + [TRAITS.NFT_AUTODETECTION_ENABLED]: false, [TRAITS.NUMBER_OF_ACCOUNTS]: 2, [TRAITS.NUMBER_OF_NFT_COLLECTIONS]: 3, + [TRAITS.OPENSEA_API_ENABLED]: true, [TRAITS.THREE_BOX_ENABLED]: false, }); }); @@ -665,67 +669,76 @@ describe('MetaMetricsController', function () { it('should return only changed traits object on subsequent calls', function () { const metaMetricsController = getMetaMetricsController(); metaMetricsController._buildUserTraitsObject({ + addressBook: { + [MAINNET_CHAIN_ID]: [{ address: '0x' }], + [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + }, frequentRpcListDetail: [ { chainId: MAINNET_CHAIN_ID }, { chainId: ROPSTEN_CHAIN_ID }, ], ledgerTransportType: 'web-hid', + openSeaEnabled: true, identities: [{}, {}], threeBoxSyncingAllowed: false, - addressBook: { - [MAINNET_CHAIN_ID]: [{ address: '0x' }], - [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], - }, + useCollectibleDetection: false, }); const updatedTraits = metaMetricsController._buildUserTraitsObject({ + addressBook: { + [MAINNET_CHAIN_ID]: [{ address: '0x' }, { address: '0x1' }], + [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + }, frequentRpcListDetail: [ { chainId: MAINNET_CHAIN_ID }, { chainId: ROPSTEN_CHAIN_ID }, ], ledgerTransportType: 'web-hid', + openSeaEnabled: false, identities: [{}, {}, {}], threeBoxSyncingAllowed: false, - addressBook: { - [MAINNET_CHAIN_ID]: [{ address: '0x' }, { address: '0x1' }], - [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], - }, + useCollectibleDetection: false, }); assert.deepEqual(updatedTraits, { [TRAITS.ADDRESS_BOOK_ENTRIES]: 4, [TRAITS.NUMBER_OF_ACCOUNTS]: 3, + [TRAITS.OPENSEA_API_ENABLED]: false, }); }); it('should return null if no traits changed', function () { const metaMetricsController = getMetaMetricsController(); metaMetricsController._buildUserTraitsObject({ + addressBook: { + [MAINNET_CHAIN_ID]: [{ address: '0x' }], + [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], + }, frequentRpcListDetail: [ { chainId: MAINNET_CHAIN_ID }, { chainId: ROPSTEN_CHAIN_ID }, ], ledgerTransportType: 'web-hid', + openSeaEnabled: true, identities: [{}, {}], threeBoxSyncingAllowed: false, + useCollectibleDetection: true, + }); + + const updatedTraits = metaMetricsController._buildUserTraitsObject({ addressBook: { [MAINNET_CHAIN_ID]: [{ address: '0x' }], [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], }, - }); - - const updatedTraits = metaMetricsController._buildUserTraitsObject({ frequentRpcListDetail: [ { chainId: MAINNET_CHAIN_ID }, { chainId: ROPSTEN_CHAIN_ID }, ], ledgerTransportType: 'web-hid', + openSeaEnabled: true, identities: [{}, {}], threeBoxSyncingAllowed: false, - addressBook: { - [MAINNET_CHAIN_ID]: [{ address: '0x' }], - [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], - }, + useCollectibleDetection: true, }); assert.equal(updatedTraits, null); diff --git a/shared/constants/metametrics.js b/shared/constants/metametrics.js index 28c6076f3..4a0911ea6 100644 --- a/shared/constants/metametrics.js +++ b/shared/constants/metametrics.js @@ -164,10 +164,14 @@ * trait * @property {'networks_added'} NETWORKS_ADDED - when user modifies networks * we identify the networks_added trait + * @property {'nft_autodetection_enabled'} NFT_AUTODETECTION_ENABLED - when Autodetect NFTs + * feature is toggled we identify the nft_autodetection_enabled trait * @property {'number_of_accounts'} NUMBER_OF_ACCOUNTS - when identities * change, we identify the new number_of_accounts trait * @property {'number_of_nft_collections'} NUMBER_OF_NFT_COLLECTIONS - user * trait for number of unique NFT addresses + * @property {'opensea_api_enabled'} OPENSEA_API_ENABLED - when the OpenSea API is enabled + * we identify the opensea_api_enabled trait * @property {'three_box_enabled'} THREE_BOX_ENABLED - when 3box feature is * toggled we identify the 3box_enabled trait */ @@ -181,8 +185,10 @@ export const TRAITS = { ADDRESS_BOOK_ENTRIES: 'address_book_entries', LEDGER_CONNECTION_TYPE: 'ledger_connection_type', NETWORKS_ADDED: 'networks_added', + NFT_AUTODETECTION_ENABLED: 'nft_autodetection_enabled', NUMBER_OF_ACCOUNTS: 'number_of_accounts', NUMBER_OF_NFT_COLLECTIONS: 'number_of_nft_collections', + OPENSEA_API_ENABLED: 'opensea_api_enabled', THREE_BOX_ENABLED: 'three_box_enabled', }; @@ -194,10 +200,14 @@ export const TRAITS = { * type of ledger connection set by user preference. * @property {Array} [networks_added] - An array consisting of chainIds * that indicate the networks a user has added to their MetaMask. + * @property {number} [nft_autodetection_enabled] - does the user have the + * use collection/nft detection enabled? * @property {number} [number_of_accounts] - A number representing the number * of identities(accounts) added to the user's MetaMask. * @property {number} [number_of_nft_collections] - A number representing the * amount of different NFT collections the user possesses an NFT from. + * @property {boolean} [opensea_api_enabled] - does the user have the OpenSea + * API enabled? * @property {boolean} [three_box_enabled] - does the user have 3box sync * enabled? */ From 21e54f471778512a3e20a7e24e09d23b1a2e7567 Mon Sep 17 00:00:00 2001 From: Ariella Vu <20778143+digiwand@users.noreply.github.com> Date: Fri, 15 Apr 2022 13:35:07 -0300 Subject: [PATCH 85/92] MetaMetrics: Identify 'number_of_tokens' user trait (#14427) * MetaMetrics: identify number_of_tokens * MetaMetrics: update number_of_tokens do not filter by unique addresses. Each token contract x chain id combo is a unique contract * MetaMetrics: update MetaMetricsTraits @typedef - add number_of_tokens * MetaMetrics: clean up number_of_tokens * MetaMetrics: alphabetize in test --- app/scripts/controllers/metametrics.js | 14 ++++++++ app/scripts/controllers/metametrics.test.js | 37 +++++++++++++++++++++ shared/constants/metametrics.js | 5 +++ 3 files changed, 56 insertions(+) diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 16a001b7a..d3960b01a 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -557,6 +557,7 @@ export default class MetaMetricsController { [TRAITS.NUMBER_OF_NFT_COLLECTIONS]: this._getNumberOfNFtCollection( metamaskState, ), + [TRAITS.NUMBER_OF_TOKENS]: this._getNumberOfTokens(metamaskState), [TRAITS.OPENSEA_API_ENABLED]: metamaskState.openSeaEnabled, [TRAITS.THREE_BOX_ENABLED]: metamaskState.threeBoxSyncingAllowed, }; @@ -619,6 +620,19 @@ export default class MetaMetricsController { return unique.length; } + /** + * @param {object} metamaskState + * @returns number of unique token addresses + */ + _getNumberOfTokens(metamaskState) { + return Object.values(metamaskState.allTokens).reduce( + (result, accountsByChain) => { + return result + sum(Object.values(accountsByChain).map(size)); + }, + 0, + ); + } + /** * Calls segment.identify with given user traits * diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 8943cd080..10a40cc21 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -611,6 +611,34 @@ describe('MetaMetricsController', function () { describe('_buildUserTraitsObject', function () { it('should return full user traits object on first call', function () { + const MOCK_ALL_TOKENS = { + '0x1': { + '0x1235ce91d74254f29d4609f25932fe6d97bf4842': [ + { + address: '0xd2cea331e5f5d8ee9fb1055c297795937645de91', + }, + { + address: '0xabc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', + }, + ], + '0xe364b0f9d1879e53e8183055c9d7dd2b7375d86b': [ + { + address: '0xd2cea331e5f5d8ee9fb1055c297795937645de91', + }, + ], + }, + '0x4': { + '0x1235ce91d74254f29d4609f25932fe6d97bf4842': [ + { + address: '0xd2cea331e5f5d8ee9fb1055c297795937645de91', + }, + { + address: '0x12317F958D2ee523a2206206994597C13D831ec7', + }, + ], + }, + }; + const metaMetricsController = getMetaMetricsController(); const traits = metaMetricsController._buildUserTraitsObject({ addressBook: { @@ -643,6 +671,7 @@ describe('MetaMetricsController', function () { ], }, }, + allTokens: MOCK_ALL_TOKENS, frequentRpcListDetail: [ { chainId: MAINNET_CHAIN_ID }, { chainId: ROPSTEN_CHAIN_ID }, @@ -661,6 +690,7 @@ describe('MetaMetricsController', function () { [TRAITS.NFT_AUTODETECTION_ENABLED]: false, [TRAITS.NUMBER_OF_ACCOUNTS]: 2, [TRAITS.NUMBER_OF_NFT_COLLECTIONS]: 3, + [TRAITS.NUMBER_OF_TOKENS]: 5, [TRAITS.OPENSEA_API_ENABLED]: true, [TRAITS.THREE_BOX_ENABLED]: false, }); @@ -673,6 +703,7 @@ describe('MetaMetricsController', function () { [MAINNET_CHAIN_ID]: [{ address: '0x' }], [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], }, + allTokens: {}, frequentRpcListDetail: [ { chainId: MAINNET_CHAIN_ID }, { chainId: ROPSTEN_CHAIN_ID }, @@ -689,6 +720,9 @@ describe('MetaMetricsController', function () { [MAINNET_CHAIN_ID]: [{ address: '0x' }, { address: '0x1' }], [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], }, + allTokens: { + '0x1': { '0xabcde': [{ '0x12345': { address: '0xtestAddress' } }] }, + }, frequentRpcListDetail: [ { chainId: MAINNET_CHAIN_ID }, { chainId: ROPSTEN_CHAIN_ID }, @@ -703,6 +737,7 @@ describe('MetaMetricsController', function () { assert.deepEqual(updatedTraits, { [TRAITS.ADDRESS_BOOK_ENTRIES]: 4, [TRAITS.NUMBER_OF_ACCOUNTS]: 3, + [TRAITS.NUMBER_OF_TOKENS]: 1, [TRAITS.OPENSEA_API_ENABLED]: false, }); }); @@ -714,6 +749,7 @@ describe('MetaMetricsController', function () { [MAINNET_CHAIN_ID]: [{ address: '0x' }], [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], }, + allTokens: {}, frequentRpcListDetail: [ { chainId: MAINNET_CHAIN_ID }, { chainId: ROPSTEN_CHAIN_ID }, @@ -730,6 +766,7 @@ describe('MetaMetricsController', function () { [MAINNET_CHAIN_ID]: [{ address: '0x' }], [ROPSTEN_CHAIN_ID]: [{ address: '0x' }, { address: '0x0' }], }, + allTokens: {}, frequentRpcListDetail: [ { chainId: MAINNET_CHAIN_ID }, { chainId: ROPSTEN_CHAIN_ID }, diff --git a/shared/constants/metametrics.js b/shared/constants/metametrics.js index 4a0911ea6..5a8041c9f 100644 --- a/shared/constants/metametrics.js +++ b/shared/constants/metametrics.js @@ -170,6 +170,8 @@ * change, we identify the new number_of_accounts trait * @property {'number_of_nft_collections'} NUMBER_OF_NFT_COLLECTIONS - user * trait for number of unique NFT addresses + * @property {'number_of_tokens'} NUMBER_OF_TOKENS - when the number of tokens change, we + * identify the new number_of_tokens trait * @property {'opensea_api_enabled'} OPENSEA_API_ENABLED - when the OpenSea API is enabled * we identify the opensea_api_enabled trait * @property {'three_box_enabled'} THREE_BOX_ENABLED - when 3box feature is @@ -188,6 +190,7 @@ export const TRAITS = { NFT_AUTODETECTION_ENABLED: 'nft_autodetection_enabled', NUMBER_OF_ACCOUNTS: 'number_of_accounts', NUMBER_OF_NFT_COLLECTIONS: 'number_of_nft_collections', + NUMBER_OF_TOKENS: 'number_of_tokens', OPENSEA_API_ENABLED: 'opensea_api_enabled', THREE_BOX_ENABLED: 'three_box_enabled', }; @@ -206,6 +209,8 @@ export const TRAITS = { * of identities(accounts) added to the user's MetaMask. * @property {number} [number_of_nft_collections] - A number representing the * amount of different NFT collections the user possesses an NFT from. + * @property {number} [number_of_tokens] - The total number of token contracts + * the user has across all networks and accounts. * @property {boolean} [opensea_api_enabled] - does the user have the OpenSea * API enabled? * @property {boolean} [three_box_enabled] - does the user have 3box sync From 40095cce67807dc89e46917a6422ad1694b2a2c5 Mon Sep 17 00:00:00 2001 From: Alex Donesky Date: Fri, 15 Apr 2022 11:35:25 -0500 Subject: [PATCH 86/92] e2e test import json file as import account strategy (#14449) --- .../test-json-import-account-file.json | 21 ++++++ test/e2e/tests/from-import-ui.spec.js | 73 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 test/e2e/fixtures/import-utc-json/test-json-import-account-file.json diff --git a/test/e2e/fixtures/import-utc-json/test-json-import-account-file.json b/test/e2e/fixtures/import-utc-json/test-json-import-account-file.json new file mode 100644 index 000000000..41c79d89e --- /dev/null +++ b/test/e2e/fixtures/import-utc-json/test-json-import-account-file.json @@ -0,0 +1,21 @@ +{ + "address": "0961ca10d49b9b8e371aa0bcf77fe5730b18f2e4", + "crypto": { + "ciphertext": "eb10547515f1bf3bb6eefed3cb39303c64a30560f378b789592b1ac632bbc14c", + "cipherparams": { + "iv": "25ebfc7358ce77462ef7b970e91309d6" + }, + "cipher": "aes-128-ctr", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "salt": "42417a49fb6cbb10167d7bc4ed8ae71d02cb4ee6309a42417e0051e7472d45c5", + "n": 8192, + "r": 8, + "p": 1 + }, + "mac": "0491462fbca0c7a71d249de141736304d699749c2faf5ab575d6a502e26099d7" + }, + "id": "03754e23-770b-43d3-a35e-bae1196e6f86", + "version": 3 +} diff --git a/test/e2e/tests/from-import-ui.spec.js b/test/e2e/tests/from-import-ui.spec.js index 3157e6a43..dc31d1551 100644 --- a/test/e2e/tests/from-import-ui.spec.js +++ b/test/e2e/tests/from-import-ui.spec.js @@ -1,4 +1,5 @@ const { strict: assert } = require('assert'); +const path = require('path'); const { convertToHexValue, withFixtures, @@ -232,6 +233,78 @@ describe('Metamask Import UI', function () { ); }); + it('Import Account using json file', async function () { + const ganacheOptions = { + accounts: [ + { + secretKey: + '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9', + balance: convertToHexValue(25000000000000000000), + }, + ], + }; + + await withFixtures( + { + fixtures: 'import-ui', + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // Imports an account with JSON file + await driver.clickElement('.account-menu__icon'); + await driver.clickElement({ text: 'Import Account', tag: 'div' }); + + await driver.clickElement('.new-account-import-form__select'); + await driver.clickElement({ text: 'JSON File', tag: 'option' }); + + const fileInput = await driver.findElement('input[type="file"]'); + const importJsonFile = path.join( + __dirname, + '..', + 'fixtures', + 'import-utc-json', + 'test-json-import-account-file.json', + ); + + fileInput.sendKeys(importJsonFile); + + await driver.fill('#json-password-box', 'foobarbazqux'); + + await driver.clickElement({ text: 'Import', tag: 'button' }); + + // should show the correct account name + const importedAccountName = await driver.findElement( + '.selected-account__name', + ); + assert.equal(await importedAccountName.getText(), 'Account 4'); + + // should show the imported label + await driver.clickElement('.account-menu__icon'); + // confirm 4th account is account 4, as expected + const accountMenuItemSelector = '.account-menu__account:nth-child(4)'; + const fourthAccountName = await driver.findElement( + `${accountMenuItemSelector} .account-menu__name`, + ); + assert.equal(await fourthAccountName.getText(), 'Account 4'); + // confirm label is present on the same menu item + const importedLabel = await driver.findElement( + `${accountMenuItemSelector} .keyring-label`, + ); + assert.equal(await importedLabel.getText(), 'IMPORTED'); + + const accountListItems = await driver.findElements( + '.account-menu__account', + ); + assert.equal(accountListItems.length, 4); + }, + ); + }); + it('Import Account using private key of an already active account should result in an error', async function () { const testPrivateKey = '0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9'; From 52b043c4f2309fdaf0bd65643ab3d14eb4b06a30 Mon Sep 17 00:00:00 2001 From: filipsekulic Date: Mon, 18 Apr 2022 14:02:16 +0200 Subject: [PATCH 87/92] HoldToRevealButton component (#13785) * Created 'HoldToRevealButton' component * Added new line within the .svg files * Lint fix * CSS fix according to BEM * Modified unit test --- app/_locales/en/messages.json | 3 + app/images/lock-icon.svg | 3 + app/images/unlock-icon.svg | 3 + ui/components/app/app-components.scss | 1 + .../hold-to-reveal-button.js | 192 ++++++++++++++++++ .../hold-to-reveal-button.stories.js | 22 ++ .../hold-to-reveal-button.test.js | 72 +++++++ .../app/hold-to-reveal-button/index.js | 1 + .../app/hold-to-reveal-button/index.scss | 164 +++++++++++++++ 9 files changed, 461 insertions(+) create mode 100644 app/images/lock-icon.svg create mode 100644 app/images/unlock-icon.svg create mode 100644 ui/components/app/hold-to-reveal-button/hold-to-reveal-button.js create mode 100644 ui/components/app/hold-to-reveal-button/hold-to-reveal-button.stories.js create mode 100644 ui/components/app/hold-to-reveal-button/hold-to-reveal-button.test.js create mode 100644 ui/components/app/hold-to-reveal-button/index.js create mode 100644 ui/components/app/hold-to-reveal-button/index.scss diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 2dce6fb82..196a8e400 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2330,6 +2330,9 @@ "origin": { "message": "Origin" }, + "padlock": { + "message": "Padlock" + }, "parameters": { "message": "Parameters" }, diff --git a/app/images/lock-icon.svg b/app/images/lock-icon.svg new file mode 100644 index 000000000..824974a09 --- /dev/null +++ b/app/images/lock-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/unlock-icon.svg b/app/images/unlock-icon.svg new file mode 100644 index 000000000..2ad1eadeb --- /dev/null +++ b/app/images/unlock-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index aac5586b8..62be2129b 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -40,6 +40,7 @@ @import 'gas-details-item/index'; @import 'gas-details-item/gas-details-item-title/index'; @import 'gas-timing/index'; +@import 'hold-to-reveal-button/index'; @import 'home-notification/index'; @import 'info-box/index'; @import 'menu-bar/index'; diff --git a/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.js b/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.js new file mode 100644 index 000000000..076b02f30 --- /dev/null +++ b/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.js @@ -0,0 +1,192 @@ +import React, { useCallback, useContext, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import Button from '../../ui/button'; +import { I18nContext } from '../../../contexts/i18n'; +import Box from '../../ui/box/box'; +import { + ALIGN_ITEMS, + DISPLAY, + JUSTIFY_CONTENT, +} from '../../../helpers/constants/design-system'; + +const radius = 14; +const strokeWidth = 2; +const radiusWithStroke = radius - strokeWidth / 2; + +export default function HoldToRevealButton({ buttonText, onLongPressed }) { + const t = useContext(I18nContext); + const isLongPressing = useRef(false); + const [isUnlocking, setIsUnlocking] = useState(false); + const [hasTriggeredUnlock, setHasTriggeredUnlock] = useState(false); + + /** + * Prevent animation events from propogating up + * + * @param e - Native animation event - React.AnimationEvent + */ + const preventPropogation = (e) => { + e.stopPropagation(); + }; + + /** + * Event for mouse click down + */ + const onMouseDown = () => { + isLongPressing.current = true; + }; + + /** + * Event for mouse click up + */ + const onMouseUp = () => { + isLongPressing.current = false; + }; + + /** + * 1. Progress cirle completed. Begin next animation phase (Shrink halo and show unlocked padlock) + */ + const onProgressComplete = () => { + isLongPressing.current && setIsUnlocking(true); + }; + + /** + * 2. Trigger onLongPressed callback. Begin next animation phase (Shrink unlocked padlock and fade in original content) + * + * @param e - Native animation event - React.AnimationEvent + */ + const triggerOnLongPressed = (e) => { + onLongPressed(); + setHasTriggeredUnlock(true); + preventPropogation(e); + }; + + /** + * 3. Reset animation states + */ + const resetAnimationStates = () => { + setIsUnlocking(false); + setHasTriggeredUnlock(false); + }; + + const renderPreCompleteContent = useCallback(() => { + return ( + + + + + + + + + + + + + {t('padlock')} + + + ); + }, [isUnlocking, hasTriggeredUnlock, t]); + + const renderPostCompleteContent = useCallback(() => { + return isUnlocking ? ( +
+
+ + + +
+
+ + + +
+
+ {t('padlock')} +
+
+ ) : null; + }, [isUnlocking, hasTriggeredUnlock, t]); + + return ( + + ); +} + +HoldToRevealButton.propTypes = { + /** + * Text to be displayed on the button + */ + buttonText: PropTypes.string.isRequired, + /** + * Function to be called after the animation is finished + */ + onLongPressed: PropTypes.func.isRequired, +}; diff --git a/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.stories.js b/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.stories.js new file mode 100644 index 000000000..3f46c1f0d --- /dev/null +++ b/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.stories.js @@ -0,0 +1,22 @@ +import React from 'react'; +import HoldToRevealButton from './hold-to-reveal-button'; + +export default { + title: 'Components/App/HoldToRevealButton', + id: __filename, + argTypes: { + buttonText: { control: 'text' }, + onLongPressed: { action: 'Revealing the SRP' }, + }, +}; + +export const DefaultStory = (args) => { + return ; +}; + +DefaultStory.storyName = 'Default'; + +DefaultStory.args = { + buttonText: 'Hold to reveal SRP', + onLongPressed: () => console.log('Revealed'), +}; diff --git a/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.test.js b/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.test.js new file mode 100644 index 000000000..f3f7c4d97 --- /dev/null +++ b/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.test.js @@ -0,0 +1,72 @@ +import React from 'react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; +import HoldToRevealButton from './hold-to-reveal-button'; + +describe('HoldToRevealButton', () => { + let props = {}; + + beforeEach(() => { + const mockOnLongPressed = jest.fn(); + + props = { + onLongPressed: mockOnLongPressed, + buttonText: 'Hold to reveal SRP', + }; + }); + + it('should render a button with label', () => { + const { getByText } = render(); + + expect(getByText('Hold to reveal SRP')).toBeInTheDocument(); + }); + + it('should render a button when mouse is down and up', () => { + const { getByText } = render(); + + const button = getByText('Hold to reveal SRP'); + + fireEvent.mouseDown(button); + + expect(button).toBeDefined(); + + fireEvent.mouseUp(button); + + expect(button).toBeDefined(); + }); + + it('should not show the locked padlock when a button is long pressed and then should show it after it was lifted off before the animation concludes', () => { + const { getByText } = render(); + + const button = getByText('Hold to reveal SRP'); + + fireEvent.mouseDown(button); + + waitFor(() => { + expect(button.firstChild).toHaveClass( + 'hold-to-reveal-button__lock-icon-container', + ); + }); + + fireEvent.mouseUp(button); + + waitFor(() => { + expect(button.firstChild).not.toHaveClass( + 'hold-to-reveal-button__lock-icon-container', + ); + }); + }); + + it('should show the unlocked padlock when a button is long pressed for the duration of the animation', () => { + const { getByText } = render(); + + const button = getByText('Hold to reveal SRP'); + + fireEvent.mouseDown(button); + + waitFor(() => { + expect(button.firstChild).toHaveClass( + 'hold-to-reveal-button__unlock-icon-container', + ); + }); + }); +}); diff --git a/ui/components/app/hold-to-reveal-button/index.js b/ui/components/app/hold-to-reveal-button/index.js new file mode 100644 index 000000000..d180f5e69 --- /dev/null +++ b/ui/components/app/hold-to-reveal-button/index.js @@ -0,0 +1 @@ +export { default } from './hold-to-reveal-button'; diff --git a/ui/components/app/hold-to-reveal-button/index.scss b/ui/components/app/hold-to-reveal-button/index.scss new file mode 100644 index 000000000..f9765ec60 --- /dev/null +++ b/ui/components/app/hold-to-reveal-button/index.scss @@ -0,0 +1,164 @@ +// Variables +$circle-radius: 14px; +$circle-diameter: $circle-radius * 2; +// Circumference ~ (2*PI*R). We reduced the number a little to create a snappier interaction +$circle-circumference: 82; +$circle-stroke-width: 2px; + +// Keyframes +@keyframes collapse { + from { + transform: scale(1); + } + + to { + transform: scale(0); + } +} + +@keyframes expand { + from { + transform: scale(0); + } + + to { + transform: scale(1); + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +.hold-to-reveal-button { + // Shared styles + &__absolute-fill { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + } + + &__icon { + height: $circle-diameter; + width: $circle-diameter; + } + + &__circle-shared { + fill: transparent; + stroke-width: $circle-stroke-width; + } + + // Class styles + &__button-hold { + padding: 6px 13px 6px 9px !important; + transform: scale(1) !important; + transition: 0.5s transform !important; + + &:active { + background-color: var(--primary-1) !important; + transform: scale(1.05) !important; + + .hold-to-reveal-button__circle-foreground { + stroke-dashoffset: 0 !important; + } + + .hold-to-reveal-button__lock-icon-container { + opacity: 0 !important; + } + } + } + + &__absolute-fill { + @extend .hold-to-reveal-button__absolute-fill; + } + + &__icon-container { + @extend .hold-to-reveal-button__icon; + + position: relative; + } + + &__main-icon-show { + animation: 0.4s fadeIn 1.2s forwards; + } + + &__invisible { + opacity: 0; + } + + &__circle-svg { + @extend .hold-to-reveal-button__icon; + + transform: rotate(-90deg); + } + + &__circle-background { + @extend .hold-to-reveal-button__circle-shared; + + stroke: var(--primary-3); + } + + &__circle-foreground { + @extend .hold-to-reveal-button__circle-shared; + + stroke: var(--ui-white); + stroke-dasharray: $circle-circumference; + stroke-dashoffset: $circle-circumference; + transition: 1s stroke-dashoffset; + } + + &__lock-icon-container { + @extend .hold-to-reveal-button__absolute-fill; + + transition: 0.3s opacity; + opacity: 1; + } + + &__lock-icon { + width: 7.88px; + height: 9px; + } + + &__unlock-icon-hide { + animation: 0.3s collapse 1s forwards; + } + + &__circle-static-outer-container { + animation: 0.25s collapse forwards; + } + + &__circle-static-outer { + fill: var(--ui-white); + } + + &__circle-static-inner-container { + animation: 0.125s collapse forwards; + } + + &__circle-static-inner { + fill: var(--primary-1); + } + + &__unlock-icon-container { + @extend .hold-to-reveal-button__absolute-fill; + + display: flex; + align-items: center; + justify-content: center; + transform: scale(0); + animation: 0.175s expand 0.2s forwards; + } + + &__unlock-icon { + width: 14px; + height: 11px; + } +} From bcdd52f55a84bb928432fdafe25be68bf62792a2 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Mon, 18 Apr 2022 11:31:44 -0500 Subject: [PATCH 88/92] Dark Mode: Implement Metrics (#14455) --- app/scripts/controllers/metametrics.js | 1 + app/scripts/controllers/metametrics.test.js | 6 ++++++ shared/constants/metametrics.js | 3 +++ .../experimental-tab/experimental-tab.component.js | 13 ++++++++++++- 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index d3960b01a..555576d00 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -560,6 +560,7 @@ export default class MetaMetricsController { [TRAITS.NUMBER_OF_TOKENS]: this._getNumberOfTokens(metamaskState), [TRAITS.OPENSEA_API_ENABLED]: metamaskState.openSeaEnabled, [TRAITS.THREE_BOX_ENABLED]: metamaskState.threeBoxSyncingAllowed, + [TRAITS.THEME]: metamaskState.theme || 'default', }; if (!this.previousTraits) { diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 10a40cc21..37269d6fc 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -681,6 +681,7 @@ describe('MetaMetricsController', function () { openSeaEnabled: true, threeBoxSyncingAllowed: false, useCollectibleDetection: false, + theme: 'default', }); assert.deepEqual(traits, { @@ -693,6 +694,7 @@ describe('MetaMetricsController', function () { [TRAITS.NUMBER_OF_TOKENS]: 5, [TRAITS.OPENSEA_API_ENABLED]: true, [TRAITS.THREE_BOX_ENABLED]: false, + [TRAITS.THEME]: 'default', }); }); @@ -713,6 +715,7 @@ describe('MetaMetricsController', function () { identities: [{}, {}], threeBoxSyncingAllowed: false, useCollectibleDetection: false, + theme: 'default', }); const updatedTraits = metaMetricsController._buildUserTraitsObject({ @@ -732,6 +735,7 @@ describe('MetaMetricsController', function () { identities: [{}, {}, {}], threeBoxSyncingAllowed: false, useCollectibleDetection: false, + theme: 'default', }); assert.deepEqual(updatedTraits, { @@ -759,6 +763,7 @@ describe('MetaMetricsController', function () { identities: [{}, {}], threeBoxSyncingAllowed: false, useCollectibleDetection: true, + theme: 'default', }); const updatedTraits = metaMetricsController._buildUserTraitsObject({ @@ -776,6 +781,7 @@ describe('MetaMetricsController', function () { identities: [{}, {}], threeBoxSyncingAllowed: false, useCollectibleDetection: true, + theme: 'default', }); assert.equal(updatedTraits, null); diff --git a/shared/constants/metametrics.js b/shared/constants/metametrics.js index 5a8041c9f..5636b5d67 100644 --- a/shared/constants/metametrics.js +++ b/shared/constants/metametrics.js @@ -176,6 +176,7 @@ * we identify the opensea_api_enabled trait * @property {'three_box_enabled'} THREE_BOX_ENABLED - when 3box feature is * toggled we identify the 3box_enabled trait + * @property {'theme'} THEME - when the user's theme changes we identify the theme trait */ /** @@ -193,6 +194,7 @@ export const TRAITS = { NUMBER_OF_TOKENS: 'number_of_tokens', OPENSEA_API_ENABLED: 'opensea_api_enabled', THREE_BOX_ENABLED: 'three_box_enabled', + THEME: 'theme', }; /** @@ -215,6 +217,7 @@ export const TRAITS = { * API enabled? * @property {boolean} [three_box_enabled] - does the user have 3box sync * enabled? + * @property {string} [theme] - which theme the user has selected */ // Mixpanel converts the zero address value to a truly anonymous event, which diff --git a/ui/pages/settings/experimental-tab/experimental-tab.component.js b/ui/pages/settings/experimental-tab/experimental-tab.component.js index e8efda0f4..0ce66ea80 100644 --- a/ui/pages/settings/experimental-tab/experimental-tab.component.js +++ b/ui/pages/settings/experimental-tab/experimental-tab.component.js @@ -247,6 +247,17 @@ export default class ExperimentalTab extends PureComponent { }, ]; + const onChange = (newTheme) => { + this.context.trackEvent({ + category: 'Settings', + event: 'Theme Changed', + properties: { + theme_selected: newTheme, + }, + }); + setTheme(newTheme); + }; + return (
@@ -261,7 +272,7 @@ export default class ExperimentalTab extends PureComponent { id="select-theme" options={themesOptions} selectedOption={theme} - onChange={async (newTheme) => setTheme(newTheme)} + onChange={onChange} />
From e702da21941cb0490546dba9cce407bec45ea7d6 Mon Sep 17 00:00:00 2001 From: VSaric <92527393+VSaric@users.noreply.github.com> Date: Mon, 18 Apr 2022 22:12:16 +0200 Subject: [PATCH 89/92] Modify import SRP page (#14425) * Modify Import SRP page --- app/_locales/de/messages.json | 7 - app/_locales/el/messages.json | 7 - app/_locales/en/messages.json | 17 +- app/_locales/es_419/messages.json | 7 - app/_locales/fr/messages.json | 7 - app/_locales/hi/messages.json | 7 - app/_locales/id/messages.json | 7 - app/_locales/ja/messages.json | 7 - app/_locales/ko/messages.json | 7 - app/_locales/pt_BR/messages.json | 7 - app/_locales/ru/messages.json | 7 - app/_locales/tl/messages.json | 7 - app/_locales/tr/messages.json | 7 - app/_locales/vi/messages.json | 7 - app/_locales/zh_CN/messages.json | 7 - .../app/create-new-vault/create-new-vault.js | 2 +- ui/components/app/srp-input/srp-input.js | 15 +- ui/components/app/srp-input/srp-input.test.js | 456 ++++++++++++++---- .../onboarding-flow/import-srp/import-srp.js | 67 +-- .../onboarding-flow/import-srp/index.scss | 71 ++- ui/pages/onboarding-flow/index.scss | 1 - 21 files changed, 428 insertions(+), 299 deletions(-) diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index c20aa22c5..e6d28003d 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -1276,13 +1276,6 @@ "importAccountSeedPhrase": { "message": "Ein Konto mit einem Seed-Schlüssel importieren" }, - "importExistingWalletDescription": { - "message": "Geben Sie die Geheime Wiederherstellungsphrase (alias Seed Phrase) ein, die Sie beim Erstellen Ihrer Wallet erhalten haben. $1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "Eine bestehende Wallet mit einer Geheime Wiederherstellungsphrase importieren" - }, "importMyWallet": { "message": "Meine Wallet importieren" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index f95600a02..0ba089f74 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -1270,13 +1270,6 @@ "importAccountSeedPhrase": { "message": "Εισαγωγή λογαριασμού με Μυστική Φράση Ανάκτησης" }, - "importExistingWalletDescription": { - "message": "Εισάγετε τη Μυστική Φράση Ανάκτησης (δλδ Seed Phrase) που σας δόθηκε όταν δημιουργήσατε το πορτοφόλι σας. $1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "Εισαγωγή υπάρχοντος πορτοφολιού με Μυστική Φράση Ανάκτησης" - }, "importMyWallet": { "message": "Εισαγωγή Πορτοφολιού μου" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 196a8e400..7a7334181 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -62,6 +62,13 @@ "message": "$1 may access and spend this asset", "description": "$1 is the url of the site requesting ability to spend" }, + "accessYourWalletWithSRP": { + "message": "Access your wallet with your Secret Recovery Phrase" + }, + "accessYourWalletWithSRPDescription": { + "message": "MetaMask cannot recover your password. We will use your Secret Recovery Phrase to validate your ownership, restore your wallet and set up a new password. First, enter the Secret Recovery Phrase that you were given when you created your wallet. $1", + "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" + }, "accessingYourCamera": { "message": "Accessing your camera..." }, @@ -1480,13 +1487,6 @@ "importAccountSeedPhrase": { "message": "Import a wallet with Secret Recovery Phrase" }, - "importExistingWalletDescription": { - "message": "Enter your Secret Recovery Phrase (aka Seed Phrase) that you were given when you created your wallet. $1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "Import existing wallet with Secret Recovery Phrase" - }, "importMyWallet": { "message": "Import My Wallet" }, @@ -3773,6 +3773,9 @@ "typePassword": { "message": "Type your MetaMask password" }, + "typeYourSRP": { + "message": "Type your Secret Recovery Phrase" + }, "u2f": { "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index a2cd398ed..3ac62593e 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -1319,13 +1319,6 @@ "importAccountSeedPhrase": { "message": "Importar una cartera con la frase secreta de recuperación" }, - "importExistingWalletDescription": { - "message": "Ingrese su frase secreta de recuperación (también conocida como Frase Semilla) que recibió al crear su cartera. $1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "Importar la cartera existente con la frase secreta de recuperación" - }, "importMyWallet": { "message": "Importar Mi cartera" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 20c298f3a..0f74f00c7 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -1270,13 +1270,6 @@ "importAccountSeedPhrase": { "message": "Importez un compte avec une phrase mnémotechnique" }, - "importExistingWalletDescription": { - "message": "Saisissez la phrase secrète de récupération (aussi appelée « phrase mnémonique » ou « seed ») qui vous a été attribuée lors de la création de votre portefeuille. $1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "Importer un portefeuille existant avec la phrase secrète de récupération" - }, "importMyWallet": { "message": "Importer mon portefeuille" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 61abe3972..d7e126397 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -1270,13 +1270,6 @@ "importAccountSeedPhrase": { "message": "गुप्त रिकवरी फ्रेज के साथ एक खाता आयात करें" }, - "importExistingWalletDescription": { - "message": "अपना सीक्रेट रिकवरी फ्रेज (उर्फ सीड फ्रेज) दर्ज करें जो आपको अपना वॉलेट बनाने पर दिया गया था। $1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "सीक्रेट रिकवरी फ्रेज के साथ मौजूदा वॉलेट आयात करें" - }, "importMyWallet": { "message": "मेरा वॉलेट आयात करें" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index b913d62a1..02c602bd2 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -1270,13 +1270,6 @@ "importAccountSeedPhrase": { "message": "Impor dompet dengan Frasa Pemulihan Rahasia" }, - "importExistingWalletDescription": { - "message": "Masukkan Frasa Pemulihan Rahasia Anda (alias Frasa Benih) yang diberikan saat Anda membuat dompet. $1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "Impor dompet yang ada dengan Frasa Pemulihan Rahasia" - }, "importMyWallet": { "message": "Impor Dompet Saya" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 7f7f5427e..50b314ddc 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -1270,13 +1270,6 @@ "importAccountSeedPhrase": { "message": "シークレットリカバリーフレーズを使用してウォレットをインポート" }, - "importExistingWalletDescription": { - "message": "ウォレットの作成時に提供されたシークレットリカバリーフレーズ (シードフレーズ) を入力してください。$1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "シークレットリカバリーフレーズで既存のウォレットをインポート" - }, "importMyWallet": { "message": "ウォレットをインポート" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index d331f4dab..c8d3ca1d8 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -1270,13 +1270,6 @@ "importAccountSeedPhrase": { "message": "비밀 복구 구문으로 계정 가져오기" }, - "importExistingWalletDescription": { - "message": "지갑을 만들 때 받은 비밀 복구 구문(시드 구문)을 입력하세요. $1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "비밀 복구 구문을 사용하여 기존 지갑 가져오기" - }, "importMyWallet": { "message": "내 지갑 가져오기" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index e6144d85c..c59a092a2 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -1303,13 +1303,6 @@ "importAccountSeedPhrase": { "message": "Importe uma carteira com a Frase de Recuperação Secreta" }, - "importExistingWalletDescription": { - "message": "Digite sua Frase de Recuperação Secreta (ou seja, a frase seed) que lhe foi dada quando você criou a sua carteira. $1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "Importar carteira existente com Frase de Recuperação Secreta" - }, "importMyWallet": { "message": "Importar minha carteira" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 47b662403..ec80758b6 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -1270,13 +1270,6 @@ "importAccountSeedPhrase": { "message": "Импорт кошелька с помощью секретной фразы для восстановления" }, - "importExistingWalletDescription": { - "message": "Введите секретную фразу для восстановления (также известную как «сид-фраза»), которую вы получили при создании кошелька. $1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "Импортируйте существующий кошелек с помощью секретной фразы для восстановления" - }, "importMyWallet": { "message": "Импорт моего кошелька" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 89e3c8c93..0ff209e95 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -1270,13 +1270,6 @@ "importAccountSeedPhrase": { "message": "Mag-import ng account gamit ang Secret Recovery Phrase" }, - "importExistingWalletDescription": { - "message": "Ilagay ang iyong Secret Recovery Phrase (kilala rin bilang Seed Phrase) na ibinigay sa iyo noong gumawa ka ng iyong wallet. $1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "Mag-import ng umiiral na wallet gamit ang Secret Recovery Phrase" - }, "importMyWallet": { "message": "I-import ang Wallet Ko" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index c581cb36d..87a748e52 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -1270,13 +1270,6 @@ "importAccountSeedPhrase": { "message": "Gizli Kurtarma İfadesi ile bir cüzdanı içe aktarın" }, - "importExistingWalletDescription": { - "message": "Cüzdanınızı oluşturduğunuzda size verilen Gizli Kurtarma İfadenizi (başka bir deyişle Tohum İfadesi) girin. $1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "Gizli Kurtarma İfadesi ile mevcut cüzdanı içe aktarın" - }, "importMyWallet": { "message": "Cüzdanımı İçe Aktar" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 440cf0df8..f0f5d65cb 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -1270,13 +1270,6 @@ "importAccountSeedPhrase": { "message": "Nhập một ví bằng Cụm mật khẩu khôi phục bí mật" }, - "importExistingWalletDescription": { - "message": "Nhập Cụm Mật Khẩu Khôi Phục Bí Mật (còn được gọi là Cụm Mật Khẩu Gốc) mà bạn được cấp khi tạo ví. $1", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "Nhập ví hiện tại bằng Cụm Mật Khẩu Khôi Phục Bí Mật" - }, "importMyWallet": { "message": "Nhập Ví Của Tôi" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index bde50eec9..ad530bbd8 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -1270,13 +1270,6 @@ "importAccountSeedPhrase": { "message": "使用账户助记词导入账户" }, - "importExistingWalletDescription": { - "message": "输入您创建$1钱包时提供的保密恢复短语(或Seed Phrase)。", - "description": "$1 is the words 'Learn More' from key 'learnMore', separated here so that it can be added as a link" - }, - "importExistingWalletTitle": { - "message": "使用账户助记词导入现有钱包" - }, "importMyWallet": { "message": "导入我的钱包" }, diff --git a/ui/components/app/create-new-vault/create-new-vault.js b/ui/components/app/create-new-vault/create-new-vault.js index bfb744c94..c14373837 100644 --- a/ui/components/app/create-new-vault/create-new-vault.js +++ b/ui/components/app/create-new-vault/create-new-vault.js @@ -108,7 +108,7 @@ export default function CreateNewVault({ return (
- +
{ const onChange = jest.fn(); const { getByText } = renderWithLocalization( - , + , ); await waitFor(() => getByText(enLocale.secretRecoveryPhrase.message)); @@ -56,7 +59,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.keyboard('test'); @@ -68,7 +74,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); for (const index of new Array(11).keys()) { @@ -83,7 +92,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); for (const index of new Array(11).keys()) { @@ -100,7 +112,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); for (const index of new Array(10).keys()) { @@ -117,7 +132,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = invalidChecksum.split(' '); @@ -133,7 +151,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = invalidWordCorrectChecksum.split(' '); @@ -149,7 +170,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); @@ -166,7 +190,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); @@ -185,7 +212,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); @@ -205,7 +235,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste('test'); @@ -217,7 +250,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); for (const index of new Array(11).keys()) { @@ -232,7 +268,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = invalidChecksum.split(' '); @@ -248,7 +287,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = invalidWordCorrectChecksum.split(' '); @@ -264,7 +306,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); @@ -281,7 +326,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); @@ -300,7 +348,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); @@ -320,7 +371,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste('test'); @@ -332,7 +386,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(tooManyWords); @@ -345,7 +402,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(invalidInput); @@ -357,7 +417,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-1').focus(); await userEvent.paste(invalidInput); @@ -369,7 +432,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-1').focus(); await userEvent.paste(correct); @@ -383,7 +449,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-1').focus(); await userEvent.paste(correct); @@ -398,7 +467,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -414,7 +486,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -433,7 +508,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -452,7 +530,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -472,7 +553,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -487,7 +571,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -504,7 +591,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -523,7 +613,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -542,7 +635,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -560,7 +656,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(correct); @@ -572,7 +671,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(invalidChecksum); @@ -586,7 +688,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(poorlyFormattedInput); @@ -598,7 +703,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-1').focus(); await userEvent.paste(poorlyFormattedInput); @@ -614,7 +722,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByText, queryByText } = renderWithLocalization( - , + , ); await waitFor(() => getByText(enLocale.secretRecoveryPhrase.message)); @@ -631,7 +742,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = tooFewWords.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -649,7 +763,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByRole, getByTestId, queryByText } = renderWithLocalization( - , + , ); await userEvent.selectOptions(getByRole('combobox'), '15'); const srpParts = invalidWordCount.split(' '); @@ -668,7 +785,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = invalidChecksum.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -688,7 +808,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = invalidWordCorrectChecksum.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -708,7 +831,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -729,7 +855,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -753,7 +882,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -777,7 +909,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -802,7 +937,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = tooFewWords.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -820,7 +958,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, getByRole, queryByText } = renderWithLocalization( - , + , ); await userEvent.selectOptions(getByRole('combobox'), '15'); const srpParts = invalidWordCount.split(' '); @@ -839,7 +980,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = invalidChecksum.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -859,7 +1003,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = invalidWordCorrectChecksum.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -879,7 +1026,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -900,7 +1050,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -924,7 +1077,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -948,7 +1104,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -971,7 +1130,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(tooFewWords); @@ -986,7 +1148,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(invalidWordCount); @@ -1001,7 +1166,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(invalidChecksum); @@ -1018,7 +1186,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(invalidWordCorrectChecksum); @@ -1035,7 +1206,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(correct); @@ -1053,7 +1227,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(poorlyFormattedInput); @@ -1074,7 +1251,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); for (const index of new Array(12).keys()) { @@ -1093,7 +1273,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryAllByRole } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -1108,7 +1291,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryAllByRole } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -1123,7 +1309,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryAllByRole } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(correct); @@ -1137,7 +1326,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -1161,7 +1353,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -1187,7 +1382,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -1217,7 +1415,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -1247,7 +1448,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(correct); @@ -1277,7 +1481,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -1304,7 +1511,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -1333,7 +1543,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryAllByRole } = renderWithLocalization( - , + , ); await userEvent.click(getByTestId('import-srp__srp-word-0-checkbox')); getByTestId('import-srp__srp-word-0').focus(); @@ -1353,7 +1566,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryAllByRole } = renderWithLocalization( - , + , ); await userEvent.click(getByTestId('import-srp__srp-word-0-checkbox')); getByTestId('import-srp__srp-word-0').focus(); @@ -1373,7 +1589,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryAllByRole } = renderWithLocalization( - , + , ); await userEvent.click(getByTestId('import-srp__srp-word-0-checkbox')); getByTestId('import-srp__srp-word-0').focus(); @@ -1390,7 +1609,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryAllByRole } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(correct); @@ -1410,7 +1632,10 @@ describe('srp-input', () => { const writeTextSpy = jest.spyOn(window.navigator.clipboard, 'writeText'); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -1426,7 +1651,10 @@ describe('srp-input', () => { const writeTextSpy = jest.spyOn(window.navigator.clipboard, 'writeText'); const { getByTestId } = renderWithLocalization( - , + , ); const srpParts = correct.split(' '); for (const index of new Array(srpParts.length).keys()) { @@ -1442,7 +1670,10 @@ describe('srp-input', () => { const writeTextSpy = jest.spyOn(window.navigator.clipboard, 'writeText'); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(tooManyWords); @@ -1455,7 +1686,10 @@ describe('srp-input', () => { const writeTextSpy = jest.spyOn(window.navigator.clipboard, 'writeText'); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(tooFewWords); @@ -1468,7 +1702,10 @@ describe('srp-input', () => { const writeTextSpy = jest.spyOn(window.navigator.clipboard, 'writeText'); const { getByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(correct); @@ -1482,7 +1719,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { queryByTestId, queryByRole } = renderWithLocalization( - , + , ); expect( @@ -1502,7 +1742,12 @@ describe('srp-input', () => { getByTestId, queryByTestId, queryByRole, - } = renderWithLocalization(); + } = renderWithLocalization( + , + ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(new Array(15).fill('test').join(' ')); @@ -1524,7 +1769,12 @@ describe('srp-input', () => { getByTestId, queryByTestId, queryByRole, - } = renderWithLocalization(); + } = renderWithLocalization( + , + ); await userEvent.selectOptions(getByRole('combobox'), '15'); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(correct); @@ -1546,7 +1796,12 @@ describe('srp-input', () => { getByTestId, queryByTestId, queryByRole, - } = renderWithLocalization(); + } = renderWithLocalization( + , + ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(invalidWordCount); @@ -1564,7 +1819,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByRole, queryByTestId, queryByRole } = renderWithLocalization( - , + , ); await userEvent.selectOptions(getByRole('combobox'), '24'); @@ -1582,7 +1840,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByRole, getByTestId, queryByTestId } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(new Array(15).fill('test').join(' ')); @@ -1603,7 +1864,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(tooManyWords); @@ -1617,7 +1881,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, getByText, queryByText } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(tooManyWords); @@ -1632,7 +1899,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(tooManyWords); @@ -1647,7 +1917,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(tooManyWords); @@ -1662,7 +1935,10 @@ describe('srp-input', () => { const onChange = jest.fn(); const { getByTestId, queryByText } = renderWithLocalization( - , + , ); getByTestId('import-srp__srp-word-0').focus(); await userEvent.paste(tooManyWords); diff --git a/ui/pages/onboarding-flow/import-srp/import-srp.js b/ui/pages/onboarding-flow/import-srp/import-srp.js index 58683b1e1..df02244fb 100644 --- a/ui/pages/onboarding-flow/import-srp/import-srp.js +++ b/ui/pages/onboarding-flow/import-srp/import-srp.js @@ -1,7 +1,5 @@ import React, { useState } from 'react'; import { useHistory } from 'react-router-dom'; -import { ethers } from 'ethers'; -import classnames from 'classnames'; import PropTypes from 'prop-types'; import { TwoStepProgressBar, @@ -16,44 +14,26 @@ import { TYPOGRAPHY, } from '../../../helpers/constants/design-system'; import { ONBOARDING_CREATE_PASSWORD_ROUTE } from '../../../helpers/constants/routes'; -import { clearClipboard } from '../../../helpers/utils/util'; import { useI18nContext } from '../../../hooks/useI18nContext'; import ZENDESK_URLS from '../../../helpers/constants/zendesk-url'; +import SrpInput from '../../../components/app/srp-input'; export default function ImportSRP({ submitSecretRecoveryPhrase }) { const [secretRecoveryPhrase, setSecretRecoveryPhrase] = useState(''); - const [revealSRP, setRevealSRP] = useState(false); - const [error, setError] = useState(''); const history = useHistory(); const t = useI18nContext(); - const { isValidMnemonic } = ethers.utils; - - const parseSeedPhrase = (seedPhrase) => - (seedPhrase || '').trim().toLowerCase().match(/\w+/gu)?.join(' ') || ''; - - const handleSecretRecoveryPhraseChange = (recoveryPhrase) => { - setError(''); - if (recoveryPhrase) { - const parsedSecretRecoveryPhrase = parseSeedPhrase(recoveryPhrase); - const wordCount = parsedSecretRecoveryPhrase.split(/\s/u).length; - if (wordCount % 3 !== 0 || wordCount > 24 || wordCount < 12) { - setError(t('seedPhraseReq')); - } else if (!isValidMnemonic(parsedSecretRecoveryPhrase)) { - setError(t('invalidSeedPhrase')); - } - } - setSecretRecoveryPhrase(recoveryPhrase); - }; return (
- {t('importExistingWalletTitle')} + {t('accessYourWalletWithSRP')} +
+
- {t('importExistingWalletDescription', [ + {t('accessYourWalletWithSRPDescription', [
- - {t('secretRecoveryPhrase')} - - -
- -