diff --git a/.circleci/config.yml b/.circleci/config.yml index ad4958166e68..2c583ad09cad 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -691,7 +691,7 @@ jobs: steps: - run: name: build:prod - command: yarn build --build-type flask prod + command: ENABLE_MV3=false yarn build --build-type flask prod - run: name: build:debug command: find dist/ -type f -exec md5sum {} \; | sort -k 2 diff --git a/.circleci/scripts/release-create-gh-release.sh b/.circleci/scripts/release-create-gh-release.sh index e138e20c5b59..37a654798b9f 100755 --- a/.circleci/scripts/release-create-gh-release.sh +++ b/.circleci/scripts/release-create-gh-release.sh @@ -69,9 +69,9 @@ then release_body="$(awk -v version="${tag##v}" -f .circleci/scripts/show-changelog.awk CHANGELOG.md)" hub release create \ --attach builds/metamask-chrome-*.zip \ - --attach builds/metamask-firefox-*.zip \ + --attach builds-mv2/metamask-firefox-*.zip \ --attach builds-flask/metamask-flask-chrome-*.zip \ - --attach builds-flask/metamask-flask-firefox-*.zip \ + --attach builds-flask-mv2/metamask-flask-firefox-*.zip \ --attach builds-mmi/metamask-mmi-chrome-*.zip \ --attach builds-mmi/metamask-mmi-firefox-*.zip \ --message "Version ${tag##v}" \ diff --git a/.storybook/3.COLORS.stories.mdx b/.storybook/3.COLORS.stories.mdx index 506983c34df5..04f3c980d38c 100644 --- a/.storybook/3.COLORS.stories.mdx +++ b/.storybook/3.COLORS.stories.mdx @@ -18,7 +18,7 @@ We follow a 3 tiered system for color design tokens and css variables.
@@ -36,9 +36,9 @@ These colors **SHOULD NOT** be used in your styles directly. They are used as a ```css /** !!!DO NOT USE BRAND COLORS DIRECTLY IN YOUR CODE!!! */ -var(--brand-colors-white-white000) -var(--brand-colors-white-white010) -var(--brand-colors-grey-grey030) +var(--brand-colors-white) +var(--brand-colors-black) +var(--brand-colors-grey-grey800) ``` ### **Theme colors** (tier 2) @@ -168,7 +168,7 @@ Don't use static hex values or brand color tokens in your code. * Not theme compatible and will break UI when using dark theme **/ .card { - background-color: var(--brand-colors-white-white000); + background-color: var(--brand-colors-white); color: var(--brand-colors-grey-grey800); } ``` diff --git a/.storybook/4.SHADOW.stories.mdx b/.storybook/4.SHADOW.stories.mdx index 3401b8b0d964..95ecac5c3f15 100644 --- a/.storybook/4.SHADOW.stories.mdx +++ b/.storybook/4.SHADOW.stories.mdx @@ -115,7 +115,7 @@ As well as the neutral colors for shadow 2 other colors exist that are used for style={{ height: 100, backgroundColor: 'var(--color-primary-default)', - boxShadow: 'var(--shadow-size-lg) var(--color-primary-shadow)', + boxShadow: 'var(--shadow-size-lg) var(--color-shadow-primary)', borderRadius: '4px', display: 'grid', alignContent: 'center', @@ -129,7 +129,7 @@ As well as the neutral colors for shadow 2 other colors exist that are used for style={{ height: 100, backgroundColor: 'var(--color-error-default)', - boxShadow: 'var(--shadow-size-lg) var(--color-error-shadow)', + boxShadow: 'var(--shadow-size-lg) var(--color-shadow-error)', borderRadius: '4px', display: 'grid', alignContent: 'center', @@ -144,8 +144,8 @@ As well as the neutral colors for shadow 2 other colors exist that are used for | Color | CSS | | ----------- | ----------------------------- | | **neutral** | `var(--color-shadow-default)` | -| **primary** | `var(--color-primary-shadow)` | -| **danger** | `var(--color-error-shadow)` | +| **primary** | `var(--color-shadow-primary)` | +| **danger** | `var(--color-shadow-error)` | ## Example usage @@ -232,7 +232,7 @@ Using both size and color tokens, different shadows can be applied to components justifyContent: 'center', height: 100, textAlign: 'center', - boxShadow: 'var(--shadow-size-sm) var(--color-primary-shadow)', + boxShadow: 'var(--shadow-size-sm) var(--color-shadow-primary)', backgroundColor: 'var(--color-primary-default)', color: 'var(--color-primary-inverse)', }} @@ -247,7 +247,7 @@ Using both size and color tokens, different shadows can be applied to components justifyContent: 'center', height: 100, textAlign: 'center', - boxShadow: 'var(--shadow-size-sm) var(--color-error-shadow)', + boxShadow: 'var(--shadow-size-sm) var(--color-shadow-error)', backgroundColor: 'var(--color-error-default)', color: 'var(--color-error-inverse)', }} @@ -263,8 +263,8 @@ Using both size and color tokens, different shadows can be applied to components | **Dropdown** | `box-shadow: var(--shadow-size-sm) var(--color-shadow-default);` | | **Toast** | `box-shadow: var(--shadow-size-md) var(--color-shadow-default);` | | **Modal** | `box-shadow: var(--shadow-size-lg) var(--color-shadow-default);` | -| **Button Primary Hover** | `box-shadow: var(--shadow-size-sm) var(--color-primary-shadow);` | -| **Button Danger Hover** | `box-shadow: var(--shadow-size-sm) var(--color-error-shadow);` | +| **Button Primary Hover** | `box-shadow: var(--shadow-size-sm) var(--color-shadow-primary);` | +| **Button Danger Hover** | `box-shadow: var(--shadow-size-sm) var(--color-shadow-error);` | ## Takeaways diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 8b44e3daa74d..44434c77b44a 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -314,6 +314,9 @@ "addingTokens": { "message": "Adding tokens" }, + "additionalNetworks": { + "message": "Additional networks" + }, "address": { "message": "Address" }, @@ -689,7 +692,7 @@ "message": "Blockaid" }, "blockaidAlertInfo": { - "message": "If you sign in, a third party known for scams might take all your assets. Please review the alerts before you proceed." + "message": "We don't recommend proceeding with this request." }, "blockaidDescriptionApproveFarming": { "message": "If you approve this request, a third party known for scams might take all your assets." diff --git a/app/images/bitcoin-logo.svg b/app/images/bitcoin-logo.svg new file mode 100644 index 000000000000..4306787921c7 --- /dev/null +++ b/app/images/bitcoin-logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/package.json b/package.json index 246bb72202ab..2d9836b3c06e 100644 --- a/package.json +++ b/package.json @@ -257,7 +257,7 @@ }, "dependencies": { "@babel/runtime": "patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch", - "@blockaid/ppom_release": "^1.4.6", + "@blockaid/ppom_release": "^1.4.7", "@contentful/rich-text-html-renderer": "^16.3.5", "@ensdomains/content-hash": "^2.5.7", "@ethereumjs/tx": "^4.1.1", @@ -290,7 +290,7 @@ "@metamask/browser-passworder": "^4.3.0", "@metamask/contract-metadata": "^2.5.0", "@metamask/controller-utils": "^10.0.0", - "@metamask/design-tokens": "^3.0.0", + "@metamask/design-tokens": "^4.0.0", "@metamask/ens-controller": "^10.0.1", "@metamask/eth-json-rpc-filters": "^7.0.0", "@metamask/eth-json-rpc-middleware": "^12.1.1", diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index 0e46bcf5046d..ed07ad3a4b58 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -793,6 +793,7 @@ export enum MetaMetricsNetworkEventSource { PopularNetworkList = 'popular_network_list', Dapp = 'dapp', DeprecatedNetworkModal = 'deprecated_network_modal', + NewAddNetworkFlow = 'new_add_network_flow', } export enum MetaMetricsSwapsEventSource { diff --git a/shared/constants/multichain/networks.ts b/shared/constants/multichain/networks.ts new file mode 100644 index 000000000000..92b9d7a89452 --- /dev/null +++ b/shared/constants/multichain/networks.ts @@ -0,0 +1,38 @@ +import { ProviderConfig } from '@metamask/network-controller'; +import { CaipChainId } from '@metamask/utils'; + +type ProviderConfigWithImageUrl = Omit & { + rpcPrefs?: { imageUrl?: string }; +}; + +export type MultichainProviderConfig = ProviderConfigWithImageUrl & { + chainId: CaipChainId; +}; + +export enum MultichainNetworks { + BITCOIN = 'bip122:000000000019d6689c085ae165831e93', + BITCOIN_TESTNET = 'bip122:000000000933ea01ad0ee984209779ba', +} + +export const BITCOIN_TOKEN_IMAGE_URL = './images/bitcoin-logo.svg'; + +export const MULTICHAIN_TOKEN_IMAGE_MAP = { + [MultichainNetworks.BITCOIN]: BITCOIN_TOKEN_IMAGE_URL, +} as const; + +export const MULTICHAIN_PROVIDER_CONFIGS: Record< + CaipChainId, + MultichainProviderConfig +> = { + [MultichainNetworks.BITCOIN]: { + chainId: MultichainNetworks.BITCOIN, + rpcUrl: '', // not used + ticker: 'BTC', + nickname: 'Bitcoin', + id: 'btc-mainnet', + type: 'rpc', + rpcPrefs: { + imageUrl: MULTICHAIN_TOKEN_IMAGE_MAP[MultichainNetworks.BITCOIN], + }, + }, +}; diff --git a/test/data/mock-accounts.ts b/test/data/mock-accounts.ts new file mode 100644 index 000000000000..ff6009ebd555 --- /dev/null +++ b/test/data/mock-accounts.ts @@ -0,0 +1,59 @@ +import { KeyringTypes } from '@metamask/keyring-controller'; +import { + InternalAccount, + EthAccountType, + BtcMethod, + BtcAccountType, +} from '@metamask/keyring-api'; +import { + ETH_EOA_METHODS, + ETH_4337_METHODS, +} from '../../shared/constants/eth-methods'; + +export const MOCK_ACCOUNT_EOA: InternalAccount = { + id: '4974fc00-c0fb-4a18-8535-8407ec6d1952', + address: '0x123', + options: {}, + methods: ETH_EOA_METHODS, + type: EthAccountType.Eoa, + metadata: { + name: 'Account 1', + keyring: { type: KeyringTypes.hd }, + importTime: 1691565967600, + lastSelected: 1691565967656, + }, +}; + +export const MOCK_ACCOUNT_ERC4337: InternalAccount = { + id: '4d5921f2-2022-44ce-a84f-9f6a0f142a5c', + address: '0x123', + options: {}, + methods: ETH_EOA_METHODS.concat(ETH_4337_METHODS), + type: EthAccountType.Erc4337, + metadata: { + name: 'Account 2', + keyring: { type: KeyringTypes.snap }, + importTime: 1691565967600, + lastSelected: 1691565967656, + }, +}; + +export const MOCK_ACCOUNT_BIP122_P2WPKH: InternalAccount = { + id: 'ae247df6-3911-47f7-9e36-28e6a7d96078', + address: 'bc1qaabb', + options: {}, + methods: [BtcMethod.SendMany], + type: BtcAccountType.P2wpkh, + metadata: { + name: 'Bitcoin Account', + keyring: { type: KeyringTypes.snap }, + importTime: 1691565967600, + lastSelected: 1955565967656, + }, +}; + +export const MOCK_ACCOUNTS = { + [MOCK_ACCOUNT_EOA.id]: MOCK_ACCOUNT_EOA, + [MOCK_ACCOUNT_ERC4337.id]: MOCK_ACCOUNT_ERC4337, + [MOCK_ACCOUNT_BIP122_P2WPKH.id]: MOCK_ACCOUNT_BIP122_P2WPKH, +}; diff --git a/test/e2e/mmi/pageObjects/mmi-main-page.ts b/test/e2e/mmi/pageObjects/mmi-main-page.ts index 6d3ab315488f..8d78ceafd39d 100644 --- a/test/e2e/mmi/pageObjects/mmi-main-page.ts +++ b/test/e2e/mmi/pageObjects/mmi-main-page.ts @@ -69,17 +69,16 @@ export class MMIMainPage { } async sendFunds(account: string, amount: string) { - await this.page - .getByTestId('recipient-group') - .locator(`text="${account}"`) - .click(); + await this.page.locator(`text="${account}"`).click(); await expect( this.page.locator('.ens-input__selected-input__title'), - ).toHaveText(`${account}`); - await this.page.locator('input.unit-input__input').type(`${amount}`); - await this.page.locator('text="Next"').click(); + ).toContainText(`${account}`); + await this.page + .locator('[data-testid="currency-input"]') + .first() + .type(`${amount}`); + await this.page.locator('text="Continue"').click(); await this.page.locator('text="Confirm"').click(); - await this.page.locator('text="Approve"').click(); } async mainPageScreenshot(screenshotName: string, accountName: string) { diff --git a/test/e2e/tests/swap-send/swap-send-test-utils.ts b/test/e2e/tests/swap-send/swap-send-test-utils.ts index 0cfff6449b7f..4140d6bb92be 100644 --- a/test/e2e/tests/swap-send/swap-send-test-utils.ts +++ b/test/e2e/tests/swap-send/swap-send-test-utils.ts @@ -106,13 +106,13 @@ export class SwapSendPage { // eslint-disable-next-line @typescript-eslint/no-explicit-any inputAmounts.map(async (e: any, index: number) => { await this.driver.delay(delayInMs); - const i = await e.nestedFindElement('input'); + const i = await this.driver.findNestedElement(e, 'input'); assert.ok(i); const v = await i.getProperty('value'); assert.equal(v, expectedInputValues[index]); - const isDisabled = !(await i.isEnabled()); if (index > 0) { - assert.ok(isDisabled); + const isDisabled = await i.getProperty('disabled'); + assert.equal(isDisabled, true); } }), ); diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 00ed3c0e74b2..6815460f3091 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -64,12 +64,6 @@ function wrapElementWithAPI(element, driver) { } }; - element.nestedFindElement = async (rawLocator) => { - const locator = driver.buildLocator(rawLocator); - const newElement = await element.findElement(locator); - return wrapElementWithAPI(newElement, driver); - }; - // We need to hold a pointer to the original click() method so that we can call it in the replaced click() method if (!element.originalClick) { element.originalClick = element.click; @@ -466,6 +460,20 @@ class Driver { return wrapElementWithAPI(element, this); } + /** + * Finds a nested element within a parent element using the given locator. + * This is useful when the parent element is already known and you want to find an element within it. + * + * @param {WebElement} element - Parent element + * @param {string | object} nestedLocator - Nested element locator + * @returns {Promise} A promise that resolves to the found nested element. + */ + async findNestedElement(element, nestedLocator) { + const locator = this.buildLocator(nestedLocator); + const nestedElement = await element.findElement(locator); + return wrapElementWithAPI(nestedElement, this); + } + /** * Finds a visible element on the page using the given locator. * diff --git a/ui/components/app/alert-system/alert-modal/alert-modal.tsx b/ui/components/app/alert-system/alert-modal/alert-modal.tsx index df6cb2b0e647..b53a49585649 100644 --- a/ui/components/app/alert-system/alert-modal/alert-modal.tsx +++ b/ui/components/app/alert-system/alert-modal/alert-modal.tsx @@ -137,7 +137,11 @@ function AlertHeader({ function BlockaidAlertDetails() { const t = useI18nContext(); - return {t('blockaidAlertInfo')}; + return ( + + {t('blockaidAlertInfo')} + + ); } function AlertDetails({ diff --git a/ui/components/app/alert-system/general-alert/general-alert.tsx b/ui/components/app/alert-system/general-alert/general-alert.tsx index 50a1975f861d..e446a5ba9dd2 100644 --- a/ui/components/app/alert-system/general-alert/general-alert.tsx +++ b/ui/components/app/alert-system/general-alert/general-alert.tsx @@ -11,6 +11,7 @@ import { DisclosureVariant } from '../../../ui/disclosure/disclosure.constants'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import { Display, + FontWeight, TextVariant, } from '../../../../helpers/constants/design-system'; import { SecurityProvider } from '../../../../../shared/constants/security-provider'; @@ -79,7 +80,12 @@ function AlertDetails({ {details.map((detail, index) => ( - {detail} + + {detail} + ))} diff --git a/ui/components/app/currency-input/currency-input.js b/ui/components/app/currency-input/currency-input.js index 187ca7fd060f..71425f797e9f 100644 --- a/ui/components/app/currency-input/currency-input.js +++ b/ui/components/app/currency-input/currency-input.js @@ -96,8 +96,7 @@ export default function CurrencyInput({ tokenToFiatConversionRate, ); - const isDisabled = !onChange; - + const isDisabled = onChange === undefined; const swap = async () => { await onPreferenceToggle(); }; diff --git a/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap b/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap index 0f6521e00795..faf8faf6dedf 100644 --- a/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap +++ b/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap @@ -63,7 +63,7 @@ exports[`IncomingTransactionToggle should render existing incoming transaction p style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" >
( diff --git a/ui/components/multichain/network-list-menu/network-confirmation-popover/__snapshots__/network-confirmation-popover.test.tsx.snap b/ui/components/multichain/network-list-menu/network-confirmation-popover/__snapshots__/network-confirmation-popover.test.tsx.snap new file mode 100644 index 000000000000..193bbae014a2 --- /dev/null +++ b/ui/components/multichain/network-list-menu/network-confirmation-popover/__snapshots__/network-confirmation-popover.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NetworkConfirmationPopover renders popular list component 1`] = `
`; diff --git a/ui/components/multichain/network-list-menu/network-confirmation-popover/network-confirmation-popover.test.tsx b/ui/components/multichain/network-list-menu/network-confirmation-popover/network-confirmation-popover.test.tsx new file mode 100644 index 000000000000..8efca30b61fc --- /dev/null +++ b/ui/components/multichain/network-list-menu/network-confirmation-popover/network-confirmation-popover.test.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import configureMockStore from 'redux-mock-store'; +import { ApprovalType } from '@metamask/controller-utils'; +import { renderWithProvider } from '../../../../../test/lib/render-helpers'; +import mockState from '../../../../../test/data/mock-state.json'; +import NetworkConfirmationPopover from './network-confirmation-popover'; + +describe('NetworkConfirmationPopover', () => { + const mockUnapprovedConfirmations = [ + { + id: '1', + origin: 'metamask', + type: ApprovalType.AddEthereumChain, + }, + ]; + + const STATE_MOCK = { + ...mockState, + unapprovedConfirmations: mockUnapprovedConfirmations, + }; + const mockStore = configureMockStore([])(STATE_MOCK); + + it('renders popular list component', () => { + const { container } = renderWithProvider( + , + mockStore, + ); + + expect(container).toMatchSnapshot(); + }); + + it('does not render the popover when there are no unapproved AddEthereumChain confirmations', () => { + const emptyStateMock = { + ...mockState, + unapprovedConfirmations: [], + }; + const emptyMockStore = configureMockStore([])(emptyStateMock); + + const { queryByText } = renderWithProvider( + , + emptyMockStore, + ); + expect(queryByText('ConfirmationPage content')).not.toBeInTheDocument(); + }); +}); diff --git a/ui/components/multichain/network-list-menu/network-confirmation-popover/network-confirmation-popover.tsx b/ui/components/multichain/network-list-menu/network-confirmation-popover/network-confirmation-popover.tsx new file mode 100644 index 000000000000..520394fb8e03 --- /dev/null +++ b/ui/components/multichain/network-list-menu/network-confirmation-popover/network-confirmation-popover.tsx @@ -0,0 +1,40 @@ +import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { ApprovalType } from '@metamask/controller-utils'; +import { ORIGIN_METAMASK } from '@metamask/approval-controller'; +import Popover from '../../../ui/popover'; +import ConfirmationPage from '../../../../pages/confirmations/confirmation/confirmation'; +import { getUnapprovedConfirmations } from '../../../../selectors'; + +const NetworkConfirmationPopover = () => { + const [showPopover, setShowPopover] = useState(false); + + const unapprovedConfirmations = useSelector(getUnapprovedConfirmations); + + useEffect(() => { + const anAddNetworkConfirmationFromMetaMaskExists = + unapprovedConfirmations?.find( + (confirmation) => + confirmation.origin === ORIGIN_METAMASK && + confirmation.type === ApprovalType.AddEthereumChain, + ); + + if (!showPopover && anAddNetworkConfirmationFromMetaMaskExists) { + setShowPopover(true); + } else if (showPopover && !anAddNetworkConfirmationFromMetaMaskExists) { + setShowPopover(false); + } + }, [unapprovedConfirmations, showPopover]); + + if (!showPopover) { + return null; + } + + return ( + + + + ); +}; + +export default NetworkConfirmationPopover; diff --git a/ui/components/multichain/network-list-menu/network-list-menu.js b/ui/components/multichain/network-list-menu/network-list-menu.js index 26e6e3d94007..da7f145e7389 100644 --- a/ui/components/multichain/network-list-menu/network-list-menu.js +++ b/ui/components/multichain/network-list-menu/network-list-menu.js @@ -16,7 +16,10 @@ import { updateNetworksList, setNetworkClientIdForDomain, } from '../../../store/actions'; -import { TEST_CHAINS } from '../../../../shared/constants/network'; +import { + FEATURED_RPCS, + TEST_CHAINS, +} from '../../../../shared/constants/network'; import { getCurrentChainId, getCurrentNetwork, @@ -28,6 +31,7 @@ import { getShowNetworkBanner, getOriginOfCurrentTab, getUseRequestQueue, + getNetworkConfigurations, } from '../../../selectors'; import ToggleButton from '../../ui/toggle-button'; import { @@ -49,9 +53,9 @@ import { Text, BannerBase, IconName, + ModalContent, + ModalHeader, } from '../../component-library'; -import { ModalContent } from '../../component-library/modal-content/deprecated'; -import { ModalHeader } from '../../component-library/modal-header/deprecated'; import { TextFieldSearch } from '../../component-library/text-field-search/deprecated'; import { ADD_POPULAR_CUSTOM_NETWORK } from '../../../helpers/constants/routes'; import { getEnvironmentType } from '../../../../app/scripts/lib/util'; @@ -65,6 +69,8 @@ import { getCompletedOnboarding, getIsUnlocked, } from '../../../ducks/metamask/metamask'; +import { getLocalNetworkMenuRedesignFeatureFlag } from '../../../helpers/utils/feature-flags'; +import PopularNetworkList from './popular-network-list/popular-network-list'; export const NetworkListMenu = ({ onClose }) => { const t = useI18nContext(); @@ -73,9 +79,13 @@ export const NetworkListMenu = ({ onClose }) => { const testNetworks = useSelector(getTestNetworks); const showTestNetworks = useSelector(getShowTestNetworks); const currentChainId = useSelector(getCurrentChainId); + const networkMenuRedesign = useSelector( + getLocalNetworkMenuRedesignFeatureFlag, + ); const selectedTabOrigin = useSelector(getOriginOfCurrentTab); const useRequestQueue = useSelector(getUseRequestQueue); + const networkConfigurations = useSelector(getNetworkConfigurations); const dispatch = useDispatch(); const history = useHistory(); @@ -95,6 +105,17 @@ export const NetworkListMenu = ({ onClose }) => { const orderedNetworksList = useSelector(getOrderedNetworksList); + const networkConfigurationChainIds = Object.values(networkConfigurations).map( + (net) => net.chainId, + ); + + const sortedFeaturedNetworks = FEATURED_RPCS.sort((a, b) => + a.nickname > b.nickname ? 1 : -1, + ).slice(0, FEATURED_RPCS.length); + + const notExistingNetworkConfigurations = sortedFeaturedNetworks.filter( + ({ chainId }) => !networkConfigurationChainIds.includes(chainId), + ); const newOrderNetworks = () => { if (!orderedNetworksList || orderedNetworksList.length === 0) { return nonTestNetworks; @@ -153,6 +174,12 @@ export const NetworkListMenu = ({ onClose }) => { let searchResults = [...networksList].length === items.length ? items : [...networksList]; + + const searchAddNetworkResults = + [...notExistingNetworkConfigurations].length === items.length + ? items + : [...notExistingNetworkConfigurations]; + const isSearching = searchQuery !== ''; if (isSearching) { @@ -173,6 +200,62 @@ export const NetworkListMenu = ({ onClose }) => { ); } + const generateNetworkListItem = ({ + network, + isCurrentNetwork, + canDeleteNetwork, + }) => { + return ( + { + dispatch(toggleNetworkMenu()); + if (network.providerType) { + dispatch(setProviderType(network.providerType)); + } else { + dispatch(setActiveNetwork(network.id)); + } + + // If presently on a dapp, communicate a change to + // the dapp via silent switchEthereumChain that the + // network has changed due to user action + if (useRequestQueue && selectedTabOrigin) { + setNetworkClientIdForDomain(selectedTabOrigin, network.id); + } + + trackEvent({ + event: MetaMetricsEventName.NavNetworkSwitched, + category: MetaMetricsEventCategory.Network, + properties: { + location: 'Network Menu', + chain_id: currentChainId, + from_network: currentChainId, + to_network: network.chainId, + }, + }); + }} + onDeleteClick={ + canDeleteNetwork + ? () => { + dispatch(toggleNetworkMenu()); + dispatch( + showModal({ + name: 'CONFIRM_DELETE_NETWORK', + target: network.id, + onConfirm: () => undefined, + }), + ); + } + : null + } + /> + ); + }; + const generateMenuItems = (desiredNetworks) => { return desiredNetworks.map((network) => { const isCurrentNetwork = @@ -182,47 +265,11 @@ export const NetworkListMenu = ({ onClose }) => { const canDeleteNetwork = isUnlocked && !isCurrentNetwork && network.removable; - return ( - { - dispatch(toggleNetworkMenu()); - if (network.providerType) { - dispatch(setProviderType(network.providerType)); - } else { - dispatch(setActiveNetwork(network.id)); - } - trackEvent({ - event: MetaMetricsEventName.NavNetworkSwitched, - category: MetaMetricsEventCategory.Network, - properties: { - location: 'Network Menu', - chain_id: currentChainId, - from_network: currentChainId, - to_network: network.chainId, - }, - }); - }} - onDeleteClick={ - canDeleteNetwork - ? () => { - dispatch(toggleNetworkMenu()); - dispatch( - showModal({ - name: 'CONFIRM_DELETE_NETWORK', - target: network.id, - onConfirm: () => undefined, - }), - ); - } - : null - } - /> - ); + return generateNetworkListItem({ + network, + isCurrentNetwork, + canDeleteNetwork, + }); }); }; @@ -328,6 +375,12 @@ export const NetworkListMenu = ({ onClose }) => { const canDeleteNetwork = isUnlocked && !isCurrentNetwork && network.removable; + const networkListItem = generateNetworkListItem({ + network, + isCurrentNetwork, + canDeleteNetwork, + }); + return ( { {...providedDrag.draggableProps} {...providedDrag.dragHandleProps} > - { - dispatch(toggleNetworkMenu()); - if (network.providerType) { - dispatch( - setProviderType(network.providerType), - ); - } else { - dispatch(setActiveNetwork(network.id)); - } - - // If presently on a dapp, communicate a change to - // the dapp via silent switchEthereumChain that the - // network has changed due to user action - if (useRequestQueue && selectedTabOrigin) { - setNetworkClientIdForDomain( - selectedTabOrigin, - network.id, - ); - } - - trackEvent({ - event: - MetaMetricsEventName.NavNetworkSwitched, - category: - MetaMetricsEventCategory.Network, - properties: { - location: 'Network Menu', - chain_id: currentChainId, - from_network: currentChainId, - to_network: network.chainId, - }, - }); - }} - onDeleteClick={ - canDeleteNetwork - ? () => { - dispatch(toggleNetworkMenu()); - dispatch( - showModal({ - name: 'CONFIRM_DELETE_NETWORK', - target: network.id, - onConfirm: () => undefined, - }), - ); - } - : null - } - /> + {networkListItem} )} @@ -405,24 +405,29 @@ export const NetworkListMenu = ({ onClose }) => { )} - - - {t('showTestnetNetworks')} - - - {showTestNetworks || currentlyOnTestNetwork ? ( - - {generateMenuItems(testNetworks)} + {networkMenuRedesign ? ( + + ) : null} + + {t('showTestnetNetworks')} + - ) : null} + {showTestNetworks || currentlyOnTestNetwork ? ( + + {generateMenuItems(testNetworks)} + + ) : null} + ({ setShowTestNetworks: () => mockSetShowTestNetworks, @@ -20,6 +21,11 @@ jest.mock('../../../store/actions.ts', () => ({ toggleNetworkMenu: () => mockToggleNetworkMenu, })); +jest.mock('../../../helpers/utils/feature-flags', () => ({ + ...jest.requireActual('../../../helpers/utils/feature-flags'), + getLocalNetworkMenuRedesignFeatureFlag: () => mockNetworkMenuRedesignToggle, +})); + const render = ({ showTestNetworks = false, currentChainId = '0x5', @@ -51,6 +57,10 @@ const render = ({ }; describe('NetworkListMenu', () => { + beforeEach(() => { + mockNetworkMenuRedesignToggle.mockReturnValue(false); + }); + it('displays important controls', () => { const { getByText, getByPlaceholderText } = render(); diff --git a/ui/components/multichain/network-list-menu/popular-network-list/PopularNetworkList.stories.js b/ui/components/multichain/network-list-menu/popular-network-list/PopularNetworkList.stories.js new file mode 100644 index 000000000000..320e951b0679 --- /dev/null +++ b/ui/components/multichain/network-list-menu/popular-network-list/PopularNetworkList.stories.js @@ -0,0 +1,85 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import testData from '../../../../../.storybook/test-data'; +import configureStore from '../../../../store/store'; +import { + CHAIN_IDS, + CURRENCY_SYMBOLS, + OPTIMISM_DISPLAY_NAME, + OPTIMISM_TOKEN_IMAGE_URL, + ZK_SYNC_ERA_DISPLAY_NAME, + ZK_SYNC_ERA_TOKEN_IMAGE_URL, +} from '../../../../../shared/constants/network'; +import PopularNetworkList from './popular-network-list'; + +const customNetworkStore = configureStore({ + ...testData, + metamask: { + ...testData.metamask, + preferences: { + showTestNetworks: true, + }, + orderedNetworkList: [], + networkConfigurations: { + ...testData.metamask.networkConfigurations, + ...{ + 'test-networkConfigurationId-1': { + chainId: CHAIN_IDS.OPTIMISM, + nickname: OPTIMISM_DISPLAY_NAME, + rpcUrl: `https://optimism-mainnet.infura.io/v3`, + ticker: CURRENCY_SYMBOLS.ETH, + rpcPrefs: { + blockExplorerUrl: 'https://optimistic.etherscan.io/', + imageUrl: OPTIMISM_TOKEN_IMAGE_URL, + }, + }, + 'test-networkConfigurationId-2': { + chainId: CHAIN_IDS.ZKSYNC_ERA, + nickname: ZK_SYNC_ERA_DISPLAY_NAME, + rpcUrl: `https://mainnet.era.zksync.io`, + ticker: CURRENCY_SYMBOLS.ETH, + rpcPrefs: { + blockExplorerUrl: 'https://explorer.zksync.io/', + imageUrl: ZK_SYNC_ERA_TOKEN_IMAGE_URL, + }, + }, + }, + }, + }, +}); + +export default { + title: 'Components/PopularNetworkList', + component: PopularNetworkList, +}; + +export const Default = (args) => ( + + + +); + +Default.args = { + searchAddNetworkResults: [ + { + nickname: OPTIMISM_DISPLAY_NAME, + rpcPrefs: { + imageUrl: OPTIMISM_TOKEN_IMAGE_URL, + }, + }, + { + nickname: ZK_SYNC_ERA_DISPLAY_NAME, + rpcPrefs: { + imageUrl: ZK_SYNC_ERA_TOKEN_IMAGE_URL, + }, + }, + ], +}; + +Default.decorators = [ + (Story) => ( + + + + ), +]; diff --git a/ui/components/multichain/network-list-menu/popular-network-list/__snapshots__/popular-network-list.test.tsx.snap b/ui/components/multichain/network-list-menu/popular-network-list/__snapshots__/popular-network-list.test.tsx.snap new file mode 100644 index 000000000000..41085f19a48c --- /dev/null +++ b/ui/components/multichain/network-list-menu/popular-network-list/__snapshots__/popular-network-list.test.tsx.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PopularNetworkList renders popular list component 1`] = ` +
+
+
+
+
+`; diff --git a/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.test.tsx b/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.test.tsx new file mode 100644 index 000000000000..f7354e0acefc --- /dev/null +++ b/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.test.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import { useDispatch, useSelector } from 'react-redux'; +import configureStore from 'redux-mock-store'; +import { renderWithProvider } from '../../../../../test/lib/render-helpers'; +import { getUnapprovedConfirmations } from '../../../../selectors'; +import { + CHAIN_IDS, + RPCDefinition, +} from '../../../../../shared/constants/network'; +import PopularNetworkList from './popular-network-list'; + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: jest.fn(), + useSelector: jest.fn(), +})); + +const STATE_MOCK = { + metamask: { + providerConfig: { + chainId: '0x1', + }, + }, +}; + +describe('PopularNetworkList', () => { + const store = configureStore()(STATE_MOCK); + const useDispatchMock = useDispatch as jest.Mock; + const useSelectorMock = useSelector as jest.Mock; + const mockDispatch = jest.fn(); + + beforeEach(() => { + jest.resetAllMocks(); + useDispatchMock.mockReturnValue(mockDispatch); + + useSelectorMock.mockImplementation((selector) => { + if (selector === getUnapprovedConfirmations) { + return []; + } + return undefined; + }); + }); + + const defaultProps = { + searchAddNetworkResults: [], + }; + + it('renders popular list component', () => { + const { container } = renderWithProvider( + , + store, + ); + + expect(container).toMatchSnapshot(); + }); + + it('displays the network list when networks are provided', () => { + const props = { + ...defaultProps, + searchAddNetworkResults: [ + { + chainId: CHAIN_IDS.MAINNET, + nickname: 'Network 1', + rpcPrefs: { + blockExplorerUrl: 'https://etherscan.com/', + imageUrl: 'https://example.com/image1.png', + }, + ticker: 'ETH', + rpcUrl: 'https://exampleEth.org/', + }, + { + chainId: CHAIN_IDS.BSC_TESTNET, + nickname: 'Network 2', + rpcPrefs: { + blockExplorerUrl: 'https://examplescan.com/', + imageUrl: 'https://example.com/image2.png', + }, + ticker: 'TST', + rpcUrl: 'https://example.org/', + }, + ] as RPCDefinition[], + }; + + render(); + expect(screen.getByText('Network 1')).toBeInTheDocument(); + expect(screen.getByText('Network 2')).toBeInTheDocument(); + }); + it('calls the dispatch function when the add button is clicked', async () => { + const props = { + ...defaultProps, + searchAddNetworkResults: [ + { + chainId: CHAIN_IDS.BSC_TESTNET, + nickname: 'Network 2', + rpcPrefs: { + blockExplorerUrl: 'https://examplescan.com/', + imageUrl: 'https://example.com/image2.png', + }, + ticker: 'TST', + rpcUrl: 'https://example.org/', + }, + ] as RPCDefinition[], + }; + + render(); + const addButton = screen.getByTestId('test-add-button'); + fireEvent.click(addButton); + + expect(useDispatchMock).toHaveBeenCalled(); + }); +}); diff --git a/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.tsx b/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.tsx new file mode 100644 index 000000000000..6d2cd02f5170 --- /dev/null +++ b/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.tsx @@ -0,0 +1,125 @@ +import React from 'react'; +import { ApprovalType } from '@metamask/controller-utils'; +import { useDispatch } from 'react-redux'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { + Box, + Text, + AvatarNetwork, + Button, + AvatarNetworkSize, + ButtonVariant, +} from '../../../component-library'; +import { MetaMetricsNetworkEventSource } from '../../../../../shared/constants/metametrics'; +import { + ENVIRONMENT_TYPE_POPUP, + ORIGIN_METAMASK, +} from '../../../../../shared/constants/app'; +import { + requestUserApproval, + toggleNetworkMenu, +} from '../../../../store/actions'; +import { getEnvironmentType } from '../../../../../app/scripts/lib/util'; +import { + AlignItems, + BackgroundColor, + Display, + JustifyContent, + TextColor, +} from '../../../../helpers/constants/design-system'; +import { RPCDefinition } from '../../../../../shared/constants/network'; + +const PopularNetworkList = ({ + searchAddNetworkResults, +}: { + searchAddNetworkResults: RPCDefinition[]; +}) => { + const t = useI18nContext(); + const isPopUp = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP; + const dispatch = useDispatch(); + + return ( + + + {Object.keys(searchAddNetworkResults).length === 0 ? null : ( + + {t('additionalNetworks')} + + )} + + {searchAddNetworkResults.map((item: RPCDefinition, index: number) => ( + + + + + + {item.nickname} + + + + + + + + ))} + + + ); +}; + +export default PopularNetworkList; diff --git a/ui/components/multichain/ramps-card/index.scss b/ui/components/multichain/ramps-card/index.scss index a394311eeda9..d46a317e9357 100644 --- a/ui/components/multichain/ramps-card/index.scss +++ b/ui/components/multichain/ramps-card/index.scss @@ -4,15 +4,15 @@ &__cta-button { width: fit-content; color: var(--brand-colors-grey-grey800); - background-color: var(--brand-colors-white-white000); + background-color: var(--brand-colors-white); } &__title { - color: var(--brand-colors-white-white000); + color: var(--brand-colors-white); } &__body { - color: var(--brand-colors-white-white000); + color: var(--brand-colors-white); width: 80%; } } diff --git a/ui/components/ui/button/buttons.scss b/ui/components/ui/button/buttons.scss index 6c6fe36bd8e7..8594cb04f4da 100644 --- a/ui/components/ui/button/buttons.scss +++ b/ui/components/ui/button/buttons.scss @@ -196,7 +196,7 @@ input[type="submit"][disabled] { border: 1px solid var(--color-primary-default); &:hover { - box-shadow: var(--shadow-size-sm) var(--color-primary-shadow); + box-shadow: var(--shadow-size-sm) var(--color-shadow-primary); } &:active { @@ -220,7 +220,7 @@ input[type="submit"][disabled] { border: 1px solid var(--color-error-default); &:hover { - box-shadow: var(--shadow-size-sm) var(--color-error-shadow); + box-shadow: var(--shadow-size-sm) var(--color-shadow-error); } &:active { @@ -250,7 +250,7 @@ input[type="submit"][disabled] { background-color: var(--color-primary-default); &:hover { - box-shadow: var(--shadow-size-sm) var(--color-primary-shadow); + box-shadow: var(--shadow-size-sm) var(--color-shadow-primary); } &:active { @@ -262,7 +262,7 @@ input[type="submit"][disabled] { background-color: var(--color-error-default); &:hover { - box-shadow: var(--shadow-size-sm) var(--color-error-shadow); + box-shadow: var(--shadow-size-sm) var(--color-shadow-error); } &:active { diff --git a/ui/css/base-styles.scss b/ui/css/base-styles.scss index 048a0d69040b..a98e965e74b8 100644 --- a/ui/css/base-styles.scss +++ b/ui/css/base-styles.scss @@ -31,7 +31,7 @@ html { */ @media (prefers-color-scheme: dark) { &:not([data-theme]) { - color: var(--brand-colors-white-white000); + color: var(--brand-colors-white); background-color: var(--brand-colors-grey-grey900); } diff --git a/ui/css/utilities/colors.scss b/ui/css/utilities/colors.scss index facb925e431c..634a484d736a 100644 --- a/ui/css/utilities/colors.scss +++ b/ui/css/utilities/colors.scss @@ -9,13 +9,21 @@ Before adding a color here make sure that there isn't a design token available. --mainnet: #29b6af; --inherit: inherit; --transparent: transparent; + // DO NOT CHANGE + // Required for the QR reader to work properly + --qr-code-white-background: #fff; + // DEPRECATED + // These third party network colors have been deprecated and should be removed once they are no longer in use. We should be using images to represent these networks instead. + --color-network-goerli-default: #1098fc; + --color-network-sepolia-default: #cfb5f0; + --color-network-goerli-inverse: #fcfcfc; + --color-network-sepolia-inverse: #fcfcfc; + --color-network-localhost-default: #bbc0c5; + --color-network-localhost-inverse: #fcfcfc; --color-network-linea-goerli-default: #61dfff; --color-network-linea-goerli-inverse: #fcfcfc; --color-network-linea-sepolia-default: #61dfff; --color-network-linea-sepolia-inverse: #fcfcfc; --color-network-linea-mainnet-default: #121212; --color-network-linea-mainnet-inverse: #fcfcfc; - // DO NOT CHANGE - // Required for the QR reader to work properly - --qr-code-white-background: #fff; } diff --git a/ui/hooks/useAlerts.ts b/ui/hooks/useAlerts.ts index ba10a371f2b6..92440822bbdd 100644 --- a/ui/hooks/useAlerts.ts +++ b/ui/hooks/useAlerts.ts @@ -62,7 +62,7 @@ const useAlerts = (ownerId: string) => { !isAlertConfirmed(alert.key) && alert.severity === Severity.Danger, ); const hasAlerts = alerts.length > 0; - const hasDangerAlerts = alerts.some( + const dangerAlerts = alerts.filter( (alert) => alert.severity === Severity.Danger, ); const hasUnconfirmedDangerAlerts = unconfirmedDangerAlerts.length > 0; @@ -73,7 +73,8 @@ const useAlerts = (ownerId: string) => { generalAlerts, getFieldAlerts, hasAlerts, - hasDangerAlerts, + dangerAlerts, + hasDangerAlerts: dangerAlerts?.length > 0, hasUnconfirmedDangerAlerts, isAlertConfirmed, setAlertConfirmed, diff --git a/ui/pages/confirmations/components/confirm/footer/footer.test.tsx b/ui/pages/confirmations/components/confirm/footer/footer.test.tsx index c0216d45e66d..1645a7684d09 100644 --- a/ui/pages/confirmations/components/confirm/footer/footer.test.tsx +++ b/ui/pages/confirmations/components/confirm/footer/footer.test.tsx @@ -202,7 +202,7 @@ describe('ConfirmFooter', () => { }; it('renders the review alerts button when there are unconfirmed alerts', () => { const { getByText } = render(stateWithAlertsMock); - expect(getByText('Review alerts')).toBeInTheDocument(); + expect(getByText('Confirm')).toBeInTheDocument(); }); it('renders the confirm button when there are no unconfirmed alerts', () => { diff --git a/ui/pages/confirmations/components/confirm/footer/footer.tsx b/ui/pages/confirmations/components/confirm/footer/footer.tsx index ee4a58634a92..b47c6fcacff1 100644 --- a/ui/pages/confirmations/components/confirm/footer/footer.tsx +++ b/ui/pages/confirmations/components/confirm/footer/footer.tsx @@ -43,7 +43,7 @@ const ConfirmButton = ({ const [confirmModalVisible, setConfirmModalVisible] = useState(false); - const { alerts, hasDangerAlerts, hasUnconfirmedDangerAlerts } = + const { alerts, dangerAlerts, hasDangerAlerts, hasUnconfirmedDangerAlerts } = useAlerts(alertOwnerId); const handleCloseConfirmModal = useCallback(() => { @@ -76,7 +76,7 @@ const ConfirmButton = ({ size={ButtonSize.Lg} disabled={hasUnconfirmedDangerAlerts ? false : disabled} > - {hasUnconfirmedDangerAlerts ? t('reviewAlerts') : t('confirm')} + {dangerAlerts?.length > 1 ? t('reviewAlerts') : t('confirm')} ); diff --git a/ui/pages/confirmations/components/confirm/pluggable-section/index.ts b/ui/pages/confirmations/components/confirm/pluggable-section/index.ts new file mode 100644 index 000000000000..a99bc86297c4 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/pluggable-section/index.ts @@ -0,0 +1 @@ +export { default as PluggableSection } from './pluggable-section'; diff --git a/ui/pages/confirmations/components/confirm/pluggable-section/pluggable-section.test.tsx b/ui/pages/confirmations/components/confirm/pluggable-section/pluggable-section.test.tsx new file mode 100644 index 000000000000..085e8392c2b1 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/pluggable-section/pluggable-section.test.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import mockState from '../../../../../../test/data/mock-state.json'; +import { renderWithProvider } from '../../../../../../test/jest'; +import { unapprovedPersonalSignMsg } from '../../../../../../test/data/confirmations/personal_sign'; +import configureStore from '../../../../../store/store'; +import PluggableSection from './pluggable-section'; + +const render = () => { + const store = configureStore({ + metamask: { + ...mockState.metamask, + }, + confirm: { + currentConfirmation: unapprovedPersonalSignMsg, + }, + }); + + return renderWithProvider(, store); +}; + +describe('PluggableSection', () => { + it('should render correctly', () => { + expect(() => render()).not.toThrow(); + }); +}); diff --git a/ui/pages/confirmations/components/confirm/pluggable-section/pluggable-section.tsx b/ui/pages/confirmations/components/confirm/pluggable-section/pluggable-section.tsx new file mode 100644 index 000000000000..c167cdb83c0f --- /dev/null +++ b/ui/pages/confirmations/components/confirm/pluggable-section/pluggable-section.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { ReactComponentLike } from 'prop-types'; +import { useSelector } from 'react-redux'; + +import { currentConfirmationSelector } from '../../../selectors'; + +// Components to be plugged into confirmation page can be added to the array below +const pluggedInSections: ReactComponentLike[] = []; + +const PluggableSection = () => { + const currentConfirmation = useSelector(currentConfirmationSelector); + + return ( + <> + {pluggedInSections.map((Section, index) => ( +
+ ))} + + ); +}; + +export default PluggableSection; diff --git a/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap b/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap index 26758de34d17..4111226eea01 100644 --- a/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap +++ b/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap @@ -54,7 +54,7 @@ exports[`Blockaid Banner Alert should render 'danger' UI when securityAlertRespo Something doesn't look right? @@ -151,7 +151,7 @@ exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResp Something doesn't look right? @@ -248,7 +248,7 @@ exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResp Something doesn't look right? @@ -346,7 +346,7 @@ exports[`Blockaid Banner Alert should render details section even when features Something doesn't look right? @@ -457,7 +457,7 @@ exports[`Blockaid Banner Alert should render details when provided 1`] = ` Something doesn't look right? @@ -556,7 +556,7 @@ exports[`Blockaid Banner Alert should render link to report url 1`] = ` Something doesn't look right? diff --git a/ui/pages/confirmations/confirm/confirm.stories.tsx b/ui/pages/confirmations/confirm/confirm.stories.tsx index 7062fb49201f..3e428eba997f 100644 --- a/ui/pages/confirmations/confirm/confirm.stories.tsx +++ b/ui/pages/confirmations/confirm/confirm.stories.tsx @@ -1,27 +1,96 @@ import React from 'react'; import { Provider } from 'react-redux'; -import configureStore from '../../../store/store'; +import { cloneDeep } from 'lodash'; +import { unapprovedPersonalSignMsg, signatureRequestSIWE } from '../../../../test/data/confirmations/personal_sign'; +import { unapprovedTypedSignMsgV1, unapprovedTypedSignMsgV4, permitSignatureMsg } from '../../../../test/data/confirmations/typed_sign'; import mockState from '../../../../test/data/mock-state.json'; +import configureStore from '../../../store/store'; import ConfirmPage from './confirm'; -const store = configureStore({ - confirm: { - currentConfirmation: { - type: 'personal_sign', - }, - }, - metamask: { - ...mockState.metamask, - }, -}); - +/** + * @note When we extend this storybook page to support more confirmation types, + * consider creating a new storybook pages. + */ const ConfirmPageStory = { title: 'Pages/Confirm/ConfirmPage', - decorators: [(story) => {story()}], + decorators: [(story) =>
{story()}
], +} + +const ARGS_SIGNATURE = { + msgParams: { ...unapprovedPersonalSignMsg.msgParams }, +} + +const ARG_TYPES_SIGNATURE = { + msgParams: { + control: 'object', + description: '(non-param) overrides currentConfirmation.msgParams', + }, +} + +function SignatureStoryTemplate(args, confirmation) { + const mockConfirmation = cloneDeep(confirmation); + mockConfirmation.msgParams = args.msgParams; + + const store = configureStore({ + confirm: { + currentConfirmation: mockConfirmation, + }, + metamask: { ...mockState.metamask }, + }); + + return ; +} + +export const PersonalSignStory = (args) => { + return SignatureStoryTemplate(args, unapprovedPersonalSignMsg); }; -export const DefaultStory = (args) => ; +PersonalSignStory.storyName = 'Personal Sign'; +PersonalSignStory.argTypes = ARG_TYPES_SIGNATURE; +PersonalSignStory.args = ARGS_SIGNATURE; -DefaultStory.storyName = 'Default'; +export const SignInWithEthereumSIWEStory = (args) => { + return SignatureStoryTemplate(args, signatureRequestSIWE); +}; + +SignInWithEthereumSIWEStory.storyName = 'Sign-in With Ethereum (SIWE)'; +SignInWithEthereumSIWEStory.argTypes = ARG_TYPES_SIGNATURE; +SignInWithEthereumSIWEStory.args = { + ...ARGS_SIGNATURE, + msgParams: signatureRequestSIWE.msgParams, +}; + +export const SignTypedDataStory = (args) => { + return SignatureStoryTemplate(args, unapprovedTypedSignMsgV1); +}; + +SignTypedDataStory.storyName = 'SignTypedData'; +SignTypedDataStory.argTypes = ARG_TYPES_SIGNATURE; +SignTypedDataStory.args = { + ...ARGS_SIGNATURE, + msgParams: unapprovedTypedSignMsgV1.msgParams, +}; + +export const PermitStory = (args) => { + return SignatureStoryTemplate(args, permitSignatureMsg); +}; + +PermitStory.storyName = 'SignTypedData Permit'; +PermitStory.argTypes = ARG_TYPES_SIGNATURE; +PermitStory.args = { + ...ARGS_SIGNATURE, + msgParams: permitSignatureMsg.msgParams, +}; + +export const SignTypedDataV4Story = (args) => { + return SignatureStoryTemplate(args, unapprovedTypedSignMsgV4); +}; + +SignTypedDataV4Story.storyName = 'SignTypedData V4'; +SignTypedDataV4Story.argTypes = ARG_TYPES_SIGNATURE; +SignTypedDataV4Story.args = { + ...ARGS_SIGNATURE, + msgParams: unapprovedTypedSignMsgV4.msgParams, +}; export default ConfirmPageStory; diff --git a/ui/pages/confirmations/confirm/confirm.tsx b/ui/pages/confirmations/confirm/confirm.tsx index 8dd75a692def..505cda5e3042 100644 --- a/ui/pages/confirmations/confirm/confirm.tsx +++ b/ui/pages/confirmations/confirm/confirm.tsx @@ -1,21 +1,22 @@ import React from 'react'; import { AlertActionHandlerProvider } from '../../../components/app/alert-system/contexts/alertActionHandler'; -import ScrollToBottom from '../components/confirm/scroll-to-bottom'; -import { Footer } from '../components/confirm/footer'; -import { Header } from '../components/confirm/header'; -import { Info } from '../components/confirm/info'; +import { Page } from '../../../components/multichain/pages/page'; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) import { MMISignatureMismatchBanner } from '../../../components/app/mmi-signature-mismatch-banner'; ///: END:ONLY_INCLUDE_IF -import { Nav } from '../components/confirm/nav'; -import { Title } from '../components/confirm/title'; -import { Page } from '../../../components/multichain/pages/page'; +import ScrollToBottom from '../components/confirm/scroll-to-bottom'; import setCurrentConfirmation from '../hooks/setCurrentConfirmation'; import syncConfirmPath from '../hooks/syncConfirmPath'; -import { LedgerInfo } from '../components/confirm/ledger-info'; import setConfirmationAlerts from '../hooks/setConfirmationAlerts'; import useConfirmationAlertActions from '../hooks/useConfirmationAlertActions'; +import { Footer } from '../components/confirm/footer'; +import { Header } from '../components/confirm/header'; +import { Info } from '../components/confirm/info'; +import { LedgerInfo } from '../components/confirm/ledger-info'; +import { Nav } from '../components/confirm/nav'; +import { Title } from '../components/confirm/title'; +import { PluggableSection } from '../components/confirm/pluggable-section'; const Confirm = () => { setCurrentConfirmation(); @@ -37,6 +38,7 @@ const Confirm = () => { <Info /> + <PluggableSection /> </ScrollToBottom> <Footer /> </Page> diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index e93b417c83fd..f33aed54aa12 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -129,6 +129,7 @@ import { getURLHost } from '../../helpers/utils/util'; import { BorderColor, IconColor } from '../../helpers/constants/design-system'; import { MILLISECOND } from '../../../shared/constants/time'; import { MultichainMetaFoxLogo } from '../../components/multichain/app-header/multichain-meta-fox-logo'; +import NetworkConfirmationPopover from '../../components/multichain/network-list-menu/network-confirmation-popover/network-confirmation-popover'; const isConfirmTransactionRoute = (pathname) => Boolean( @@ -199,6 +200,7 @@ export default class Routes extends Component { currentExtensionPopupId: PropTypes.number, useRequestQueue: PropTypes.bool, showSurveyToast: PropTypes.bool.isRequired, + networkMenuRedesign: PropTypes.bool.isRequired, showPrivacyPolicyToast: PropTypes.bool.isRequired, newPrivacyPolicyToastShownDate: PropTypes.number, setSurveyLinkLastClickedOrClosed: PropTypes.func.isRequired, @@ -782,6 +784,7 @@ export default class Routes extends Component { hideDeprecatedNetworkModal, switchedNetworkDetails, clearSwitchedNetworkDetails, + networkMenuRedesign, ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) isShowKeyringSnapRemovalResultModal, hideShowKeyringSnapRemovalResultModal, @@ -864,6 +867,7 @@ export default class Routes extends Component { {isNetworkMenuOpen ? ( <NetworkListMenu onClose={() => toggleNetworkMenu()} /> ) : null} + {networkMenuRedesign ? <NetworkConfirmationPopover /> : null} {accountDetailsAddress ? ( <AccountDetails address={accountDetailsAddress} /> ) : null} diff --git a/ui/pages/routes/routes.component.test.js b/ui/pages/routes/routes.component.test.js index 79941ea87fd3..e6274bfc3223 100644 --- a/ui/pages/routes/routes.component.test.js +++ b/ui/pages/routes/routes.component.test.js @@ -63,6 +63,11 @@ jest.mock( '../../components/app/metamask-template-renderer/safe-component-list', ); +jest.mock('../../helpers/utils/feature-flags', () => ({ + ...jest.requireActual('../../helpers/utils/feature-flags'), + getLocalNetworkMenuRedesignFeatureFlag: () => false, +})); + const render = async (route, state) => { const store = configureMockStore()({ ...mockSendState, diff --git a/ui/pages/routes/routes.container.js b/ui/pages/routes/routes.container.js index da708e147bcc..efa7afad4908 100644 --- a/ui/pages/routes/routes.container.js +++ b/ui/pages/routes/routes.container.js @@ -27,6 +27,7 @@ import { getShowPrivacyPolicyToast, getUseRequestQueue, } from '../../selectors'; +import { getLocalNetworkMenuRedesignFeatureFlag } from '../../helpers/utils/feature-flags'; import { getSmartTransactionsOptInStatus } from '../../../shared/modules/selectors'; import { lockMetamask, @@ -132,6 +133,7 @@ function mapStateToProps(state) { newPrivacyPolicyToastShownDate: getNewPrivacyPolicyToastShownDate(state), showPrivacyPolicyToast: getShowPrivacyPolicyToast(state), showSurveyToast: getShowSurveyToast(state), + networkMenuRedesign: getLocalNetworkMenuRedesignFeatureFlag(state), ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) isShowKeyringSnapRemovalResultModal: state.appState.showKeyringRemovalSnapModal, diff --git a/ui/pages/settings/advanced-tab/__snapshots__/advanced-tab.component.test.js.snap b/ui/pages/settings/advanced-tab/__snapshots__/advanced-tab.component.test.js.snap index 39f2a38c9f88..72c46529b4db 100644 --- a/ui/pages/settings/advanced-tab/__snapshots__/advanced-tab.component.test.js.snap +++ b/ui/pages/settings/advanced-tab/__snapshots__/advanced-tab.component.test.js.snap @@ -105,7 +105,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -172,7 +172,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -238,7 +238,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -304,7 +304,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(16, 152, 252);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(67, 174, 252);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;" @@ -370,7 +370,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -436,7 +436,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -622,7 +622,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -688,7 +688,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" diff --git a/ui/pages/settings/developer-options-tab/__snapshots__/developer-options-tab.test.tsx.snap b/ui/pages/settings/developer-options-tab/__snapshots__/developer-options-tab.test.tsx.snap index ad9599831cd8..ff16d1cf726d 100644 --- a/ui/pages/settings/developer-options-tab/__snapshots__/developer-options-tab.test.tsx.snap +++ b/ui/pages/settings/developer-options-tab/__snapshots__/developer-options-tab.test.tsx.snap @@ -127,7 +127,7 @@ exports[`Develop options tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(16, 152, 252);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(67, 174, 252);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;" @@ -197,7 +197,7 @@ exports[`Develop options tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" diff --git a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap index ab4fc6467faa..d2d94434223a 100644 --- a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap +++ b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap @@ -44,7 +44,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -155,7 +155,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -248,7 +248,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -327,7 +327,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(16, 152, 252);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(67, 174, 252);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;" @@ -404,7 +404,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(16, 152, 252);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(67, 174, 252);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;" @@ -507,7 +507,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(16, 152, 252);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(67, 174, 252);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;" @@ -607,7 +607,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(16, 152, 252);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(67, 174, 252);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;" @@ -694,7 +694,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -777,7 +777,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -860,7 +860,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(16, 152, 252);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(67, 174, 252);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;" @@ -947,7 +947,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -1026,7 +1026,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -1152,7 +1152,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -1223,7 +1223,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(16, 152, 252);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(67, 174, 252);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;" @@ -1337,7 +1337,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -1427,7 +1427,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(16, 152, 252);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(67, 174, 252);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;" @@ -1493,7 +1493,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" @@ -1560,7 +1560,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(16, 152, 252);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(67, 174, 252);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;" @@ -1626,7 +1626,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(16, 152, 252);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(67, 174, 252);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;" @@ -1707,7 +1707,7 @@ exports[`Security Tab should match snapshot 1`] = ` style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" > <div - style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(159, 166, 174);" + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(132, 140, 150);" > <div style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" diff --git a/ui/pages/unlock-page/index.scss b/ui/pages/unlock-page/index.scss index 44081cc81b05..abb1f837bdef 100644 --- a/ui/pages/unlock-page/index.scss +++ b/ui/pages/unlock-page/index.scss @@ -23,9 +23,8 @@ position: relative; &__beta { - /* these colors should be used on both light and dark mode */ - background: var(--brand-colors-blue-blue500); - color: var(--brand-colors-white-white000); + background: var(--color-primary-default); + color: var(--color-primary-inverse); padding: 3px 6px; font-size: 16px; position: absolute; diff --git a/ui/selectors/accounts.test.ts b/ui/selectors/accounts.test.ts index bf9e9038b655..8f6bc4e14b17 100644 --- a/ui/selectors/accounts.test.ts +++ b/ui/selectors/accounts.test.ts @@ -1,67 +1,16 @@ -import { KeyringTypes } from '@metamask/keyring-controller'; import { - InternalAccount, - EthAccountType, - BtcMethod, - BtcAccountType, -} from '@metamask/keyring-api'; -import { - ETH_EOA_METHODS, - ETH_4337_METHODS, -} from '../../shared/constants/eth-methods'; + MOCK_ACCOUNTS, + MOCK_ACCOUNT_EOA, + MOCK_ACCOUNT_ERC4337, + MOCK_ACCOUNT_BIP122_P2WPKH, +} from '../../test/data/mock-accounts'; import { AccountsState, isSelectedInternalAccountEth } from './accounts'; -const MOCK_ACCOUNT_EOA: InternalAccount = { - id: '4974fc00-c0fb-4a18-8535-8407ec6d1952', - address: '0x123', - options: {}, - methods: ETH_EOA_METHODS, - type: EthAccountType.Eoa, - metadata: { - name: 'Account 1', - keyring: { type: KeyringTypes.hd }, - importTime: 1691565967600, - lastSelected: 1691565967656, - }, -}; - -const MOCK_ACCOUNT_ERC4337: InternalAccount = { - id: '4d5921f2-2022-44ce-a84f-9f6a0f142a5c', - address: '0x123', - options: {}, - methods: ETH_EOA_METHODS.concat(ETH_4337_METHODS), - type: EthAccountType.Erc4337, - metadata: { - name: 'Account 2', - keyring: { type: KeyringTypes.snap }, - importTime: 1691565967600, - lastSelected: 1691565967656, - }, -}; - -const MOCK_ACCOUNT_BIP122_P2WPKH: InternalAccount = { - id: 'ae247df6-3911-47f7-9e36-28e6a7d96078', - address: 'bc1qaabb', - options: {}, - methods: [BtcMethod.SendMany], - type: BtcAccountType.P2wpkh, - metadata: { - name: 'Bitcoin Account', - keyring: { type: KeyringTypes.snap }, - importTime: 1691565967600, - lastSelected: 1955565967656, - }, -}; - const MOCK_STATE: AccountsState = { metamask: { internalAccounts: { selectedAccount: MOCK_ACCOUNT_EOA.id, - accounts: { - [MOCK_ACCOUNT_EOA.id]: MOCK_ACCOUNT_EOA, - [MOCK_ACCOUNT_ERC4337.id]: MOCK_ACCOUNT_ERC4337, - [MOCK_ACCOUNT_BIP122_P2WPKH.id]: MOCK_ACCOUNT_BIP122_P2WPKH, - }, + accounts: MOCK_ACCOUNTS, }, }, }; diff --git a/ui/selectors/multichain.test.ts b/ui/selectors/multichain.test.ts new file mode 100644 index 000000000000..582ba681403a --- /dev/null +++ b/ui/selectors/multichain.test.ts @@ -0,0 +1,212 @@ +import { + getNativeCurrency, + getProviderConfig, +} from '../ducks/metamask/metamask'; +import { + MULTICHAIN_PROVIDER_CONFIGS, + MultichainNetworks, + MultichainProviderConfig, +} from '../../shared/constants/multichain/networks'; +import { + MOCK_ACCOUNTS, + MOCK_ACCOUNT_EOA, + MOCK_ACCOUNT_BIP122_P2WPKH, +} from '../../test/data/mock-accounts'; +import { AccountsState } from './accounts'; +import { + getMultichainCurrentCurrency, + getMultichainDefaultToken, + getMultichainIsEvm, + getMultichainNativeCurrency, + getMultichainNetwork, + getMultichainNetworkProviders, + getMultichainProviderConfig, + getMultichainShouldShowFiat, +} from './multichain'; +import { getCurrentCurrency, getShouldShowFiat } from '.'; + +type TestState = AccountsState & { + metamask: { + preferences: { showFiatInTestnets: boolean }; + providerConfig: { ticker: string; chainId: string }; + currentCurrency: string; + currencyRates: Record<string, { conversionRate: string }>; + }; +}; + +const MOCK_EVM_STATE: TestState = { + metamask: { + preferences: { + showFiatInTestnets: false, + }, + providerConfig: { + ticker: 'ETH', + chainId: '0x1', + }, + currentCurrency: 'ETH', + currencyRates: { + ETH: { + conversionRate: 'usd', + }, + }, + internalAccounts: { + selectedAccount: MOCK_ACCOUNT_EOA.id, + accounts: MOCK_ACCOUNTS, + }, + }, +}; + +const MOCK_NON_EVM_STATE: AccountsState = { + metamask: { + ...MOCK_EVM_STATE.metamask, + internalAccounts: { + selectedAccount: MOCK_ACCOUNT_BIP122_P2WPKH.id, + accounts: MOCK_ACCOUNTS, + }, + }, +}; + +function getBip122ProviderConfig(): MultichainProviderConfig { + // For now, we only have Bitcoin non-EVM network, so we are expecting to have + // this one with `bip122:*` account type + return MULTICHAIN_PROVIDER_CONFIGS[MultichainNetworks.BITCOIN]; +} + +describe('Multichain Selectors', () => { + describe('getMultichainNetworkProviders', () => { + it('has some providers', () => { + const state = MOCK_EVM_STATE; + + const networkProviders = getMultichainNetworkProviders(state); + expect(Array.isArray(networkProviders)).toBe(true); + expect(networkProviders.length).toBeGreaterThan(0); + }); + }); + + describe('getMultichainNetwork', () => { + it('returns an EVM network provider if account is EVM', () => { + const state = MOCK_EVM_STATE; + + const network = getMultichainNetwork(state); + expect(network.isEvmNetwork).toBe(true); + }); + + it('returns an non-EVM network provider if account is non-EVM', () => { + const state = MOCK_NON_EVM_STATE; + + const network = getMultichainNetwork(state); + expect(network.isEvmNetwork).toBe(false); + }); + }); + + describe('getMultichainIsEvm', () => { + it('returns true if selected account is EVM compatible', () => { + const state = MOCK_EVM_STATE; + + expect(getMultichainIsEvm(state)).toBe(true); + }); + + it('returns false if selected account is not EVM compatible', () => { + const state = MOCK_NON_EVM_STATE; + + expect(getMultichainIsEvm(state)).toBe(false); + }); + }); + + describe('getMultichain{ProviderConfig,CurrentNetwork}', () => { + it('returns a ProviderConfig if account is EVM', () => { + const state = MOCK_EVM_STATE; + + expect(getMultichainProviderConfig(state)).toBe(getProviderConfig(state)); + }); + + it('returns a MultichainProviderConfig if account is non-EVM (bip122:*)', () => { + const state = MOCK_NON_EVM_STATE; + + const bip122ProviderConfig = getBip122ProviderConfig(); + expect(getMultichainProviderConfig(state)).toBe(bip122ProviderConfig); + }); + }); + + describe('getMultichainNativeCurrency', () => { + it('returns same native currency if account is EVM', () => { + const state = MOCK_EVM_STATE; + + expect(getMultichainNativeCurrency(state)).toBe(getNativeCurrency(state)); + }); + + it('returns MultichainProviderConfig.ticker if account is non-EVM (bip122:*)', () => { + const state = MOCK_NON_EVM_STATE; + + const bip122ProviderConfig = getBip122ProviderConfig(); + expect(getMultichainNativeCurrency(state)).toBe( + bip122ProviderConfig.ticker, + ); + }); + }); + + describe('getMultichainCurrentCurrency', () => { + it('returns same currency currency if account is EVM', () => { + const state = MOCK_EVM_STATE; + + expect(getMultichainCurrentCurrency(state)).toBe( + getCurrentCurrency(state), + ); + }); + + // @ts-expect-error This is missing from the Mocha type definitions + it.each(['usd', 'ETH'])( + "returns current currency '%s' if account is EVM", + (currency: string) => { + const state = MOCK_EVM_STATE; + + state.metamask.currentCurrency = currency; + expect(getCurrentCurrency(state)).toBe(currency); + expect(getMultichainCurrentCurrency(state)).toBe(currency); + }, + ); + + it('fallbacks to ticker as currency if account is non-EVM (bip122:*)', () => { + const state = MOCK_NON_EVM_STATE; // .currentCurrency = 'ETH' + + const bip122ProviderConfig = getBip122ProviderConfig(); + expect(getCurrentCurrency(state).toLowerCase()).not.toBe('usd'); + expect(getMultichainCurrentCurrency(state)).toBe( + bip122ProviderConfig.ticker, + ); + }); + }); + + describe('getMultichainShouldShowFiat', () => { + it('returns same value as getShouldShowFiat if account is EVM', () => { + const state = MOCK_EVM_STATE; + + expect(getMultichainShouldShowFiat(state)).toBe(getShouldShowFiat(state)); + }); + + it('returns true if account is non-EVM', () => { + const state = MOCK_NON_EVM_STATE; + + expect(getMultichainShouldShowFiat(state)).toBe(true); + }); + }); + + describe('getMultichainDefaultToken', () => { + it('returns ETH if account is EVM', () => { + const state = MOCK_EVM_STATE; + + expect(getMultichainDefaultToken(state)).toEqual({ + symbol: 'ETH', + }); + }); + + it('returns true if account is non-EVM (bip122:*)', () => { + const state = MOCK_NON_EVM_STATE; + + const bip122ProviderConfig = getBip122ProviderConfig(); + expect(getMultichainDefaultToken(state)).toEqual({ + symbol: bip122ProviderConfig.ticker, + }); + }); + }); +}); diff --git a/ui/selectors/multichain.ts b/ui/selectors/multichain.ts new file mode 100644 index 000000000000..e97b08b85030 --- /dev/null +++ b/ui/selectors/multichain.ts @@ -0,0 +1,154 @@ +import { isEvmAccountType } from '@metamask/keyring-api'; +import { ProviderConfig } from '@metamask/network-controller'; +import { + CaipChainId, + KnownCaipNamespace, + parseCaipChainId, +} from '@metamask/utils'; +import { + MultichainProviderConfig, + MULTICHAIN_PROVIDER_CONFIGS, +} from '../../shared/constants/multichain/networks'; +import { + getNativeCurrency, + getProviderConfig, +} from '../ducks/metamask/metamask'; +import { AccountsState } from './accounts'; +import { + getAllNetworks, + getCurrentCurrency, + getNativeCurrencyImage, + getSelectedInternalAccount, + getShouldShowFiat, +} from '.'; + +export type MultichainState = AccountsState & { + metamask: { + // TODO: Use states from new {Rates,Balances,Chain}Controller + }; +}; + +export type MultichainNetwork = { + nickname: string; + isEvmNetwork: boolean; + chainId?: CaipChainId; + network?: ProviderConfig | MultichainProviderConfig; +}; + +export function getMultichainNetworkProviders( + _state: MultichainState, +): MultichainProviderConfig[] { + // TODO: need state from the ChainController? + return Object.values(MULTICHAIN_PROVIDER_CONFIGS); +} + +export function getMultichainNetwork( + state: MultichainState, +): MultichainNetwork { + const selectedAccount = getSelectedInternalAccount(state); + const isEvm = isEvmAccountType(selectedAccount.type); + + // EVM networks + const evmNetworks: ProviderConfig[] = getAllNetworks(state); + const evmProvider: ProviderConfig = getProviderConfig(state); + + if (isEvm) { + const evmChainId = + `${KnownCaipNamespace.Eip155}:${evmProvider.chainId}` as CaipChainId; + const evmNetwork = evmNetworks.find( + (network) => network.chainId === evmProvider.chainId, + ); + + return { + nickname: 'Ethereum', + isEvmNetwork: true, + chainId: evmChainId, + network: evmNetwork, + }; + } + + // Non-EVM networks + // (Hardcoded for testing) + // HACK: For now, we rely on the account type being "sort-of" CAIP compliant, so use + // this as a CAIP-2 namespace and apply our filter with it + const nonEvmNetworks = getMultichainNetworkProviders(state); + const nonEvmNetwork = nonEvmNetworks.find((provider) => { + const { namespace } = parseCaipChainId(provider.chainId); + return selectedAccount.type.startsWith(namespace); + }); + + return { + // TODO: Adapt this for other non-EVM networks + // TODO: We need to have a way of setting nicknames of other non-EVM networks + nickname: 'Bitcoin', + isEvmNetwork: false, + // FIXME: We should use CAIP-2 chain ID here, and not only the reference part + chainId: nonEvmNetwork?.chainId, + network: nonEvmNetwork, + }; +} + +// FIXME: All the following might have side-effect, like if the current account is a bitcoin one and that +// a popup (for ethereum related stuffs) is being shown (and uses this function), then the native +// currency will be BTC.. + +export function getMultichainIsEvm(state: MultichainState) { + const selectedAccount = getSelectedInternalAccount(state); + + // There are no selected account during onboarding. we default to the current EVM provider. + return !selectedAccount || isEvmAccountType(selectedAccount.type); +} + +export function getMultichainProviderConfig( + state: MultichainState, +): ProviderConfig | MultichainProviderConfig { + return getMultichainIsEvm(state) + ? getProviderConfig(state) + : getMultichainNetwork(state).network; +} + +export function getMultichainCurrentNetwork(state: MultichainState) { + return getMultichainProviderConfig(state); +} + +export function getMultichainNativeCurrency(state: MultichainState) { + return getMultichainIsEvm(state) + ? getNativeCurrency(state) + : getMultichainProviderConfig(state).ticker; +} + +export function getMultichainCurrentCurrency(state: MultichainState) { + const currentCurrency = getCurrentCurrency(state).toLowerCase(); + + // To mimic `getCurrentCurrency` we only consider fiat values, otherwise we + // fallback to the current ticker symbol value + return currentCurrency === 'usd' + ? 'usd' + : getMultichainProviderConfig(state).ticker; +} + +export function getMultichainCurrencyImage(state: MultichainState) { + if (getMultichainIsEvm(state)) { + return getNativeCurrencyImage(state); + } + + const provider = getMultichainProviderConfig( + state, + ) as MultichainProviderConfig; + return provider.rpcPrefs?.imageUrl; +} + +export function getMultichainShouldShowFiat(state: MultichainState) { + return getMultichainIsEvm(state) + ? getShouldShowFiat(state) + : // For now we force this for non-EVM + true; +} + +export function getMultichainDefaultToken(state: MultichainState) { + const symbol = getMultichainIsEvm(state) + ? getProviderConfig(state).ticker + : getMultichainProviderConfig(state).ticker; + + return { symbol }; +} diff --git a/ui/store/background-connection.ts b/ui/store/background-connection.ts index 5c7222c040e6..3928675419a8 100644 --- a/ui/store/background-connection.ts +++ b/ui/store/background-connection.ts @@ -41,6 +41,7 @@ type CallbackMethod<R = unknown> = (error?: unknown, result?: R) => void; * [Deprecated] Callback-style call to background method * invokes promisifiedBackground method directly. * + * @deprecated Use async `submitRequestToBackground` function instead. * @param method - name of the background method * @param [args] - arguments to that method, if any * @param callback - Node style (error, result) callback for finishing the operation diff --git a/yarn.lock b/yarn.lock index f68f0999f41c..cf50e0a9db6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1710,10 +1710,10 @@ __metadata: languageName: node linkType: hard -"@blockaid/ppom_release@npm:^1.4.6": - version: 1.4.6 - resolution: "@blockaid/ppom_release@npm:1.4.6" - checksum: 10/53b9774f97ba24d98dee4a66653c83223afd2db8598d8b631ee4851731b1f68a99d05a8a2b68584d7b9a8e71370a09e79aba024d171af8a823c1053371009756 +"@blockaid/ppom_release@npm:^1.4.7": + version: 1.4.7 + resolution: "@blockaid/ppom_release@npm:1.4.7" + checksum: 10/2d376796afe4dc4e008418120977f22202b52d32ab2d036e8ab511a7673afeffff8b36e9b194cceeb4ffceb3808dcc6a276e1e55fd39e37833b3e7c288fec295 languageName: node linkType: hard @@ -4975,10 +4975,10 @@ __metadata: languageName: node linkType: hard -"@metamask/design-tokens@npm:^3.0.0": - version: 3.0.0 - resolution: "@metamask/design-tokens@npm:3.0.0" - checksum: 10/65c809fb5877398a0e45f3a22c09c8f7b64972961497f465a4c85eb6492e7e7d1168d4718cd21442dfb54a1f4b79d0287c6d5f5f5faa652dfcc5ababb1894095 +"@metamask/design-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "@metamask/design-tokens@npm:4.0.0" + checksum: 10/337968d86bf963ccdf7ab416cc8f87ec1d35d9fb56f686dea954964edd6f5cb0067a920cb2c1f9008150d3decac51cb1b392b3e67dc1d46ca308b503ffe7eabd languageName: node linkType: hard @@ -24838,7 +24838,7 @@ __metadata: "@babel/preset-typescript": "npm:^7.23.2" "@babel/register": "npm:^7.22.15" "@babel/runtime": "patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch" - "@blockaid/ppom_release": "npm:^1.4.6" + "@blockaid/ppom_release": "npm:^1.4.7" "@contentful/rich-text-html-renderer": "npm:^16.3.5" "@ensdomains/content-hash": "npm:^2.5.7" "@ethereumjs/tx": "npm:^4.1.1" @@ -24877,7 +24877,7 @@ __metadata: "@metamask/build-utils": "npm:^1.0.0" "@metamask/contract-metadata": "npm:^2.5.0" "@metamask/controller-utils": "npm:^10.0.0" - "@metamask/design-tokens": "npm:^3.0.0" + "@metamask/design-tokens": "npm:^4.0.0" "@metamask/ens-controller": "npm:^10.0.1" "@metamask/eslint-config": "npm:^9.0.0" "@metamask/eslint-config-jest": "npm:^9.0.0"