+
+
+ {estimateToUse === 'high' && '⚠ '}
+
+
({
+ disconnectGasFeeEstimatePoller: jest.fn(),
+ getGasFeeEstimatesAndStartPolling: jest
+ .fn()
+ .mockImplementation(() => Promise.resolve()),
+ addPollingTokenToAppState: jest.fn(),
+}));
+
const render = (props) => {
const store = configureStore({
metamask: {
@@ -15,10 +24,23 @@ const render = (props) => {
useNativeCurrencyAsPrimaryCurrency: true,
},
provider: {},
+ cachedBalances: {},
+ accounts: {
+ '0xAddress': {
+ address: '0xAddress',
+ balance: '0x176e5b6f173ebe66',
+ },
+ },
+ selectedAddress: '0xAddress',
},
});
- return renderWithProvider(
, store);
+ return renderWithProvider(
+
+
+ ,
+ store,
+ );
};
describe('GasDetailsItem', () => {
@@ -29,4 +51,14 @@ describe('GasDetailsItem', () => {
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
expect(screen.queryByText('ETH')).toBeInTheDocument();
});
+
+ it('should show warning icon if estimates are high', () => {
+ render({ defaultEstimateToUse: 'high' });
+ expect(screen.queryByText('⚠ Max fee:')).toBeInTheDocument();
+ });
+
+ it('should not show warning icon if estimates are not high', () => {
+ render({ defaultEstimateToUse: 'low' });
+ expect(screen.queryByText('Max fee:')).toBeInTheDocument();
+ });
});
diff --git a/ui/pages/confirm-transaction-base/low-priority-message/index.js b/ui/pages/confirm-transaction-base/low-priority-message/index.js
new file mode 100644
index 000000000..7b2838d48
--- /dev/null
+++ b/ui/pages/confirm-transaction-base/low-priority-message/index.js
@@ -0,0 +1 @@
+export { default } from './low-priority-message';
diff --git a/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.js b/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.js
new file mode 100644
index 000000000..41bb5d132
--- /dev/null
+++ b/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.js
@@ -0,0 +1,24 @@
+import React from 'react';
+
+import ActionableMessage from '../../../components/ui/actionable-message/actionable-message';
+import { useGasFeeContext } from '../../../contexts/gasFee';
+import { useI18nContext } from '../../../hooks/useI18nContext';
+
+const LowPriorityMessage = () => {
+ const { estimateToUse } = useGasFeeContext();
+ const t = useI18nContext();
+
+ if (estimateToUse !== 'low') return null;
+ return (
+
+ );
+};
+
+export default LowPriorityMessage;
diff --git a/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.scss b/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.scss
new file mode 100644
index 000000000..1a99a03af
--- /dev/null
+++ b/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.scss
@@ -0,0 +1,3 @@
+.low-priority-message {
+ margin-top: 20px;
+}
diff --git a/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.test.js b/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.test.js
new file mode 100644
index 000000000..9d8e233f3
--- /dev/null
+++ b/ui/pages/confirm-transaction-base/low-priority-message/low-priority-message.test.js
@@ -0,0 +1,59 @@
+import React from 'react';
+
+import { renderWithProvider } from '../../../../test/lib/render-helpers';
+import { ETH } from '../../../helpers/constants/common';
+import { GasFeeContextProvider } from '../../../contexts/gasFee';
+import configureStore from '../../../store/store';
+
+import LowPriorityMessage from './low-priority-message';
+
+jest.mock('../../../store/actions', () => ({
+ disconnectGasFeeEstimatePoller: jest.fn(),
+ getGasFeeEstimatesAndStartPolling: jest
+ .fn()
+ .mockImplementation(() => Promise.resolve()),
+ addPollingTokenToAppState: jest.fn(),
+}));
+
+const render = (props) => {
+ const store = configureStore({
+ metamask: {
+ nativeCurrency: ETH,
+ preferences: {
+ useNativeCurrencyAsPrimaryCurrency: true,
+ },
+ provider: {},
+ cachedBalances: {},
+ accounts: {
+ '0xAddress': {
+ address: '0xAddress',
+ balance: '0x176e5b6f173ebe66',
+ },
+ },
+ selectedAddress: '0xAddress',
+ },
+ });
+
+ return renderWithProvider(
+
+
+ ,
+ store,
+ );
+};
+
+describe('LowPriorityMessage', () => {
+ it('should returning warning message for low gas estimate', () => {
+ render({ transaction: { userFeeLevel: 'low' } });
+ expect(
+ document.getElementsByClassName('actionable-message--warning'),
+ ).toHaveLength(1);
+ });
+
+ it('should return null for gas estimate other than low', () => {
+ render({ transaction: { userFeeLevel: 'high' } });
+ expect(
+ document.getElementsByClassName('actionable-message--warning'),
+ ).toHaveLength(0);
+ });
+});
diff --git a/ui/pages/pages.scss b/ui/pages/pages.scss
index df9de3f4e..878344063 100644
--- a/ui/pages/pages.scss
+++ b/ui/pages/pages.scss
@@ -6,6 +6,7 @@
@import 'confirm-decrypt-message/confirm-decrypt-message';
@import 'confirm-encryption-public-key/confirm-encryption-public-key';
@import 'confirm-transaction-base/gas-details-item/gas-details-item';
+@import 'confirm-transaction-base/low-priority-message/low-priority-message';
@import 'confirmation/confirmation';
@import 'connected-sites/index';
@import 'connected-accounts/index';
From 4ef716265606bfa8462f2a1eac6212abd3ab42de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=99=BD=E5=AE=A6=E6=88=90?=
Date: Fri, 12 Nov 2021 22:40:50 +0800
Subject: [PATCH 23/56] fix typo (#12672)
---
app/scripts/controllers/swaps.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js
index 0d69dd77c..57cd11b9c 100644
--- a/app/scripts/controllers/swaps.js
+++ b/app/scripts/controllers/swaps.js
@@ -280,7 +280,7 @@ export default class SwapsController {
// For a user to be able to swap a token, they need to have approved the MetaSwap contract to withdraw that token.
// _getERC20Allowance() returns the amount of the token they have approved for withdrawal. If that amount is greater
- // than 0, it means that approval has already occured and is not needed. Otherwise, for tokens to be swapped, a new
+ // than 0, it means that approval has already occurred and is not needed. Otherwise, for tokens to be swapped, a new
// call of the ERC-20 approve method is required.
approvalRequired =
allowance.eq(0) &&
From 39cb29d5b530000c8478088a91f99c8a3476ec0a Mon Sep 17 00:00:00 2001
From: Jyoti Puri
Date: Sat, 13 Nov 2021 00:39:59 +0530
Subject: [PATCH 24/56] Changes in edit link on confirm transaction page
(#12567)
---
app/_locales/en/messages.json | 25 +++++
.../app/transaction-detail/index.scss | 51 ++++++++++
.../transaction-detail.component.js | 83 ++++++++++++++++
.../transaction-detail.component.test.js | 94 +++++++++++++++++++
ui/hooks/gasFeeInput/useGasFeeInputs.js | 26 ++++-
5 files changed, 276 insertions(+), 3 deletions(-)
create mode 100644 ui/components/app/transaction-detail/transaction-detail.component.test.js
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 2bc416fb2..9fc4cfa44 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -549,6 +549,9 @@
"currentlyUnavailable": {
"message": "Unavailable on this network"
},
+ "custom": {
+ "message": "Advanced"
+ },
"customGas": {
"message": "Customize Gas"
},
@@ -561,6 +564,13 @@
"customToken": {
"message": "Custom Token"
},
+ "dappSuggested": {
+ "message": "Site suggested"
+ },
+ "dappSuggestedTooltip": {
+ "message": "$1 has recommended this price.",
+ "description": "$1 represents the Dapp's origin"
+ },
"data": {
"message": "Data"
},
@@ -1015,6 +1025,9 @@
"gasPriceInfoTooltipContent": {
"message": "Gas price specifies the amount of Ether you are willing to pay for each unit of gas."
},
+ "gasPriceLabel": {
+ "message": "Gas price"
+ },
"gasTimingMinutes": {
"message": "$1 minutes",
"description": "$1 represents a number of minutes"
@@ -1113,6 +1126,9 @@
"hideZeroBalanceTokens": {
"message": "Hide Tokens Without Balance"
},
+ "high": {
+ "message": "Aggressive"
+ },
"history": {
"message": "History"
},
@@ -1349,6 +1365,9 @@
"lockTimeTooGreat": {
"message": "Lock time is too great"
},
+ "low": {
+ "message": "Low"
+ },
"lowPriorityMessage": {
"message": "Future transactions will queue after this one. This price was last seen was some time ago."
},
@@ -1365,12 +1384,18 @@
"max": {
"message": "Max"
},
+ "maxBaseFee": {
+ "message": "Max base fee"
+ },
"maxFee": {
"message": "Max fee"
},
"maxPriorityFee": {
"message": "Max priority fee"
},
+ "medium": {
+ "message": "Market"
+ },
"memo": {
"message": "memo"
},
diff --git a/ui/components/app/transaction-detail/index.scss b/ui/components/app/transaction-detail/index.scss
index cba477119..35bcf27c9 100644
--- a/ui/components/app/transaction-detail/index.scss
+++ b/ui/components/app/transaction-detail/index.scss
@@ -15,4 +15,55 @@
text-transform: uppercase;
}
}
+
+ &-edit-V2 {
+ margin-bottom: 10px;
+ display: flex;
+ align-items: baseline;
+ justify-content: flex-end;
+ padding-top: 20px;
+
+ button {
+ @include H7;
+
+ display: flex;
+ align-items: baseline;
+ color: $primary-1;
+ background: transparent;
+ border: 0;
+ padding-inline-end: 0;
+ white-space: pre;
+ }
+
+ i {
+ color: $primary-1;
+ margin-right: 2px;
+ }
+
+ &__icon {
+ font-size: 1rem;
+ }
+
+ &__label {
+ font-size: 12px;
+ margin-right: 8px;
+ }
+
+ .info-tooltip {
+ align-self: center;
+ margin-left: 6px;
+ }
+
+ &__tooltip {
+ p {
+ color: $Grey-500;
+ }
+
+ b {
+ color: $neutral-black;
+ display: inline-block;
+ min-width: 60%;
+ }
+ }
+ }
}
diff --git a/ui/components/app/transaction-detail/transaction-detail.component.js b/ui/components/app/transaction-detail/transaction-detail.component.js
index 0829df684..b06d43af8 100644
--- a/ui/components/app/transaction-detail/transaction-detail.component.js
+++ b/ui/components/app/transaction-detail/transaction-detail.component.js
@@ -2,11 +2,94 @@ import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { I18nContext } from '../../../contexts/i18n';
+import { useGasFeeContext } from '../../../contexts/gasFee';
+import InfoTooltip from '../../ui/info-tooltip/info-tooltip';
+import Typography from '../../ui/typography/typography';
import TransactionDetailItem from '../transaction-detail-item/transaction-detail-item.component';
+import { COLORS } from '../../../helpers/constants/design-system';
+
+const GasLevelIconMap = {
+ low: '🐢',
+ medium: '🦊',
+ high: '🦍',
+ dappSuggested: '🌐',
+ custom: '⚙',
+};
export default function TransactionDetail({ rows = [], onEdit }) {
+ // eslint-disable-next-line prefer-destructuring
+ const EIP_1559_V2 = process.env.EIP_1559_V2;
+
const t = useContext(I18nContext);
+ const {
+ estimateToUse,
+ gasLimit,
+ gasPrice,
+ isUsingDappSuggestedGasFees,
+ maxFeePerGas,
+ maxPriorityFeePerGas,
+ transaction,
+ supportsEIP1559,
+ } = useGasFeeContext();
+ const estimateUsed = isUsingDappSuggestedGasFees
+ ? 'dappSuggested'
+ : estimateToUse;
+
+ if (EIP_1559_V2 && estimateUsed) {
+ return (
+
+
+
+ {estimateUsed === 'custom' && onEdit && (
+
+ )}
+ {estimateUsed === 'dappSuggested' && (
+
+
+ {t('dappSuggestedTooltip', [transaction.origin])}
+
+ {supportsEIP1559 ? (
+ <>
+
+ {t('maxBaseFee')}
+ {maxFeePerGas}
+
+
+ {t('maxPriorityFee')}
+ {maxPriorityFeePerGas}
+
+ >
+ ) : (
+
+ {t('gasPriceLabel')}
+ {gasPrice}
+
+ )}
+
+ {t('gasLimit')}
+ {gasLimit}
+
+
+ }
+ position="top"
+ />
+ )}
+
+ {rows}
+
+ );
+ }
return (
diff --git a/ui/components/app/transaction-detail/transaction-detail.component.test.js b/ui/components/app/transaction-detail/transaction-detail.component.test.js
new file mode 100644
index 000000000..a95cf1565
--- /dev/null
+++ b/ui/components/app/transaction-detail/transaction-detail.component.test.js
@@ -0,0 +1,94 @@
+import React from 'react';
+import { screen } from '@testing-library/react';
+
+import { ETH } from '../../../helpers/constants/common';
+import { GasFeeContextProvider } from '../../../contexts/gasFee';
+import { renderWithProvider } from '../../../../test/jest';
+import configureStore from '../../../store/store';
+
+import TransactionDetail from './transaction-detail.component';
+
+jest.mock('../../../store/actions', () => ({
+ disconnectGasFeeEstimatePoller: jest.fn(),
+ getGasFeeEstimatesAndStartPolling: jest
+ .fn()
+ .mockImplementation(() => Promise.resolve()),
+ addPollingTokenToAppState: jest.fn(),
+}));
+
+const render = (props) => {
+ const store = configureStore({
+ metamask: {
+ nativeCurrency: ETH,
+ preferences: {
+ useNativeCurrencyAsPrimaryCurrency: true,
+ },
+ provider: {},
+ cachedBalances: {},
+ accounts: {
+ '0xAddress': {
+ address: '0xAddress',
+ balance: '0x176e5b6f173ebe66',
+ },
+ },
+ selectedAddress: '0xAddress',
+ featureFlags: { advancedInlineGas: true },
+ },
+ });
+
+ return renderWithProvider(
+
+ {
+ console.log('on edit');
+ }}
+ rows={[]}
+ {...props}
+ />
+ ,
+ store,
+ );
+};
+
+describe('TransactionDetail', () => {
+ beforeEach(() => {
+ process.env.EIP_1559_V2 = true;
+ });
+ afterEach(() => {
+ process.env.EIP_1559_V2 = false;
+ });
+ it('should render edit link with text low if low gas estimates are selected', () => {
+ render({ transaction: { userFeeLevel: 'low' } });
+ expect(screen.queryByText('🐢')).toBeInTheDocument();
+ expect(screen.queryByText('Low')).toBeInTheDocument();
+ });
+ it('should render edit link with text markey if medium gas estimates are selected', () => {
+ render({ transaction: { userFeeLevel: 'medium' } });
+ expect(screen.queryByText('🦊')).toBeInTheDocument();
+ expect(screen.queryByText('Market')).toBeInTheDocument();
+ });
+ it('should render edit link with text agressive if high gas estimates are selected', () => {
+ render({ transaction: { userFeeLevel: 'high' } });
+ expect(screen.queryByText('🦍')).toBeInTheDocument();
+ expect(screen.queryByText('Aggressive')).toBeInTheDocument();
+ });
+ it('should render edit link with text Site suggested if site suggested estimated are used', () => {
+ render({
+ transaction: {
+ dappSuggestedGasFees: { maxFeePerGas: 1, maxPriorityFeePerGas: 1 },
+ txParams: { maxFeePerGas: 1, maxPriorityFeePerGas: 1 },
+ },
+ });
+ expect(screen.queryByText('🌐')).toBeInTheDocument();
+ expect(screen.queryByText('Site suggested')).toBeInTheDocument();
+ expect(document.getElementsByClassName('info-tooltip')).toHaveLength(1);
+ });
+ it('should render edit link with text advance if custom gas estimates are used', () => {
+ render({
+ defaultEstimateToUse: 'custom',
+ });
+ expect(screen.queryByText('⚙')).toBeInTheDocument();
+ expect(screen.queryByText('Advanced')).toBeInTheDocument();
+ expect(screen.queryByText('Edit')).toBeInTheDocument();
+ });
+});
diff --git a/ui/hooks/gasFeeInput/useGasFeeInputs.js b/ui/hooks/gasFeeInput/useGasFeeInputs.js
index 696a9a5a3..51021cc42 100644
--- a/ui/hooks/gasFeeInput/useGasFeeInputs.js
+++ b/ui/hooks/gasFeeInput/useGasFeeInputs.js
@@ -1,15 +1,19 @@
import { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
-import { getAdvancedInlineGasShown } from '../../selectors';
-import { hexToDecimal } from '../../helpers/utils/conversions.util';
import {
CUSTOM_GAS_ESTIMATE,
GAS_RECOMMENDATIONS,
EDIT_GAS_MODES,
} from '../../../shared/constants/gas';
import { GAS_FORM_ERRORS } from '../../helpers/constants/gas';
-
+import { areDappSuggestedAndTxParamGasFeesTheSame } from '../../helpers/utils/confirm-tx.util';
+import {
+ checkNetworkAndAccountSupports1559,
+ getAdvancedInlineGasShown,
+} from '../../selectors';
+import { hexToDecimal } from '../../helpers/utils/conversions.util';
+import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
import { useGasFeeEstimates } from '../useGasFeeEstimates';
import { useGasFeeErrors } from './useGasFeeErrors';
@@ -69,6 +73,10 @@ export function useGasFeeInputs(
minimumGasLimit = '0x5208',
editGasMode = EDIT_GAS_MODES.MODIFY_IN_PLACE,
) {
+ const supportsEIP1559 =
+ useSelector(checkNetworkAndAccountSupports1559) &&
+ !isLegacyTransaction(transaction?.txParams);
+
// We need the gas estimates from the GasFeeController in the background.
// Calling this hooks initiates polling for new gas estimates and returns the
// current estimate.
@@ -92,6 +100,13 @@ export function useGasFeeInputs(
return defaultEstimateToUse;
});
+ const [
+ isUsingDappSuggestedGasFees,
+ setIsUsingDappSuggestedGasFees,
+ ] = useState(() =>
+ Boolean(areDappSuggestedAndTxParamGasFeesTheSame(transaction)),
+ );
+
const [gasLimit, setGasLimit] = useState(
Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0')),
);
@@ -193,6 +208,7 @@ export function useGasFeeInputs(
setMaxPriorityFeePerGas(null);
setGasPrice(null);
setGasPriceHasBeenManuallySet(false);
+ setIsUsingDappSuggestedGasFees(false);
},
[
setInternalEstimateToUse,
@@ -201,6 +217,7 @@ export function useGasFeeInputs(
setMaxPriorityFeePerGas,
setGasPrice,
setGasPriceHasBeenManuallySet,
+ setIsUsingDappSuggestedGasFees,
],
);
@@ -228,6 +245,7 @@ export function useGasFeeInputs(
]);
return {
+ transaction,
maxFeePerGas,
maxFeePerGasFiat,
setMaxFeePerGas,
@@ -245,6 +263,7 @@ export function useGasFeeInputs(
estimatedMaximumNative,
estimatedMinimumNative,
isGasEstimatesLoading,
+ isUsingDappSuggestedGasFees,
gasFeeEstimates,
gasEstimateType,
estimatedGasFeeTimeBounds,
@@ -256,5 +275,6 @@ export function useGasFeeInputs(
gasErrors,
gasWarnings,
hasGasErrors,
+ supportsEIP1559,
};
}
From caee4f288d1b62a345a8bc7d66a32c5cb4592848 Mon Sep 17 00:00:00 2001
From: Dan J Miller
Date: Fri, 12 Nov 2021 19:52:24 -0330
Subject: [PATCH 25/56] Ensure approve button can be enabled when using ledger
webhid (#12685)
---
.../confirm-approve-content.component.js | 6 +++---
ui/pages/confirm-approve/confirm-approve.js | 21 ++++++++-----------
2 files changed, 12 insertions(+), 15 deletions(-)
diff --git a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js
index 5ef9171f8..a00a46e0d 100644
--- a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js
+++ b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js
@@ -56,7 +56,7 @@ export default class ConfirmApproveContent extends Component {
showCustomizeNonceModal: PropTypes.func,
warning: PropTypes.string,
txData: PropTypes.object,
- ledgerWalletRequiredHidConnection: PropTypes.bool,
+ fromAddressIsLedger: PropTypes.bool,
tokenImage: PropTypes.string,
chainId: PropTypes.string,
rpcPrefs: PropTypes.object,
@@ -281,7 +281,7 @@ export default class ConfirmApproveContent extends Component {
useNonceField,
warning,
txData,
- ledgerWalletRequiredHidConnection,
+ fromAddressIsLedger,
tokenImage,
toAddress,
chainId,
@@ -479,7 +479,7 @@ export default class ConfirmApproveContent extends Component {
})}
- {ledgerWalletRequiredHidConnection ? (
+ {fromAddressIsLedger ? (
(
- state,
-) => {
- return doesAddressRequireLedgerHidConnection(state, address);
+const isAddressLedgerByFromAddress = (address) => (state) => {
+ return isAddressLedger(state, address);
};
export default function ConfirmApprove() {
@@ -64,9 +65,7 @@ export default function ConfirmApprove() {
const chainId = useSelector(getCurrentChainId);
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider);
- const ledgerWalletRequiredHidConnection = useSelector(
- doesAddressRequireLedgerHidConnectionByFromAddress(from),
- );
+ const fromAddressIsLedger = useSelector(isAddressLedgerByFromAddress(from));
const transaction =
currentNetworkTxList.find(
@@ -239,9 +238,7 @@ export default function ConfirmApprove() {
}
warning={submitWarning}
txData={transaction}
- ledgerWalletRequiredHidConnection={
- ledgerWalletRequiredHidConnection
- }
+ fromAddressIsLedger={fromAddressIsLedger}
chainId={chainId}
rpcPrefs={rpcPrefs}
isContract={isContract}
From a323a5fe59b9ea52510d040c54e09a1698d0edc3 Mon Sep 17 00:00:00 2001
From: Olaf Tomalka
Date: Mon, 15 Nov 2021 17:13:51 +0100
Subject: [PATCH 26/56] Reject popup confirmations on close (#12643)
* Background clears confirmations on popup close
* [WIP] Remove clearing confirmations through UI
* Confirmations are now rejected instead of cleared
* Erased commented out code
* Fix linter errors
* Changes after code review
* Moved metrics events from onWindowUnload to background
* PR review fixes
* Added abillity to add reason to rejection of messages
* Fix prettier
* Added type metrics event to signature cancel
* Fix test
* The uncofirmed transactions are now cleared even if Metamask is locked
---
app/scripts/background.js | 71 ++++++++++++++++++-
app/scripts/lib/decrypt-message-manager.js | 14 +++-
.../lib/encryption-public-key-manager.js | 14 +++-
app/scripts/lib/message-manager.js | 16 ++++-
app/scripts/lib/message-manager.test.js | 5 +-
app/scripts/lib/notification-manager.js | 16 ++++-
app/scripts/lib/personal-message-manager.js | 16 ++++-
.../lib/personal-message-manager.test.js | 3 +-
app/scripts/lib/typed-message-manager.js | 17 ++++-
app/scripts/lib/typed-message-manager.test.js | 1 +
app/scripts/metamask-controller.js | 27 +++++--
app/scripts/platforms/extension.js | 4 ++
shared/constants/metametrics.js | 4 ++
.../signature-request-original.component.js | 38 +---------
.../signature-request.component.js | 32 ---------
.../signature-request.container.js | 13 +---
.../confirm-decrypt-message.component.js | 40 -----------
...confirm-encryption-public-key.component.js | 40 -----------
.../confirm-transaction-base.component.js | 14 ----
19 files changed, 192 insertions(+), 193 deletions(-)
diff --git a/app/scripts/background.js b/app/scripts/background.js
index d8d99150f..ed546dc26 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -17,13 +17,19 @@ import {
ENVIRONMENT_TYPE_FULLSCREEN,
} from '../../shared/constants/app';
import { SECOND } from '../../shared/constants/time';
+import {
+ REJECT_NOTFICIATION_CLOSE,
+ REJECT_NOTFICIATION_CLOSE_SIG,
+} from '../../shared/constants/metametrics';
import migrations from './migrations';
import Migrator from './lib/migrator';
import ExtensionPlatform from './platforms/extension';
import LocalStore from './lib/local-store';
import ReadOnlyNetworkStore from './lib/network-store';
import createStreamSink from './lib/createStreamSink';
-import NotificationManager from './lib/notification-manager';
+import NotificationManager, {
+ NOTIFICATION_MANAGER_EVENTS,
+} from './lib/notification-manager';
import MetamaskController, {
METAMASK_CONTROLLER_EVENTS,
} from './metamask-controller';
@@ -475,6 +481,69 @@ function setupController(initState, initLangCode) {
extension.browserAction.setBadgeBackgroundColor({ color: '#037DD6' });
}
+ notificationManager.on(
+ NOTIFICATION_MANAGER_EVENTS.POPUP_CLOSED,
+ rejectUnapprovedNotifications,
+ );
+
+ function rejectUnapprovedNotifications() {
+ Object.keys(
+ controller.txController.txStateManager.getUnapprovedTxList(),
+ ).forEach((txId) =>
+ controller.txController.txStateManager.setTxStatusRejected(txId),
+ );
+ controller.messageManager.messages
+ .filter((msg) => msg.status === 'unapproved')
+ .forEach((tx) =>
+ controller.messageManager.rejectMsg(
+ tx.id,
+ REJECT_NOTFICIATION_CLOSE_SIG,
+ ),
+ );
+ controller.personalMessageManager.messages
+ .filter((msg) => msg.status === 'unapproved')
+ .forEach((tx) =>
+ controller.personalMessageManager.rejectMsg(
+ tx.id,
+ REJECT_NOTFICIATION_CLOSE_SIG,
+ ),
+ );
+ controller.typedMessageManager.messages
+ .filter((msg) => msg.status === 'unapproved')
+ .forEach((tx) =>
+ controller.typedMessageManager.rejectMsg(
+ tx.id,
+ REJECT_NOTFICIATION_CLOSE_SIG,
+ ),
+ );
+ controller.decryptMessageManager.messages
+ .filter((msg) => msg.status === 'unapproved')
+ .forEach((tx) =>
+ controller.decryptMessageManager.rejectMsg(
+ tx.id,
+ REJECT_NOTFICIATION_CLOSE,
+ ),
+ );
+ controller.encryptionPublicKeyManager.messages
+ .filter((msg) => msg.status === 'unapproved')
+ .forEach((tx) =>
+ controller.encryptionPublicKeyManager.rejectMsg(
+ tx.id,
+ REJECT_NOTFICIATION_CLOSE,
+ ),
+ );
+
+ // We're specifcally avoid using approvalController directly for better
+ // Error support during rejection
+ Object.keys(
+ controller.permissionsController.approvals.state.pendingApprovals,
+ ).forEach((approvalId) =>
+ controller.permissionsController.rejectPermissionsRequest(approvalId),
+ );
+
+ updateBadge();
+ }
+
return Promise.resolve();
}
diff --git a/app/scripts/lib/decrypt-message-manager.js b/app/scripts/lib/decrypt-message-manager.js
index 56eabf552..534f4b62a 100644
--- a/app/scripts/lib/decrypt-message-manager.js
+++ b/app/scripts/lib/decrypt-message-manager.js
@@ -38,13 +38,14 @@ export default class DecryptMessageManager extends EventEmitter {
* @property {Array} messages Holds all messages that have been created by this DecryptMessageManager
*
*/
- constructor() {
+ constructor(opts) {
super();
this.memStore = new ObservableStore({
unapprovedDecryptMsgs: {},
unapprovedDecryptMsgCount: 0,
});
this.messages = [];
+ this.metricsEvent = opts.metricsEvent;
}
/**
@@ -237,7 +238,16 @@ export default class DecryptMessageManager extends EventEmitter {
* @param {number} msgId The id of the DecryptMessage to reject.
*
*/
- rejectMsg(msgId) {
+ rejectMsg(msgId, reason = undefined) {
+ if (reason) {
+ this.metricsEvent({
+ event: reason,
+ category: 'Messages',
+ properties: {
+ action: 'Decrypt Message Request',
+ },
+ });
+ }
this._setMsgStatus(msgId, 'rejected');
}
diff --git a/app/scripts/lib/encryption-public-key-manager.js b/app/scripts/lib/encryption-public-key-manager.js
index 2a4b2296e..a6aead82c 100644
--- a/app/scripts/lib/encryption-public-key-manager.js
+++ b/app/scripts/lib/encryption-public-key-manager.js
@@ -34,13 +34,14 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
* @property {Array} messages Holds all messages that have been created by this EncryptionPublicKeyManager
*
*/
- constructor() {
+ constructor(opts) {
super();
this.memStore = new ObservableStore({
unapprovedEncryptionPublicKeyMsgs: {},
unapprovedEncryptionPublicKeyMsgCount: 0,
});
this.messages = [];
+ this.metricsEvent = opts.metricsEvent;
}
/**
@@ -226,7 +227,16 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
* @param {number} msgId The id of the EncryptionPublicKey to reject.
*
*/
- rejectMsg(msgId) {
+ rejectMsg(msgId, reason = undefined) {
+ if (reason) {
+ this.metricsEvent({
+ event: reason,
+ category: 'Messages',
+ properties: {
+ action: 'Encryption public key Request',
+ },
+ });
+ }
this._setMsgStatus(msgId, 'rejected');
}
diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js
index bb7756b33..0a83a2214 100644
--- a/app/scripts/lib/message-manager.js
+++ b/app/scripts/lib/message-manager.js
@@ -35,13 +35,14 @@ export default class MessageManager extends EventEmitter {
* @property {Array} messages Holds all messages that have been created by this MessageManager
*
*/
- constructor() {
+ constructor({ metricsEvent }) {
super();
this.memStore = new ObservableStore({
unapprovedMsgs: {},
unapprovedMsgCount: 0,
});
this.messages = [];
+ this.metricsEvent = metricsEvent;
}
/**
@@ -217,7 +218,18 @@ export default class MessageManager extends EventEmitter {
* @param {number} msgId - The id of the Message to reject.
*
*/
- rejectMsg(msgId) {
+ rejectMsg(msgId, reason = undefined) {
+ if (reason) {
+ const msg = this.getMsg(msgId);
+ this.metricsEvent({
+ event: reason,
+ category: 'Transactions',
+ properties: {
+ action: 'Sign Request',
+ type: msg.type,
+ },
+ });
+ }
this._setMsgStatus(msgId, 'rejected');
}
diff --git a/app/scripts/lib/message-manager.test.js b/app/scripts/lib/message-manager.test.js
index 39183fb7a..82fa147b3 100644
--- a/app/scripts/lib/message-manager.test.js
+++ b/app/scripts/lib/message-manager.test.js
@@ -1,4 +1,5 @@
import { strict as assert } from 'assert';
+import sinon from 'sinon';
import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction';
import MessageManager from './message-manager';
@@ -6,7 +7,9 @@ describe('Message Manager', function () {
let messageManager;
beforeEach(function () {
- messageManager = new MessageManager();
+ messageManager = new MessageManager({
+ metricsEvent: sinon.fake(),
+ });
});
describe('#getMsgList', function () {
diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js
index d7a7a89e0..f07871211 100644
--- a/app/scripts/lib/notification-manager.js
+++ b/app/scripts/lib/notification-manager.js
@@ -1,9 +1,14 @@
+import EventEmitter from 'safe-event-emitter';
import ExtensionPlatform from '../platforms/extension';
const NOTIFICATION_HEIGHT = 620;
const NOTIFICATION_WIDTH = 360;
-export default class NotificationManager {
+export const NOTIFICATION_MANAGER_EVENTS = {
+ POPUP_CLOSED: 'onPopupClosed',
+};
+
+export default class NotificationManager extends EventEmitter {
/**
* A collection of methods for controlling the showing and hiding of the notification popup.
*
@@ -12,7 +17,9 @@ export default class NotificationManager {
*/
constructor() {
+ super();
this.platform = new ExtensionPlatform();
+ this.platform.addOnRemovedListener(this._onWindowClosed.bind(this));
}
/**
@@ -62,6 +69,13 @@ export default class NotificationManager {
}
}
+ _onWindowClosed(windowId) {
+ if (windowId === this._popupId) {
+ this._popupId = undefined;
+ this.emit(NOTIFICATION_MANAGER_EVENTS.POPUP_CLOSED);
+ }
+ }
+
/**
* Checks all open MetaMask windows, and returns the first one it finds that is a notification window (i.e. has the
* type 'popup')
diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js
index 6002b0709..907f3ee5a 100644
--- a/app/scripts/lib/personal-message-manager.js
+++ b/app/scripts/lib/personal-message-manager.js
@@ -40,13 +40,14 @@ export default class PersonalMessageManager extends EventEmitter {
* @property {Array} messages Holds all messages that have been created by this PersonalMessageManager
*
*/
- constructor() {
+ constructor({ metricsEvent }) {
super();
this.memStore = new ObservableStore({
unapprovedPersonalMsgs: {},
unapprovedPersonalMsgCount: 0,
});
this.messages = [];
+ this.metricsEvent = metricsEvent;
}
/**
@@ -238,7 +239,18 @@ export default class PersonalMessageManager extends EventEmitter {
* @param {number} msgId - The id of the PersonalMessage to reject.
*
*/
- rejectMsg(msgId) {
+ rejectMsg(msgId, reason = undefined) {
+ if (reason) {
+ const msg = this.getMsg(msgId);
+ this.metricsEvent({
+ event: reason,
+ category: 'Transactions',
+ properties: {
+ action: 'Sign Request',
+ type: msg.type,
+ },
+ });
+ }
this._setMsgStatus(msgId, 'rejected');
}
diff --git a/app/scripts/lib/personal-message-manager.test.js b/app/scripts/lib/personal-message-manager.test.js
index de241fff0..add3ac28e 100644
--- a/app/scripts/lib/personal-message-manager.test.js
+++ b/app/scripts/lib/personal-message-manager.test.js
@@ -1,4 +1,5 @@
import { strict as assert } from 'assert';
+import sinon from 'sinon';
import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction';
import PersonalMessageManager from './personal-message-manager';
@@ -6,7 +7,7 @@ describe('Personal Message Manager', function () {
let messageManager;
beforeEach(function () {
- messageManager = new PersonalMessageManager();
+ messageManager = new PersonalMessageManager({ metricsEvent: sinon.fake() });
});
describe('#getMsgList', function () {
diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js
index 534ecf818..4d972fdf0 100644
--- a/app/scripts/lib/typed-message-manager.js
+++ b/app/scripts/lib/typed-message-manager.js
@@ -32,7 +32,7 @@ export default class TypedMessageManager extends EventEmitter {
/**
* Controller in charge of managing - storing, adding, removing, updating - TypedMessage.
*/
- constructor({ getCurrentChainId }) {
+ constructor({ getCurrentChainId, metricEvents }) {
super();
this._getCurrentChainId = getCurrentChainId;
this.memStore = new ObservableStore({
@@ -40,6 +40,7 @@ export default class TypedMessageManager extends EventEmitter {
unapprovedTypedMessagesCount: 0,
});
this.messages = [];
+ this.metricEvents = metricEvents;
}
/**
@@ -301,7 +302,19 @@ export default class TypedMessageManager extends EventEmitter {
* @param {number} msgId - The id of the TypedMessage to reject.
*
*/
- rejectMsg(msgId) {
+ rejectMsg(msgId, reason = undefined) {
+ if (reason) {
+ const msg = this.getMsg(msgId);
+ this.metricsEvent({
+ event: reason,
+ category: 'Transactions',
+ properties: {
+ action: 'Sign Request',
+ version: msg.msgParams.version,
+ type: msg.type,
+ },
+ });
+ }
this._setMsgStatus(msgId, 'rejected');
}
diff --git a/app/scripts/lib/typed-message-manager.test.js b/app/scripts/lib/typed-message-manager.test.js
index a9722211e..ce70011ce 100644
--- a/app/scripts/lib/typed-message-manager.test.js
+++ b/app/scripts/lib/typed-message-manager.test.js
@@ -17,6 +17,7 @@ describe('Typed Message Manager', function () {
beforeEach(async function () {
typedMessageManager = new TypedMessageManager({
getCurrentChainId: sinon.fake.returns('0x1'),
+ metricsEvent: sinon.fake(),
});
msgParamsV1 = {
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 531cdfb7a..c4942ea89 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -526,14 +526,33 @@ export default class MetamaskController extends EventEmitter {
}
});
this.networkController.lookupNetwork();
- this.messageManager = new MessageManager();
- this.personalMessageManager = new PersonalMessageManager();
- this.decryptMessageManager = new DecryptMessageManager();
- this.encryptionPublicKeyManager = new EncryptionPublicKeyManager();
+ this.messageManager = new MessageManager({
+ metricsEvent: this.metaMetricsController.trackEvent.bind(
+ this.metaMetricsController,
+ ),
+ });
+ this.personalMessageManager = new PersonalMessageManager({
+ metricsEvent: this.metaMetricsController.trackEvent.bind(
+ this.metaMetricsController,
+ ),
+ });
+ this.decryptMessageManager = new DecryptMessageManager({
+ metricsEvent: this.metaMetricsController.trackEvent.bind(
+ this.metaMetricsController,
+ ),
+ });
+ this.encryptionPublicKeyManager = new EncryptionPublicKeyManager({
+ metricsEvent: this.metaMetricsController.trackEvent.bind(
+ this.metaMetricsController,
+ ),
+ });
this.typedMessageManager = new TypedMessageManager({
getCurrentChainId: this.networkController.getCurrentChainId.bind(
this.networkController,
),
+ metricsEvent: this.metaMetricsController.trackEvent.bind(
+ this.metaMetricsController,
+ ),
});
this.swapsController = new SwapsController({
diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js
index 4f7705a95..c2f931b49 100644
--- a/app/scripts/platforms/extension.js
+++ b/app/scripts/platforms/extension.js
@@ -162,6 +162,10 @@ export default class ExtensionPlatform {
}
}
+ addOnRemovedListener(listener) {
+ extension.windows.onRemoved.addListener(listener);
+ }
+
getAllWindows() {
return new Promise((resolve, reject) => {
extension.windows.getAll((windows) => {
diff --git a/shared/constants/metametrics.js b/shared/constants/metametrics.js
index 7f0a3804f..948a3d8d7 100644
--- a/shared/constants/metametrics.js
+++ b/shared/constants/metametrics.js
@@ -140,3 +140,7 @@ export const METAMETRICS_BACKGROUND_PAGE_OBJECT = {
* @property {() => void} identify - Identify an anonymous user. We do not
* currently use this method.
*/
+
+export const REJECT_NOTFICIATION_CLOSE = 'Cancel Via Notification Close';
+export const REJECT_NOTFICIATION_CLOSE_SIG =
+ 'Cancel Sig Request Via Notification Close';
diff --git a/ui/components/app/signature-request-original/signature-request-original.component.js b/ui/components/app/signature-request-original/signature-request-original.component.js
index a8233fc98..d4467a75c 100644
--- a/ui/components/app/signature-request-original/signature-request-original.component.js
+++ b/ui/components/app/signature-request-original/signature-request-original.component.js
@@ -5,11 +5,7 @@ import classnames from 'classnames';
import { ObjectInspector } from 'react-inspector';
import LedgerInstructionField from '../ledger-instruction-field';
-import {
- ENVIRONMENT_TYPE_NOTIFICATION,
- MESSAGE_TYPE,
-} from '../../../../shared/constants/app';
-import { getEnvironmentType } from '../../../../app/scripts/lib/util';
+import { MESSAGE_TYPE } from '../../../../shared/constants/app';
import Identicon from '../../ui/identicon';
import AccountListItem from '../account-list-item';
import { conversionUtil } from '../../../../shared/modules/conversion.utils';
@@ -45,36 +41,6 @@ export default class SignatureRequestOriginal extends Component {
fromAccount: this.props.fromAccount,
};
- componentDidMount = () => {
- if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION) {
- window.addEventListener('beforeunload', this._beforeUnload);
- }
- };
-
- componentWillUnmount = () => {
- this._removeBeforeUnload();
- };
-
- _beforeUnload = (event) => {
- const { clearConfirmTransaction, cancel } = this.props;
- const { metricsEvent } = this.context;
- metricsEvent({
- eventOpts: {
- category: 'Transactions',
- action: 'Sign Request',
- name: 'Cancel Sig Request Via Notification Close',
- },
- });
- clearConfirmTransaction();
- cancel(event);
- };
-
- _removeBeforeUnload = () => {
- if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION) {
- window.removeEventListener('beforeunload', this._beforeUnload);
- }
- };
-
renderHeader = () => {
return (
@@ -300,7 +266,6 @@ export default class SignatureRequestOriginal extends Component {
large
className="request-signature__footer__cancel-button"
onClick={async (event) => {
- this._removeBeforeUnload();
await cancel(event);
metricsEvent({
eventOpts: {
@@ -325,7 +290,6 @@ export default class SignatureRequestOriginal extends Component {
className="request-signature__footer__sign-button"
disabled={hardwareWalletRequiresConnection}
onClick={async (event) => {
- this._removeBeforeUnload();
await sign(event);
metricsEvent({
eventOpts: {
diff --git a/ui/components/app/signature-request/signature-request.component.js b/ui/components/app/signature-request/signature-request.component.js
index 73d28ec26..4acfac4c0 100644
--- a/ui/components/app/signature-request/signature-request.component.js
+++ b/ui/components/app/signature-request/signature-request.component.js
@@ -1,12 +1,10 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
-import { getEnvironmentType } from '../../../../app/scripts/lib/util';
import Identicon from '../../ui/identicon';
import LedgerInstructionField from '../ledger-instruction-field';
import Header from './signature-request-header';
import Footer from './signature-request-footer';
import Message from './signature-request-message';
-import { ENVIRONMENT_TYPE_NOTIFICATION } from './signature-request.constants';
export default class SignatureRequest extends PureComponent {
static propTypes = {
@@ -17,7 +15,6 @@ export default class SignatureRequest extends PureComponent {
name: PropTypes.string,
}).isRequired,
isLedgerWallet: PropTypes.bool,
- clearConfirmTransaction: PropTypes.func.isRequired,
cancel: PropTypes.func.isRequired,
sign: PropTypes.func.isRequired,
hardwareWalletRequiresConnection: PropTypes.func.isRequired,
@@ -28,33 +25,6 @@ export default class SignatureRequest extends PureComponent {
metricsEvent: PropTypes.func,
};
- componentDidMount() {
- if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION) {
- window.addEventListener('beforeunload', this._beforeUnload);
- }
- }
-
- _beforeUnload = (event) => {
- const {
- clearConfirmTransaction,
- cancel,
- txData: { type },
- } = this.props;
- const { metricsEvent } = this.context;
- metricsEvent({
- eventOpts: {
- category: 'Transactions',
- action: 'Sign Request',
- name: 'Cancel Sig Request Via Notification Close',
- },
- customVariables: {
- type,
- },
- });
- clearConfirmTransaction();
- cancel(event);
- };
-
formatWallet(wallet) {
return `${wallet.slice(0, 8)}...${wallet.slice(
wallet.length - 8,
@@ -79,7 +49,6 @@ export default class SignatureRequest extends PureComponent {
const { metricsEvent } = this.context;
const onSign = (event) => {
- window.removeEventListener('beforeunload', this._beforeUnload);
sign(event);
metricsEvent({
eventOpts: {
@@ -95,7 +64,6 @@ export default class SignatureRequest extends PureComponent {
};
const onCancel = (event) => {
- window.removeEventListener('beforeunload', this._beforeUnload);
cancel(event);
metricsEvent({
eventOpts: {
diff --git a/ui/components/app/signature-request/signature-request.container.js b/ui/components/app/signature-request/signature-request.container.js
index 292bbbf73..11da8ab42 100644
--- a/ui/components/app/signature-request/signature-request.container.js
+++ b/ui/components/app/signature-request/signature-request.container.js
@@ -1,5 +1,4 @@
import { connect } from 'react-redux';
-import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck';
import {
accountsWithSendEtherInfoSelector,
doesAddressRequireLedgerHidConnection,
@@ -28,12 +27,6 @@ function mapStateToProps(state, ownProps) {
};
}
-function mapDispatchToProps(dispatch) {
- return {
- clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
- };
-}
-
function mergeProps(stateProps, dispatchProps, ownProps) {
const {
allAccounts,
@@ -83,8 +76,4 @@ function mergeProps(stateProps, dispatchProps, ownProps) {
};
}
-export default connect(
- mapStateToProps,
- mapDispatchToProps,
- mergeProps,
-)(SignatureRequest);
+export default connect(mapStateToProps, null, mergeProps)(SignatureRequest);
diff --git a/ui/pages/confirm-decrypt-message/confirm-decrypt-message.component.js b/ui/pages/confirm-decrypt-message/confirm-decrypt-message.component.js
index 29455db4e..b8335e42d 100644
--- a/ui/pages/confirm-decrypt-message/confirm-decrypt-message.component.js
+++ b/ui/pages/confirm-decrypt-message/confirm-decrypt-message.component.js
@@ -9,9 +9,7 @@ import Identicon from '../../components/ui/identicon';
import Tooltip from '../../components/ui/tooltip';
import Copy from '../../components/ui/icon/copy-icon.component';
-import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../shared/constants/app';
import { SECOND } from '../../../shared/constants/time';
-import { getEnvironmentType } from '../../../app/scripts/lib/util';
import { conversionUtil } from '../../../shared/modules/conversion.utils';
export default class ConfirmDecryptMessage extends Component {
@@ -44,44 +42,6 @@ export default class ConfirmDecryptMessage extends Component {
hasCopied: false,
};
- componentDidMount = () => {
- if (
- getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION
- ) {
- window.addEventListener('beforeunload', this._beforeUnload);
- }
- };
-
- componentWillUnmount = () => {
- this._removeBeforeUnload();
- };
-
- _beforeUnload = async (event) => {
- const {
- clearConfirmTransaction,
- cancelDecryptMessage,
- txData,
- } = this.props;
- const { metricsEvent } = this.context;
- await cancelDecryptMessage(txData, event);
- metricsEvent({
- eventOpts: {
- category: 'Messages',
- action: 'Decrypt Message Request',
- name: 'Cancel Via Notification Close',
- },
- });
- clearConfirmTransaction();
- };
-
- _removeBeforeUnload = () => {
- if (
- getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION
- ) {
- window.removeEventListener('beforeunload', this._beforeUnload);
- }
- };
-
copyMessage = () => {
copyToClipboard(this.state.rawMessage);
this.context.metricsEvent({
diff --git a/ui/pages/confirm-encryption-public-key/confirm-encryption-public-key.component.js b/ui/pages/confirm-encryption-public-key/confirm-encryption-public-key.component.js
index cf31ee051..e1a8bfdf8 100644
--- a/ui/pages/confirm-encryption-public-key/confirm-encryption-public-key.component.js
+++ b/ui/pages/confirm-encryption-public-key/confirm-encryption-public-key.component.js
@@ -5,8 +5,6 @@ import AccountListItem from '../../components/app/account-list-item';
import Button from '../../components/ui/button';
import Identicon from '../../components/ui/identicon';
-import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../shared/constants/app';
-import { getEnvironmentType } from '../../../app/scripts/lib/util';
import { conversionUtil } from '../../../shared/modules/conversion.utils';
export default class ConfirmEncryptionPublicKey extends Component {
@@ -33,44 +31,6 @@ export default class ConfirmEncryptionPublicKey extends Component {
nativeCurrency: PropTypes.string.isRequired,
};
- componentDidMount = () => {
- if (
- getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION
- ) {
- window.addEventListener('beforeunload', this._beforeUnload);
- }
- };
-
- componentWillUnmount = () => {
- this._removeBeforeUnload();
- };
-
- _beforeUnload = async (event) => {
- const {
- clearConfirmTransaction,
- cancelEncryptionPublicKey,
- txData,
- } = this.props;
- const { metricsEvent } = this.context;
- await cancelEncryptionPublicKey(txData, event);
- metricsEvent({
- eventOpts: {
- category: 'Messages',
- action: 'Encryption public key Request',
- name: 'Cancel Via Notification Close',
- },
- });
- clearConfirmTransaction();
- };
-
- _removeBeforeUnload = () => {
- if (
- getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION
- ) {
- window.removeEventListener('beforeunload', this._beforeUnload);
- }
- };
-
renderHeader = () => {
return (
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 aa4eac2f2..fc2d7e29a 100644
--- a/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -1,7 +1,5 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../shared/constants/app';
-import { getEnvironmentType } from '../../../app/scripts/lib/util';
import ConfirmPageContainer from '../../components/app/confirm-page-container';
import { isBalanceSufficient } from '../send/send.utils';
import {
@@ -826,11 +824,6 @@ export default class ConfirmTransactionBase extends Component {
};
}
- _beforeUnload = () => {
- const { txData: { id } = {}, cancelTransaction } = this.props;
- cancelTransaction({ id });
- };
-
_beforeUnloadForGasPolling = () => {
this._isMounted = false;
if (this.state.pollingToken) {
@@ -840,9 +833,6 @@ export default class ConfirmTransactionBase extends Component {
};
_removeBeforeUnload = () => {
- if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION) {
- window.removeEventListener('beforeunload', this._beforeUnload);
- }
window.removeEventListener('beforeunload', this._beforeUnloadForGasPolling);
};
@@ -866,10 +856,6 @@ export default class ConfirmTransactionBase extends Component {
},
});
- if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION) {
- window.addEventListener('beforeunload', this._beforeUnload);
- }
-
getNextNonce();
if (toAddress) {
tryReverseResolveAddress(toAddress);
From a2033377e997563c507c36833270e9c583fd20f8 Mon Sep 17 00:00:00 2001
From: ryanml
Date: Mon, 15 Nov 2021 10:19:14 -0700
Subject: [PATCH 27/56] Show correct base asset in Signature Request view
(#12696)
---
.../signature-request-original.component.js | 7 ++++---
.../signature-request-original.container.js | 6 +++++-
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/ui/components/app/signature-request-original/signature-request-original.component.js b/ui/components/app/signature-request-original/signature-request-original.component.js
index d4467a75c..2ee7d005f 100644
--- a/ui/components/app/signature-request-original/signature-request-original.component.js
+++ b/ui/components/app/signature-request-original/signature-request-original.component.js
@@ -35,6 +35,7 @@ export default class SignatureRequestOriginal extends Component {
domainMetadata: PropTypes.object,
hardwareWalletRequiresConnection: PropTypes.bool,
isLedgerWallet: PropTypes.bool,
+ nativeCurrency: PropTypes.string.isRequired,
};
state = {
@@ -74,12 +75,12 @@ export default class SignatureRequestOriginal extends Component {
};
renderBalance = () => {
- const { conversionRate } = this.props;
+ const { conversionRate, nativeCurrency } = this.props;
const {
fromAccount: { balance },
} = this.state;
- const balanceInEther = conversionUtil(balance, {
+ const balanceInBaseAsset = conversionUtil(balance, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
fromDenomination: 'WEI',
@@ -93,7 +94,7 @@ export default class SignatureRequestOriginal extends Component {
{`${this.context.t('balance')}:`}
- {`${balanceInEther} ETH`}
+ {`${balanceInBaseAsset} ${nativeCurrency}`}
);
diff --git a/ui/components/app/signature-request-original/signature-request-original.container.js b/ui/components/app/signature-request-original/signature-request-original.container.js
index 82f313bd4..bcbc140bf 100644
--- a/ui/components/app/signature-request-original/signature-request-original.container.js
+++ b/ui/components/app/signature-request-original/signature-request-original.container.js
@@ -13,7 +13,10 @@ import {
import { getAccountByAddress } from '../../../helpers/utils/util';
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck';
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
-import { isAddressLedger } from '../../../ducks/metamask/metamask';
+import {
+ isAddressLedger,
+ getNativeCurrency,
+} from '../../../ducks/metamask/metamask';
import SignatureRequestOriginal from './signature-request-original.component';
function mapStateToProps(state, ownProps) {
@@ -34,6 +37,7 @@ function mapStateToProps(state, ownProps) {
mostRecentOverviewPage: getMostRecentOverviewPage(state),
hardwareWalletRequiresConnection,
isLedgerWallet,
+ nativeCurrency: getNativeCurrency(state),
// not passed to component
allAccounts: accountsWithSendEtherInfoSelector(state),
domainMetadata: getDomainMetadata(state),
From 5712539a38f94f4fd4c87a344cdf22d14f2791ae Mon Sep 17 00:00:00 2001
From: ryanml
Date: Mon, 15 Nov 2021 10:28:35 -0700
Subject: [PATCH 28/56] Implement 'Add NFT' screen (#12544)
* Implement 'Add NFT' screen
* Guarding added route with feature flag
---
app/_locales/en/messages.json | 12 ++++
ui/helpers/constants/routes.js | 2 +
.../add-collectible.component.js | 65 +++++++++++++++++++
ui/pages/add-collectible/index.js | 1 +
ui/pages/home/home.component.js | 3 +-
ui/pages/routes/routes.component.js | 9 +++
6 files changed, 91 insertions(+), 1 deletion(-)
create mode 100644 ui/pages/add-collectible/add-collectible.component.js
create mode 100644 ui/pages/add-collectible/index.js
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 9fc4cfa44..3e2986125 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -43,6 +43,9 @@
"activityLog": {
"message": "activity log"
},
+ "add": {
+ "message": "Add"
+ },
"addANetwork": {
"message": "Add a network"
},
@@ -100,6 +103,9 @@
"addToken": {
"message": "Add Token"
},
+ "address": {
+ "message": "Address"
+ },
"addressBookIcon": {
"message": "Address book icon"
},
@@ -1132,6 +1138,9 @@
"history": {
"message": "History"
},
+ "id": {
+ "message": "ID"
+ },
"import": {
"message": "Import",
"description": "Button to import an account from a selected file"
@@ -1601,6 +1610,9 @@
"message": "Nonce is higher than suggested nonce of $1",
"description": "The next nonce according to MetaMask's internal logic"
},
+ "nftTokenIdPlaceholder": {
+ "message": "Enter the collectible ID"
+ },
"nfts": {
"message": "NFTs"
},
diff --git a/ui/helpers/constants/routes.js b/ui/helpers/constants/routes.js
index 7883647de..d1f1dcf3d 100644
--- a/ui/helpers/constants/routes.js
+++ b/ui/helpers/constants/routes.js
@@ -38,6 +38,7 @@ const AWAITING_SIGNATURES_ROUTE = '/swaps/awaiting-signatures';
const AWAITING_SWAP_ROUTE = '/swaps/awaiting-swap';
const SWAPS_ERROR_ROUTE = '/swaps/swaps-error';
const SWAPS_MAINTENANCE_ROUTE = '/swaps/maintenance';
+const ADD_COLLECTIBLE_ROUTE = '/add-collectible';
const INITIALIZE_ROUTE = '/initialize';
const INITIALIZE_WELCOME_ROUTE = '/initialize/welcome';
@@ -218,6 +219,7 @@ export {
AWAITING_SIGNATURES_ROUTE,
SWAPS_ERROR_ROUTE,
SWAPS_MAINTENANCE_ROUTE,
+ ADD_COLLECTIBLE_ROUTE,
ONBOARDING_ROUTE,
ONBOARDING_HELP_US_IMPROVE_ROUTE,
ONBOARDING_CREATE_PASSWORD_ROUTE,
diff --git a/ui/pages/add-collectible/add-collectible.component.js b/ui/pages/add-collectible/add-collectible.component.js
new file mode 100644
index 000000000..4bf0ecdb0
--- /dev/null
+++ b/ui/pages/add-collectible/add-collectible.component.js
@@ -0,0 +1,65 @@
+import React, { useState } from 'react';
+import { useHistory } from 'react-router-dom';
+import { useI18nContext } from '../../hooks/useI18nContext';
+import { DEFAULT_ROUTE } from '../../helpers/constants/routes';
+
+import Box from '../../components/ui/box';
+import TextField from '../../components/ui/text-field';
+import PageContainer from '../../components/ui/page-container';
+
+export default function AddCollectible() {
+ const t = useI18nContext();
+ const history = useHistory();
+
+ const [address, setAddress] = useState('');
+ const [tokenId, setTokenId] = useState('');
+
+ return (
+ {
+ console.log(
+ `Adding collectible with ID: ${tokenId} and address ${address}`,
+ );
+ history.push(DEFAULT_ROUTE);
+ }}
+ submitText={t('add')}
+ onCancel={() => {
+ history.push(DEFAULT_ROUTE);
+ }}
+ onClose={() => {
+ history.push(DEFAULT_ROUTE);
+ }}
+ disabled={false}
+ contentComponent={
+
+
+ setAddress(e.target.value)}
+ fullWidth
+ autoFocus
+ margin="normal"
+ />
+
+
+ setTokenId(e.target.value)}
+ fullWidth
+ margin="normal"
+ />
+
+
+ }
+ />
+ );
+}
diff --git a/ui/pages/add-collectible/index.js b/ui/pages/add-collectible/index.js
new file mode 100644
index 000000000..71b5d8dd5
--- /dev/null
+++ b/ui/pages/add-collectible/index.js
@@ -0,0 +1 @@
+export { default } from './add-collectible.component';
diff --git a/ui/pages/home/home.component.js b/ui/pages/home/home.component.js
index c46449cfd..ba99b1035 100644
--- a/ui/pages/home/home.component.js
+++ b/ui/pages/home/home.component.js
@@ -35,6 +35,7 @@ import {
BUILD_QUOTE_ROUTE,
VIEW_QUOTE_ROUTE,
CONFIRMATION_V_NEXT_ROUTE,
+ ADD_COLLECTIBLE_ROUTE,
} from '../../helpers/constants/routes';
import BetaHomeFooter from './beta-home-footer.component';
@@ -438,7 +439,7 @@ export default class Home extends PureComponent {
>
{
- console.log('Added NFT');
+ history.push(ADD_COLLECTIBLE_ROUTE);
}}
/>
diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js
index 91a68e17c..8e4ca834f 100644
--- a/ui/pages/routes/routes.component.js
+++ b/ui/pages/routes/routes.component.js
@@ -18,6 +18,7 @@ import RestoreVaultPage from '../keychains/restore-vault';
import RevealSeedConfirmation from '../keychains/reveal-seed';
import MobileSyncPage from '../mobile-sync';
import ImportTokenPage from '../import-token';
+import AddCollectiblePage from '../add-collectible';
import ConfirmImportTokenPage from '../confirm-import-token';
import ConfirmAddSuggestedTokenPage from '../confirm-add-suggested-token';
import CreateAccountPage from '../create-account';
@@ -55,6 +56,7 @@ import {
CONFIRM_IMPORT_TOKEN_ROUTE,
INITIALIZE_ROUTE,
ONBOARDING_ROUTE,
+ ADD_COLLECTIBLE_ROUTE,
} from '../../helpers/constants/routes';
import {
@@ -155,6 +157,13 @@ export default class Routes extends Component {
component={ImportTokenPage}
exact
/>
+ {process.env.COLLECTIBLES_V1 ? (
+
+ ) : null}
Date: Mon, 15 Nov 2021 10:13:35 -0800
Subject: [PATCH 29/56] Updating Chip component (#12671)
---
ui/components/ui/chip/README.mdx | 15 ++++
ui/components/ui/chip/chip.js | 39 +++++++-
ui/components/ui/chip/chip.scss | 5 +-
ui/components/ui/chip/chip.stories.js | 125 +++++++++++++++++++-------
4 files changed, 148 insertions(+), 36 deletions(-)
create mode 100644 ui/components/ui/chip/README.mdx
diff --git a/ui/components/ui/chip/README.mdx b/ui/components/ui/chip/README.mdx
new file mode 100644
index 000000000..a873620d6
--- /dev/null
+++ b/ui/components/ui/chip/README.mdx
@@ -0,0 +1,15 @@
+import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
+
+import Chip from '.';
+
+# Chip
+
+Chips are compact elements that represent an input, status, or action.
+
+
+
+## Component API
+
+
diff --git a/ui/components/ui/chip/chip.js b/ui/components/ui/chip/chip.js
index 45157fd51..e23dd8289 100644
--- a/ui/components/ui/chip/chip.js
+++ b/ui/components/ui/chip/chip.js
@@ -10,6 +10,7 @@ export default function Chip({
className,
children,
borderColor = COLORS.UI1,
+ backgroundColor,
label,
labelProps = {},
leftIcon,
@@ -32,7 +33,8 @@ export default function Chip({
className={classnames(className, 'chip', {
'chip--with-left-icon': Boolean(leftIcon),
'chip--with-right-icon': Boolean(rightIcon),
- [`chip--${borderColor}`]: true,
+ [`chip--border-color-${borderColor}`]: true,
+ [`chip--background-color-${backgroundColor}`]: true,
})}
role={isInteractive ? 'button' : undefined}
tabIndex={isInteractive ? 0 : undefined}
@@ -55,17 +57,46 @@ export default function Chip({
}
Chip.propTypes = {
+ /**
+ * Data test id used for testing of the Chip component
+ */
dataTestId: PropTypes.string,
+ /**
+ * The border color of the Chip
+ */
borderColor: PropTypes.oneOf(Object.values(COLORS)),
+ /**
+ * The background color of the Chip component
+ */
+ backgroundColor: PropTypes.oneOf(Object.values(COLORS)),
+ /**
+ * The label of the Chip component has a default typography variant of h6 and is a span html element
+ */
label: PropTypes.string,
- children: PropTypes.node,
+ /**
+ * The label props of the component. Most Typography props can be used
+ */
labelProps: PropTypes.shape({
...omit(Typography.propTypes, ['children', 'className']),
}),
+ /**
+ * Children will replace the label of the Chip component.
+ */
+ children: PropTypes.node,
+ /**
+ * An icon component that can be passed to appear on the left of the label
+ */
leftIcon: PropTypes.node,
+ /**
+ * An icon component that can be passed to appear on the right of the label
+ */
rightIcon: PropTypes.node,
+ /**
+ * The className of the Chip
+ */
className: PropTypes.string,
+ /**
+ * The onClick handler to be passed to the Chip component
+ */
onClick: PropTypes.func,
- inputValue: PropTypes.string,
- setInputValue: PropTypes.func,
};
diff --git a/ui/components/ui/chip/chip.scss b/ui/components/ui/chip/chip.scss
index 02c4df6a5..a52e37afa 100644
--- a/ui/components/ui/chip/chip.scss
+++ b/ui/components/ui/chip/chip.scss
@@ -18,9 +18,12 @@
}
@each $variant, $color in design-system.$color-map {
- &--#{$variant} {
+ &--border-color-#{$variant} {
border-color: $color;
}
+ &--background-color-#{$variant} {
+ background-color: $color;
+ }
}
&--with-left-icon,
diff --git a/ui/components/ui/chip/chip.stories.js b/ui/components/ui/chip/chip.stories.js
index c1cb04473..06c638812 100644
--- a/ui/components/ui/chip/chip.stories.js
+++ b/ui/components/ui/chip/chip.stories.js
@@ -1,51 +1,111 @@
-/* eslint-disable react/prop-types */
-
import React, { useState } from 'react';
-import { select, text } from '@storybook/addon-knobs';
+
import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system';
+
import ApproveIcon from '../icon/approve-icon.component';
import Identicon from '../identicon/identicon.component';
import { ChipWithInput } from './chip-with-input';
+
+import README from './README.mdx';
+
import Chip from '.';
export default {
- title: 'Chip',
+ title: 'UI/Chip',
id: __filename,
+ component: Chip,
+ parameters: {
+ docs: {
+ page: README,
+ },
+ },
+ argTypes: {
+ leftIcon: {
+ control: {
+ type: 'select',
+ },
+ options: ['ApproveIcon'],
+ mapping: {
+ ApproveIcon: ,
+ },
+ },
+ rightIcon: {
+ control: {
+ type: 'select',
+ },
+ options: ['Identicon'],
+ mapping: {
+ Identicon: (
+
+ ),
+ },
+ },
+ label: {
+ control: 'text',
+ },
+ labelProps: {
+ color: {
+ control: {
+ type: 'select',
+ },
+ options: Object.values(COLORS),
+ },
+ variant: {
+ color: {
+ control: {
+ type: 'select',
+ },
+ options: Object.values(TYPOGRAPHY),
+ },
+ },
+ },
+ borderColor: {
+ control: {
+ type: 'select',
+ },
+ options: Object.values(COLORS),
+ },
+ backgroundColor: {
+ control: {
+ type: 'select',
+ },
+ options: Object.values(COLORS),
+ },
+ children: {
+ control: 'text',
+ },
+ },
};
-export const Plain = ({
- leftIcon,
- rightIcon,
- label = 'Hello',
- borderColor = COLORS.UI1,
- fontColor = COLORS.BLACK,
-}) => (
-
-);
+export const DefaultStory = (args) => ;
+
+DefaultStory.storyName = 'Default';
+
+DefaultStory.args = {
+ label: 'Chip',
+ borderColor: COLORS.UI3,
+ backgroundColor: COLORS.UI1,
+ labelProps: {
+ color: COLORS.BLACK,
+ variant: TYPOGRAPHY.H6,
+ },
+};
export const WithLeftIcon = () => (
- }
/>
);
export const WithRightIcon = () => (
- (
);
export const WithBothIcons = () => (
- (
}
/>
);
-export const WithInput = () => {
- const [inputValue, setInputValue] = useState('');
+export const WithInput = (args) => {
+ const [inputValue, setInputValue] = useState('Chip with input');
return (
);
};
+
+WithInput.args = {
+ borderColor: COLORS.UI3,
+};
From bd9d97600646aa231c394b415727f9d49c98350d Mon Sep 17 00:00:00 2001
From: George Marshall
Date: Mon, 15 Nov 2021 10:13:54 -0800
Subject: [PATCH 30/56] Updating Card ui component and adding story (#12666)
---
ui/components/ui/card/README.mdx | 42 +++++
ui/components/ui/card/card.component.js | 23 ---
ui/components/ui/card/card.component.test.js | 21 ---
ui/components/ui/card/card.js | 60 +++++++
ui/components/ui/card/card.stories.js | 169 +++++++++++++++++++
ui/components/ui/card/card.test.js | 11 ++
ui/components/ui/card/index.js | 2 +-
ui/components/ui/card/index.scss | 11 --
ui/components/ui/ui-components.scss | 1 -
9 files changed, 283 insertions(+), 57 deletions(-)
create mode 100644 ui/components/ui/card/README.mdx
delete mode 100644 ui/components/ui/card/card.component.js
delete mode 100644 ui/components/ui/card/card.component.test.js
create mode 100644 ui/components/ui/card/card.js
create mode 100644 ui/components/ui/card/card.stories.js
create mode 100644 ui/components/ui/card/card.test.js
delete mode 100644 ui/components/ui/card/index.scss
diff --git a/ui/components/ui/card/README.mdx b/ui/components/ui/card/README.mdx
new file mode 100644
index 000000000..88110ef1d
--- /dev/null
+++ b/ui/components/ui/card/README.mdx
@@ -0,0 +1,42 @@
+import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
+
+import Card from '.';
+
+# Card
+
+Cards are used to group related content or actions together.
+
+
+
+## Component API
+
+The `Card` component extends the `Box` component. See the `Box` component for an extended list of props.
+
+
+
+## Usage
+
+The following describes the props and example usage for this component.
+
+### Padding, Border and Background Color
+
+The Card component has a set of default props that should meet most card use cases. There is a strong recommendation to not overwrite these to ensure our cards stay consistent across the app.
+
+That being said all props can be overwritten if necessary.
+
+```jsx
+import { COLORS } from '../../../helpers/constants/design-system';
+
+// To remove the border
+
+// All border related props of the Box component will work
+
+// To remove or change padding
+
+// All padding related props of the Box component will work
+
+// To change the background color
+
+```
diff --git a/ui/components/ui/card/card.component.js b/ui/components/ui/card/card.component.js
deleted file mode 100644
index d75506b9a..000000000
--- a/ui/components/ui/card/card.component.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import React, { PureComponent } from 'react';
-import PropTypes from 'prop-types';
-import classnames from 'classnames';
-
-export default class Card extends PureComponent {
- static propTypes = {
- className: PropTypes.string,
- overrideClassName: PropTypes.bool,
- title: PropTypes.string,
- children: PropTypes.node,
- };
-
- render() {
- const { className, overrideClassName, title } = this.props;
-
- return (
-
-
{title}
- {this.props.children}
-
- );
- }
-}
diff --git a/ui/components/ui/card/card.component.test.js b/ui/components/ui/card/card.component.test.js
deleted file mode 100644
index 83205ec9c..000000000
--- a/ui/components/ui/card/card.component.test.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import Card from './card.component';
-
-describe('Card Component', () => {
- it('should render a card with a title and child element', () => {
- const wrapper = shallow(
-
- Child
- ,
- );
-
- expect(wrapper.hasClass('card-test-class')).toStrictEqual(true);
- const title = wrapper.find('.card__title');
- expect(title).toHaveLength(1);
- expect(title.text()).toStrictEqual('Test');
- const child = wrapper.find('.child-test-class');
- expect(child).toHaveLength(1);
- expect(child.text()).toStrictEqual('Child');
- });
-});
diff --git a/ui/components/ui/card/card.js b/ui/components/ui/card/card.js
new file mode 100644
index 000000000..fc1beafaf
--- /dev/null
+++ b/ui/components/ui/card/card.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import Box from '../box';
+import {
+ BORDER_STYLE,
+ COLORS,
+ SIZES,
+} from '../../../helpers/constants/design-system';
+
+const Card = ({
+ border = true,
+ padding = 4,
+ backgroundColor = COLORS.WHITE,
+ children,
+ ...props
+}) => {
+ const defaultBorderProps = {
+ borderColor: border && COLORS.UI2,
+ borderRadius: border && SIZES.MD,
+ borderStyle: border && BORDER_STYLE.SOLID,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+Card.propTypes = {
+ /**
+ * Whether the Card has a border or not.
+ * Defaults to true
+ */
+ border: PropTypes.bool,
+ /**
+ * Padding of the Card component accepts number or an array of 2 numbers.
+ * Defaults to 4 (16px)
+ */
+ padding: Box.propTypes.padding,
+ /**
+ * The background color of the card
+ * Defaults to COLORS.WHITE
+ */
+ backgroundColor: Box.propTypes.backgroundColor,
+ /**
+ * The Card component accepts all Box component props
+ */
+ ...Box.propTypes,
+};
+
+export default Card;
diff --git a/ui/components/ui/card/card.stories.js b/ui/components/ui/card/card.stories.js
new file mode 100644
index 000000000..1dd2075e2
--- /dev/null
+++ b/ui/components/ui/card/card.stories.js
@@ -0,0 +1,169 @@
+import React from 'react';
+import {
+ ALIGN_ITEMS,
+ BLOCK_SIZES,
+ BORDER_STYLE,
+ COLORS,
+ DISPLAY,
+ JUSTIFY_CONTENT,
+ TEXT_ALIGN,
+} from '../../../helpers/constants/design-system';
+
+import README from './README.mdx';
+import Card from '.';
+
+const sizeOptions = [undefined, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
+
+export default {
+ title: 'UI/Card',
+ id: __filename,
+ component: Card,
+ parameters: {
+ docs: {
+ page: README,
+ },
+ },
+ argTypes: {
+ children: { control: 'text' },
+ border: {
+ control: 'boolean',
+ },
+ borderStyle: {
+ control: {
+ type: 'select',
+ },
+ options: Object.values(BORDER_STYLE),
+ },
+ borderWidth: {
+ control: {
+ type: 'select',
+ },
+ options: [...sizeOptions],
+ },
+ borderColor: {
+ control: {
+ type: 'select',
+ },
+ options: Object.values(COLORS),
+ },
+ backgroundColor: {
+ control: {
+ type: 'select',
+ },
+ options: Object.values(COLORS),
+ },
+ width: {
+ control: {
+ type: 'select',
+ },
+ options: Object.values(BLOCK_SIZES),
+ },
+ height: {
+ control: {
+ type: 'select',
+ },
+ options: Object.values(BLOCK_SIZES),
+ },
+ textAlign: {
+ control: {
+ type: 'select',
+ },
+ options: Object.values(TEXT_ALIGN),
+ },
+ margin: {
+ control: {
+ type: 'select',
+ },
+ options: [...sizeOptions],
+ },
+ marginTop: {
+ control: {
+ type: 'select',
+ },
+ options: [...sizeOptions],
+ },
+ marginRight: {
+ control: {
+ type: 'select',
+ },
+ options: [...sizeOptions],
+ },
+ marginBottom: {
+ control: {
+ type: 'select',
+ },
+ options: [...sizeOptions],
+ },
+ marginLeft: {
+ control: {
+ type: 'select',
+ },
+ options: [...sizeOptions],
+ },
+ padding: {
+ control: {
+ type: 'select',
+ },
+ options: [...sizeOptions],
+ },
+ paddingTop: {
+ control: {
+ type: 'select',
+ },
+ options: [...sizeOptions],
+ },
+ paddingRight: {
+ control: {
+ type: 'select',
+ },
+ options: [...sizeOptions],
+ },
+ paddingBottom: {
+ control: {
+ type: 'select',
+ },
+ options: [...sizeOptions],
+ },
+ paddingLeft: {
+ control: {
+ type: 'select',
+ },
+ options: [...sizeOptions],
+ },
+ display: {
+ control: {
+ type: 'select',
+ },
+ options: Object.values(DISPLAY),
+ },
+ justifyContent: {
+ control: {
+ type: 'select',
+ },
+ options: Object.values(JUSTIFY_CONTENT),
+ },
+ alignItems: {
+ control: {
+ type: 'select',
+ },
+ options: Object.values(ALIGN_ITEMS),
+ },
+ },
+ args: {
+ children: 'Card children',
+ },
+};
+
+export const DefaultStory = (args) => {args.children};
+
+DefaultStory.storyName = 'Default';
+
+DefaultStory.args = {
+ padding: 4,
+ border: true,
+ borderWidth: 1,
+ borderColor: COLORS.UI2,
+ borderStyle: BORDER_STYLE.SOLID,
+ backgroundColor: COLORS.WHITE,
+ display: DISPLAY.BLOCK,
+};
diff --git a/ui/components/ui/card/card.test.js b/ui/components/ui/card/card.test.js
new file mode 100644
index 000000000..cbfd0db1c
--- /dev/null
+++ b/ui/components/ui/card/card.test.js
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { render } from '@testing-library/react';
+import Card from '.';
+
+describe('Card', () => {
+ it('should render the Card without crashing', () => {
+ const { getByText } = render(Card content);
+
+ expect(getByText('Card content')).toBeDefined();
+ });
+});
diff --git a/ui/components/ui/card/index.js b/ui/components/ui/card/index.js
index 643fad74d..2c9f20524 100644
--- a/ui/components/ui/card/index.js
+++ b/ui/components/ui/card/index.js
@@ -1 +1 @@
-export { default } from './card.component';
+export { default } from './card';
diff --git a/ui/components/ui/card/index.scss b/ui/components/ui/card/index.scss
deleted file mode 100644
index 4fa32e1bc..000000000
--- a/ui/components/ui/card/index.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-.card {
- border-radius: 4px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
- padding: 8px;
-
- &__title {
- border-bottom: 1px solid #d8d8d8;
- padding-bottom: 4px;
- text-transform: capitalize;
- }
-}
diff --git a/ui/components/ui/ui-components.scss b/ui/components/ui/ui-components.scss
index 75cddcb90..6200bb21f 100644
--- a/ui/components/ui/ui-components.scss
+++ b/ui/components/ui/ui-components.scss
@@ -8,7 +8,6 @@
@import 'button-group/index';
@import 'button/buttons';
@import 'callout/callout';
-@import 'card/index';
@import 'check-box/index';
@import 'chip/chip';
@import 'circle-icon/index';
From d810e7f0c6e653e72c5a3ab9231dbc58486e9b62 Mon Sep 17 00:00:00 2001
From: Alex Miller
Date: Mon, 15 Nov 2021 12:15:01 -0600
Subject: [PATCH 31/56] GridPlus: Updates `eth-lattice-keyring` to v0.4.0 for
UX improvements (#12649)
Most notably this adds the ability to manage multiple Lattice/SafeCard
wallets simultaneously. If a user makes a request from an address not
associated with the device's active wallet, an error will display.
See: https://github.com/GridPlus/eth-lattice-keyring/pull/19
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index 1d8b2c01f..3d4764ef7 100644
--- a/package.json
+++ b/package.json
@@ -138,7 +138,7 @@
"eth-json-rpc-middleware": "^8.0.0",
"eth-keyring-controller": "^6.2.0",
"eth-method-registry": "^2.0.0",
- "eth-lattice-keyring": "^0.3.0",
+ "eth-lattice-keyring": "^0.4.0",
"eth-query": "^2.1.2",
"eth-rpc-errors": "^4.0.2",
"eth-sig-util": "^3.0.0",
diff --git a/yarn.lock b/yarn.lock
index 92caf6047..ec0324c22 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -13001,10 +13001,10 @@ eth-keyring-controller@^6.1.0, eth-keyring-controller@^6.2.0, eth-keyring-contro
loglevel "^1.5.0"
obs-store "^4.0.3"
-eth-lattice-keyring@^0.3.0:
- version "0.3.5"
- resolved "https://registry.yarnpkg.com/eth-lattice-keyring/-/eth-lattice-keyring-0.3.5.tgz#2e0aa5893206c62e4a184c5481abb13c49b07ad2"
- integrity sha512-HWMfbwO+UCFA4OOuPS2tfY1mqG2u6k0+GLY9us+WLsFJTII6D/NzdN8OBQorYoYG+ce9VEJrJtRPjpOG11ZA1w==
+eth-lattice-keyring@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/eth-lattice-keyring/-/eth-lattice-keyring-0.4.0.tgz#0afab94fe1c9b13377e1c78660a725c9bc0639a0"
+ integrity sha512-AB+FzgnyqapcZmdLeVMUDGZqA09yRQZxoVm/qZaT3kb5e4jhWon4BGRo/g65JuNagC8SltIQY6fpDh/q5gXNTQ==
dependencies:
"@ethereumjs/common" "^2.4.0"
"@ethereumjs/tx" "^3.1.1"
From 405b56c73080a0cb9afb284837cc0a1258021615 Mon Sep 17 00:00:00 2001
From: Elliot Winkler
Date: Mon, 15 Nov 2021 12:16:11 -0700
Subject: [PATCH 32/56] Replace history during onboarding (#12693)
---
.../import-with-seed-phrase.component.js | 2 +-
.../confirm-seed-phrase-component.test.js | 18 +++++++++---------
.../confirm-seed-phrase.component.js | 2 +-
.../reveal-seed-phrase.component.js | 4 ++--
.../onboarding-flow/import-srp/import-srp.js | 2 +-
5 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/ui/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
index 62c7769f4..7522899c6 100644
--- a/ui/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
+++ b/ui/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
@@ -142,7 +142,7 @@ export default class ImportWithSeedPhrase extends PureComponent {
setSeedPhraseBackedUp(true).then(async () => {
initializeThreeBox();
- history.push(INITIALIZE_END_OF_FLOW_ROUTE);
+ history.replace(INITIALIZE_END_OF_FLOW_ROUTE);
});
} catch (error) {
this.setState({ seedPhraseError: error.message });
diff --git a/ui/pages/first-time-flow/seed-phrase/confirm-seed-phrase-component.test.js b/ui/pages/first-time-flow/seed-phrase/confirm-seed-phrase-component.test.js
index 6627eabc7..ada4039ec 100644
--- a/ui/pages/first-time-flow/seed-phrase/confirm-seed-phrase-component.test.js
+++ b/ui/pages/first-time-flow/seed-phrase/confirm-seed-phrase-component.test.js
@@ -25,11 +25,11 @@ describe('ConfirmSeedPhrase Component', () => {
it('should add/remove selected on click', () => {
const metricsEventSpy = sinon.spy();
- const pushSpy = sinon.spy();
+ const replaceSpy = sinon.spy();
const root = shallowRender(
{
seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
- history: { push: pushSpy },
+ history: { replace: replaceSpy },
},
{
metricsEvent: metricsEventSpy,
@@ -58,11 +58,11 @@ describe('ConfirmSeedPhrase Component', () => {
it('should render correctly on hover', () => {
const metricsEventSpy = sinon.spy();
- const pushSpy = sinon.spy();
+ const replaceSpy = sinon.spy();
const root = shallowRender(
{
seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
- history: { push: pushSpy },
+ history: { replace: replaceSpy },
},
{
metricsEvent: metricsEventSpy,
@@ -93,11 +93,11 @@ describe('ConfirmSeedPhrase Component', () => {
it('should insert seed in place on drop', () => {
const metricsEventSpy = sinon.spy();
- const pushSpy = sinon.spy();
+ const replaceSpy = sinon.spy();
const root = shallowRender(
{
seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
- history: { push: pushSpy },
+ history: { replace: replaceSpy },
},
{
metricsEvent: metricsEventSpy,
@@ -138,12 +138,12 @@ describe('ConfirmSeedPhrase Component', () => {
'豬',
];
const metricsEventSpy = sinon.spy();
- const pushSpy = sinon.spy();
+ const replaceSpy = sinon.spy();
const initialize3BoxSpy = sinon.spy();
const root = shallowRender(
{
seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
- history: { push: pushSpy },
+ history: { replace: replaceSpy },
setSeedPhraseBackedUp: () => Promise.resolve(),
initializeThreeBox: initialize3BoxSpy,
},
@@ -174,6 +174,6 @@ describe('ConfirmSeedPhrase Component', () => {
},
});
expect(initialize3BoxSpy.calledOnce).toStrictEqual(true);
- expect(pushSpy.args[0][0]).toStrictEqual('/initialize/end-of-flow');
+ expect(replaceSpy.args[0][0]).toStrictEqual('/initialize/end-of-flow');
});
});
diff --git a/ui/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
index d93cf74e4..72f1d0b45 100644
--- a/ui/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
+++ b/ui/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
@@ -87,7 +87,7 @@ export default class ConfirmSeedPhrase extends PureComponent {
setSeedPhraseBackedUp(true).then(async () => {
initializeThreeBox();
- history.push(INITIALIZE_END_OF_FLOW_ROUTE);
+ history.replace(INITIALIZE_END_OF_FLOW_ROUTE);
});
} catch (error) {
console.error(error.message);
diff --git a/ui/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js b/ui/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
index 62dc30fc6..1d9a47b8d 100644
--- a/ui/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
+++ b/ui/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
@@ -54,7 +54,7 @@ export default class RevealSeedPhrase extends PureComponent {
return;
}
- history.push(INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE);
+ history.replace(INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE);
};
handleSkip = async () => {
@@ -78,7 +78,7 @@ export default class RevealSeedPhrase extends PureComponent {
if (onboardingInitiator) {
await returnToOnboardingInitiator(onboardingInitiator);
}
- history.push(DEFAULT_ROUTE);
+ history.replace(DEFAULT_ROUTE);
};
renderSecretWordsContainer() {
diff --git a/ui/pages/onboarding-flow/import-srp/import-srp.js b/ui/pages/onboarding-flow/import-srp/import-srp.js
index 797aacef4..5d3f6b7f1 100644
--- a/ui/pages/onboarding-flow/import-srp/import-srp.js
+++ b/ui/pages/onboarding-flow/import-srp/import-srp.js
@@ -101,7 +101,7 @@ export default function ImportSRP({ submitSecretRecoveryPhrase }) {
large
onClick={() => {
submitSecretRecoveryPhrase(secretRecoveryPhrase);
- history.push(ONBOARDING_CREATE_PASSWORD_ROUTE);
+ history.replace(ONBOARDING_CREATE_PASSWORD_ROUTE);
}}
disabled={error || secretRecoveryPhrase.length === 0}
>
From 2ced3a8bfa75b073a6357820ecfe21cd59858bc0 Mon Sep 17 00:00:00 2001
From: Dan J Miller
Date: Mon, 15 Nov 2021 15:50:33 -0330
Subject: [PATCH 33/56] Add migration to set showTestNetworks to true if there
is evidence of testnet use (#12675)
* Add migration to set showTestNetworks to true if there is evidence the user has used a test network
* Add migration to index file
* Remove console.log
* Clean up conditional structure in migration 67
---
app/scripts/migrations/067.js | 66 +++++++++
app/scripts/migrations/067.test.js | 217 +++++++++++++++++++++++++++++
app/scripts/migrations/index.js | 2 +
3 files changed, 285 insertions(+)
create mode 100644 app/scripts/migrations/067.js
create mode 100644 app/scripts/migrations/067.test.js
diff --git a/app/scripts/migrations/067.js b/app/scripts/migrations/067.js
new file mode 100644
index 000000000..98150edf7
--- /dev/null
+++ b/app/scripts/migrations/067.js
@@ -0,0 +1,66 @@
+import { cloneDeep } from 'lodash';
+import BigNumber from 'bignumber.js';
+import { TEST_CHAINS } from '../../../shared/constants/network';
+
+const hexNumberIsGreaterThanZero = (hexNumber) =>
+ new BigNumber(hexNumber || '0x0', 16).gt(0);
+
+const version = 67;
+
+/**
+ * Sets the showTestNetworks property to true if it was false or undefined, and there is evidence
+ * that the user has used a test net
+ */
+export default {
+ version,
+ async migrate(originalVersionedData) {
+ const versionedData = cloneDeep(originalVersionedData);
+ versionedData.meta.version = version;
+ const state = versionedData.data;
+ const newState = transformState(state);
+ versionedData.data = newState;
+ return versionedData;
+ },
+};
+
+function transformState(state) {
+ const PreferencesController = state?.PreferencesController || {};
+ const preferences = PreferencesController.preferences || {};
+
+ if (preferences.showTestNetworks) {
+ return state;
+ }
+
+ const transactions = state?.TransactionController?.transactions || {};
+ const provider = state.NetworkController?.provider || {};
+ const cachedBalances = state.CachedBalancesController?.cachedBalances || {};
+
+ const userIsCurrentlyOnATestNet = TEST_CHAINS.includes(provider?.chainId);
+ const userHasMadeATestNetTransaction = Object.values(
+ transactions,
+ ).some(({ chainId }) => TEST_CHAINS.includes(chainId));
+ const userHasACachedBalanceOnATestnet = TEST_CHAINS.some((chainId) => {
+ const cachedBalancesForChain = Object.values(cachedBalances[chainId] || {});
+ const userHasABalanceGreaterThanZeroOnThisChain = cachedBalancesForChain.some(
+ hexNumberIsGreaterThanZero,
+ );
+ return userHasABalanceGreaterThanZeroOnThisChain;
+ });
+ const userHasUsedATestnet =
+ userIsCurrentlyOnATestNet ||
+ userHasMadeATestNetTransaction ||
+ userHasACachedBalanceOnATestnet;
+
+ const newState = {
+ ...state,
+ PreferencesController: {
+ ...PreferencesController,
+ preferences: {
+ ...preferences,
+ showTestNetworks: userHasUsedATestnet,
+ },
+ },
+ };
+
+ return newState;
+}
diff --git a/app/scripts/migrations/067.test.js b/app/scripts/migrations/067.test.js
new file mode 100644
index 000000000..85bfa76cf
--- /dev/null
+++ b/app/scripts/migrations/067.test.js
@@ -0,0 +1,217 @@
+import { TEST_CHAINS } from '../../../shared/constants/network';
+import migration67 from './067';
+
+describe('migration #67', () => {
+ it('should update the version metadata', async () => {
+ const oldStorage = {
+ meta: {
+ version: 66,
+ },
+ data: {},
+ };
+
+ const newStorage = await migration67.migrate(oldStorage);
+ expect(newStorage.meta).toStrictEqual({
+ version: 67,
+ });
+ });
+
+ it('should set showTestNetworks to true if the user is currently on a test network', async () => {
+ const oldStorage = {
+ meta: {},
+ data: {
+ PreferencesController: {
+ preferences: {
+ showTestNetworks: false,
+ },
+ },
+ NetworkController: {
+ provider: {
+ chainId: TEST_CHAINS[0],
+ },
+ },
+ },
+ };
+
+ const newStorage = await migration67.migrate(oldStorage);
+ expect(
+ newStorage.data.PreferencesController.preferences.showTestNetworks,
+ ).toBe(true);
+ });
+
+ it('should set showTestNetworks to true if there is a transaction on a test network in state', async () => {
+ const oldStorage = {
+ meta: {},
+ data: {
+ PreferencesController: {
+ preferences: {
+ showTestNetworks: false,
+ },
+ },
+ NetworkController: {
+ provider: {
+ chainId: 'not a test net',
+ },
+ },
+ TransactionController: {
+ transactions: {
+ abc123: {
+ chainId: TEST_CHAINS[0],
+ },
+ },
+ },
+ },
+ };
+
+ const newStorage = await migration67.migrate(oldStorage);
+ expect(
+ newStorage.data.PreferencesController.preferences.showTestNetworks,
+ ).toBe(true);
+ });
+
+ it('should set showTestNetworks to true if the user has a cached balance on a test network', async () => {
+ const oldStorage = {
+ meta: {},
+ data: {
+ PreferencesController: {
+ preferences: {
+ showTestNetworks: false,
+ },
+ },
+ NetworkController: {
+ provider: {
+ chainId: 'not a test net',
+ },
+ },
+ TransactionController: {
+ transactions: {
+ abc123: {
+ chainId: 'not a test net',
+ },
+ },
+ },
+ CachedBalancesController: {
+ cachedBalances: {
+ '0x1': {
+ '0x027d4ae98b79d0c52918bab4c3170bea701fb8ab': '0x0',
+ '0x2f318c334780961fb129d2a6c30d0763d9a5c970': '0x0',
+ '0x7a46ce51fbbb29c34aea1fe9833c27b5d2781925': '0x0',
+ },
+ [TEST_CHAINS[0]]: {
+ '0x027d4ae98b79d0c52918bab4c3170bea701fb8ab': '0x0',
+ '0x2f318c334780961fb129d2a6c30d0763d9a5c970': '0x1',
+ '0x7a46ce51fbbb29c34aea1fe9833c27b5d2781925': '0x0',
+ },
+ [TEST_CHAINS[1]]: {
+ '0x027d4ae98b79d0c52918bab4c3170bea701fb8ab': '0x0',
+ '0x2f318c334780961fb129d2a6c30d0763d9a5c970': '0x0',
+ '0x7a46ce51fbbb29c34aea1fe9833c27b5d2781925': '0x0',
+ },
+ },
+ },
+ },
+ };
+
+ const newStorage = await migration67.migrate(oldStorage);
+ expect(
+ newStorage.data.PreferencesController.preferences.showTestNetworks,
+ ).toBe(true);
+ });
+
+ it('should leave showTestNetworks false if there is no evidence of test network usage', async () => {
+ const oldStorage = {
+ meta: {},
+ data: {
+ PreferencesController: {
+ preferences: {
+ showTestNetworks: false,
+ },
+ },
+ NetworkController: {
+ provider: {
+ chainId: 'not a test net',
+ },
+ },
+ TransactionController: {
+ transactions: {
+ abc123: {
+ chainId: 'not a test net',
+ },
+ },
+ },
+ CachedBalancesController: {
+ cachedBalances: {
+ '0x1': {
+ '0x027d4ae98b79d0c52918bab4c3170bea701fb8ab': '0x10',
+ '0x2f318c334780961fb129d2a6c30d0763d9a5c970': '0x20',
+ '0x7a46ce51fbbb29c34aea1fe9833c27b5d2781925': '0x30',
+ },
+ [TEST_CHAINS[0]]: {
+ '0x027d4ae98b79d0c52918bab4c3170bea701fb8ab': '0x0',
+ '0x2f318c334780961fb129d2a6c30d0763d9a5c970': '0x0',
+ '0x7a46ce51fbbb29c34aea1fe9833c27b5d2781925': '0x0',
+ },
+ [TEST_CHAINS[1]]: {
+ '0x027d4ae98b79d0c52918bab4c3170bea701fb8ab': '0x0',
+ '0x2f318c334780961fb129d2a6c30d0763d9a5c970': '0x0',
+ '0x7a46ce51fbbb29c34aea1fe9833c27b5d2781925': '0x0',
+ },
+ },
+ },
+ },
+ };
+ const newStorage = await migration67.migrate(oldStorage);
+ expect(
+ newStorage.data.PreferencesController.preferences.showTestNetworks,
+ ).toBe(false);
+ });
+
+ it('should leave showTestNetworks true if it was true but there is no evidence of test network usage', async () => {
+ const oldStorage = {
+ meta: {},
+ data: {
+ PreferencesController: {
+ preferences: {
+ showTestNetworks: true,
+ },
+ },
+ NetworkController: {
+ provider: {
+ chainId: 'not a test net',
+ },
+ },
+ TransactionController: {
+ transactions: {
+ abc123: {
+ chainId: 'not a test net',
+ },
+ },
+ },
+ CachedBalancesController: {
+ cachedBalances: {
+ '0x1': {
+ '0x027d4ae98b79d0c52918bab4c3170bea701fb8ab': '0x10',
+ '0x2f318c334780961fb129d2a6c30d0763d9a5c970': '0x20',
+ '0x7a46ce51fbbb29c34aea1fe9833c27b5d2781925': '0x30',
+ },
+ [TEST_CHAINS[0]]: {
+ '0x027d4ae98b79d0c52918bab4c3170bea701fb8ab': '0x0',
+ '0x2f318c334780961fb129d2a6c30d0763d9a5c970': '0x0',
+ '0x7a46ce51fbbb29c34aea1fe9833c27b5d2781925': '0x0',
+ },
+ [TEST_CHAINS[1]]: {
+ '0x027d4ae98b79d0c52918bab4c3170bea701fb8ab': '0x0',
+ '0x2f318c334780961fb129d2a6c30d0763d9a5c970': '0x0',
+ '0x7a46ce51fbbb29c34aea1fe9833c27b5d2781925': '0x0',
+ },
+ },
+ },
+ },
+ };
+
+ const newStorage = await migration67.migrate(oldStorage);
+ expect(
+ newStorage.data.PreferencesController.preferences.showTestNetworks,
+ ).toBe(true);
+ });
+});
diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js
index 7baf840e3..2471381f5 100644
--- a/app/scripts/migrations/index.js
+++ b/app/scripts/migrations/index.js
@@ -70,6 +70,7 @@ import m063 from './063';
import m064 from './064';
import m065 from './065';
import m066 from './066';
+import m067 from './067';
const migrations = [
m002,
@@ -137,6 +138,7 @@ const migrations = [
m064,
m065,
m066,
+ m067,
];
export default migrations;
From c84604b5e475c1a4d9f3ce870e87978865caac65 Mon Sep 17 00:00:00 2001
From: Dan J Miller
Date: Mon, 15 Nov 2021 16:49:14 -0330
Subject: [PATCH 34/56] Adding scroll bar to handle overflow of long network
lists (#12706)
* Adding scroll bar to handle overflow of long network lists
* set scroll property to auto
* Add margin below scrollable network list
---
.../app/dropdowns/network-dropdown.js | 27 ++++++++++---------
ui/css/itcss/components/network.scss | 6 +++++
2 files changed, 21 insertions(+), 12 deletions(-)
diff --git a/ui/components/app/dropdowns/network-dropdown.js b/ui/components/app/dropdowns/network-dropdown.js
index 4dd785874..7838a9864 100644
--- a/ui/components/app/dropdowns/network-dropdown.js
+++ b/ui/components/app/dropdowns/network-dropdown.js
@@ -338,20 +338,23 @@ class NetworkDropdown extends Component {
) : null}