diff --git a/.eslintrc.js b/.eslintrc.js index cbfe6a1a0b29..a8ed5a7b6d8c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -280,6 +280,54 @@ module.exports = { }, }, }, + + /** + * TypeScript React-specific code + * + * Similar to above, but marks a majority of errors to warnings. + * TODO - combine rulesets and resolve errors + */ + { + files: ['ui/**/*.ts', 'ui/**/*.tsx'], + extends: ['plugin:react/recommended', 'plugin:react-hooks/recommended'], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + plugins: ['react'], + rules: { + 'react/no-unused-prop-types': 'warn', + 'react/no-unused-state': 'warn', + 'react/jsx-boolean-value': 'warn', + 'react/jsx-curly-brace-presence': [ + 'warn', + { + props: 'never', + children: 'never', + }, + ], + 'react/no-deprecated': 'warn', + 'react/default-props-match-prop-types': 'warn', + 'react/jsx-no-duplicate-props': 'warn', + 'react/display-name': 'off', + 'react/no-unescaped-entities': 'warn', + 'react/prop-types': 'off', + 'react/no-children-prop': 'off', + 'react/jsx-key': 'warn', // TODO - increase this into 'error' level + 'react-hooks/rules-of-hooks': 'warn', // TODO - increase this into 'error' level + }, + settings: { + react: { + // If this is set to 'detect', ESLint will import React in order to + // find its version. Because we run ESLint in the build system under + // LavaMoat, this means that detecting the React version requires a + // LavaMoat policy for all of React, in the build system. That's a + // no-go, so we grab it from React's package.json. + version: reactVersion, + }, + }, + }, /** * Mocha tests * diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index c6fc7e658eea..61eae204b921 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -22,6 +22,6 @@ jobs: url-to-cladocument: 'https://metamask.io/cla.html' # This branch can't have protections, commits are made directly to the specified branch. branch: 'cla-signatures' - allowlist: 'dependabot[bot],metamaskbot,crowdin-bot' + allowlist: 'dependabot[bot],metamaskbot,crowdin-bot,runway-github[bot]' allow-organization-members: true blockchain-storage-flag: false diff --git a/.yarn/patches/@metamask-assets-controllers-npm-48.0.0-7a6e6586a9.patch b/.yarn/patches/@metamask-assets-controllers-npm-49.0.0-e9c0266958.patch similarity index 100% rename from .yarn/patches/@metamask-assets-controllers-npm-48.0.0-7a6e6586a9.patch rename to .yarn/patches/@metamask-assets-controllers-npm-49.0.0-e9c0266958.patch diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index fc1f5e46ff83..bab9e3962021 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -42,66 +42,7 @@ "message": "Connect your QR hardware wallet" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (coming soon)" - }, - "SIWEAddressInvalid": { - "message": "The address in the sign-in request does not match the address of the account you are using to sign in." - }, - "SIWEDomainInvalidText": { - "message": "The site you're attempting to sign into doesn't match the domain in the request. Proceed with caution." - }, - "SIWEDomainInvalidTitle": { - "message": "Deceptive site request." - }, - "SIWEDomainWarningBody": { - "message": "The website ($1) is asking you to sign in to the wrong domain. This may be a phishing attack.", - "description": "$1 represents the website domain" - }, - "SIWEDomainWarningLabel": { - "message": "Unsafe" - }, - "SIWELabelChainID": { - "message": "Chain ID:" - }, - "SIWELabelExpirationTime": { - "message": "Expires At:" - }, - "SIWELabelIssuedAt": { - "message": "Issued At:" - }, - "SIWELabelMessage": { - "message": "Message:" - }, - "SIWELabelNonce": { - "message": "Nonce:" - }, - "SIWELabelNotBefore": { - "message": "Not Before:" - }, - "SIWELabelRequestID": { - "message": "Request ID:" - }, - "SIWELabelResources": { - "message": "Resources: $1", - "description": "$1 represents the number of resources" - }, - "SIWELabelURI": { - "message": "URI:" - }, - "SIWELabelVersion": { - "message": "Version:" - }, - "SIWESiteRequestSubtitle": { - "message": "This site is requesting to sign in with" - }, - "SIWESiteRequestTitle": { - "message": "Sign-in request" - }, - "SIWEWarningSubtitle": { - "message": "To confirm you understand, check:" - }, - "SIWEWarningTitle": { - "message": "Are you sure?" + "message": "Ngrave Zero" }, "about": { "message": "About" @@ -132,6 +73,9 @@ "accountActivityText": { "message": "Select the accounts you want to be notified about:" }, + "accountBalance": { + "message": "Account balance" + }, "accountDetails": { "message": "Account details" }, @@ -155,6 +99,9 @@ "accountOptions": { "message": "Account options" }, + "accountPermissionToast": { + "message": "Account permissions updated" + }, "accountSelectionRequired": { "message": "You need to select an account!" }, @@ -167,6 +114,12 @@ "accountsConnected": { "message": "Accounts connected" }, + "accountsPermissionsTitle": { + "message": "See your accounts and suggest transactions" + }, + "accountsSmallCase": { + "message": "accounts" + }, "active": { "message": "Active" }, @@ -179,15 +132,18 @@ "add": { "message": "Add" }, + "addACustomNetwork": { + "message": "Add a custom network" + }, "addANetwork": { "message": "Add a network" }, - "addANetworkManually": { - "message": "Add a network manually" - }, "addANickname": { "message": "Add a nickname" }, + "addAUrl": { + "message": "Add a URL" + }, "addAccount": { "message": "Add account" }, @@ -203,29 +159,15 @@ "addBlockExplorer": { "message": "Add a block explorer" }, + "addBlockExplorerUrl": { + "message": "Add a block explorer URL" + }, "addContact": { "message": "Add contact" }, "addCustomNetwork": { "message": "Add custom network" }, - "addEthereumChainConfirmationDescription": { - "message": "This will allow this network to be used within MetaMask." - }, - "addEthereumChainConfirmationRisks": { - "message": "MetaMask does not verify custom networks." - }, - "addEthereumChainConfirmationRisksLearnMore": { - "message": "Learn about $1.", - "description": "$1 is a link with text that is provided by the 'addEthereumChainConfirmationRisksLearnMoreLink' key" - }, - "addEthereumChainConfirmationRisksLearnMoreLink": { - "message": "scams and network security risks", - "description": "Link text for the 'addEthereumChainConfirmationRisksLearnMore' translation key" - }, - "addEthereumChainConfirmationTitle": { - "message": "Allow this site to add a network?" - }, "addEthereumChainWarningModalHeader": { "message": "Only add this RPC provider if you’re sure you can trust it. $1", "description": "$1 is addEthereumChainWarningModalHeaderPartTwo passed separately so that it can be bolded" @@ -248,12 +190,12 @@ "addEthereumChainWarningModalTitle": { "message": "You are adding a new RPC provider for Ethereum Mainnet" }, + "addEthereumWatchOnlyAccount": { + "message": "Watch an Ethereum account (Beta)" + }, "addFriendsAndAddresses": { "message": "Add friends and addresses you trust" }, - "addFromAListOfPopularNetworks": { - "message": "Add from a list of popular networks or add a network manually. Only interact with the entities you trust." - }, "addHardwareWallet": { "message": "Add hardware wallet" }, @@ -266,15 +208,12 @@ "addMemo": { "message": "Add memo" }, - "addMoreNetworks": { - "message": "add more networks manually" - }, "addNetwork": { "message": "Add network" }, - "addNetworkTooltipWarning": { - "message": "This network connection relies on third parties. This connection may be less reliable or enable third-parties to track activity. $1", - "description": "$1 is Learn more link" + "addNetworkConfirmationTitle": { + "message": "Add $1", + "description": "$1 represents network name" }, "addNewAccount": { "message": "Add a new Ethereum account" @@ -288,9 +227,6 @@ "addNewSolanaAccount": { "message": "Add a new Solana account (Beta)" }, - "addNewToken": { - "message": "Add new token" - }, "addNft": { "message": "Add NFT" }, @@ -328,30 +264,32 @@ "addingCustomNetwork": { "message": "Adding Network" }, - "addingTokens": { - "message": "Adding tokens" - }, "additionalNetworks": { "message": "Additional networks" }, - "additionalRpcUrl": { - "message": "Additional RPC URL" - }, "address": { "message": "Address" }, "addressCopied": { "message": "Address copied!" }, + "addressMismatch": { + "message": "Site address mismatch" + }, + "addressMismatchOriginal": { + "message": "Current URL: $1", + "description": "$1 replaced by origin URL in confirmation request" + }, + "addressMismatchPunycode": { + "message": "Punycode version: $1", + "description": "$1 replaced by punycode version of the URL in confirmation request" + }, "advanced": { "message": "Advanced" }, "advancedBaseGasFeeToolTip": { "message": "When your transaction gets included in the block, any difference between your max base fee and the actual base fee will be refunded. Total amount is calculated as max base fee (in GWEI) * gas limit." }, - "advancedConfiguration": { - "message": "Advanced configuration" - }, "advancedDetailsDataDesc": { "message": "Data" }, @@ -377,6 +315,10 @@ "advancedPriorityFeeToolTip": { "message": "Priority fee (aka “miner tip”) goes directly to miners and incentivizes them to prioritize your transaction." }, + "aggregatedBalancePopover": { + "message": "This reflects the value of all tokens you own on a given network. If you prefer seeing this value in ETH or other currencies, go to $1.", + "description": "$1 represents the settings page" + }, "agreeTermsOfUse": { "message": "I agree to MetaMask's $1", "description": "$1 is the `terms` link" @@ -387,8 +329,8 @@ "alert": { "message": "Alert" }, - "alertActionBuy": { - "message": "Buy ETH" + "alertActionBuyWithNativeCurrency": { + "message": "Buy $1" }, "alertActionUpdateGas": { "message": "Update gas limit" @@ -402,6 +344,15 @@ "alertDisableTooltip": { "message": "This can be changed in \"Settings > Alerts\"" }, + "alertMessageAddressMismatchWarning": { + "message": "Attackers sometimes mimic sites by making small changes to the site address. Make sure you're interacting with the intended site before you continue." + }, + "alertMessageChangeInSimulationResults": { + "message": "Estimated changes for this transaction have been updated. Review them closely before proceeding." + }, + "alertMessageFirstTimeInteraction": { + "message": "You're interacting with this address for the first time. Make sure that it's correct before you continue." + }, "alertMessageGasEstimateFailed": { "message": "We’re unable to provide an accurate fee and this estimate might be high. We suggest you to input a custom gas limit, but there’s a risk the transaction will still fail." }, @@ -411,6 +362,9 @@ "alertMessageGasTooLow": { "message": "To continue with this transaction, you’ll need to increase the gas limit to 21000 or higher." }, + "alertMessageInsufficientBalanceWithNativeCurrency": { + "message": "You do not have enough $1 in your account to pay for network fees." + }, "alertMessageNetworkBusy": { "message": "Gas prices are high and estimates are less accurate." }, @@ -432,6 +386,12 @@ "alertModalReviewAllAlerts": { "message": "Review all alerts" }, + "alertReasonChangeInSimulationResults": { + "message": "Results have changed" + }, + "alertReasonFirstTimeInteraction": { + "message": "1st interaction" + }, "alertReasonGasEstimateFailed": { "message": "Inaccurate fee" }, @@ -459,12 +419,18 @@ "alertReasonWrongAccount": { "message": "Wrong account" }, + "alertSelectedAccountWarning": { + "message": "This request is for a different account than the one selected in your wallet. To use another account, connect it to the site." + }, "alerts": { "message": "Alerts" }, "all": { "message": "All" }, + "allNetworks": { + "message": "All networks" + }, "allPermissions": { "message": "All Permissions" }, @@ -474,12 +440,6 @@ "allTimeLow": { "message": "All time low" }, - "allowMetaMaskToDetectNFTs": { - "message": "Allow MetaMask to detect and display your NFTs with autodetection. You’ll be able to:" - }, - "allowMetaMaskToDetectTokens": { - "message": "Allow MetaMask to detect and display your tokens with autodetection. You’ll be able to:" - }, "allowNotifications": { "message": "Allow notifications" }, @@ -524,6 +484,9 @@ "message": "MetaMask Institutional", "description": "The name of the application (MMI)" }, + "apply": { + "message": "Apply" + }, "approve": { "message": "Approve spend limit" }, @@ -555,14 +518,20 @@ "asset": { "message": "Asset" }, + "assetMultipleNFTsBalance": { + "message": "$1 NFTs" + }, "assetOptions": { "message": "Asset options" }, - "attemptSendingAssets": { - "message": "You may lose your assets if you try to send them from another network. Transfer funds safely between networks by using a bridge." + "assetSingleNFTBalance": { + "message": "$1 NFT" }, - "attemptSendingAssetsWithPortfolio": { - "message": "You may lose your assets if you try to send them from another network. Transfer funds safely between networks by using a bridge, like $1" + "assets": { + "message": "Assets" + }, + "assetsDescription": { + "message": "Autodetect tokens in your wallet, display NFTs, and get batched account balance updates" }, "attemptToCancelSwapForFree": { "message": "Attempt to cancel swap for free" @@ -595,9 +564,6 @@ "average": { "message": "Average" }, - "awaitingApproval": { - "message": "Awaiting approval..." - }, "back": { "message": "Back" }, @@ -653,18 +619,14 @@ "basicConfigurationModalHeadingOn": { "message": "Turn on basic functionality" }, - "beCareful": { - "message": "Be careful" + "bestPrice": { + "message": "Best price" }, "beta": { "message": "Beta" }, "betaHeaderText": { - "message": "This is a beta version. Please report bugs $1", - "description": "$1 represents the word 'here' in a hyperlink" - }, - "betaMetamaskInstitutionalVersion": { - "message": "MetaMask Institutional Beta Version" + "message": "This is a beta version. Please report bugs $1" }, "betaMetamaskVersion": { "message": "MetaMask Beta Version" @@ -672,12 +634,6 @@ "betaTerms": { "message": "Beta Terms of use" }, - "betaWalletCreationSuccessReminder1": { - "message": "MetaMask Beta can’t recover your Secret Recovery Phrase." - }, - "betaWalletCreationSuccessReminder2": { - "message": "MetaMask Beta will never ask you for your Secret Recovery Phrase." - }, "billionAbbreviation": { "message": "B", "description": "Shortened form of 'billion'" @@ -747,9 +703,6 @@ "blockaidDescriptionTransferFarming": { "message": "If you approve this request, a third party known for scams will take all your assets." }, - "blockaidDescriptionWarning": { - "message": "This could be a deceptive request. Only continue if you trust every address involved." - }, "blockaidMessage": { "message": "Privacy preserving - no data is shared with third parties. Available on Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon, Base and Sepolia." }, @@ -771,8 +724,132 @@ "bridge": { "message": "Bridge" }, - "bridgeDontSend": { - "message": "Bridge, don't send" + "bridgeAllowSwappingOf": { + "message": "Allow exact access to $1 $2 on $3 for bridging", + "description": "Shows a user that they need to allow a token for swapping on their hardware wallet" + }, + "bridgeApproval": { + "message": "Approve $1 for bridge", + "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be bridged. $1 is the symbol of a token that has been approved." + }, + "bridgeApprovalWarning": { + "message": "You are allowing access to the specified amount, $1 $2. The contract will not access any additional funds." + }, + "bridgeApprovalWarningForHardware": { + "message": "You will need to allow access to $1 $2 for bridging, and then approve bridging to $2. This will require two separate confirmations." + }, + "bridgeCalculatingAmount": { + "message": "Calculating..." + }, + "bridgeConfirmTwoTransactions": { + "message": "You'll need to confirm 2 transactions on your hardware wallet:" + }, + "bridgeEnterAmount": { + "message": "Select amount" + }, + "bridgeExplorerLinkViewOn": { + "message": "View on $1" + }, + "bridgeFetchNewQuotes": { + "message": "Fetch a new one?" + }, + "bridgeFrom": { + "message": "Bridge from" + }, + "bridgeFromTo": { + "message": "Bridge $1 $2 to $3", + "description": "Tells a user that they need to confirm on their hardware wallet a bridge. $1 is amount of source token, $2 is the source network, and $3 is the destination network" + }, + "bridgeGasFeesSplit": { + "message": "Any network fee quoted on the previous screen includes both transactions and will be split." + }, + "bridgeNetCost": { + "message": "Net cost" + }, + "bridgeQuoteExpired": { + "message": "Your quote timed out." + }, + "bridgeSelectNetwork": { + "message": "Select network" + }, + "bridgeSelectTokenAndAmount": { + "message": "Select token and amount" + }, + "bridgeStepActionBridgeComplete": { + "message": "$1 received on $2", + "description": "$1 is the amount of the destination asset, $2 is the name of the destination network" + }, + "bridgeStepActionBridgePending": { + "message": "Receiving $1 on $2", + "description": "$1 is the amount of the destination asset, $2 is the name of the destination network" + }, + "bridgeStepActionSwapComplete": { + "message": "Swapped $1 for $2", + "description": "$1 is the amount of the source asset, $2 is the amount of the destination asset" + }, + "bridgeStepActionSwapPending": { + "message": "Swapping $1 for $2", + "description": "$1 is the amount of the source asset, $2 is the amount of the destination asset" + }, + "bridgeTerms": { + "message": "Terms" + }, + "bridgeTimingMinutes": { + "message": "$1 min", + "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" + }, + "bridgeTo": { + "message": "Bridge to" + }, + "bridgeToChain": { + "message": "Bridge to $1" + }, + "bridgeTxDetailsBridging": { + "message": "Bridging" + }, + "bridgeTxDetailsDelayedDescription": { + "message": "Reach out to" + }, + "bridgeTxDetailsDelayedDescriptionSupport": { + "message": "MetaMask Support" + }, + "bridgeTxDetailsDelayedTitle": { + "message": "Has it been longer than 3 hours?" + }, + "bridgeTxDetailsNonce": { + "message": "Nonce" + }, + "bridgeTxDetailsStatus": { + "message": "Status" + }, + "bridgeTxDetailsTimestamp": { + "message": "Time stamp" + }, + "bridgeTxDetailsTimestampValue": { + "message": "$1 at $2", + "description": "$1 is the date, $2 is the time" + }, + "bridgeTxDetailsTokenAmountOnChain": { + "message": "$1 $2 on", + "description": "$1 is the amount of the token, $2 is the ticker symbol of the token" + }, + "bridgeTxDetailsTotalGasFee": { + "message": "Total gas fee" + }, + "bridgeTxDetailsYouReceived": { + "message": "You received" + }, + "bridgeTxDetailsYouSent": { + "message": "You sent" + }, + "bridgeValidationInsufficientGasMessage": { + "message": "You don't have enough $1 to pay the gas fee for this bridge. Enter a smaller amount or buy more $1." + }, + "bridgeValidationInsufficientGasTitle": { + "message": "More $1 needed for gas" + }, + "bridging": { + "message": "Bridging" }, "browserNotSupported": { "message": "Your browser is not supported..." @@ -783,16 +860,15 @@ "builtAroundTheWorld": { "message": "MetaMask is designed and built around the world." }, + "bulletpoint": { + "message": "·" + }, "busy": { "message": "Busy" }, "buyAndSell": { "message": "Buy & Sell" }, - "buyAsset": { - "message": "Buy $1", - "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" - }, "buyMoreAsset": { "message": "Buy more $1", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" @@ -800,10 +876,6 @@ "buyNow": { "message": "Buy Now" }, - "buyToken": { - "message": "Buy $1", - "description": "$1 is the token symbol" - }, "bytes": { "message": "Bytes" }, @@ -816,9 +888,6 @@ "cancelPopoverTitle": { "message": "Cancel transaction" }, - "cancelSpeedUp": { - "message": "cancel or speed up a transaction." - }, "cancelSpeedUpLabel": { "message": "This gas fee will $1 the original.", "description": "$1 is text 'replace' in bold" @@ -874,9 +943,6 @@ "message": "Click here to connect your Ledger via WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, - "clickToManuallyAdd": { - "message": "You can always add tokens manually." - }, "close": { "message": "Close" }, @@ -920,18 +986,6 @@ "confirmAlertModalAcknowledgeSingle": { "message": "I have acknowledged the alert and still want to proceed" }, - "confirmConnectCustodianRedirect": { - "message": "We will redirect you to $1 upon clicking continue." - }, - "confirmConnectCustodianText": { - "message": "To connect your accounts log into your $1 account and click on the 'connect to MMI' button." - }, - "confirmConnectionTitle": { - "message": "Confirm connection to $1" - }, - "confirmDeletion": { - "message": "Confirm deletion" - }, "confirmFieldPaymaster": { "message": "Fee paid by" }, @@ -944,8 +998,20 @@ "confirmRecoveryPhrase": { "message": "Confirm Secret Recovery Phrase" }, - "confirmRpcUrlDeletionMessage": { - "message": "Are you sure you want to delete the RPC URL? Your information will not be saved for this network." + "confirmTitleApproveTransactionNFT": { + "message": "Withdrawal request" + }, + "confirmTitleDeployContract": { + "message": "Deploy a contract" + }, + "confirmTitleDescApproveTransaction": { + "message": "This site wants permission to withdraw your NFTs" + }, + "confirmTitleDescDeployContract": { + "message": "This site wants you to deploy a contract" + }, + "confirmTitleDescERC20ApproveTransaction": { + "message": "This site wants permission to withdraw your tokens" }, "confirmTitleDescPermitSignature": { "message": "This site wants permission to spend your tokens." @@ -953,18 +1019,33 @@ "confirmTitleDescSIWESignature": { "message": "A site wants you to sign in to prove you own this account." }, + "confirmTitleDescSign": { + "message": "Review request details before you confirm." + }, "confirmTitlePermitTokens": { "message": "Spending cap request" }, + "confirmTitleRevokeApproveTransaction": { + "message": "Remove permission" + }, "confirmTitleSIWESignature": { "message": "Sign-in request" }, + "confirmTitleSetApprovalForAllRevokeTransaction": { + "message": "Remove permission" + }, "confirmTitleSignature": { "message": "Signature request" }, "confirmTitleTransaction": { "message": "Transaction request" }, + "confirmationAlertDetails": { + "message": "To protect your assets, we suggest you reject the request." + }, + "confirmationAlertModalTitle": { + "message": "This request is suspicious" + }, "confirmed": { "message": "Confirmed" }, @@ -977,6 +1058,9 @@ "confusingEnsDomain": { "message": "We have detected a confusable character in the ENS name. Check the ENS name to avoid a potential scam." }, + "congratulations": { + "message": "Congratulations!" + }, "connect": { "message": "Connect" }, @@ -989,18 +1073,6 @@ "connectAccounts": { "message": "Connect accounts" }, - "connectCustodialAccountMenu": { - "message": "Connect Custodial Account" - }, - "connectCustodialAccountMsg": { - "message": "Please choose the custodian you want to connect in order to add or refresh a token." - }, - "connectCustodialAccountTitle": { - "message": "Custodial Accounts" - }, - "connectCustodianAccounts": { - "message": "Connect $1 accounts" - }, "connectManually": { "message": "Manually connect to current site" }, @@ -1037,6 +1109,9 @@ "connectedSites": { "message": "Connected sites" }, + "connectedSitesAndSnaps": { + "message": "Connected sites and Snaps" + }, "connectedSitesDescription": { "message": "$1 is connected to these sites. They can view your account address.", "description": "$1 is the account name" @@ -1048,8 +1123,24 @@ "connectedSnapAndNoAccountDescription": { "message": "MetaMask is connected to this site, but no accounts are connected yet" }, - "connectedWith": { - "message": "Connected with" + "connectedSnaps": { + "message": "Connected Snaps" + }, + "connectedWithAccount": { + "message": "$1 accounts connected", + "description": "$1 represents account length" + }, + "connectedWithAccountName": { + "message": "Connected with $1", + "description": "$1 represents account name" + }, + "connectedWithNetwork": { + "message": "$1 networks connected", + "description": "$1 represents network length" + }, + "connectedWithNetworkName": { + "message": "Connected with $1", + "description": "$1 represents network name" }, "connecting": { "message": "Connecting" @@ -1078,6 +1169,9 @@ "connectingToSepolia": { "message": "Connecting to Sepolia test network" }, + "connectionDescription": { + "message": "This site wants to" + }, "connectionFailed": { "message": "Connection failed" }, @@ -1101,12 +1195,6 @@ "continue": { "message": "Continue" }, - "continueMmiOnboarding": { - "message": "Continue MetaMask Institutional onboarding" - }, - "continueToWallet": { - "message": "Continue to wallet" - }, "contract": { "message": "Contract" }, @@ -1119,30 +1207,9 @@ "contractDeployment": { "message": "Contract deployment" }, - "contractDescription": { - "message": "To protect yourself against scammers, take a moment to verify third-party details." - }, "contractInteraction": { "message": "Contract interaction" }, - "contractNFT": { - "message": "NFT contract" - }, - "contractRequestingAccess": { - "message": "Third party requesting access" - }, - "contractRequestingSignature": { - "message": "Third party requesting signature" - }, - "contractRequestingSpendingCap": { - "message": "Third party requesting spending cap" - }, - "contractTitle": { - "message": "Third-party details" - }, - "contractToken": { - "message": "Token contract" - }, "convertTokenToNFTDescription": { "message": "We've detected that this asset is an NFT. MetaMask now has full native support for NFTs. Would you like to remove it from your token list and add it as an NFT?" }, @@ -1158,12 +1225,12 @@ "copyAddress": { "message": "Copy address to clipboard" }, + "copyAddressShort": { + "message": "Copy address" + }, "copyPrivateKey": { "message": "Copy private key" }, - "copyRawTransactionData": { - "message": "Copy raw transaction data" - }, "copyToClipboard": { "message": "Copy to clipboard" }, @@ -1188,14 +1255,21 @@ "creatorAddress": { "message": "Creator address" }, + "crossChainAggregatedBalancePopover": { + "message": "This reflects the value of all tokens you own on all networks. If you prefer seeing this value in ETH or other currencies, go to $1.", + "description": "$1 represents the settings page" + }, "crossChainSwapsLink": { "message": "Swap across networks with MetaMask Portfolio" }, + "crossChainSwapsLinkNative": { + "message": "Swap across networks with Bridge" + }, "cryptoCompare": { "message": "CryptoCompare" }, "currencyConversion": { - "message": "Currency conversion" + "message": "Currency" }, "currencyRateCheckToggle": { "message": "Show balance and token price checker" @@ -1219,6 +1293,10 @@ "currentLanguage": { "message": "Current language" }, + "currentNetwork": { + "message": "Current network", + "description": "Speicifies to token network filter to filter by current Network. Will render when network nickname is not available" + }, "currentRpcUrlDeprecated": { "message": "The current rpc url for this network has been deprecated." }, @@ -1237,76 +1315,19 @@ "curveMediumGasEstimate": { "message": "Market gas estimate graph" }, - "custodian": { - "message": "Custodian" - }, - "custodianAccountAddedDesc": { - "message": "You can now use your accounts in MetaMask Institutional." - }, - "custodianAccountAddedTitle": { - "message": "Selected $1 accounts have been added." - }, - "custodianQRCodeScan": { - "message": "Scan QR code with your $1 mobile app" - }, - "custodianQRCodeScanDescription": { - "message": "Or log into your $1 account and click on the 'Connect to MMI' button" - }, - "custodianReplaceRefreshTokenChangedFailed": { - "message": "Please go to $1 and click the 'Connect to MMI' button within their user interface to connect your accounts to MMI again." - }, - "custodianReplaceRefreshTokenChangedSubtitle": { - "message": "You can now use your custodian accounts in MetaMask Institutional." - }, - "custodianReplaceRefreshTokenChangedTitle": { - "message": "Your custodian token has been refreshed" - }, - "custodianReplaceRefreshTokenSubtitle": { - "message": "This is will replace the custodian token for the following address:" - }, - "custodianReplaceRefreshTokenTitle": { - "message": "Replace custodian token" - }, - "custodyDeeplinkDescription": { - "message": "Approve the transaction in the $1 app. Once all required custody approvals have been performed the transaction will complete. Check your $1 app for status." - }, - "custodyRefreshTokenModalDescription": { - "message": "Please go to $1 and click the 'Connect to MMI' button within their user interface to connect your accounts to MMI again." - }, - "custodyRefreshTokenModalDescription1": { - "message": "Your custodian issues a token that authenticates the MetaMask Institutional extension, allowing you to connect your accounts." - }, - "custodyRefreshTokenModalDescription2": { - "message": "This token expires after a certain period for security reasons. This requires you to reconnect to MMI." - }, - "custodyRefreshTokenModalSubtitle": { - "message": "Why am I seeing this?" - }, - "custodyRefreshTokenModalTitle": { - "message": "Your custodian session has expired" - }, - "custodySessionExpired": { - "message": "Custodian session expired." - }, - "custodyWrongChain": { - "message": "This account is not set up for use with $1" - }, "custom": { "message": "Advanced" }, - "customContentSearch": { - "message": "Search for a previously added network" - }, "customGasSettingToolTipMessage": { "message": "Use $1 to customize the gas price. This can be confusing if you aren’t familiar. Interact at your own risk.", "description": "$1 is key 'advanced' (text: 'Advanced') separated here so that it can be passed in with bold font-weight" }, + "customSlippage": { + "message": "Custom" + }, "customSpendLimit": { "message": "Custom spend limit" }, - "customSpendingCap": { - "message": "Custom spending cap" - }, "customToken": { "message": "Custom token" }, @@ -1328,9 +1349,6 @@ "customizeYourNotificationsText": { "message": "Turn on the types of notifications you want to receive:" }, - "dappRequestedSpendingCap": { - "message": "Site requested spending cap" - }, "dappSuggested": { "message": "Site suggested" }, @@ -1369,9 +1387,6 @@ "dataCollectionWarningPopoverDescription": { "message": "You turned off data collection for our marketing purposes. This only applies to this device. If you use MetaMask on other devices, make sure to opt out there as well." }, - "dataHex": { - "message": "Hex" - }, "dataUnavailable": { "message": "data unavailable" }, @@ -1381,6 +1396,9 @@ "dcent": { "message": "D'Cent" }, + "debitCreditPurchaseOptions": { + "message": "Debit or credit card purchase options" + }, "decimal": { "message": "Token decimal" }, @@ -1410,14 +1428,40 @@ "defaultRpcUrl": { "message": "Default RPC URL" }, + "defaultSettingsSubTitle": { + "message": "MetaMask uses default settings to best balance safety and ease of use. Change these settings to further increase your privacy." + }, + "defaultSettingsTitle": { + "message": "Default privacy settings" + }, "delete": { "message": "Delete" }, "deleteContact": { "message": "Delete contact" }, - "deleteNetwork": { - "message": "Delete network?" + "deleteMetaMetricsData": { + "message": "Delete MetaMetrics data" + }, + "deleteMetaMetricsDataDescription": { + "message": "This will delete historical MetaMetrics data associated with your use on this device. Your wallet and accounts will remain exactly as they are now after this data has been deleted. This process may take up to 30 days. View our $1.", + "description": "$1 will have text saying Privacy Policy " + }, + "deleteMetaMetricsDataErrorDesc": { + "message": "This request can't be completed right now due to an analytics system server issue, please try again later" + }, + "deleteMetaMetricsDataErrorTitle": { + "message": "We are unable to delete this data right now" + }, + "deleteMetaMetricsDataModalDesc": { + "message": "We are about to remove all your MetaMetrics data. Are you sure?" + }, + "deleteMetaMetricsDataModalTitle": { + "message": "Delete MetaMetrics data?" + }, + "deleteMetaMetricsDataRequestedDescription": { + "message": "You initiated this action on $1. This process can take up to 30 days. View the $2", + "description": "$1 will be the date on which teh deletion is requested and $2 will have text saying Privacy Policy " }, "deleteNetworkIntro": { "message": "If you delete this network, you will need to add it again to view your assets in this network" @@ -1426,8 +1470,8 @@ "message": "Delete $1 network?", "description": "$1 represents the name of the network" }, - "deleteRpcUrl": { - "message": "Delete RPC URL" + "depositCrypto": { + "message": "Deposit crypto from another account with a wallet address or QR code." }, "deprecatedGoerliNtwrkMsg": { "message": "Because of updates to the Ethereum system, the Goerli test network will be phased out soon." @@ -1451,6 +1495,9 @@ "details": { "message": "Details" }, + "developerOptions": { + "message": "Developer Options" + }, "disabledGasOptionToolTipMessage": { "message": "“$1” is disabled because it does not meet the minimum of a 10% increase from the original gas fee.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1467,16 +1514,14 @@ "disconnectAllAccountsText": { "message": "accounts" }, + "disconnectAllDescriptionText": { + "message": "If you disconnect from this site, you’ll need to reconnect your accounts and networks to use this site again." + }, "disconnectAllSnapsText": { "message": "Snaps" }, - "disconnectAllText": { - "message": "If you disconnect your $1 from $2, you'll need to reconnect to use them again.", - "description": "$1 will map to `disconnectAllAccountsText` or `disconnectAllSnapsText`, $2 represents the website hostname" - }, - "disconnectAllTitle": { - "message": "Disconnect all $1", - "description": "$1 will map to `disconnectAllAccountsText` or `disconnectAllSnapsText`" + "disconnectMessage": { + "message": "This will disconnect you from this site" }, "disconnectPrompt": { "message": "Disconnect $1" @@ -1511,12 +1556,6 @@ "displayNftMediaDescription": { "message": "Displaying NFT media and data exposes your IP address to OpenSea or other third parties. This can allow attackers to associate your IP address with your Ethereum address. NFT autodetection relies on this setting, and won't be available when this is turned off." }, - "diveStraightIntoUsingYourNFTs": { - "message": "Dive straight into using your NFTs" - }, - "diveStraightIntoUsingYourTokens": { - "message": "Dive straight into using your tokens" - }, "doNotShare": { "message": "Do not share this with anyone" }, @@ -1547,12 +1586,21 @@ "dropped": { "message": "Dropped" }, + "duplicateContactTooltip": { + "message": "This contact name collides with an existing account or contact" + }, + "duplicateContactWarning": { + "message": "You have duplicate contacts" + }, "edit": { "message": "Edit" }, "editANickname": { "message": "Edit nickname" }, + "editAccounts": { + "message": "Edit accounts" + }, "editAddressNickname": { "message": "Edit address nickname" }, @@ -1626,9 +1674,15 @@ "editGasTooLow": { "message": "Unknown processing time" }, + "editInPortfolio": { + "message": "Edit in Portfolio" + }, "editNetworkLink": { "message": "edit the original network" }, + "editNetworksTitle": { + "message": "Edit networks" + }, "editNonceField": { "message": "Edit nonce" }, @@ -1638,11 +1692,26 @@ "editPermission": { "message": "Edit permission" }, + "editPermissions": { + "message": "Edit permissions" + }, "editSpeedUpEditGasFeeModalTitle": { "message": "Edit speed up gas fee" }, - "effortlesslyNavigateYourDigitalAssets": { - "message": "Effortlessly navigate your digital assets" + "editSpendingCap": { + "message": "Edit spending cap" + }, + "editSpendingCapAccountBalance": { + "message": "Account balance: $1 $2" + }, + "editSpendingCapDesc": { + "message": "Enter the amount that you feel comfortable being spent on your behalf." + }, + "editSpendingCapError": { + "message": "The spending cap can’t exceed $1 decimal digits. Remove decimal digits to continue." + }, + "editSpendingCapSpecialCharError": { + "message": "Enter numbers only" }, "enableAutoDetect": { "message": " Enable autodetect" @@ -1650,9 +1719,6 @@ "enableFromSettings": { "message": " Enable it from Settings." }, - "enableNftAutoDetection": { - "message": "Enable NFT autodetection" - }, "enableSnap": { "message": "Enable" }, @@ -1660,12 +1726,6 @@ "message": "enable $1", "description": "$1 is a token symbol, e.g. ETH" }, - "enableTokenAutoDetection": { - "message": "Enable token autodetection" - }, - "enable_auto_detection_toggle_automatically": { - "message": "Users with the Basic Functionality toggle on will have this automatically turned on in the Metamask Extension v12.3.0" - }, "enabled": { "message": "Enabled" }, @@ -1680,7 +1740,7 @@ "message": "Request encryption public key" }, "endpointReturnedDifferentChainId": { - "message": "The RPC URL you have entered returned a different chain ID ($1). Please update the Chain ID to match the RPC URL of the network you are trying to add.", + "message": "The RPC URL you have entered returned a different chain ID ($1).", "description": "$1 is the return value of eth_chainId from an RPC endpoint" }, "enhancedTokenDetectionAlertMessage": { @@ -1704,21 +1764,30 @@ "ensUnknownError": { "message": "ENS lookup failed." }, - "enterANumber": { - "message": "Enter a number" + "enterANameToIdentifyTheUrl": { + "message": "Enter a name to identify the URL" }, - "enterCustodianToken": { - "message": "Enter your $1 token or add a new token" + "enterChainId": { + "message": "Enter Chain ID" }, "enterMaxSpendLimit": { "message": "Enter max spend limit" }, + "enterNetworkName": { + "message": "Enter network name" + }, "enterOptionalPassword": { "message": "Enter optional password" }, "enterPasswordContinue": { "message": "Enter password to continue" }, + "enterRpcUrl": { + "message": "Enter RPC URL" + }, + "enterSymbol": { + "message": "Enter symbol" + }, "enterTokenNameOrAddress": { "message": "Enter token name or paste address" }, @@ -1740,10 +1809,42 @@ "message": "Code: $1", "description": "Displayed error name for debugging purposes. $1 is the error name" }, + "errorPageContactSupport": { + "message": "Contact support", + "description": "Button for contact MM support" + }, + "errorPageDescribeUsWhatHappened": { + "message": "Describe what happened", + "description": "Button for submitting report to sentry" + }, + "errorPageInfo": { + "message": "Your information can’t be shown. Don’t worry, your wallet and funds are safe.", + "description": "Information banner shown in the error page" + }, + "errorPageMessageTitle": { + "message": "Error message", + "description": "Title for description, which is displayed for debugging purposes" + }, + "errorPageSentryFormTitle": { + "message": "Describe what happened", + "description": "In sentry feedback form, The title at the top of the feedback form." + }, + "errorPageSentryMessagePlaceholder": { + "message": "Sharing details like how we can reproduce the bug will help us fix the problem.", + "description": "In sentry feedback form, The placeholder for the feedback description input field." + }, + "errorPageSentrySuccessMessageText": { + "message": "Thanks! We will take a look soon.", + "description": "In sentry feedback form, The message displayed after a successful feedback submission." + }, "errorPageTitle": { "message": "MetaMask encountered an error", "description": "Title of generic error page" }, + "errorPageTryAgain": { + "message": "Try again", + "description": "Button for try again" + }, "errorStack": { "message": "Stack:", "description": "Title for error stack, which is displayed for debugging purposes" @@ -1786,25 +1887,25 @@ "existingRequestsBannerAlertDesc": { "message": "To view and confirm your most recent request, you'll need to approve or reject existing requests first." }, - "existingRpcUrl": { - "message": "This URL is associated with another chain ID." - }, "expandView": { "message": "Expand view" }, "experimental": { "message": "Experimental" }, + "exportYourData": { + "message": "Export your data" + }, + "exportYourDataButton": { + "message": "Download" + }, + "exportYourDataDescription": { + "message": "You can export data like your contacts and preferences." + }, "extendWalletWithSnaps": { "message": "Explore community-built Snaps to customize your web3 experience", "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, - "extensionInsallCompleteDescription": { - "message": "Return to the MetaMask Institutional product onboarding to connect your custodial or self-custodial accounts." - }, - "extensionInsallCompleteTitle": { - "message": "Extension install complete" - }, "externalExtension": { "message": "External extension" }, @@ -1820,9 +1921,6 @@ "failedToFetchChainId": { "message": "Could not fetch chain ID. Is your RPC URL correct?" }, - "failedToFetchTickerSymbolData": { - "message": "Ticker symbol verification data is currently unavailable, make sure that the symbol you have entered is correct. It will impact the conversion rates that you see for this network" - }, "failureMessage": { "message": "Something went wrong, and we were unable to complete the action" }, @@ -1836,9 +1934,6 @@ "message": "File import not working? Click here!", "description": "Helps user import their account from a JSON file" }, - "findTheRightChainId": { - "message": "Find the right one on:" - }, "flaskWelcomeUninstall": { "message": "you should uninstall this extension", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1894,15 +1989,8 @@ "function": { "message": "Function: $1" }, - "functionType": { - "message": "Function type" - }, - "fundYourWallet": { - "message": "Fund your wallet" - }, - "fundYourWalletDescription": { - "message": "Get started by adding some $1 to your wallet.", - "description": "$1 is the token symbol" + "fundingMethod": { + "message": "Funding method" }, "gas": { "message": "Gas" @@ -1914,8 +2002,8 @@ "message": "This gas fee has been suggested by $1. Overriding this may cause a problem with your transaction. Please reach out to $1 if you have questions.", "description": "$1 represents the Dapp's origin" }, - "gasIsETH": { - "message": "Gas is $1 " + "gasFee": { + "message": "Gas fee" }, "gasLimit": { "message": "Gas limit" @@ -1984,17 +2072,12 @@ "generalCameraErrorTitle": { "message": "Something went wrong...." }, + "generalDescription": { + "message": "Sync settings across devices, select network preferences, and track token data" + }, "genericExplorerView": { "message": "View account on $1" }, - "getStartedWithNFTs": { - "message": "Get $1 to buy NFTs", - "description": "$1 is the token symbol" - }, - "getStartedWithNFTsDescription": { - "message": "Get started with NFTs by adding some $1 to your wallet.", - "description": "$1 is the token symbol" - }, "goBack": { "message": "Go back" }, @@ -2007,6 +2090,9 @@ "gotIt": { "message": "Got it" }, + "grantExactAccess": { + "message": "Grant exact access" + }, "gwei": { "message": "GWEI" }, @@ -2048,15 +2134,15 @@ "hideAccount": { "message": "Hide account" }, + "hideAdvancedDetails": { + "message": "Hide advanced details" + }, "hideSeedPhrase": { "message": "Hide seed phrase" }, "hideSentitiveInfo": { "message": "Hide sensitive information" }, - "hideToken": { - "message": "Hide token" - }, "hideTokenPrompt": { "message": "Hide token?" }, @@ -2132,9 +2218,22 @@ "holdToRevealUnlockedLabel": { "message": "hold to reveal circle unlocked" }, + "howNetworkFeesWorkExplanation": { + "message": "Estimated fee required to process the transaction. The max fee is $1." + }, + "howQuotesWork": { + "message": "How quotes work" + }, + "howQuotesWorkExplanation": { + "message": "This quote has the best return of the quotes we searched. This is based on the swap rate, which includes bridging fees and a $1% MetaMask fee, minus gas fees. Gas fees depend on how busy the network is and how complex the transaction is." + }, "id": { "message": "ID" }, + "ifYouGetLockedOut": { + "message": "If you get locked out of the app or get a new device, you will lose your funds. Be sure to back up your Secret Recovery Phrase in $1 ", + "description": "$1 is the menu path to be shown with font weight bold" + }, "ignoreAll": { "message": "Ignore all" }, @@ -2144,12 +2243,6 @@ "imToken": { "message": "imToken" }, - "immediateAccessToYourNFTs": { - "message": "Immediately access your NFTs" - }, - "immediateAccessToYourTokens": { - "message": "Immediate access to your tokens" - }, "import": { "message": "Import", "description": "Button to import an account from a selected file" @@ -2222,6 +2315,9 @@ "inYourSettings": { "message": "in your Settings" }, + "included": { + "message": "included" + }, "infuraBlockedNotification": { "message": "MetaMask is unable to connect to the blockchain host. Review possible reasons $1.", "description": "$1 is a clickable link with with text defined by the 'here' key" @@ -2229,37 +2325,6 @@ "initialTransactionConfirmed": { "message": "Your initial transaction was confirmed by the network. Click OK to go back." }, - "inputLogicEmptyState": { - "message": "Only enter a number that you're comfortable with the third party spending now or in the future. You can always increase the spending cap later." - }, - "inputLogicEqualOrSmallerNumber": { - "message": "This allows the third party to spend $1 from your current balance.", - "description": "$1 is the current token balance in the account and the name of the current token" - }, - "inputLogicHigherNumber": { - "message": "This allows the third party to spend all your token balance until it reaches the cap or you revoke the spending cap. If this is not intended, consider setting a lower spending cap." - }, - "insightWarning": { - "message": "warning" - }, - "insightWarningCheckboxMessage": { - "message": "$1 the request by $2", - "description": "$1 is the action i.e. sign, confirm. $2 is the origin making the request." - }, - "insightWarningContentPlural": { - "message": "Review $1 before $2. Once made, the $3 is irreversible.", - "description": "$1 the 'insightWarnings' message (2 warnings) representing warnings, $2 is the action (i.e. signing) and $3 is the result (i.e. signature, transaction)" - }, - "insightWarningContentSingular": { - "message": "Review $1 before $2. Once made, the $3 is irreversible.", - "description": "$1 is the 'insightWarning' message (1 warning), $2 is the action (i.e. signing) and $3 is the result (i.e. signature, transaction)" - }, - "insightWarningHeader": { - "message": "This request may be risky" - }, - "insightWarnings": { - "message": "warnings" - }, "insightsFromSnap": { "message": "Insights from $1", "description": "$1 represents the name of the snap" @@ -2267,12 +2332,6 @@ "install": { "message": "Install" }, - "installExtension": { - "message": "Install extension" - }, - "installExtensionDescription": { - "message": "The institution-compliant version of the world's leading web3 wallet, MetaMask." - }, "installOrigin": { "message": "Install origin" }, @@ -2286,14 +2345,6 @@ "insufficientBalance": { "message": "Insufficient balance." }, - "insufficientCurrencyBuyOrDeposit": { - "message": "You do not have enough $1 in your account to pay for transaction fees on $2 network. $3 or deposit from another account.", - "description": "$1 is the native currency of the network, $2 is the name of the current network, $3 is the key 'buy' + the ticker symbol of the native currency of the chain wrapped in a button" - }, - "insufficientCurrencyDeposit": { - "message": "You do not have enough $1 in your account to pay for transaction fees on $2 network. Deposit $1 from another account.", - "description": "$1 is the native currency of the network, $2 is the name of the current network" - }, "insufficientFunds": { "message": "Insufficient funds." }, @@ -2318,9 +2369,6 @@ "invalidAssetType": { "message": "This asset is an NFT and needs to be re-added on the Import NFTs page found under the NFTs tab" }, - "invalidBlockExplorerURL": { - "message": "Invalid block explorer URL" - }, "invalidChainIdTooBig": { "message": "Invalid chain ID. The chain ID is too big." }, @@ -2387,13 +2435,14 @@ "jazzicons": { "message": "Jazzicons" }, - "jsDeliver": { - "message": "jsDeliver" - }, "jsonFile": { "message": "JSON File", "description": "format for importing an account" }, + "keepReminderOfSRP": { + "message": "Keep a reminder of your Secret Recovery Phrase somewhere safe. If you lose it, no one can help you get it back. Even worse, you won’t be able access to your wallet ever again. $1", + "description": "$1 is a learn more link" + }, "keyringAccountName": { "message": "Account name" }, @@ -2448,9 +2497,8 @@ "layer2Fees": { "message": "Layer 2 fees" }, - "learnCancelSpeeedup": { - "message": "Learn how to $1", - "description": "$1 is link to cancel or speed up transactions" + "learnHow": { + "message": "Learn how" }, "learnMore": { "message": "learn more" @@ -2459,6 +2507,9 @@ "message": "Want to $1 about gas?", "description": "$1 will be replaced by the learnMore translation key" }, + "learnMoreAboutPrivacy": { + "message": "Learn more about privacy best practices." + }, "learnMoreKeystone": { "message": "Learn More" }, @@ -2471,9 +2522,6 @@ "learnScamRisk": { "message": "scams and security risks." }, - "learnToBridge": { - "message": "Learn to bridge" - }, "leaveMetaMask": { "message": "Leave MetaMask?" }, @@ -2547,6 +2595,9 @@ "link": { "message": "Link" }, + "linkCentralizedExchanges": { + "message": "Link your Coinbase or Binance accounts to transfer crypto to MetaMask for free." + }, "links": { "message": "Links" }, @@ -2559,6 +2610,9 @@ "loadingScreenSnapMessage": { "message": "Please complete the transaction on the Snap." }, + "loadingTokenList": { + "message": "Loading token list" + }, "localhost": { "message": "Localhost 8545" }, @@ -2578,6 +2632,12 @@ "low": { "message": "Low" }, + "lowEstimatedReturnTooltipMessage": { + "message": "You’ll pay more than $1% of your starting amount in fees. Check your receiving amount and network fees." + }, + "lowEstimatedReturnTooltipTitle": { + "message": "High cost" + }, "lowGasSettingToolTipMessage": { "message": "Use $1 to wait for a cheaper price. Time estimates are much less accurate as prices are somewhat unpredictable.", "description": "$1 is key 'low' separated here so that it can be passed in with bold font-weight" @@ -2585,9 +2645,6 @@ "lowLowercase": { "message": "low" }, - "lowPriorityMessage": { - "message": "Future transactions will queue after this one." - }, "mainnet": { "message": "Ethereum Mainnet" }, @@ -2601,6 +2658,9 @@ "message": "Make sure nobody is looking", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "manageDefaultSettings": { + "message": "Manage default privacy settings" + }, "marketCap": { "message": "Market cap" }, @@ -2644,19 +2704,16 @@ "metaMaskConnectStatusParagraphTwo": { "message": "The connection status button shows if the website you’re visiting is connected to your currently selected account." }, + "metaMetricsIdNotAvailableError": { + "message": "Since you've never opted into MetaMetrics, there's no data to delete here." + }, "metadataModalSourceTooltip": { "message": "$1 is hosted on npm and $2 is this Snap’s unique identifier.", "description": "$1 is the snap name and $2 is the snap NPM id." }, - "metamaskInstitutionalVersion": { - "message": "MetaMask Institutional Version" - }, "metamaskNotificationsAreOff": { "message": "Wallet notifications are currently not active." }, - "metamaskPortfolio": { - "message": "MetaMask Portfolio." - }, "metamaskSwapsOfflineDescription": { "message": "MetaMask Swaps is undergoing maintenance. Please check back later." }, @@ -2679,9 +2736,6 @@ "message": "M", "description": "Shortened form of 'million'" }, - "mismatchAccount": { - "message": "Your selected account ($1) is different than the account trying to sign ($2)" - }, "mismatchedChainLinkText": { "message": "verify the network details", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." @@ -2708,17 +2762,22 @@ "missingSettingRequest": { "message": "Request here" }, - "mmiBuiltAroundTheWorld": { - "message": "MetaMask Institutional is designed and built around the world." + "more": { + "message": "more" }, - "mmiNewNFTDetectedInNFTsTabMessage": { - "message": "Let MetaMask Institutional automatically detect and display NFTs in your wallet." + "moreAccounts": { + "message": "+ $1 more accounts", + "description": "$1 is the number of accounts" }, - "mmiPasswordSetupDetails": { - "message": "This password will unlock your MetaMask Institutional extension only." + "moreNetworks": { + "message": "+ $1 more networks", + "description": "$1 is the number of networks" }, - "more": { - "message": "more" + "moreQuotes": { + "message": "More quotes" + }, + "multichainAddEthereumChainConfirmationDescription": { + "message": "You're adding this network to MetaMask and giving this site permission to use it." }, "multipleSnapConnectionWarning": { "message": "$1 wants to use $2 Snaps", @@ -2734,6 +2793,9 @@ "message": "Address", "description": "Label above address field in name component modal." }, + "nameAlreadyInUse": { + "message": "Name is already in use" + }, "nameInstructionsNew": { "message": "If you know this address, give it a nickname to recognize it in the future.", "description": "Instruction text in name component modal when value is not recognised." @@ -2786,23 +2848,24 @@ "message": "Choose a nickname...", "description": "Placeholder text for name input field in name component modal." }, - "nativePermissionRequestDescription": { - "message": "Do you want this site to do the following?", - "description": "Description below header used on Permission Connect screen for native permissions." - }, - "nativeToken": { - "message": "The native token on this network is $1. It is the token used for gas fees. ", - "description": "$1 represents the name of the native token on the current network" + "nativeNetworkPermissionRequestDescription": { + "message": "$1 is asking for your approval to:", + "description": "$1 represents dapp name" }, "nativeTokenScamWarningConversion": { "message": "Edit network details" }, "nativeTokenScamWarningDescription": { - "message": "This network doesn't match its associated chain ID or name. Many popular tokens use the name $1, making it a target for scams. Scammers may trick you into sending them more valuable currency in return. Verify everything before you continue.", - "description": "$1 represents the currency name" + "message": "The native token symbol does not match the expected symbol of the native token for the network with the associated chain ID. You have entered $1 while the expected token symbol is $2. Please verify you are connected to the correct chain.", + "description": "$1 represents the currency name, $2 represents the expected currency symbol" + }, + "nativeTokenScamWarningDescriptionExpectedTokenFallback": { + "message": "something else", + "description": "graceful fallback for when token symbol isn't found" }, "nativeTokenScamWarningTitle": { - "message": "This is a potential scam" + "message": "Unexpected Native Token Symbol", + "description": "Title for nativeTokenScamWarningDescription" }, "needHelp": { "message": "Need help? Contact $1", @@ -2830,12 +2893,12 @@ "network": { "message": "Network:" }, - "networkAddedSuccessfully": { - "message": "Network added successfully!" - }, "networkDetails": { "message": "Network details" }, + "networkFee": { + "message": "Network fee" + }, "networkIsBusy": { "message": "Network is busy. Gas prices are high and estimates are less accurate." }, @@ -2881,6 +2944,9 @@ "networkNamePolygon": { "message": "Polygon" }, + "networkNameSolana": { + "message": "Solana" + }, "networkNameTestnet": { "message": "Testnet" }, @@ -2890,12 +2956,12 @@ "networkOptions": { "message": "Network options" }, + "networkPermissionToast": { + "message": "Network permissions updated" + }, "networkProvider": { "message": "Network provider" }, - "networkSettingsChainIdDescription": { - "message": "The chain ID is used for signing transactions. It must match the chain ID returned by the network. You can enter a decimal or '0x'-prefixed hexadecimal number, but we will display the number in decimal." - }, "networkStatus": { "message": "Network status" }, @@ -2914,15 +2980,26 @@ "message": "We can't connect to $1", "description": "$1 represents the network name" }, + "networkSwitchMessage": { + "message": "Network switched to $1", + "description": "$1 represents the network name" + }, "networkURL": { "message": "Network URL" }, "networkURLDefinition": { "message": "The URL used to access this network." }, + "networkUrlErrorWarning": { + "message": "Attackers sometimes mimic sites by making small changes to the site address. Make sure you're interacting with the intended site before you continue. Punycode version: $1", + "description": "$1 replaced by RPC URL for network" + }, "networks": { "message": "Networks" }, + "networksSmallCase": { + "message": "networks" + }, "nevermind": { "message": "Nevermind" }, @@ -2973,6 +3050,9 @@ "newPrivacyPolicyTitle": { "message": "We’ve updated our privacy policy" }, + "newRpcUrl": { + "message": "New RPC URL" + }, "newTokensImportedMessage": { "message": "You’ve successfully imported $1.", "description": "$1 is the string of symbols of all the tokens imported" @@ -3028,12 +3108,12 @@ "noAccountsFound": { "message": "No accounts found for the given search query" }, - "noConnectedAccountDescription": { - "message": "Select an account you want to use on this site to continue." - }, "noConnectedAccountTitle": { "message": "MetaMask isn’t connected to this site" }, + "noConnectionDescription": { + "message": "To connect to a site, find and select the \"connect\" button. Remember MetaMask can only connect to sites on web3" + }, "noConversionRateAvailable": { "message": "No conversion rate available" }, @@ -3049,24 +3129,36 @@ "noNetworksFound": { "message": "No networks found for the given search query" }, + "noOptionsAvailableMessage": { + "message": "This trade route isn't available right now. Try changing the amount, network, or token and we'll find the best option." + }, "noSnaps": { "message": "You don't have any snaps installed." }, "noThanks": { "message": "No thanks" }, + "noTransactions": { + "message": "You have no transactions" + }, "noWebcamFound": { "message": "Your computer's webcam was not found. Please try again." }, "noWebcamFoundTitle": { "message": "Webcam not found" }, - "nonCustodialAccounts": { - "message": "MetaMask Institutional allows you to use non-custodial accounts, if you plan to use these accounts backup the Secret Recovery Phrase." + "nonContractAddressAlertDesc": { + "message": "You're sending call data to an address that isn't a contract. This could cause you to lose funds. Make sure you're using the correct address and network before continuing." + }, + "nonContractAddressAlertTitle": { + "message": "Potential mistake" }, "nonce": { "message": "Nonce" }, + "none": { + "message": "None" + }, "notBusy": { "message": "Not busy" }, @@ -3079,15 +3171,6 @@ "notEnoughGas": { "message": "Not enough gas" }, - "notRightNow": { - "message": "Not right now" - }, - "note": { - "message": "Note" - }, - "notePlaceholder": { - "message": "The approver will see this note when approving the transaction at the custodian." - }, "notificationDetail": { "message": "Details" }, @@ -3328,9 +3411,6 @@ "onboardingAdvancedPrivacyIPFSValid": { "message": "IPFS gateway URL is valid" }, - "onboardingAdvancedPrivacyNetworkButton": { - "message": "Add custom network" - }, "onboardingAdvancedPrivacyNetworkDescription": { "message": "We use Infura as our remote procedure call (RPC) provider to offer the most reliable and private access to Ethereum data we can. You can choose your own RPC, but remember that any RPC will receive your IP address and Ethereum wallet to make transactions. Read our $1 to learn more about how Infura handles data." }, @@ -3359,9 +3439,6 @@ "onboardingMetametricsInfuraTermsPolicy": { "message": "Privacy Policy" }, - "onboardingMetametricsModalTitle": { - "message": "Add custom network" - }, "onboardingMetametricsNeverCollect": { "message": "$1 clicks and views on the app are stored, but other details (like your public address) are not.", "description": "$1 represents `onboardingMetametricsNeverCollectEmphasis`" @@ -3428,13 +3505,6 @@ "onboardingPinExtensionTitle": { "message": "Your MetaMask install is complete!" }, - "onboardingPinMmiExtensionLabel": { - "message": "Pin MetaMask Institutional" - }, - "onboardingUsePhishingDetectionDescription": { - "message": "Phishing detection alerts rely on communication with $1. jsDeliver will have access to your IP address. View $2.", - "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" - }, "oneDayAbbreviation": { "message": "1D", "description": "Shortened form of '1 day'" @@ -3454,17 +3524,10 @@ "onekey": { "message": "OneKey" }, - "onlyAddTrustedNetworks": { - "message": "A malicious network provider can lie about the state of the blockchain and record your network activity. Only add custom networks you trust." - }, "onlyConnectTrust": { "message": "Only connect with sites you trust. $1", "description": "Text displayed above the buttons for connection confirmation. $1 is the link to the learn more web page." }, - "openCustodianApp": { - "message": "Open $1 app", - "description": "The $1 is the name of the Custodian that will be open" - }, "openFullScreenForLedgerWebHid": { "message": "Go to full screen to connect your Ledger.", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." @@ -3472,24 +3535,12 @@ "openInBlockExplorer": { "message": "Open in block explorer" }, - "openSeaNew": { - "message": "OpenSea" - }, - "operationFailed": { - "message": "Operation Failed" - }, "optional": { "message": "Optional" }, - "optionalWithParanthesis": { - "message": "(Optional)" - }, "options": { "message": "Options" }, - "or": { - "message": "or" - }, "origin": { "message": "Origin" }, @@ -3503,12 +3554,15 @@ "outdatedBrowserNotification": { "message": "Your browser is out of date. If you don't update your browser, you won't be able to get security patches and new features from MetaMask." }, + "overrideContentSecurityPolicyHeader": { + "message": "Override Content-Security-Policy header" + }, + "overrideContentSecurityPolicyHeaderDescription": { + "message": "This option is a workaround for a known issue in Firefox, where a dapp's Content-Security-Policy header may prevent the extension from loading properly. Disabling this option is not recommended unless required for specific web page compatibility." + }, "padlock": { "message": "Padlock" }, - "parameters": { - "message": "Parameters" - }, "participateInMetaMetrics": { "message": "Participate in MetaMetrics" }, @@ -3518,9 +3572,6 @@ "password": { "message": "Password" }, - "passwordMmiTermsWarning": { - "message": "I understand that MetaMask Institutional cannot recover this password for me. $1" - }, "passwordNotLongEnough": { "message": "Password not long enough" }, @@ -3540,33 +3591,30 @@ "passwordsDontMatch": { "message": "Passwords don't match" }, - "pasteJWTToken": { - "message": "Paste or drop your token here:" - }, "pastePrivateKey": { "message": "Enter your private key string here:", "description": "For importing an account from a private key" }, - "paymasterInUse": { - "message": "The gas for this transaction will be paid by a paymaster.", - "description": "Alert shown in transaction confirmation if paymaster in use." - }, "pending": { "message": "Pending" }, - "pendingTransactionInfo": { - "message": "This transaction will not process until that one is complete." + "pendingTransactionAlertMessage": { + "message": "This transaction won't go through until a previous transaction is complete. $1", + "description": "$1 represents the words 'how to cancel or speed up a transaction' in a hyperlink" }, - "pendingTransactionMultiple": { - "message": "You have ($1) pending transactions." - }, - "pendingTransactionSingle": { - "message": "You have (1) pending transaction.", - "description": "$1 is count of pending transactions" + "pendingTransactionAlertMessageHyperlink": { + "message": "Learn how to cancel or speed up a transaction.", + "description": "The text for the hyperlink in the pending transaction alert message" }, "permissionDetails": { "message": "Permission details" }, + "permissionFor": { + "message": "Permission for" + }, + "permissionFrom": { + "message": "Permission from" + }, "permissionRequested": { "message": "Requested now" }, @@ -3601,6 +3649,14 @@ "message": "Allow the website or snap to interact with $1.", "description": "The description for the `wallet_snap_*` permission. $1 is the name of the Snap." }, + "permission_assets": { + "message": "Display account assets in MetaMask.", + "description": "The description for the `endowment:assets` permission." + }, + "permission_assetsDescription": { + "message": "Allow $1 to provide asset information to the MetaMask client. The assets can be onchain or offchain.", + "description": "An extended description for the `endowment:assets` permission. $1 is the name of the Snap." + }, "permission_cronjob": { "message": "Schedule and execute periodic actions.", "description": "The description for the `snap_cronjob` permission" @@ -3645,6 +3701,14 @@ "message": "Let $1 access your preferred language from your MetaMask settings. This can be used to localize and display $1's content using your language.", "description": "An extended description for the `snap_getLocale` permission. $1 is the snap name." }, + "permission_getPreferences": { + "message": "See information like your preferred language and fiat currency.", + "description": "The description for the `snap_getPreferences` permission" + }, + "permission_getPreferencesDescription": { + "message": "Let $1 access information like your preferred language and fiat currency in your MetaMask settings. This helps $1 display content tailored to your preferences. ", + "description": "An extended description for the `snap_getPreferences` permission. $1 is the snap name." + }, "permission_homePage": { "message": "Display a custom screen", "description": "The description for the `endowment:page-home` permission" @@ -3713,6 +3777,14 @@ "message": "Allow $1 to display notifications within MetaMask. A short notification text can be triggered by a snap for actionable or time-sensitive information.", "description": "An extended description for the `snap_notify` permission. $1 is the snap name." }, + "permission_protocol": { + "message": "Provide protocol data for one or more chains.", + "description": "The description for the `endowment:protocol` permission." + }, + "permission_protocolDescription": { + "message": "Allow $1 to provide MetaMask with protocol data such as gas estimates or token information.", + "description": "An extended description for the `endowment:protocol` permission. $1 is the name of the Snap." + }, "permission_rpc": { "message": "Allow $1 to communicate directly with $2.", "description": "The description for the `endowment:rpc` permission. $1 is 'other snaps' or 'websites', $2 is the snap name." @@ -3774,7 +3846,7 @@ "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, "permission_walletSwitchEthereumChain": { - "message": "Switch to and use the following network", + "message": "Use your enabled networks", "description": "The label for the `wallet_switchEthereumChain` permission" }, "permission_webAssembly": { @@ -3794,15 +3866,33 @@ "permissionsPageEmptySubContent": { "message": "This is where you can see the permissions you've given to installed Snaps or connected sites." }, - "permissionsPageTourDescription": { - "message": "This is your control panel for managing permissions given to connected sites and installed Snaps." + "permitSimulationChange_approve": { + "message": "Spending cap" + }, + "permitSimulationChange_bidding": { + "message": "You bid" + }, + "permitSimulationChange_listing": { + "message": "You list" }, - "permissionsPageTourTitle": { - "message": "Connected sites are now permissions" + "permitSimulationChange_nft_listing": { + "message": "Listing price" + }, + "permitSimulationChange_receive": { + "message": "You receive" + }, + "permitSimulationChange_revoke": { + "message": "Spending cap" + }, + "permitSimulationChange_transfer": { + "message": "You send" }, "permitSimulationDetailInfo": { "message": "You're giving the spender permission to spend this many tokens from your account." }, + "permittedChainToastUpdate": { + "message": "$1 has access to $2." + }, "personalAddressDetected": { "message": "Personal address detected. Input the token contract address." }, @@ -3812,12 +3902,6 @@ "petnamesEnabledToggleDescription": { "message": "This lets you assign a nickname to any address. We’ll suggest names for addresses that you interact with when possible." }, - "pinExtensionDescription": { - "message": "Navigate to the extension menu and pin MetaMask Institutional for seamless access." - }, - "pinExtensionTitle": { - "message": "Pin extension" - }, "pinToTop": { "message": "Pin to top" }, @@ -3832,19 +3916,16 @@ "message": "+ $1 more", "description": "$1 is a number of additional but unshown items in a list- this message will be shown in place of those items" }, - "popularCustomNetworks": { - "message": "Popular custom networks" - }, "popularNetworkAddToolTip": { "message": "Some of these networks rely on third parties. The connections may be less reliable or enable third-parties to track activity. $1", "description": "$1 is Learn more link" }, + "popularNetworks": { + "message": "Popular networks" + }, "portfolio": { "message": "Portfolio" }, - "portfolioDashboard": { - "message": "Portfolio Dashboard" - }, "preparingSwap": { "message": "Preparing swap..." }, @@ -4003,9 +4084,18 @@ "quoteRate": { "message": "Quote rate" }, + "quotedReceiveAmount": { + "message": "$1 receive amount" + }, + "quotedTotalCost": { + "message": "$1 total cost" + }, "rank": { "message": "Rank" }, + "rateIncludesMMFee": { + "message": "Rate includes $1% fee" + }, "reAddAccounts": { "message": "re-add any other accounts" }, @@ -4018,7 +4108,10 @@ "receive": { "message": "Receive" }, - "recipientAddressPlaceholder": { + "receiveCrypto": { + "message": "Receive crypto" + }, + "recipientAddressPlaceholderNew": { "message": "Enter public address (0x) or domain name" }, "recommendedGasLabel": { @@ -4072,8 +4165,11 @@ "rejected": { "message": "Rejected" }, - "remember": { - "message": "Remember:" + "rememberSRPIfYouLooseAccess": { + "message": "Remember, if you lose your Secret Recovery Phrase, you lose access to your wallet. $1 to keep this set of words safe so you can always access your funds." + }, + "reminderSet": { + "message": "Reminder set!" }, "remove": { "message": "Remove" @@ -4084,12 +4180,6 @@ "removeAccountDescription": { "message": "This account will be removed from your wallet. Please make sure you have the original Secret Recovery Phrase or private key for this imported account before continuing. You can import or create accounts again from the account drop-down. " }, - "removeJWT": { - "message": "Remove custodian token" - }, - "removeJWTDescription": { - "message": "Are you sure you want to remove this token? All accounts assigned to this token will be removed from extension as well: " - }, "removeKeyringSnap": { "message": "Removing this Snap removes these accounts from MetaMask:" }, @@ -4127,35 +4217,28 @@ "reportIssue": { "message": "Report an issue" }, - "requestFlaggedAsMaliciousFallbackCopyReason": { - "message": "The security provider has not shared additional details" - }, - "requestFlaggedAsMaliciousFallbackCopyReasonTitle": { - "message": "Request flagged as malicious" - }, "requestFrom": { "message": "Request from" }, "requestFromInfo": { "message": "This is the site asking for your signature." }, + "requestFromInfoSnap": { + "message": "This is the Snap asking for your signature." + }, "requestFromTransactionDescription": { "message": "This is the site asking for your confirmation." }, - "requestMayNotBeSafe": { - "message": "Request may not be safe" - }, - "requestMayNotBeSafeError": { - "message": "The security provider didn't detect any known malicious activity, but it still may not be safe to continue." - }, - "requestNotVerified": { - "message": "Request not verified" + "requestingFor": { + "message": "Requesting for" }, - "requestNotVerifiedError": { - "message": "Because of an error, this request was not verified by the security provider. Proceed with caution." + "requestingForAccount": { + "message": "Requesting for $1", + "description": "Name of Account" }, - "requestsAwaitingAcknowledgement": { - "message": "requests waiting to be acknowledged" + "requestingForNetwork": { + "message": "Requesting for $1", + "description": "Name of Network" }, "required": { "message": "Required" @@ -4184,9 +4267,6 @@ "restoreUserData": { "message": "Restore user data" }, - "restoreUserDataDescription": { - "message": "You can restore data like contacts and preferences from a backup file." - }, "resultPageError": { "message": "Error" }, @@ -4245,17 +4325,29 @@ "revealTheSeedPhrase": { "message": "Reveal seed phrase" }, + "review": { + "message": "Review" + }, + "reviewAlert": { + "message": "Review alert" + }, "reviewAlerts": { "message": "Review alerts" }, + "reviewPermissions": { + "message": "Review permissions" + }, "revokePermission": { "message": "Revoke permission" }, - "revokeSpendingCapTooltipText": { - "message": "This third party will be unable to spend any more of your current or future tokens." + "revokeSimulationDetailsDesc": { + "message": "You're removing someone's permission to spend tokens from your account." + }, + "rpcNameOptional": { + "message": "RPC Name (Optional)" }, "rpcUrl": { - "message": "New RPC URL" + "message": "RPC URL" }, "safeTransferFrom": { "message": "Safe transfer from" @@ -4284,6 +4376,9 @@ "searchTokens": { "message": "Search tokens" }, + "searchTokensByNameOrAddress": { + "message": "Search tokens by name or address" + }, "secretRecoveryPhrase": { "message": "Secret Recovery Phrase" }, @@ -4300,16 +4395,29 @@ "message": "Security alerts" }, "securityAlertsDescription": { - "message": "This feature alerts you to malicious activity by actively reviewing transaction and signature requests. $1", + "message": "This feature alerts you to malicious or unusual activity by actively reviewing transaction and signature requests. $1", "description": "Link to learn more about security alerts" }, "securityAndPrivacy": { "message": "Security & privacy" }, + "securityDescription": { + "message": "Reduce your chances of joining unsafe networks and protect your accounts" + }, + "securityMessageLinkForNetworks": { + "message": "network scams and security risks" + }, + "securityPrivacyPath": { + "message": "Settings > Security & Privacy." + }, "securityProviderPoweredBy": { "message": "Powered by $1", "description": "The security provider that is providing data" }, + "seeAllPermissions": { + "message": "See all permissions", + "description": "Used for revealing more content (e.g. permission list, etc.)" + }, "seeDetails": { "message": "See details" }, @@ -4325,13 +4433,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "Secure my wallet (recommended)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Write down and store in multiple secret places" - }, "seedPhraseIntroSidebarBulletOne": { - "message": "Save in a password manager" + "message": "Write down and store in multiple secret places" }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Store in a safe deposit box" }, "seedPhraseIntroSidebarCopyOne": { @@ -4379,36 +4484,33 @@ "selectAll": { "message": "Select all" }, - "selectAllAccounts": { - "message": "Select all accounts" - }, "selectAnAccount": { "message": "Select an account" }, "selectAnAccountAlreadyConnected": { "message": "This account has already been connected to MetaMask" }, - "selectAnAccountHelp": { - "message": "Select the custodian accounts to use in MetaMask Institutional." - }, "selectEnableDisplayMediaPrivacyPreference": { "message": "Turn on Display NFT Media" }, "selectHdPath": { "message": "Select HD path" }, - "selectJWT": { - "message": "Select token" - }, "selectNFTPrivacyPreference": { "message": "Enable NFT Autodetection" }, "selectPathHelp": { "message": "If you don't see the accounts you expect, try switching the HD path or current selected network." }, + "selectRpcUrl": { + "message": "Select RPC URL" + }, "selectType": { "message": "Select Type" }, + "selectedAccountMismatch": { + "message": "Different account selected" + }, "selectingAllWillAllow": { "message": "Selecting all will allow this site to view all of your current accounts. Make sure you trust this site." }, @@ -4459,18 +4561,15 @@ "message": "Warning: you are about to send to a token contract which could result in a loss of funds. $1", "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning" }, - "sendingZeroAmount": { - "message": "You are sending 0 $1." - }, "sepolia": { "message": "Sepolia test network" }, - "setAdvancedPrivacySettingsDetails": { - "message": "MetaMask uses these trusted third-party services to enhance product usability and safety." - }, "setApprovalForAll": { "message": "Set approval for all" }, + "setApprovalForAllRedesignedTitle": { + "message": "Withdrawal request" + }, "setApprovalForAllTitle": { "message": "Approve $1 with no spend limit", "description": "The token symbol that is being approved" @@ -4481,6 +4580,9 @@ "settings": { "message": "Settings" }, + "settingsOptimisedForEaseOfUseAndSecurity": { + "message": "Settings are optimised for ease of use and security. Change these any time." + }, "settingsSearchMatchingNotFound": { "message": "No matching results found." }, @@ -4493,6 +4595,9 @@ "showAccount": { "message": "Show account" }, + "showAdvancedDetails": { + "message": "Show advanced details" + }, "showExtensionInFullSizeView": { "message": "Show extension in full-size view" }, @@ -4527,6 +4632,9 @@ "showMore": { "message": "Show more" }, + "showNativeTokenAsMainBalance": { + "message": "Show native token as main balance" + }, "showNft": { "message": "Show NFT" }, @@ -4542,39 +4650,57 @@ "showTestnetNetworksDescription": { "message": "Select this to show test networks in network list" }, - "sigRequest": { - "message": "Signature request" - }, "sign": { "message": "Sign" }, "signatureRequest": { "message": "Signature request" }, - "signatureRequestGuidance": { - "message": "Only sign this message if you fully understand the content and trust the requesting site." + "signature_decoding_bid_nft_tooltip": { + "message": "The NFT will be reflected in your wallet, when the bid is accepted." + }, + "signature_decoding_list_nft_tooltip": { + "message": "Expect changes only if someone buys your NFTs." }, "signed": { "message": "Signed" }, - "signin": { - "message": "Sign-In" - }, "signing": { "message": "Signing" }, "signingInWith": { "message": "Signing in with" }, + "signingWith": { + "message": "Signing with" + }, + "simulationApproveHeading": { + "message": "Withdraw" + }, + "simulationDetailsApproveDesc": { + "message": "You're giving someone else permission to withdraw NFTs from your account." + }, + "simulationDetailsERC20ApproveDesc": { + "message": "You're giving someone else permission to spend this amount from your account." + }, "simulationDetailsFiatNotAvailable": { "message": "Not Available" }, "simulationDetailsIncomingHeading": { "message": "You receive" }, + "simulationDetailsNoChanges": { + "message": "No changes" + }, "simulationDetailsOutgoingHeading": { "message": "You send" }, + "simulationDetailsRevokeSetApprovalForAllDesc": { + "message": "You're removing someone else's permission to withdraw NFTs from your account." + }, + "simulationDetailsSetApprovalForAllDesc": { + "message": "You're giving permission for someone else to withdraw NFTs from your account." + }, "simulationDetailsTitle": { "message": "Estimated changes" }, @@ -4588,15 +4714,21 @@ "simulationDetailsTransactionReverted": { "message": "This transaction is likely to fail" }, + "simulationDetailsUnavailable": { + "message": "Unavailable" + }, "simulationErrorMessageV2": { "message": "We were not able to estimate gas. There might be an error in the contract and this transaction may fail." }, "simulationsSettingDescription": { - "message": "Turn this on to estimate balance changes of transactions before you confirm them. This doesn't guarantee the final outcome of your transactions. $1" + "message": "Turn this on to estimate balance changes of transactions and signatures before you confirm them. This doesn't guarantee their final outcome. $1" }, "simulationsSettingSubHeader": { "message": "Estimate balance changes" }, + "singleNetwork": { + "message": "1 network" + }, "siweIssued": { "message": "Issued" }, @@ -4621,6 +4753,30 @@ "skipAccountSecurityDetails": { "message": "I understand that until I back up my Secret Recovery Phrase, I may lose my accounts and all of their assets." }, + "slideBridgeDescription": { + "message": "Move across 9 chains, all within your wallet" + }, + "slideBridgeTitle": { + "message": "Ready to bridge?" + }, + "slideCashOutDescription": { + "message": "Sell your crypto for cash" + }, + "slideCashOutTitle": { + "message": "Cash out with MetaMask" + }, + "slideDebitCardDescription": { + "message": "Available in selected regions" + }, + "slideDebitCardTitle": { + "message": "MetaMask debit card" + }, + "slideFundWalletDescription": { + "message": "Get started by adding funds" + }, + "slideFundWalletTitle": { + "message": "Fund your wallet" + }, "smartContracts": { "message": "Smart contracts" }, @@ -4648,16 +4804,18 @@ "smartTransactionSuccess": { "message": "Your transaction is complete" }, - "smartTransactionTakingTooLong": { - "message": "Sorry for the wait" - }, - "smartTransactionTakingTooLongDescription": { - "message": "If your transaction is not finalized within $1, it will be canceled and you will not be charged for gas.", - "description": "$1 is remaining time in seconds" - }, "smartTransactions": { "message": "Smart Transactions" }, + "smartTransactionsEnabledDescription": { + "message": " and MEV protection. Now on by default." + }, + "smartTransactionsEnabledLink": { + "message": "Higher success rates" + }, + "smartTransactionsEnabledTitle": { + "message": "Transactions just got smarter" + }, "snapAccountCreated": { "message": "Account created" }, @@ -4797,7 +4955,7 @@ "message": "Snaps connected" }, "snapsNoInsight": { - "message": "The snap didn't return any insight" + "message": "No insight to show" }, "snapsPrivacyWarningFirstMessage": { "message": "You acknowledge that any Snap that you install is a Third Party Service, unless otherwise identified, as defined in the Consensys $1. Your use of Third Party Services is governed by separate terms and conditions set forth by the Third Party Service provider. Consensys does not recommend the use of any Snap by any particular person for any particular reason. You access, rely upon or use the Third Party Service at your own risk. Consensys disclaims all responsibility and liability for any losses on account of your use of Third Party Services.", @@ -4824,8 +4982,17 @@ "message": "Contact the creators of $1 for further support.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "someNetworksMayPoseSecurity": { - "message": "Some networks may pose security and/or privacy risks. Understand the risks before adding & using a network." + "solanaSupportSectionTitle": { + "message": "Solana" + }, + "solanaSupportToggleDescription": { + "message": "Turning on this feature will give you the option to add a Solana Account to your MetaMask Extension derived from your existing Secret Recovery Phrase. This is an experimental Beta feature, so you should use it at your own risk." + }, + "solanaSupportToggleTitle": { + "message": "Enable \"Add a new Solana account (Beta)\"" + }, + "someNetworks": { + "message": "$1 networks" }, "somethingDoesntLookRight": { "message": "Something doesn't look right? $1", @@ -4837,9 +5004,34 @@ "somethingWentWrong": { "message": "Oops! Something went wrong." }, + "sortBy": { + "message": "Sort by" + }, + "sortByAlphabetically": { + "message": "Alphabetically (A-Z)" + }, + "sortByDecliningBalance": { + "message": "Declining balance ($1 high-low)", + "description": "Indicates a descending order based on token fiat balance. $1 is the preferred currency symbol" + }, "source": { "message": "Source" }, + "spamModalBlockedDescription": { + "message": "This site will be blocked for 1 minute." + }, + "spamModalBlockedTitle": { + "message": "You've temporarily blocked this site" + }, + "spamModalDescription": { + "message": "If you're being spammed with multiple requests, you can temporarily block the site." + }, + "spamModalTemporaryBlockButton": { + "message": "Temporarily block this site" + }, + "spamModalTitle": { + "message": "We've noticed multiple requests" + }, "speed": { "message": "Speed" }, @@ -4880,15 +5072,20 @@ "spender": { "message": "Spender" }, + "spenderTooltipDesc": { + "message": "This is the address that will be able to withdraw your NFTs." + }, + "spenderTooltipERC20ApproveDesc": { + "message": "This is the address that will be able to spend your tokens on your behalf." + }, "spendingCap": { "message": "Spending cap" }, - "spendingCapError": { - "message": "Error: Enter numbers only" + "spendingCapTooltipDesc": { + "message": "This is the amount of tokens the spender will be able to access on your behalf." }, - "spendingCapErrorDescription": { - "message": "Only enter a number that you're comfortable with $1 accessing now or in the future. You can always increase the token limit later.", - "description": "$1 is origin of the site requesting the token limit" + "spendingCaps": { + "message": "Spending caps" }, "srpInputNumberOfWords": { "message": "I have a $1-word phrase", @@ -4977,14 +5174,6 @@ "stake": { "message": "Stake" }, - "startYourJourney": { - "message": "Start your journey with $1", - "description": "$1 is the token symbol" - }, - "startYourJourneyDescription": { - "message": "Get started with web3 by adding some $1 to your wallet.", - "description": "$1 is the token symbol" - }, "stateLogError": { "message": "Error in retrieving state logs." }, @@ -5020,6 +5209,13 @@ "message": "Download, set up, and enter your password to unlock $1.", "description": "$1 represents the `ledgerLiveApp` localization value" }, + "step1OneKeyWallet": { + "message": "Connect your OneKey" + }, + "step1OneKeyWalletMsg": { + "message": "Plug your OneKey directly into your computer and unlock it. Make sure you use the correct passphrase.", + "description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key" + }, "step1TrezorWallet": { "message": "Connect your Trezor" }, @@ -5049,10 +5245,6 @@ "stxCancelledSubDescription": { "message": "Try your swap again. We’ll be here to protect you against similar risks next time." }, - "stxEstimatedCompletion": { - "message": "Estimated completion in < $1", - "description": "$1 is remeaning time in minutes and seconds, e.g. 0:10" - }, "stxFailure": { "message": "Swap failed" }, @@ -5105,18 +5297,21 @@ "message": "Suggested by $1", "description": "$1 is the snap name" }, + "suggestedCurrencySymbol": { + "message": "Suggested currency symbol:" + }, "suggestedTokenName": { "message": "Suggested name:" }, - "suggestedTokenSymbol": { - "message": "Suggested ticker symbol:" - }, "support": { "message": "Support" }, "supportCenter": { "message": "Visit our support center" }, + "supportMultiRpcInformation": { + "message": "We now support multiple RPCs for a single network. Your most recent RPC has been selected as the default one to resolve conflicting information." + }, "surveyConversion": { "message": "Take our survey" }, @@ -5233,6 +5428,14 @@ "swapGasFeesDetails": { "message": "Gas fees are estimated and will fluctuate based on network traffic and transaction complexity." }, + "swapGasFeesExplanation": { + "message": "MetaMask doesn't make money from gas fees. These fees are estimates and can change based on how busy the network is and how complex a transaction is. Learn more $1.", + "description": "$1 is a link (text in link can be found at 'swapGasFeesSummaryLinkText')" + }, + "swapGasFeesExplanationLinkText": { + "message": "here", + "description": "Text for link in swapGasFeesExplanation" + }, "swapGasFeesLearnMore": { "message": "Learn more about gas fees" }, @@ -5243,9 +5446,19 @@ "message": "Gas fees are paid to crypto miners who process transactions on the $1 network. MetaMask does not profit from gas fees.", "description": "$1 is the selected network, e.g. Ethereum or BSC" }, + "swapGasIncludedTooltipExplanation": { + "message": "This quote incorporates gas fees by adjusting the token amount sent or received. You may receive ETH in a separate transaction on your activity list." + }, + "swapGasIncludedTooltipExplanationLinkText": { + "message": "Learn more about gas fees" + }, "swapHighSlippage": { "message": "High slippage" }, + "swapIncludesGasAndMetaMaskFee": { + "message": "Includes gas and a $1% MetaMask fee", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." + }, "swapIncludesMMFee": { "message": "Includes a $1% MetaMask fee.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." @@ -5454,6 +5667,10 @@ "swapTokenVerifiedOn1SourceTitle": { "message": "Potentially inauthentic token" }, + "swapTokenVerifiedSources": { + "message": "Confirmed by $1 sources. Verify on $2.", + "description": "$1 the number of sources that have verified the token, $2 points the user to a block explorer as a place they can verify information about the token." + }, "swapTooManyDecimalsError": { "message": "$1 allows up to $2 decimals", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -5512,8 +5729,9 @@ "message": "$1 is now active on $2", "description": "$1 represents the account name, $2 represents the network name" }, - "switchedTo": { - "message": "You're now using" + "switchedNetworkToastMessageNoOrigin": { + "message": "You're now using $1", + "description": "$1 represents the network name" }, "switchingNetworksCancelsPendingConfirmations": { "message": "Switching networks will cancel all pending confirmations" @@ -5542,8 +5760,8 @@ "termsOfUseTitle": { "message": "Our Terms of Use have updated" }, - "testNetworks": { - "message": "Test networks" + "testnets": { + "message": "Testnets" }, "theme": { "message": "Theme" @@ -5551,9 +5769,6 @@ "themeDescription": { "message": "Choose your preferred MetaMask theme." }, - "thingsToKeep": { - "message": "Keep in mind:" - }, "thirdPartySoftware": { "message": "Third-party software notice", "description": "Title of a popup modal displayed when installing a snap for the first time." @@ -5568,6 +5783,12 @@ "tips": { "message": "Tips" }, + "tipsForUsingAWallet": { + "message": "Tips for using a wallet" + }, + "tipsForUsingAWalletDescription": { + "message": "Adding tokens unlocks more ways to use web3." + }, "to": { "message": "To" }, @@ -5575,44 +5796,8 @@ "message": "To: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "You’re at risk for phishing attacks. Protect yourself by turning off eth_sign." - }, - "toggleEthSignDescriptionField": { - "message": "If you enable this setting, you might get signature requests that aren’t readable. By signing a message you don't understand, you could be agreeing to give away your funds and NFTs." - }, - "toggleEthSignField": { - "message": "Eth_sign requests" - }, - "toggleEthSignModalBannerBoldText": { - "message": " you might be getting scammed" - }, - "toggleEthSignModalBannerText": { - "message": "If you've been asked to turn this setting on," - }, - "toggleEthSignModalCheckBox": { - "message": "I understand that I can lose all of my funds and NFTs if I enable eth_sign requests. " - }, - "toggleEthSignModalDescription": { - "message": "Allowing eth_sign requests can make you vulnerable to phishing attacks. Always review the URL and be careful when signing messages that contain code." - }, - "toggleEthSignModalFormError": { - "message": "The text is incorrect" - }, - "toggleEthSignModalFormLabel": { - "message": "Enter “I only sign what I understand” to continue" - }, - "toggleEthSignModalFormValidation": { - "message": "I only sign what I understand" - }, - "toggleEthSignModalTitle": { - "message": "Use at your own risk" - }, - "toggleEthSignOff": { - "message": "OFF (Recommended)" - }, - "toggleEthSignOn": { - "message": "ON (Not recommended)" + "toggleDecodeDescription": { + "message": "We use 4byte.directory and Sourcify services to decode and display more readable transaction data. This helps you understand the outcome of pending and past transactions, but can result in your IP address being shared." }, "token": { "message": "Token" @@ -5635,9 +5820,6 @@ "tokenDecimalFetchFailed": { "message": "Token decimal required. Find it on: $1" }, - "tokenDecimalTitle": { - "message": "Token decimal:" - }, "tokenDetails": { "message": "Token details" }, @@ -5650,12 +5832,12 @@ "tokenList": { "message": "Token lists" }, + "tokenMarketplace": { + "message": "Token marketplace" + }, "tokenScamSecurityRisk": { "message": "token scams and security risks" }, - "tokenShowUp": { - "message": "Your tokens may not automatically show up in your wallet. " - }, "tokenStandard": { "message": "Token standard" }, @@ -5726,6 +5908,12 @@ "transactionErrored": { "message": "Transaction encountered an error." }, + "transactionFailedBannerMessage": { + "message": "This transaction would have cost you extra fees, so we stopped it. Your money is still in your wallet." + }, + "transactionFlowNetwork": { + "message": "Network" + }, "transactionHistoryBaseFee": { "message": "Base fee (GWEI)" }, @@ -5747,9 +5935,6 @@ "transactionHistoryTotalGasFee": { "message": "Total gas fee" }, - "transactionNote": { - "message": "Transaction note" - }, "transactionResubmitted": { "message": "Transaction resubmitted with estimated gas fee increased to $1 at $2" }, @@ -5768,9 +5953,15 @@ "transfer": { "message": "Transfer" }, + "transferCrypto": { + "message": "Transfer crypto" + }, "transferFrom": { "message": "Transfer from" }, + "transferRequest": { + "message": "Transfer request" + }, "trillionAbbreviation": { "message": "T", "description": "Shortened form of 'trillion'" @@ -5850,9 +6041,6 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, - "unMatchedChain": { - "message": "According to our records, this URL does not match a known provider for this chain ID." - }, "unapproved": { "message": "Unapproved" }, @@ -5865,9 +6053,6 @@ "unknownCollection": { "message": "Unnamed collection" }, - "unknownNetwork": { - "message": "Unknown private network" - }, "unknownNetworkForKeyEntropy": { "message": "Unknown network", "description": "Displayed on places like Snap install warning when regular name is not available." @@ -5895,21 +6080,28 @@ "message": "Sending NFT (ERC-721) tokens is not currently supported", "description": "This is an error message we show the user if they attempt to send an NFT asset type, for which currently don't support sending" }, - "unverifiedContractAddressMessage": { - "message": "We cannot verify this contract. Make sure you trust this address." - }, "upArrow": { "message": "up arrow" }, "update": { "message": "Update" }, + "updateEthereumChainConfirmationDescription": { + "message": "This site is requesting to update your default network URL. You can edit defaults and network information any time." + }, + "updateNetworkConfirmationTitle": { + "message": "Update $1", + "description": "$1 represents network name" + }, "updateOrEditNetworkInformations": { "message": "Update your information or" }, "updateRequest": { "message": "Update request" }, + "updatedRpcForNetworks": { + "message": "Network RPCs Updated" + }, "uploadDropFile": { "message": "Drop your file here" }, @@ -5919,15 +6111,9 @@ "urlErrorMsg": { "message": "URLs require the appropriate HTTP/HTTPS prefix." }, - "urlExistsErrorMsg": { - "message": "This URL is currently used by the $1 network." - }, "use4ByteResolution": { "message": "Decode smart contracts" }, - "use4ByteResolutionDescription": { - "message": "To improve user experience, we customize the activity tab with messages based on the smart contracts you interact with. MetaMask uses a service called 4byte.directory to decode data and show you a version of a smart contract that's easier to read. This helps reduce your chances of approving malicious smart contract actions, but can result in your IP address being shared." - }, "useMultiAccountBalanceChecker": { "message": "Batch account balance requests" }, @@ -5956,9 +6142,6 @@ "message": "chainid.network", "description": "useSafeChainsListValidationWebsite is separated from the rest of the text so that we can bold the third party service name in the middle of them" }, - "useSiteSuggestion": { - "message": "Use site suggestion" - }, "useTokenDetectionPrivacyDesc": { "message": "Automatically displaying tokens sent to your account involves communication with third party servers to fetch token’s images. Those serves will have access to your IP address." }, @@ -5971,9 +6154,6 @@ "userOpContractDeployError": { "message": "Contract deployment from a smart contract account is not supported" }, - "verifyContractDetails": { - "message": "Verify third-party details" - }, "version": { "message": "Version" }, @@ -5983,15 +6163,15 @@ "viewActivity": { "message": "View activity" }, - "viewAllDetails": { - "message": "View all details" - }, "viewAllQuotes": { "message": "view all quotes" }, "viewContact": { "message": "View contact" }, + "viewDetails": { + "message": "View details" + }, "viewMore": { "message": "View more" }, @@ -6015,9 +6195,6 @@ "viewTransaction": { "message": "View transaction" }, - "viewinCustodianApp": { - "message": "View in custodian app" - }, "viewinExplorer": { "message": "View $1 in explorer", "description": "$1 is the action type. e.g (Account, Transaction, Swap)" @@ -6034,25 +6211,9 @@ "walletConnectionGuide": { "message": "our hardware wallet connection guide" }, - "walletCreationSuccessDetail": { - "message": "You’ve successfully protected your wallet. Keep your Secret Recovery Phrase safe and secret -- it’s your responsibility!" - }, - "walletCreationSuccessReminder1": { - "message": "MetaMask can’t recover your Secret Recovery Phrase." - }, - "walletCreationSuccessReminder2": { - "message": "MetaMask will never ask you for your Secret Recovery Phrase." - }, - "walletCreationSuccessReminder3": { - "message": "$1 with anyone or risk your funds being stolen", - "description": "$1 is separated as walletCreationSuccessReminder3BoldSection so that we can bold it" - }, - "walletCreationSuccessReminder3BoldSection": { - "message": "Never share your Secret Recovery Phrase", - "description": "This string is localized separately from walletCreationSuccessReminder3 so that we can bold it" - }, - "walletCreationSuccessTitle": { - "message": "Wallet creation successful" + "walletProtectedAndReadyToUse": { + "message": "Your wallet is protected and ready to use. You can find your Secret Recovery Phrase in $1 ", + "description": "$1 is the menu path to be shown with font weight bold" }, "wantToAddThisNetwork": { "message": "Want to add this network?" @@ -6067,9 +6228,16 @@ "message": "Warning from $1", "description": "$1 represents the name of the snap" }, - "warningTooltipText": { - "message": "$1 The third party could spend your entire token balance without further notice or consent. Protect yourself by customizing a lower spending cap.", - "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour" + "watchEthereumAccountsDescription": { + "message": "Turning this option on will give you the ability to watch Ethereum accounts via a public address or ENS name. For feedback on this Beta feature please complete this $1.", + "description": "$1 is the link to a product feedback form" + }, + "watchEthereumAccountsToggle": { + "message": "Watch Ethereum Accounts (Beta)" + }, + "watchOutMessage": { + "message": "Beware of $1.", + "description": "$1 is a link with text that is provided by the 'securityMessageLinkForNetworks' key" }, "weak": { "message": "Weak" @@ -6093,7 +6261,7 @@ "message": "Welcome back!" }, "welcomeExploreDescription": { - "message": "Store, send and spend crypto currencies and assets." + "message": "Store, send, and spend crypto currencies and assets." }, "welcomeExploreTitle": { "message": "Explore decentralized apps" @@ -6117,6 +6285,15 @@ "whatsThis": { "message": "What's this?" }, + "willApproveAmountForBridging": { + "message": "This will approve $1 for bridging." + }, + "willApproveAmountForBridgingHardware": { + "message": "You’ll need to confirm two transactions on your hardware wallet." + }, + "withdrawing": { + "message": "Withdrawing" + }, "wrongNetworkName": { "message": "According to our records, the network name may not correctly match this chain ID." }, @@ -6126,16 +6303,12 @@ "you": { "message": "You" }, - "youHaveAddedAll": { - "message": "You've added all the popular networks. You can discover more networks $1 Or you can $2", - "description": "$1 is a link with the text 'here' and $2 is a button with the text 'add more networks manually'" + "youDeclinedTheTransaction": { + "message": "You declined the transaction." }, "youNeedToAllowCameraAccess": { "message": "You need to allow camera access to use this feature." }, - "youSign": { - "message": "You are signing" - }, "yourAccounts": { "message": "Your accounts" }, @@ -6145,9 +6318,15 @@ "yourBalance": { "message": "Your balance" }, + "yourBalanceIsAggregated": { + "message": "Your balance is aggregated" + }, "yourNFTmayBeAtRisk": { "message": "Your NFT may be at risk" }, + "yourNetworks": { + "message": "Your networks" + }, "yourPrivateSeedPhrase": { "message": "Your Secret Recovery Phrase" }, @@ -6157,6 +6336,9 @@ "yourTransactionJustConfirmed": { "message": "We weren't able to cancel your transaction before it was confirmed on the blockchain." }, + "yourWalletIsReady": { + "message": "Your wallet is ready" + }, "zeroGasPriceOnSpeedUpError": { "message": "Zero gas price on speed up" } diff --git a/app/build-types/beta/images/icon-128.png b/app/build-types/beta/images/icon-128.png index 95b2ec2c773e..547e7cf55278 100644 Binary files a/app/build-types/beta/images/icon-128.png and b/app/build-types/beta/images/icon-128.png differ diff --git a/app/build-types/beta/images/icon-16.png b/app/build-types/beta/images/icon-16.png index e1c32722f144..671cb3fcf061 100644 Binary files a/app/build-types/beta/images/icon-16.png and b/app/build-types/beta/images/icon-16.png differ diff --git a/app/build-types/beta/images/icon-19.png b/app/build-types/beta/images/icon-19.png index 5073efb79648..80f0d7f20142 100644 Binary files a/app/build-types/beta/images/icon-19.png and b/app/build-types/beta/images/icon-19.png differ diff --git a/app/build-types/beta/images/icon-32.png b/app/build-types/beta/images/icon-32.png index f0ac3fb28bdc..dd6b2b0f3532 100644 Binary files a/app/build-types/beta/images/icon-32.png and b/app/build-types/beta/images/icon-32.png differ diff --git a/app/build-types/beta/images/icon-34.png b/app/build-types/beta/images/icon-34.png deleted file mode 100644 index bd70156f3f19..000000000000 Binary files a/app/build-types/beta/images/icon-34.png and /dev/null differ diff --git a/app/build-types/beta/images/icon-38.png b/app/build-types/beta/images/icon-38.png index ae868f268dcf..0d761ef40d1e 100644 Binary files a/app/build-types/beta/images/icon-38.png and b/app/build-types/beta/images/icon-38.png differ diff --git a/app/build-types/beta/images/icon-48.png b/app/build-types/beta/images/icon-48.png index 306a7bce07c9..257841bb4e19 100644 Binary files a/app/build-types/beta/images/icon-48.png and b/app/build-types/beta/images/icon-48.png differ diff --git a/app/build-types/beta/images/icon-512.png b/app/build-types/beta/images/icon-512.png index 0b9ccdc08832..f5eb847c5920 100644 Binary files a/app/build-types/beta/images/icon-512.png and b/app/build-types/beta/images/icon-512.png differ diff --git a/app/build-types/beta/images/icon-64.png b/app/build-types/beta/images/icon-64.png index 2bcda034c529..7eaf94c1a55b 100644 Binary files a/app/build-types/beta/images/icon-64.png and b/app/build-types/beta/images/icon-64.png differ diff --git a/app/build-types/beta/images/info-logo.png b/app/build-types/beta/images/info-logo.png index a855a15b150b..ec8a71ce8eeb 100644 Binary files a/app/build-types/beta/images/info-logo.png and b/app/build-types/beta/images/info-logo.png differ diff --git a/app/build-types/beta/images/logo/metamask-fox.svg b/app/build-types/beta/images/logo/metamask-fox.svg index b6516605d1d8..aac4729d032f 100644 --- a/app/build-types/beta/images/logo/metamask-fox.svg +++ b/app/build-types/beta/images/logo/metamask-fox.svg @@ -1,36 +1,17 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/app/build-types/flask/images/icon-128.png b/app/build-types/flask/images/icon-128.png index 4dddd86c3fcb..3b7333cf7f7b 100644 Binary files a/app/build-types/flask/images/icon-128.png and b/app/build-types/flask/images/icon-128.png differ diff --git a/app/build-types/flask/images/icon-16.png b/app/build-types/flask/images/icon-16.png index f67228dbc10c..f39dbf018c00 100644 Binary files a/app/build-types/flask/images/icon-16.png and b/app/build-types/flask/images/icon-16.png differ diff --git a/app/build-types/flask/images/icon-19.png b/app/build-types/flask/images/icon-19.png index 40ff28ba7b0c..c159c8edd69b 100644 Binary files a/app/build-types/flask/images/icon-19.png and b/app/build-types/flask/images/icon-19.png differ diff --git a/app/build-types/flask/images/icon-32.png b/app/build-types/flask/images/icon-32.png index 79918f98ff0b..060fb7bc29cc 100644 Binary files a/app/build-types/flask/images/icon-32.png and b/app/build-types/flask/images/icon-32.png differ diff --git a/app/build-types/flask/images/icon-38.png b/app/build-types/flask/images/icon-38.png index 05f0b4b51b3e..a751db40695b 100644 Binary files a/app/build-types/flask/images/icon-38.png and b/app/build-types/flask/images/icon-38.png differ diff --git a/app/build-types/flask/images/icon-48.png b/app/build-types/flask/images/icon-48.png index 5e280b4e07be..b29434b5c0e3 100644 Binary files a/app/build-types/flask/images/icon-48.png and b/app/build-types/flask/images/icon-48.png differ diff --git a/app/build-types/flask/images/icon-512.png b/app/build-types/flask/images/icon-512.png index eae504a57c24..4d17caa08e07 100644 Binary files a/app/build-types/flask/images/icon-512.png and b/app/build-types/flask/images/icon-512.png differ diff --git a/app/build-types/flask/images/icon-64.png b/app/build-types/flask/images/icon-64.png index 680a96b10f0e..bc58d1da0135 100644 Binary files a/app/build-types/flask/images/icon-64.png and b/app/build-types/flask/images/icon-64.png differ diff --git a/app/build-types/flask/images/logo/metamask-fox.svg b/app/build-types/flask/images/logo/metamask-fox.svg index 094aebc36408..38ebd9a91075 100644 --- a/app/build-types/flask/images/logo/metamask-fox.svg +++ b/app/build-types/flask/images/logo/metamask-fox.svg @@ -1,102 +1,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/build-types/main/fox.json b/app/build-types/main/fox.json new file mode 100644 index 000000000000..25dc7e333915 --- /dev/null +++ b/app/build-types/main/fox.json @@ -0,0 +1,236 @@ +{ + "chunks": [ + { + "color": [102, 24, 0], + "faces": [ + [2, 3, 1], + [1, 3, 4], + [5, 1, 4], + [6, 7, 61], + [6, 61, 8], + [15, 20, 12], + [15, 9, 19], + [19, 20, 15], + [1, 5, 0], + [1, 0, 2], + [31, 30, 32], + [30, 33, 32], + [34, 33, 30], + [35, 37, 60], + [35, 60, 36], + [44, 41, 49], + [44, 48, 38], + [48, 44, 49], + [30, 29, 34], + [30, 31, 29], + [12, 20, 49], + [12, 49, 41], + [20, 19, 48], + [20, 48, 49], + [19, 9, 38], + [19, 38, 48] + ], + "materialName": "model:model1:brown" + }, + { + "color": [227, 72, 7], + "faces": [ + [9, 6, 63], + [17, 28, 13], + [13, 16, 17], + [8, 65, 10], + [27, 7, 59], + [63, 8, 10], + [18, 2, 21], + [2, 0, 21], + [21, 22, 23], + [24, 22, 21], + [21, 5, 24], + [14, 16, 13], + [25, 16, 14], + [16, 24, 17], + [25, 22, 24], + [16, 25, 24], + [17, 24, 5], + [0, 5, 21], + [61, 27, 8], + [62, 37, 35], + [42, 46, 45], + [46, 42, 57], + [37, 64, 56], + [56, 36, 60], + [37, 62, 39], + [47, 50, 31], + [31, 50, 29], + [50, 52, 51], + [53, 50, 51], + [50, 53, 34], + [43, 42, 45], + [45, 54, 43], + [53, 45, 46], + [54, 53, 51], + [54, 45, 53], + [53, 46, 34], + [29, 50, 34], + [36, 56, 58], + [23, 22, 51], + [23, 51, 52], + [22, 25, 54], + [22, 54, 51], + [25, 14, 43], + [25, 43, 54], + [56, 60, 37], + [7, 27, 61], + [38, 62, 35], + [8, 63, 6], + [37, 39, 64], + [8, 27, 65] + ], + "materialName": "model:model1:orange_dark" + }, + { + "color": [255, 92, 22], + "faces": [ + [27, 59, 4], + [65, 3, 10], + [3, 2, 18], + [23, 18, 21], + [27, 4, 3], + [28, 15, 13], + [56, 32, 33], + [56, 64, 32], + [32, 47, 31], + [52, 50, 47], + [56, 33, 58], + [57, 42, 44], + [18, 23, 52], + [18, 52, 47], + [32, 64, 39], + [27, 3, 65] + ], + "materialName": "model:model1:orange_mid" + }, + { + "color": [192, 196, 205], + "faces": [ + [26, 13, 11], + [14, 13, 26], + [43, 55, 42], + [55, 40, 42], + [14, 26, 55], + [14, 55, 43], + [26, 11, 40], + [26, 40, 55], + [11, 12, 41], + [11, 41, 40] + ], + "materialName": "model:model1:white_underside" + }, + { + "color": [255, 141, 93], + "faces": [ + [17, 59, 7], + [17, 7, 28], + [28, 9, 15], + [9, 28, 6], + [28, 7, 6], + [58, 34, 46], + [58, 46, 57], + [58, 57, 36], + [57, 44, 38], + [38, 35, 57], + [36, 57, 35], + [62, 63, 10], + [62, 10, 39], + [10, 3, 32], + [10, 32, 39], + [3, 18, 47], + [3, 47, 32], + [59, 5, 4], + [34, 58, 33], + [5, 59, 17], + [38, 9, 63], + [38, 63, 62] + ], + "materialName": "model:model1:pasted__orange_bright_alt" + }, + { + "color": [231, 235, 246], + "faces": [ + [15, 12, 11], + [15, 11, 13], + [44, 42, 40], + [44, 40, 41] + ], + "materialName": "model:model1:white_side" + } + ], + "positions": [ + [11.086484, 5.960324999999999, 2.77823], + [6.465275, 4.255375000000001, 1.88451], + [9.594237, 11.702960999999998, 4.818539], + [2.466232, 4.250507000000001, 6.037715], + [6.464263, 1.4749859999999995, 3.628665], + [9.605342, 1.4489300000000007, 0.910938], + [2.231848, -2.4387, 8.109403], + [4.922223, -1.7745820000000005, 6.462612], + [2.730904, -0.9981170000000006, 8.231533], + [0.93571, -5.683305, 13.898631], + [1.826048, 0.1982759999999999, 8.967493], + [1.258694, -9.521664999999999, 11.713494], + [1.255813, -7.606733, 14.061978], + [4.829895, -7.400282, 5.474004], + [4.015327, -7.042688, 0.703314], + [1.612633, -6.389058, 13.879456], + [9.587111, -8.813012, -0.560606], + [11.053112, -3.933592, 1.23187], + [2.146133, 7.2576350000000005, 2.344184], + [0.566381, -6.040558, 14.725849], + [1.08419, -6.719231000000001, 14.706674], + [5.171019, 4.21175, -3.034384], + [2.0921, 2.645106, -5.246524], + [1.66333, 6.2937319999999985, -1.261715], + [7.22446, 0.4266179999999995, -1.816326], + [2.34563, -2.4331899999999997, -5.660416], + [1.060229, -9.526682000000001, 3.893901], + [4.81298, 0.19828200000000074, 7.707912], + [4.82989, -4.000793, 8.547805], + [-11.086484, 5.960324999999999, 2.77823], + [-6.465275, 4.255375000000001, 1.88451], + [-9.594237, 11.702960999999998, 4.818539], + [-2.466232, 4.250507000000001, 6.037715], + [-6.464263, 1.4749859999999995, 3.628665], + [-9.605342, 1.4489300000000007, 0.910938], + [-2.231848, -2.4387, 8.109403], + [-4.922223, -1.7745820000000005, 6.462612], + [-2.730904, -0.9981170000000006, 8.231533], + [-0.93571, -5.683305, 13.898631], + [-1.826048, 0.1982759999999999, 8.967493], + [-1.258695, -9.521664999999999, 11.713494], + [-1.255813, -7.606733, 14.061978], + [-4.829895, -7.400282, 5.474004], + [-4.015327, -7.042687, 0.703314], + [-1.612633, -6.389058, 13.879456], + [-9.587111, -8.813012, -0.560606], + [-11.053112, -3.933592, 1.23187], + [-2.146133, 7.2576350000000005, 2.344184], + [-0.566381, -6.040558, 14.725849], + [-1.08419, -6.719231000000001, 14.706674], + [-5.171019, 4.21175, -3.034384], + [-2.0921, 2.645106, -5.246524], + [-1.66333, 6.2937319999999985, -1.261715], + [-7.22446, 0.4266179999999995, -1.816326], + [-2.34563, -2.4331899999999997, -5.660416], + [-1.060229, -9.526682000000001, 3.893901], + [-4.81298, 0.19828200000000074, 7.707912], + [-4.82989, -4.000793, 8.547805], + [-6.599504, -1.3817730000000008, 5.348948], + [6.599504, -1.3817730000000008, 5.348948], + [-4.135162, -0.9990760000000005, 7.580709], + [4.135162, -0.9990760000000005, 7.580709], + [-1.551688, -1.8464950000000009, 9.58765], + [1.551688, -1.8464950000000009, 9.58765], + [-3.089554, 0.19827800000000018, 8.422374], + [3.089554, 0.19827800000000018, 8.422374] + ] +} diff --git a/app/build-types/mmi/images/icon-128.png b/app/build-types/mmi/images/icon-128.png index b71f4fa6fadc..1cd0d3c26108 100644 Binary files a/app/build-types/mmi/images/icon-128.png and b/app/build-types/mmi/images/icon-128.png differ diff --git a/app/build-types/mmi/images/icon-16.png b/app/build-types/mmi/images/icon-16.png index f5e8dadf537c..6f0c0223e5c9 100644 Binary files a/app/build-types/mmi/images/icon-16.png and b/app/build-types/mmi/images/icon-16.png differ diff --git a/app/build-types/mmi/images/icon-19.png b/app/build-types/mmi/images/icon-19.png index 40cd13e87d79..bf56060d9593 100644 Binary files a/app/build-types/mmi/images/icon-19.png and b/app/build-types/mmi/images/icon-19.png differ diff --git a/app/build-types/mmi/images/icon-32.png b/app/build-types/mmi/images/icon-32.png index c2813c7c9456..c266a8876a7f 100644 Binary files a/app/build-types/mmi/images/icon-32.png and b/app/build-types/mmi/images/icon-32.png differ diff --git a/app/build-types/mmi/images/icon-38.png b/app/build-types/mmi/images/icon-38.png index abcd19c892f1..5658c447eb97 100644 Binary files a/app/build-types/mmi/images/icon-38.png and b/app/build-types/mmi/images/icon-38.png differ diff --git a/app/build-types/mmi/images/icon-48.png b/app/build-types/mmi/images/icon-48.png index ec3fe0204c01..23f81e590e60 100644 Binary files a/app/build-types/mmi/images/icon-48.png and b/app/build-types/mmi/images/icon-48.png differ diff --git a/app/build-types/mmi/images/icon-512.png b/app/build-types/mmi/images/icon-512.png index 0b00612f5225..bb6c4473868a 100644 Binary files a/app/build-types/mmi/images/icon-512.png and b/app/build-types/mmi/images/icon-512.png differ diff --git a/app/build-types/mmi/images/icon-64.png b/app/build-types/mmi/images/icon-64.png index cfbd477017ed..20d3dafd922d 100644 Binary files a/app/build-types/mmi/images/icon-64.png and b/app/build-types/mmi/images/icon-64.png differ diff --git a/app/build-types/mmi/images/logo/metamask-fox.svg b/app/build-types/mmi/images/logo/metamask-fox.svg index cabdd95696f6..05615efdd7ca 100644 --- a/app/build-types/mmi/images/logo/metamask-fox.svg +++ b/app/build-types/mmi/images/logo/metamask-fox.svg @@ -1,31 +1,24 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/build-types/mmi/images/logo/mmi-logo-xl.svg b/app/build-types/mmi/images/logo/mmi-logo-xl.svg index afa6ac519741..22f9132de3ae 100644 --- a/app/build-types/mmi/images/logo/mmi-logo-xl.svg +++ b/app/build-types/mmi/images/logo/mmi-logo-xl.svg @@ -1,31 +1,24 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/build-types/mmi/images/logo/mmi-logo.svg b/app/build-types/mmi/images/logo/mmi-logo.svg index cabdd95696f6..05615efdd7ca 100644 --- a/app/build-types/mmi/images/logo/mmi-logo.svg +++ b/app/build-types/mmi/images/logo/mmi-logo.svg @@ -1,31 +1,24 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/images/fox.png b/app/images/fox.png new file mode 100644 index 000000000000..13965559ed98 Binary files /dev/null and b/app/images/fox.png differ diff --git a/app/images/icon-128.png b/app/images/icon-128.png index 8d593bc0c921..9986cc8e52ee 100644 Binary files a/app/images/icon-128.png and b/app/images/icon-128.png differ diff --git a/app/images/icon-16.png b/app/images/icon-16.png index 3343f97743f3..6c3fb215e6e1 100644 Binary files a/app/images/icon-16.png and b/app/images/icon-16.png differ diff --git a/app/images/icon-19.png b/app/images/icon-19.png index 5257d444aa66..4cc2d7534fbd 100644 Binary files a/app/images/icon-19.png and b/app/images/icon-19.png differ diff --git a/app/images/icon-32.png b/app/images/icon-32.png index a7dfe7f6cd68..4918054416c4 100644 Binary files a/app/images/icon-32.png and b/app/images/icon-32.png differ diff --git a/app/images/icon-38.png b/app/images/icon-38.png index 6654687143ca..3ef9b8b60828 100644 Binary files a/app/images/icon-38.png and b/app/images/icon-38.png differ diff --git a/app/images/icon-48.png b/app/images/icon-48.png index 91d0467a13d5..1ce52a14ce0f 100644 Binary files a/app/images/icon-48.png and b/app/images/icon-48.png differ diff --git a/app/images/icon-512.png b/app/images/icon-512.png index a506fabe7bbc..b28fbcf75fab 100644 Binary files a/app/images/icon-512.png and b/app/images/icon-512.png differ diff --git a/app/images/icon-64.png b/app/images/icon-64.png index 643c02b3108f..9c9de3880f6b 100644 Binary files a/app/images/icon-64.png and b/app/images/icon-64.png differ diff --git a/app/images/logo/metamask-fox.svg b/app/images/logo/metamask-fox.svg index a6cffef03d8e..b37cc95e3d6a 100644 --- a/app/images/logo/metamask-fox.svg +++ b/app/images/logo/metamask-fox.svg @@ -1 +1,24 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/images/onboarding-pin-browser.svg b/app/images/onboarding-pin-browser.svg index 042d3979a246..80e0acf3bf24 100644 --- a/app/images/onboarding-pin-browser.svg +++ b/app/images/onboarding-pin-browser.svg @@ -1 +1,47 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/images/onboarding-welcome-decentralised-apps.png b/app/images/onboarding-welcome-decentralised-apps.png new file mode 100644 index 000000000000..06e73c6a906a Binary files /dev/null and b/app/images/onboarding-welcome-decentralised-apps.png differ diff --git a/app/images/onboarding-welcome-say-hello.png b/app/images/onboarding-welcome-say-hello.png new file mode 100644 index 000000000000..a9286a2a88e8 Binary files /dev/null and b/app/images/onboarding-welcome-say-hello.png differ diff --git a/app/images/product-announcement-logo.svg b/app/images/product-announcement-logo.svg index fc10362e71fc..fbd441ed54c6 100644 --- a/app/images/product-announcement-logo.svg +++ b/app/images/product-announcement-logo.svg @@ -1,32 +1,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/scripts/controller-init/controller-list.ts b/app/scripts/controller-init/controller-list.ts index 8813acfdd69d..c0ea242e8c31 100644 --- a/app/scripts/controller-init/controller-list.ts +++ b/app/scripts/controller-init/controller-list.ts @@ -11,6 +11,11 @@ import SmartTransactionsController from '@metamask/smart-transactions-controller import { TransactionController } from '@metamask/transaction-controller'; import { TransactionUpdateController } from '@metamask-institutional/transaction-update'; import { AccountsController } from '@metamask/accounts-controller'; +import { + MultichainAssetsController, + MultichainBalancesController, +} from '@metamask/assets-controllers'; +import { MultichainTransactionsController } from '@metamask/multichain-transactions-controller'; import { CronjobController, ExecutionService, @@ -36,6 +41,9 @@ export type Controller = | GasFeeController | JsonSnapsRegistry | KeyringController + | MultichainAssetsController + | MultichainBalancesController + | MultichainTransactionsController | NetworkController | OnboardingController | PermissionController< @@ -64,6 +72,9 @@ export type ControllerFlatState = AccountsController['state'] & GasFeeController['state'] & JsonSnapsRegistry['state'] & KeyringController['state'] & + MultichainAssetsController['state'] & + MultichainBalancesController['state'] & + MultichainTransactionsController['state'] & NetworkController['state'] & OnboardingController['state'] & PermissionController< diff --git a/app/scripts/controller-init/messengers/index.ts b/app/scripts/controller-init/messengers/index.ts index 573a9a56149a..a00540f4aa15 100644 --- a/app/scripts/controller-init/messengers/index.ts +++ b/app/scripts/controller-init/messengers/index.ts @@ -17,6 +17,11 @@ import { getTransactionControllerMessenger, getTransactionControllerInitMessenger, } from './transaction-controller-messenger'; +import { + getMultichainBalancesControllerMessenger, + getMultichainTransactionsControllerMessenger, + getMultichainAssetsControllerMessenger, +} from './multichain'; export const CONTROLLER_MESSENGERS = { CronjobController: { @@ -25,6 +30,15 @@ export const CONTROLLER_MESSENGERS = { ExecutionService: { getMessenger: getExecutionServiceMessenger, }, + MultichainAssetsController: { + getMessenger: getMultichainAssetsControllerMessenger, + }, + MultichainBalancesController: { + getMessenger: getMultichainBalancesControllerMessenger, + }, + MultichainTransactionsController: { + getMessenger: getMultichainTransactionsControllerMessenger, + }, RateLimitController: { getMessenger: getRateLimitControllerMessenger, getInitMessenger: getRateLimitControllerInitMessenger, diff --git a/app/scripts/controller-init/messengers/multichain/index.ts b/app/scripts/controller-init/messengers/multichain/index.ts new file mode 100644 index 000000000000..691c5e1e45ab --- /dev/null +++ b/app/scripts/controller-init/messengers/multichain/index.ts @@ -0,0 +1,7 @@ +export { getMultichainAssetsControllerMessenger } from './multichain-assets-controller-messenger'; +export { getMultichainBalancesControllerMessenger } from './multichain-balances-controller-messenger'; +export { getMultichainTransactionsControllerMessenger } from './multichain-transactions-controller-messenger'; + +export type { MultichainAssetsControllerMessenger } from './multichain-assets-controller-messenger'; +export type { MultichainBalancesControllerMessenger } from './multichain-balances-controller-messenger'; +export type { MultichainTransactionsControllerMessenger } from './multichain-transactions-controller-messenger'; diff --git a/app/scripts/controller-init/messengers/multichain/multichain-assets-controller-messenger.test.ts b/app/scripts/controller-init/messengers/multichain/multichain-assets-controller-messenger.test.ts new file mode 100644 index 000000000000..80896cbe660f --- /dev/null +++ b/app/scripts/controller-init/messengers/multichain/multichain-assets-controller-messenger.test.ts @@ -0,0 +1,14 @@ +import { Messenger, RestrictedMessenger } from '@metamask/base-controller'; +import { getMultichainAssetsControllerMessenger } from './multichain-assets-controller-messenger'; + +describe('getMultichainAssetsControllerMessenger', () => { + it('returns a restricted messenger', () => { + const messenger = new Messenger(); + const multichainAssetsControllerMessenger = + getMultichainAssetsControllerMessenger(messenger); + + expect(multichainAssetsControllerMessenger).toBeInstanceOf( + RestrictedMessenger, + ); + }); +}); diff --git a/app/scripts/controller-init/messengers/multichain/multichain-assets-controller-messenger.ts b/app/scripts/controller-init/messengers/multichain/multichain-assets-controller-messenger.ts new file mode 100644 index 000000000000..6ee2767b1fa2 --- /dev/null +++ b/app/scripts/controller-init/messengers/multichain/multichain-assets-controller-messenger.ts @@ -0,0 +1,50 @@ +import { + AccountsControllerAccountAddedEvent, + AccountsControllerAccountAssetListUpdatedEvent, + AccountsControllerAccountRemovedEvent, + AccountsControllerListMultichainAccountsAction, +} from '@metamask/accounts-controller'; +import { Messenger } from '@metamask/base-controller'; +import { GetPermissions } from '@metamask/permission-controller'; +import { GetAllSnaps, HandleSnapRequest } from '@metamask/snaps-controllers'; + +type Actions = + | HandleSnapRequest + | GetAllSnaps + | GetPermissions + | AccountsControllerListMultichainAccountsAction; + +type Events = + | AccountsControllerAccountAddedEvent + | AccountsControllerAccountRemovedEvent + | AccountsControllerAccountAssetListUpdatedEvent; + +export type MultichainAssetsControllerMessenger = ReturnType< + typeof getMultichainAssetsControllerMessenger +>; + +/** + * Get a restricted messenger for the Multichain Assets controller. This is scoped to the + * actions and events that the multichain Assets controller is allowed to handle. + * + * @param messenger - The controller messenger to restrict. + * @returns The restricted controller messenger. + */ +export function getMultichainAssetsControllerMessenger( + messenger: Messenger, +) { + return messenger.getRestricted({ + name: 'MultichainAssetsController', + allowedEvents: [ + 'AccountsController:accountAdded', + 'AccountsController:accountRemoved', + 'AccountsController:accountAssetListUpdated', + ], + allowedActions: [ + 'PermissionController:getPermissions', + 'SnapController:handleRequest', + 'SnapController:getAll', + 'AccountsController:listMultichainAccounts', + ], + }); +} diff --git a/app/scripts/controller-init/messengers/multichain/multichain-balances-controller-messenger.test.ts b/app/scripts/controller-init/messengers/multichain/multichain-balances-controller-messenger.test.ts new file mode 100644 index 000000000000..b49fd43bf850 --- /dev/null +++ b/app/scripts/controller-init/messengers/multichain/multichain-balances-controller-messenger.test.ts @@ -0,0 +1,14 @@ +import { Messenger, RestrictedMessenger } from '@metamask/base-controller'; +import { getMultichainBalancesControllerMessenger } from './multichain-balances-controller-messenger'; + +describe('getMultichainBalancesControllerMessenger', () => { + it('returns a restricted messenger', () => { + const messenger = new Messenger(); + const multichainBalancesControllerMessenger = + getMultichainBalancesControllerMessenger(messenger); + + expect(multichainBalancesControllerMessenger).toBeInstanceOf( + RestrictedMessenger, + ); + }); +}); diff --git a/app/scripts/controller-init/messengers/multichain/multichain-balances-controller-messenger.ts b/app/scripts/controller-init/messengers/multichain/multichain-balances-controller-messenger.ts new file mode 100644 index 000000000000..3ab3d5436dbb --- /dev/null +++ b/app/scripts/controller-init/messengers/multichain/multichain-balances-controller-messenger.ts @@ -0,0 +1,53 @@ +import { Messenger } from '@metamask/base-controller'; +import { + AccountsControllerAccountAddedEvent, + AccountsControllerAccountRemovedEvent, + AccountsControllerListMultichainAccountsAction, + AccountsControllerAccountBalancesUpdatesEvent, +} from '@metamask/accounts-controller'; +import { HandleSnapRequest } from '@metamask/snaps-controllers'; +import { + MultichainAssetsControllerGetStateAction, + MultichainAssetsControllerStateChangeEvent, +} from '@metamask/assets-controllers'; + +type Actions = + | AccountsControllerListMultichainAccountsAction + | HandleSnapRequest + | MultichainAssetsControllerGetStateAction; + +type Events = + | AccountsControllerAccountAddedEvent + | AccountsControllerAccountRemovedEvent + | AccountsControllerAccountBalancesUpdatesEvent + | MultichainAssetsControllerStateChangeEvent; + +export type MultichainBalancesControllerMessenger = ReturnType< + typeof getMultichainBalancesControllerMessenger +>; + +/** + * Get a restricted messenger for the Multichain Balances controller. This is scoped to the + * actions and events that the Multichain Balances controller is allowed to handle. + * + * @param messenger - The controller messenger to restrict. + * @returns The restricted controller messenger. + */ +export function getMultichainBalancesControllerMessenger( + messenger: Messenger, +) { + return messenger.getRestricted({ + name: 'MultichainBalancesController', + allowedEvents: [ + 'AccountsController:accountAdded', + 'AccountsController:accountRemoved', + 'AccountsController:accountBalancesUpdated', + 'MultichainAssetsController:stateChange', + ], + allowedActions: [ + 'AccountsController:listMultichainAccounts', + 'SnapController:handleRequest', + 'MultichainAssetsController:getState', + ], + }); +} diff --git a/app/scripts/controller-init/messengers/multichain/multichain-transactions-controller-messenger.test.ts b/app/scripts/controller-init/messengers/multichain/multichain-transactions-controller-messenger.test.ts new file mode 100644 index 000000000000..a287b2badc2d --- /dev/null +++ b/app/scripts/controller-init/messengers/multichain/multichain-transactions-controller-messenger.test.ts @@ -0,0 +1,14 @@ +import { Messenger, RestrictedMessenger } from '@metamask/base-controller'; +import { getMultichainTransactionsControllerMessenger } from './multichain-transactions-controller-messenger'; + +describe('getMultichainTransactionsControllerMessenger', () => { + it('returns a restricted messenger', () => { + const messenger = new Messenger(); + const multichainTransactionsControllerMessenger = + getMultichainTransactionsControllerMessenger(messenger); + + expect(multichainTransactionsControllerMessenger).toBeInstanceOf( + RestrictedMessenger, + ); + }); +}); diff --git a/app/scripts/controller-init/messengers/multichain/multichain-transactions-controller-messenger.ts b/app/scripts/controller-init/messengers/multichain/multichain-transactions-controller-messenger.ts new file mode 100644 index 000000000000..cbdd7a71e612 --- /dev/null +++ b/app/scripts/controller-init/messengers/multichain/multichain-transactions-controller-messenger.ts @@ -0,0 +1,45 @@ +import { Messenger } from '@metamask/base-controller'; +import { + AccountsControllerAccountAddedEvent, + AccountsControllerAccountRemovedEvent, + AccountsControllerListMultichainAccountsAction, + AccountsControllerAccountTransactionsUpdatedEvent, +} from '@metamask/accounts-controller'; +import { HandleSnapRequest } from '@metamask/snaps-controllers'; + +type Actions = + | AccountsControllerListMultichainAccountsAction + | HandleSnapRequest; + +type Events = + | AccountsControllerAccountAddedEvent + | AccountsControllerAccountRemovedEvent + | AccountsControllerAccountTransactionsUpdatedEvent; + +export type MultichainTransactionsControllerMessenger = ReturnType< + typeof getMultichainTransactionsControllerMessenger +>; + +/** + * Get a restricted messenger for the Multichain Transactions controller. This is scoped to the + * actions and events that the Multichain Transactions controller is allowed to handle. + * + * @param messenger - The controller messenger to restrict. + * @returns The restricted controller messenger. + */ +export function getMultichainTransactionsControllerMessenger( + messenger: Messenger, +) { + return messenger.getRestricted({ + name: 'MultichainTransactionsController', + allowedEvents: [ + 'AccountsController:accountAdded', + 'AccountsController:accountRemoved', + 'AccountsController:accountTransactionsUpdated', + ], + allowedActions: [ + 'AccountsController:listMultichainAccounts', + 'SnapController:handleRequest', + ], + }); +} diff --git a/app/scripts/controller-init/multichain/index.ts b/app/scripts/controller-init/multichain/index.ts new file mode 100644 index 000000000000..09a29be3f581 --- /dev/null +++ b/app/scripts/controller-init/multichain/index.ts @@ -0,0 +1,3 @@ +export { MultichainAssetsControllerInit } from './multichain-assets-controller-init'; +export { MultichainBalancesControllerInit } from './multichain-balances-controller-init'; +export { MultichainTransactionsControllerInit } from './multichain-transactions-controller-init'; diff --git a/app/scripts/controller-init/multichain/multichain-assets-controller-init.test.ts b/app/scripts/controller-init/multichain/multichain-assets-controller-init.test.ts new file mode 100644 index 000000000000..fa2f8e470e6a --- /dev/null +++ b/app/scripts/controller-init/multichain/multichain-assets-controller-init.test.ts @@ -0,0 +1,51 @@ +import { MultichainAssetsController } from '@metamask/assets-controllers'; +import { Messenger } from '@metamask/base-controller'; +import { buildControllerInitRequestMock } from '../test/utils'; +import { ControllerInitRequest } from '../types'; +import { + getMultichainAssetsControllerMessenger, + MultichainAssetsControllerMessenger, +} from '../messengers/multichain'; +import { MultichainAssetsControllerInit } from './multichain-assets-controller-init'; + +jest.mock('@metamask/assets-controllers'); + +function buildInitRequestMock(): jest.Mocked< + ControllerInitRequest +> { + const baseControllerMessenger = new Messenger(); + + return { + ...buildControllerInitRequestMock(), + controllerMessenger: getMultichainAssetsControllerMessenger( + baseControllerMessenger, + ), + }; +} + +describe('MultichainAssetsControllerInit', () => { + const MultichainAssetsControllerClassMock = jest.mocked( + MultichainAssetsController, + ); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('returns controller instance', () => { + const requestMock = buildInitRequestMock(); + expect( + MultichainAssetsControllerInit(requestMock).controller, + ).toBeInstanceOf(MultichainAssetsController); + }); + + it('initializes with correct messenger and state', () => { + const requestMock = buildInitRequestMock(); + MultichainAssetsControllerInit(requestMock); + + expect(MultichainAssetsControllerClassMock).toHaveBeenCalledWith({ + messenger: requestMock.controllerMessenger, + state: requestMock.persistedState.MultichainAssetsController, + }); + }); +}); diff --git a/app/scripts/controller-init/multichain/multichain-assets-controller-init.ts b/app/scripts/controller-init/multichain/multichain-assets-controller-init.ts new file mode 100644 index 000000000000..72439263760d --- /dev/null +++ b/app/scripts/controller-init/multichain/multichain-assets-controller-init.ts @@ -0,0 +1,25 @@ +import { MultichainAssetsController } from '@metamask/assets-controllers'; +import { ControllerInitFunction } from '../types'; +import { MultichainAssetsControllerMessenger } from '../messengers/multichain'; + +/** + * Initialize the Multichain Assets controller. + * + * @param request - The request object. + * @param request.controllerMessenger - The messenger to use for the controller. + * @param request.persistedState - The persisted state of the extension. + * @returns The initialized controller. + */ +export const MultichainAssetsControllerInit: ControllerInitFunction< + MultichainAssetsController, + MultichainAssetsControllerMessenger +> = ({ controllerMessenger, persistedState }) => { + const controller = new MultichainAssetsController({ + messenger: controllerMessenger, + state: persistedState.MultichainAssetsController, + }); + + return { + controller, + }; +}; diff --git a/app/scripts/controller-init/multichain/multichain-balances-controller-init.test.ts b/app/scripts/controller-init/multichain/multichain-balances-controller-init.test.ts new file mode 100644 index 000000000000..453aa81092a0 --- /dev/null +++ b/app/scripts/controller-init/multichain/multichain-balances-controller-init.test.ts @@ -0,0 +1,51 @@ +import { MultichainBalancesController } from '@metamask/assets-controllers'; +import { Messenger } from '@metamask/base-controller'; +import { buildControllerInitRequestMock } from '../test/utils'; +import { ControllerInitRequest } from '../types'; +import { + getMultichainBalancesControllerMessenger, + MultichainBalancesControllerMessenger, +} from '../messengers/multichain'; +import { MultichainBalancesControllerInit } from './multichain-balances-controller-init'; + +jest.mock('@metamask/assets-controllers'); + +function buildInitRequestMock(): jest.Mocked< + ControllerInitRequest +> { + const baseControllerMessenger = new Messenger(); + + return { + ...buildControllerInitRequestMock(), + controllerMessenger: getMultichainBalancesControllerMessenger( + baseControllerMessenger, + ), + }; +} + +describe('MultichainBalancesControllerInit', () => { + const multichainBalancesControllerClassMock = jest.mocked( + MultichainBalancesController, + ); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('returns controller instance', () => { + const requestMock = buildInitRequestMock(); + expect( + MultichainBalancesControllerInit(requestMock).controller, + ).toBeInstanceOf(MultichainBalancesController); + }); + + it('initializes with correct messenger and state', () => { + const requestMock = buildInitRequestMock(); + MultichainBalancesControllerInit(requestMock); + + expect(multichainBalancesControllerClassMock).toHaveBeenCalledWith({ + messenger: requestMock.controllerMessenger, + state: requestMock.persistedState.MultichainBalancesController, + }); + }); +}); diff --git a/app/scripts/controller-init/multichain/multichain-balances-controller-init.ts b/app/scripts/controller-init/multichain/multichain-balances-controller-init.ts new file mode 100644 index 000000000000..28d1d60730c2 --- /dev/null +++ b/app/scripts/controller-init/multichain/multichain-balances-controller-init.ts @@ -0,0 +1,23 @@ +import { MultichainBalancesController } from '@metamask/assets-controllers'; +import { ControllerInitFunction } from '../types'; +import { MultichainBalancesControllerMessenger } from '../messengers/multichain'; + +/** + * Initialize the Multichain Balances controller. + * + * @param request - The request object. + * @param request.controllerMessenger - The messenger to use for the controller. + * @param request.persistedState - The persisted state of the extension. + * @returns The initialized controller. + */ +export const MultichainBalancesControllerInit: ControllerInitFunction< + MultichainBalancesController, + MultichainBalancesControllerMessenger +> = ({ controllerMessenger, persistedState }) => { + const controller = new MultichainBalancesController({ + messenger: controllerMessenger, + state: persistedState.MultichainBalancesController, + }); + + return { controller }; +}; diff --git a/app/scripts/controller-init/multichain/multichain-transactions-controller-init.test.ts b/app/scripts/controller-init/multichain/multichain-transactions-controller-init.test.ts new file mode 100644 index 000000000000..ca41b5b177a5 --- /dev/null +++ b/app/scripts/controller-init/multichain/multichain-transactions-controller-init.test.ts @@ -0,0 +1,51 @@ +import { MultichainTransactionsController } from '@metamask/multichain-transactions-controller'; +import { Messenger } from '@metamask/base-controller'; +import { buildControllerInitRequestMock } from '../test/utils'; +import { ControllerInitRequest } from '../types'; +import { + getMultichainTransactionsControllerMessenger, + MultichainTransactionsControllerMessenger, +} from '../messengers/multichain'; +import { MultichainTransactionsControllerInit } from './multichain-transactions-controller-init'; + +jest.mock('@metamask/multichain-transactions-controller'); + +function buildInitRequestMock(): jest.Mocked< + ControllerInitRequest +> { + const baseControllerMessenger = new Messenger(); + + return { + ...buildControllerInitRequestMock(), + controllerMessenger: getMultichainTransactionsControllerMessenger( + baseControllerMessenger, + ), + }; +} + +describe('MultichainTransactionsControllerInit', () => { + const multichainTransactionsControllerClassMock = jest.mocked( + MultichainTransactionsController, + ); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('returns controller instance', () => { + const requestMock = buildInitRequestMock(); + expect( + MultichainTransactionsControllerInit(requestMock).controller, + ).toBeInstanceOf(MultichainTransactionsController); + }); + + it('initializes with correct messenger and state', () => { + const requestMock = buildInitRequestMock(); + MultichainTransactionsControllerInit(requestMock); + + expect(multichainTransactionsControllerClassMock).toHaveBeenCalledWith({ + messenger: requestMock.controllerMessenger, + state: requestMock.persistedState.MultichainTransactionsController, + }); + }); +}); diff --git a/app/scripts/controller-init/multichain/multichain-transactions-controller-init.ts b/app/scripts/controller-init/multichain/multichain-transactions-controller-init.ts new file mode 100644 index 000000000000..6fac48f31c96 --- /dev/null +++ b/app/scripts/controller-init/multichain/multichain-transactions-controller-init.ts @@ -0,0 +1,25 @@ +import { MultichainTransactionsController } from '@metamask/multichain-transactions-controller'; +import { ControllerInitFunction } from '../types'; +import { MultichainTransactionsControllerMessenger } from '../messengers/multichain'; + +/** + * Initialize the Multichain Transactions controller. + * + * @param request - The request object. + * @param request.controllerMessenger - The messenger to use for the controller. + * @param request.persistedState - The persisted state of the extension. + * @returns The initialized controller. + */ +export const MultichainTransactionsControllerInit: ControllerInitFunction< + MultichainTransactionsController, + MultichainTransactionsControllerMessenger +> = ({ controllerMessenger, persistedState }) => { + const controller = new MultichainTransactionsController({ + messenger: controllerMessenger, + state: persistedState.MultichainTransactionsController, + }); + + return { + controller, + }; +}; diff --git a/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.js b/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.js index 0075729873d0..536bb3b44c1f 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.js @@ -170,6 +170,7 @@ export function validateAddEthereumChainParams(params) { * @param {Function} hooks.getCaveat - The callback to get the CAIP-25 caveat for the origin. * @param {Function} hooks.requestPermittedChainsPermissionForOrigin - The callback to request a new permittedChains-equivalent CAIP-25 permission. * @param {Function} hooks.requestPermittedChainsPermissionIncrementalForOrigin - The callback to add a new chain to the permittedChains-equivalent CAIP-25 permission. + * @param {Function} hooks.setTokenNetworkFilter - The callback to set the token network filter. * @returns a null response on success or an error if user rejects an approval when autoApprove is false or on unexpected errors. */ export async function switchChain( @@ -183,6 +184,7 @@ export async function switchChain( getCaveat, requestPermittedChainsPermissionForOrigin, requestPermittedChainsPermissionIncrementalForOrigin, + setTokenNetworkFilter, }, ) { try { @@ -208,6 +210,7 @@ export async function switchChain( } await setActiveNetwork(networkClientId); + setTokenNetworkFilter(chainId); response.result = null; return end(); } catch (error) { diff --git a/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.test.ts b/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.test.ts index e699337106c3..344ec00341df 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.test.ts +++ b/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.test.ts @@ -15,6 +15,7 @@ describe('Ethereum Chain Utils', () => { getCaveat: jest.fn(), requestPermittedChainsPermissionForOrigin: jest.fn(), requestPermittedChainsPermissionIncrementalForOrigin: jest.fn(), + setTokenNetworkFilter: jest.fn(), }; const response: { result?: true } = {}; const switchChain = (chainId: Hex, networkClientId: string) => @@ -55,6 +56,7 @@ describe('Ethereum Chain Utils', () => { await switchChain('0x1', 'mainnet'); expect(mocks.setActiveNetwork).toHaveBeenCalledWith('mainnet'); + expect(mocks.setTokenNetworkFilter).toHaveBeenCalledWith('0x1'); }); it('should throw an error if the switch chain approval is rejected', async () => { @@ -92,6 +94,7 @@ describe('Ethereum Chain Utils', () => { mocks.requestPermittedChainsPermissionIncrementalForOrigin, ).toHaveBeenCalledWith({ chainId: '0x1', autoApprove: true }); expect(mocks.setActiveNetwork).toHaveBeenCalledWith('mainnet'); + expect(mocks.setTokenNetworkFilter).toHaveBeenCalledWith('0x1'); }); it('requests permittedChains approval without autoApprove then switches to it if autoApprove: false', async () => { @@ -110,6 +113,7 @@ describe('Ethereum Chain Utils', () => { mocks.requestPermittedChainsPermissionIncrementalForOrigin, ).toHaveBeenCalledWith({ chainId: '0x1', autoApprove: false }); expect(mocks.setActiveNetwork).toHaveBeenCalledWith('mainnet'); + expect(mocks.setTokenNetworkFilter).toHaveBeenCalledWith('0x1'); }); it('should throw errors if the permittedChains grant fails', async () => { @@ -176,6 +180,7 @@ describe('Ethereum Chain Utils', () => { await switchChain('0x1', 'mainnet'); expect(mocks.setActiveNetwork).not.toHaveBeenCalled(); + expect(mocks.setTokenNetworkFilter).not.toHaveBeenCalled(); }); it('return error about not being able to switch chain', async () => { @@ -246,6 +251,7 @@ describe('Ethereum Chain Utils', () => { await switchChain('0x1', 'mainnet'); expect(mocks.setActiveNetwork).toHaveBeenCalledWith('mainnet'); + expect(mocks.setTokenNetworkFilter).toHaveBeenCalledWith('0x1'); }); }, ); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js index 9bed994ed33a..1b0b4d0f14c7 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js @@ -15,6 +15,7 @@ const switchEthereumChain = { getCurrentChainIdForDomain: true, requestPermittedChainsPermissionForOrigin: true, requestPermittedChainsPermissionIncrementalForOrigin: true, + setTokenNetworkFilter: true, }, }; @@ -32,6 +33,7 @@ async function switchEthereumChainHandler( getCurrentChainIdForDomain, requestPermittedChainsPermissionForOrigin, requestPermittedChainsPermissionIncrementalForOrigin, + setTokenNetworkFilter, }, ) { let chainId; @@ -69,5 +71,6 @@ async function switchEthereumChainHandler( getCaveat, requestPermittedChainsPermissionForOrigin, requestPermittedChainsPermissionIncrementalForOrigin, + setTokenNetworkFilter, }); } diff --git a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.test.js b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.test.js index fedf456eb61c..2cb0e5d41bfb 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.test.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.test.js @@ -10,6 +10,7 @@ jest.mock('./ethereum-chain-utils', () => ({ ...jest.requireActual('./ethereum-chain-utils'), validateSwitchEthereumChainParams: jest.fn(), switchChain: jest.fn(), + setTokenNetworkFilter: jest.fn(), })); const NON_INFURA_CHAIN_ID = '0x123456789'; @@ -46,6 +47,7 @@ const createMockedHandler = () => { getCurrentChainIdForDomain: jest.fn().mockReturnValue(NON_INFURA_CHAIN_ID), requestPermittedChainsPermissionForOrigin: jest.fn(), requestPermittedChainsPermissionIncrementalForOrigin: jest.fn(), + setTokenNetworkFilter: jest.fn(), }; const response = {}; const handler = (request) => @@ -171,6 +173,7 @@ describe('switchEthereumChainHandler', () => { mocks.requestPermittedChainsPermissionForOrigin, requestPermittedChainsPermissionIncrementalForOrigin: mocks.requestPermittedChainsPermissionIncrementalForOrigin, + setTokenNetworkFilter: mocks.setTokenNetworkFilter, }, ); }); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 20b798177fc9..d4fc0ab09272 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -13,8 +13,6 @@ import { RatesController, fetchMultiExchangeRate, TokenBalancesController, - MultichainBalancesController, - MultichainAssetsController, } from '@metamask/assets-controllers'; import { JsonRpcEngine } from '@metamask/json-rpc-engine'; import { createEngineStream } from '@metamask/json-rpc-middleware-stream'; @@ -161,9 +159,6 @@ import { setEthAccounts, addPermittedEthChainId, } from '@metamask/multichain'; -///: BEGIN:ONLY_INCLUDE_IF(build-flask) -import { MultichainTransactionsController } from '@metamask/multichain-transactions-controller'; -///: END:ONLY_INCLUDE_IF import { isProduction } from '../../shared/modules/environment'; import { methodsRequiringNetworkSwitch, @@ -363,6 +358,13 @@ import { handleBridgeTransactionFailed, handleTransactionFailedTypeBridge, } from './lib/bridge-status/metrics'; +///: BEGIN:ONLY_INCLUDE_IF(build-flask) +import { + MultichainAssetsControllerInit, + MultichainTransactionsControllerInit, + MultichainBalancesControllerInit, +} from './controller-init/multichain'; +///: END:ONLY_INCLUDE_IF import { TransactionControllerInit } from './controller-init/confirmations/transaction-controller-init'; import { PPOMControllerInit } from './controller-init/confirmations/ppom-controller-init'; import { initControllers } from './controller-init/utils'; @@ -790,27 +792,6 @@ export default class MetamaskController extends EventEmitter { disabled: !this.preferencesController.state.useNftDetection, }); - const multichainAssetsControllerMessenger = - this.controllerMessenger.getRestricted({ - name: 'MultichainAssetsController', - allowedEvents: [ - 'AccountsController:accountAdded', - 'AccountsController:accountRemoved', - 'AccountsController:accountAssetListUpdated', - ], - allowedActions: [ - 'SnapController:handleRequest', - 'SnapController:getAll', - 'PermissionController:getPermissions', - 'AccountsController:listMultichainAccounts', - ], - }); - - this.multichainAssetsController = new MultichainAssetsController({ - state: initState.MultichainAssetsController, - messenger: multichainAssetsControllerMessenger, - }); - const metaMetricsControllerMessenger = this.controllerMessenger.getRestricted({ name: 'MetaMetricsController', @@ -980,28 +961,6 @@ export default class MetamaskController extends EventEmitter { state: initState.AnnouncementController, }); - ///: BEGIN:ONLY_INCLUDE_IF(build-flask) - const multichainTransactionsControllerMessenger = - this.controllerMessenger.getRestricted({ - name: 'MultichainTransactionsController', - allowedEvents: [ - 'AccountsController:accountAdded', - 'AccountsController:accountRemoved', - 'AccountsController:accountTransactionsUpdated', - ], - allowedActions: [ - 'AccountsController:listMultichainAccounts', - 'SnapController:handleRequest', - ], - }); - - this.multichainTransactionsController = - new MultichainTransactionsController({ - messenger: multichainTransactionsControllerMessenger, - state: initState.MultichainTransactionsController, - }); - ///: END:ONLY_INCLUDE_IF - const networkOrderMessenger = this.controllerMessenger.getRestricted({ name: 'NetworkOrderController', allowedEvents: ['NetworkController:stateChange'], @@ -1019,25 +978,6 @@ export default class MetamaskController extends EventEmitter { state: initState.AccountOrderController, }); - const multichainBalancesControllerMessenger = - this.controllerMessenger.getRestricted({ - name: 'MultichainBalancesController', - allowedEvents: [ - 'AccountsController:accountAdded', - 'AccountsController:accountRemoved', - 'AccountsController:accountBalancesUpdated', - ], - allowedActions: [ - 'AccountsController:listMultichainAccounts', - 'SnapController:handleRequest', - ], - }); - - this.multichainBalancesController = new MultichainBalancesController({ - messenger: multichainBalancesControllerMessenger, - state: initState.MultichainBalancesController, - }); - const multichainRatesControllerMessenger = this.controllerMessenger.getRestricted({ name: 'RatesController', @@ -2112,6 +2052,11 @@ export default class MetamaskController extends EventEmitter { CronjobController: CronjobControllerInit, PPOMController: PPOMControllerInit, TransactionController: TransactionControllerInit, + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + MultichainAssetsController: MultichainAssetsControllerInit, + MultichainBalancesController: MultichainBalancesControllerInit, + MultichainTransactionsController: MultichainTransactionsControllerInit, + ///: END:ONLY_INCLUDE_IF }; const { @@ -2139,6 +2084,14 @@ export default class MetamaskController extends EventEmitter { this.snapsRegistry = controllersByName.SnapsRegistry; this.ppomController = controllersByName.PPOMController; this.txController = controllersByName.TransactionController; + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + this.multichainAssetsController = + controllersByName.MultichainAssetsController; + this.multichainBalancesController = + controllersByName.MultichainBalancesController; + this.multichainTransactionsController = + controllersByName.MultichainTransactionsController; + ///: END:ONLY_INCLUDE_IF this.controllerMessenger.subscribe( 'TransactionController:transactionStatusUpdated', @@ -2260,11 +2213,6 @@ export default class MetamaskController extends EventEmitter { AccountsController: this.accountsController, AppStateController: this.appStateController, AppMetadataController: this.appMetadataController, - MultichainBalancesController: this.multichainBalancesController, - MultichainAssetsController: this.multichainAssetsController, - ///: BEGIN:ONLY_INCLUDE_IF(build-flask) - MultichainTransactionsController: this.multichainTransactionsController, - ///: END:ONLY_INCLUDE_IF KeyringController: this.keyringController, PreferencesController: this.preferencesController, MetaMetricsController: this.metaMetricsController, @@ -2314,9 +2262,9 @@ export default class MetamaskController extends EventEmitter { AccountsController: this.accountsController, AppStateController: this.appStateController, AppMetadataController: this.appMetadataController, - MultichainBalancesController: this.multichainBalancesController, - MultichainAssetsController: this.multichainAssetsController, ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + MultichainAssetsController: this.multichainAssetsController, + MultichainBalancesController: this.multichainBalancesController, MultichainTransactionsController: this.multichainTransactionsController, ///: END:ONLY_INCLUDE_IF NetworkController: this.networkController, @@ -4117,10 +4065,11 @@ export default class MetamaskController extends EventEmitter { ), setName: this.nameController.setName.bind(this.nameController), + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) // MultichainBalancesController multichainUpdateBalance: (accountId) => this.multichainBalancesController.updateBalance(accountId), - + ///: END:ONLY_INCLUDE_IF // Transaction Decode decodeTransactionData: (request) => decodeTransactionData({ @@ -6171,6 +6120,15 @@ export default class MetamaskController extends EventEmitter { this.networkController.getNetworkConfigurationByChainId.bind( this.networkController, ), + setTokenNetworkFilter: (chainId) => { + const { tokenNetworkFilter } = + this.preferencesController.getPreferences(); + if (chainId && Object.keys(tokenNetworkFilter).length === 1) { + this.preferencesController.setPreference('tokenNetworkFilter', { + [chainId]: true, + }); + } + }, getCurrentChainIdForDomain: (domain) => { const networkClientId = this.selectedNetworkController.getNetworkClientIdForDomain(domain); diff --git a/development/circular-deps.jsonc b/development/circular-deps.jsonc index a2f7618b4531..dcd08ec5b46d 100644 --- a/development/circular-deps.jsonc +++ b/development/circular-deps.jsonc @@ -12,197 +12,10 @@ "ui/components/app/alert-system/confirm-alert-modal/index.tsx", "ui/pages/confirmations/components/confirm/footer/footer.tsx" ], - [ - "ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx", - "ui/components/app/assets/asset-list/asset-list-control-bar/index.ts", - "ui/components/app/assets/asset-list/asset-list.tsx", - "ui/components/app/assets/asset-list/network-filter/index.ts", - "ui/components/app/assets/asset-list/network-filter/network-filter.tsx", - "ui/hooks/useAccountTotalCrossChainFiatBalance.ts" - ], - [ - "ui/components/app/assets/asset-list/asset-list.tsx", - "ui/components/app/assets/asset-list/native-token/index.ts", - "ui/components/app/assets/asset-list/native-token/native-token.tsx" - ], - [ - "ui/components/app/assets/asset-list/asset-list.tsx", - "ui/components/app/assets/asset-list/native-token/index.ts", - "ui/components/app/assets/asset-list/native-token/native-token.tsx", - "ui/components/app/assets/asset-list/native-token/use-native-token-balance.ts" - ], [ "ui/components/app/name/name-details/name-details.tsx", "ui/components/app/name/name.tsx" ], - [ - "ui/components/component-library/avatar-account/avatar-account.tsx", - "ui/components/component-library/avatar-account/index.ts", - "ui/components/component-library/avatar-base/avatar-base.tsx", - "ui/components/component-library/avatar-base/index.ts", - "ui/components/component-library/index.ts", - "ui/components/component-library/text/index.ts", - "ui/components/component-library/text/text.tsx" - ], - [ - "ui/components/component-library/badge-wrapper/badge-wrapper.tsx", - "ui/components/component-library/badge-wrapper/index.ts", - "ui/components/component-library/index.ts" - ], - [ - "ui/components/component-library/banner-alert/banner-alert.tsx", - "ui/components/component-library/banner-alert/index.ts", - "ui/components/component-library/banner-base/banner-base.tsx", - "ui/components/component-library/banner-base/index.ts", - "ui/components/component-library/index.ts" - ], - [ - "ui/components/component-library/banner-alert/banner-alert.tsx", - "ui/components/component-library/banner-alert/index.ts", - "ui/components/component-library/index.ts" - ], - [ - "ui/components/component-library/banner-tip/banner-tip.tsx", - "ui/components/component-library/banner-tip/index.ts", - "ui/components/component-library/index.ts" - ], - [ - "ui/components/component-library/button-base/button-base.tsx", - "ui/components/component-library/button-base/index.ts", - "ui/components/component-library/index.ts" - ], - [ - "ui/components/component-library/button-icon/button-icon.tsx", - "ui/components/component-library/button-icon/index.ts", - "ui/components/component-library/index.ts" - ], - [ - "ui/components/component-library/button-link/button-link.tsx", - "ui/components/component-library/button-link/index.ts", - "ui/components/component-library/index.ts" - ], - [ - "ui/components/component-library/checkbox/checkbox.tsx", - "ui/components/component-library/checkbox/index.ts", - "ui/components/component-library/index.ts" - ], - [ - "ui/components/component-library/container/container.tsx", - "ui/components/component-library/container/index.ts", - "ui/components/component-library/index.ts" - ], - [ - "ui/components/component-library/form-text-field/form-text-field.tsx", - "ui/components/component-library/form-text-field/index.ts", - "ui/components/component-library/index.ts" - ], - [ - "ui/components/component-library/header-base/header-base.tsx", - "ui/components/component-library/header-base/index.ts", - "ui/components/component-library/index.ts" - ], - [ - "ui/components/component-library/help-text/help-text.tsx", - "ui/components/component-library/help-text/index.ts" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/modal-body/index.ts", - "ui/components/component-library/modal-body/modal-body.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/modal-content/index.ts", - "ui/components/component-library/modal-content/modal-content.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/modal-footer/index.ts", - "ui/components/component-library/modal-footer/modal-footer.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/modal-header/index.ts", - "ui/components/component-library/modal-header/modal-header.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/picker-network/index.ts", - "ui/components/component-library/picker-network/picker-network.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/picker-network/index.ts", - "ui/components/component-library/picker-network/picker-network.tsx", - "ui/components/component-library/picker-network/picker-network.types.ts", - "ui/components/multichain/avatar-group/avatar-group.types.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/picker-network/index.ts", - "ui/components/component-library/picker-network/picker-network.tsx", - "ui/components/multichain/avatar-group/avatar-group.tsx", - "ui/components/multichain/avatar-group/index.ts" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/popover-header/index.ts", - "ui/components/component-library/popover-header/popover-header.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/popover/index.ts", - "ui/components/component-library/popover/popover.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/select-button/index.ts", - "ui/components/component-library/select-button/select-button.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/select-button/index.ts", - "ui/components/component-library/select-button/select-button.tsx", - "ui/components/component-library/select-wrapper/index.ts", - "ui/components/component-library/select-wrapper/select-wrapper.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/select-option/index.ts", - "ui/components/component-library/select-option/select-option.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/tag-url/index.ts", - "ui/components/component-library/tag-url/tag-url.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/tag/index.ts", - "ui/components/component-library/tag/tag.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/text-field-search/index.ts", - "ui/components/component-library/text-field-search/text-field-search.tsx" - ], - [ - "ui/components/component-library/index.ts", - "ui/components/component-library/text-field/index.ts", - "ui/components/component-library/text-field/text-field.tsx" - ], - [ - "ui/components/component-library/modal-header/deprecated/index.ts", - "ui/components/component-library/modal-header/deprecated/modal-header.tsx" - ], - [ - "ui/components/component-library/modal-header/index.ts", - "ui/components/component-library/modal-header/modal-header.tsx" - ], - [ - "ui/components/component-library/popover-header/index.ts", - "ui/components/component-library/popover-header/popover-header.tsx" - ], [ "ui/components/multichain/pages/send/components/account-picker.tsx", "ui/components/multichain/pages/send/components/index.ts" diff --git a/development/verify-locale-strings.js b/development/verify-locale-strings.js index 75f9ca52cd4c..64708518405a 100755 --- a/development/verify-locale-strings.js +++ b/development/verify-locale-strings.js @@ -22,6 +22,7 @@ // // ////////////////////////////////////////////////////////////////////////////// +const { deepStrictEqual, AssertionError } = require('node:assert'); const fs = require('fs'); const { promisify } = require('util'); const log = require('loglevel'); @@ -117,6 +118,31 @@ async function writeLocale(code, locale) { async function verifyLocale(code) { const englishLocale = await getLocale('en'); + let failed = false; + + try { + // `en_GB` is a special case, added for compliance reasons + // Not used in-app. Should be identical to `en`. + const englishGbLocale = await getLocale('en_GB'); + deepStrictEqual( + englishLocale, + englishGbLocale, + 'en_GB should be identical to en', + ); + } catch (error) { + if (!(error instanceof AssertionError)) { + throw error; + } + + if (fix) { + console.info('Differences detected in `en_GB` local; overwriting'); + await writeLocale('en_GB', englishLocale); + } else { + console.error(error); + } + failed = true; + } + const targetLocale = await getLocale(code); const extraItems = compareLocalesForMissingItems({ @@ -161,10 +187,10 @@ async function verifyLocale(code) { } await writeLocale(code, newLocale); } - return true; + failed = true; } - return false; + return failed; } async function verifyEnglishLocale() { diff --git a/docs/README.md b/docs/README.md index 80f5901b57ba..74cf749827bf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ For help using MetaMask, visit our [User Support Site](https://support.metamask. For up to the minute news, follow our [Twitter](https://twitter.com/metamask_io) or [Medium](https://medium.com/metamask) pages. -To learn how to develop MetaMask-compatible applications, visit our [Developer Docs](https://metamask.github.io/metamask-docs/). +To learn how to develop MetaMask-compatible applications, visit our [Developer Docs](https://docs.metamask.io/). - [How to add custom build to Chrome](./add-to-chrome.md) - [How to add custom build to Firefox](./add-to-firefox.md) diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 743fb7fbcc42..7a723ae2c5cc 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -919,7 +919,7 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/contract-metadata": true, "@metamask/controller-utils": true, "@metamask/controller-utils>@metamask/eth-query": true, @@ -931,7 +931,6 @@ "@metamask/snaps-utils": true, "@metamask/assets-controllers>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, - "bitcoin-address-validation": true, "bn.js": true, "lodash": true, "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true, @@ -963,14 +962,6 @@ "immer": true } }, - "@metamask/assets-controllers>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/network-controller>@metamask/base-controller": { "globals": { "setTimeout": true @@ -1514,7 +1505,7 @@ "console.error": true }, "packages": { - "@metamask/multichain>@metamask/api-specs": true, + "@metamask/api-specs": true, "@metamask/controller-utils": true, "@metamask/eth-json-rpc-filters": true, "@metamask/json-rpc-engine": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index bb1b8b3a8a51..19761e4e6568 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -919,7 +919,7 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/contract-metadata": true, "@metamask/controller-utils": true, "@metamask/controller-utils>@metamask/eth-query": true, @@ -931,7 +931,6 @@ "@metamask/snaps-utils": true, "@metamask/assets-controllers>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, - "bitcoin-address-validation": true, "bn.js": true, "lodash": true, "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true, @@ -963,14 +962,6 @@ "immer": true } }, - "@metamask/assets-controllers>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/network-controller>@metamask/base-controller": { "globals": { "setTimeout": true @@ -1530,7 +1521,7 @@ "console.error": true }, "packages": { - "@metamask/multichain>@metamask/api-specs": true, + "@metamask/api-specs": true, "@metamask/controller-utils": true, "@metamask/eth-json-rpc-filters": true, "@metamask/json-rpc-engine": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 743fb7fbcc42..7a723ae2c5cc 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -919,7 +919,7 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/contract-metadata": true, "@metamask/controller-utils": true, "@metamask/controller-utils>@metamask/eth-query": true, @@ -931,7 +931,6 @@ "@metamask/snaps-utils": true, "@metamask/assets-controllers>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, - "bitcoin-address-validation": true, "bn.js": true, "lodash": true, "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true, @@ -963,14 +962,6 @@ "immer": true } }, - "@metamask/assets-controllers>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/network-controller>@metamask/base-controller": { "globals": { "setTimeout": true @@ -1514,7 +1505,7 @@ "console.error": true }, "packages": { - "@metamask/multichain>@metamask/api-specs": true, + "@metamask/api-specs": true, "@metamask/controller-utils": true, "@metamask/eth-json-rpc-filters": true, "@metamask/json-rpc-engine": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 309b6c6548e9..ba2776ee595c 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -1011,7 +1011,7 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/contract-metadata": true, "@metamask/controller-utils": true, "@metamask/controller-utils>@metamask/eth-query": true, @@ -1023,7 +1023,6 @@ "@metamask/snaps-utils": true, "@metamask/assets-controllers>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, - "bitcoin-address-validation": true, "bn.js": true, "lodash": true, "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true, @@ -1055,14 +1054,6 @@ "immer": true } }, - "@metamask/assets-controllers>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/network-controller>@metamask/base-controller": { "globals": { "setTimeout": true @@ -1606,7 +1597,7 @@ "console.error": true }, "packages": { - "@metamask/multichain>@metamask/api-specs": true, + "@metamask/api-specs": true, "@metamask/controller-utils": true, "@metamask/eth-json-rpc-filters": true, "@metamask/json-rpc-engine": true, diff --git a/package.json b/package.json index 625916184c13..f715248a7422 100644 --- a/package.json +++ b/package.json @@ -295,7 +295,7 @@ "@metamask/address-book-controller": "^6.0.3", "@metamask/announcement-controller": "^7.0.3", "@metamask/approval-controller": "^7.0.0", - "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A48.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-48.0.0-7a6e6586a9.patch", + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A49.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-49.0.0-e9c0266958.patch", "@metamask/base-controller": "^8.0.0", "@metamask/bitcoin-wallet-snap": "^0.9.0", "@metamask/browser-passworder": "^4.3.0", @@ -321,7 +321,7 @@ "@metamask/keyring-internal-api": "^4.0.2", "@metamask/keyring-snap-client": "^4.0.0", "@metamask/logging-controller": "^6.0.4", - "@metamask/logo": "^3.1.2", + "@metamask/logo": "^4.0.0", "@metamask/message-manager": "^12.0.1", "@metamask/message-signing-snap": "^0.6.0", "@metamask/metamask-eth-abis": "^3.1.1", @@ -462,7 +462,7 @@ "@lavamoat/lavadome-core": "0.0.20", "@lavamoat/lavapack": "^7.0.5", "@lydell/node-pty": "^1.0.1", - "@metamask/api-specs": "^0.9.3", + "@metamask/api-specs": "^0.10.15", "@metamask/auto-changelog": "^2.1.0", "@metamask/build-utils": "^3.0.0", "@metamask/eslint-config": "^9.0.0", diff --git a/privacy-snapshot.json b/privacy-snapshot.json index 499423cdbaf5..81c2e6e2d665 100644 --- a/privacy-snapshot.json +++ b/privacy-snapshot.json @@ -16,6 +16,7 @@ "bafybeidxfmwycgzcp4v2togflpqh2gnibuexjy4m4qqwxp7nh3jx5zlh4y.ipfs.dweb.link", "bridge.api.cx.metamask.io", "bridge.dev-api.cx.metamask.io", + "cdn.contentful.com", "cdn.segment.com", "cdn.segment.io", "cdnjs.cloudflare.com", @@ -46,6 +47,7 @@ "metametrics.metamask.test", "min-api.cryptocompare.com", "nft.api.cx.metamask.io", + "notification.api.cx.metamask.io", "oidc.api.cx.metamask.io", "on-ramp-content.api.cx.metamask.io", "on-ramp-content.uat-api.cx.metamask.io", @@ -55,6 +57,7 @@ "price.api.cx.metamask.io", "proxy.api.cx.metamask.io", "proxy.dev-api.cx.metamask.io", + "push.api.cx.metamask.io", "raw.githubusercontent.com", "registry.npmjs.org", "responsive-rpc.test", @@ -76,6 +79,7 @@ "token.api.cx.metamask.io", "tokens.api.cx.metamask.io", "transaction.api.cx.metamask.io", + "trigger.api.cx.metamask.io", "tx-sentinel-ethereum-mainnet.api.cx.metamask.io", "unresponsive-rpc.test", "unresponsive-rpc.url", diff --git a/test/e2e/constants.ts b/test/e2e/constants.ts index 23c52a91c653..eaf5a856261c 100644 --- a/test/e2e/constants.ts +++ b/test/e2e/constants.ts @@ -41,9 +41,10 @@ export const DEFAULT_GANACHE_ETH_BALANCE_DEC = '25'; /* Dapp host addresses and URL*/ export const DAPP_HOST_ADDRESS = '127.0.0.1:8080'; +export const DAPP_ONE_ADDRESS = '127.0.0.1:8081'; export const DAPP_URL_LOCALHOST = 'http://localhost:8080'; export const DAPP_URL = `http://${DAPP_HOST_ADDRESS}`; -export const DAPP_ONE_URL = 'http://127.0.0.1:8081'; +export const DAPP_ONE_URL = `http://${DAPP_ONE_ADDRESS}`; /* Default BTC address created using test SRP */ export const DEFAULT_BTC_ACCOUNT = 'bc1qg6whd6pc0cguh6gpp3ewujm53hv32ta9hdp252'; diff --git a/test/e2e/flask/btc/btc-account-overview.spec.ts b/test/e2e/flask/btc/btc-account-overview.spec.ts index 963820bf26ac..a097eb6a0ce1 100644 --- a/test/e2e/flask/btc/btc-account-overview.spec.ts +++ b/test/e2e/flask/btc/btc-account-overview.spec.ts @@ -40,8 +40,8 @@ describe('BTC Account - Overview', function (this: Suite) { }, ); }); - - it('has balance', async function () { + // Skipping btc test for now because btc snap is outdated and does not yet allow for new assets fetching logic. + it.skip('has balance', async function () { await withBtcAccountSnap( { title: this.test?.fullTitle() }, async (driver) => { diff --git a/test/e2e/flask/btc/btc-send.spec.ts b/test/e2e/flask/btc/btc-send.spec.ts index 9fece11446c2..ae61c4f9758b 100644 --- a/test/e2e/flask/btc/btc-send.spec.ts +++ b/test/e2e/flask/btc/btc-send.spec.ts @@ -7,7 +7,8 @@ import BitcoinHomepage from '../../page-objects/pages/home/bitcoin-homepage'; import BitcoinReviewTxPage from '../../page-objects/pages/send/bitcoin-review-tx-page'; import { getTransactionRequest, withBtcAccountSnap } from './common-btc'; -describe('BTC Account - Send', function (this: Suite) { +// Skipping btc test for now because btc snap is outdated and does not yet allow for new assets fetching logic. +describe.skip('BTC Account - Send', function (this: Suite) { it('can complete the send flow', async function () { await withBtcAccountSnap( { title: this.test?.fullTitle() }, diff --git a/test/e2e/page-objects/pages/account-list-page.ts b/test/e2e/page-objects/pages/account-list-page.ts index 8f24cfffede3..bf8fe585b2f7 100644 --- a/test/e2e/page-objects/pages/account-list-page.ts +++ b/test/e2e/page-objects/pages/account-list-page.ts @@ -132,6 +132,11 @@ class AccountListPage { private readonly selectAccountSelector = '.multichain-account-list-item__account-name'; + private readonly viewAccountOnExplorerButton = { + text: 'View on explorer', + tag: 'p', + }; + constructor(driver: Driver) { this.driver = driver; } @@ -378,6 +383,19 @@ class AccountListPage { ); } + /** + * View the account on explorer for the specified account in account list. + * + * @param accountLabel - The label of the account to view on explorer. + */ + async viewAccountOnExplorer(accountLabel: string): Promise { + console.log( + `View account on explorer in account list for account ${accountLabel}`, + ); + await this.openAccountOptionsInAccountList(accountLabel); + await this.driver.clickElement(this.viewAccountOnExplorerButton); + } + /** * Checks that the account value and suffix is displayed in the account list. * diff --git a/test/e2e/page-objects/pages/confirmations/redesign/add-network-confirmations.ts b/test/e2e/page-objects/pages/confirmations/redesign/add-network-confirmations.ts new file mode 100644 index 000000000000..1a71d4e5bfc4 --- /dev/null +++ b/test/e2e/page-objects/pages/confirmations/redesign/add-network-confirmations.ts @@ -0,0 +1,30 @@ +import { Driver } from '../../../../webdriver/driver'; + +class AddNetworkConfirmation { + driver: Driver; + + constructor(driver: Driver) { + this.driver = driver; + } + + /** + * @param networkName - The name of the network to check for in the confirmation page + */ + async check_pageIsLoaded(networkName: string): Promise { + try { + await this.driver.waitForSelector({ + text: `Add ${networkName}`, + tag: 'h3', + }); + } catch (e) { + console.log( + `Timeout while waiting for Add network ${networkName} confirmation page to be loaded`, + e, + ); + throw e; + } + console.log(`Add network ${networkName} confirmation page is loaded`); + } +} + +export default AddNetworkConfirmation; diff --git a/test/e2e/page-objects/pages/confirmations/redesign/connect-account-confirmation.ts b/test/e2e/page-objects/pages/confirmations/redesign/connect-account-confirmation.ts new file mode 100644 index 000000000000..fac0ca685b95 --- /dev/null +++ b/test/e2e/page-objects/pages/confirmations/redesign/connect-account-confirmation.ts @@ -0,0 +1,44 @@ +import { Driver } from '../../../../webdriver/driver'; + +class ConnectAccountConfirmation { + driver: Driver; + + private readonly connectAccountConfirmationButton = { + text: 'Connect', + tag: 'button', + }; + + private readonly connectAccountConfirmationTitle = { + text: 'Connect with MetaMask', + tag: 'h2', + }; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.connectAccountConfirmationTitle, + this.connectAccountConfirmationButton, + ]); + } catch (e) { + console.log( + `Timeout while waiting for Connect Account confirmation page to be loaded`, + e, + ); + throw e; + } + console.log(`Connect Account confirmation page is loaded`); + } + + async confirmConnect(): Promise { + console.log('Confirm connection on Connect Account confirmation page'); + await this.driver.clickElementAndWaitForWindowToClose( + this.connectAccountConfirmationButton, + ); + } +} + +export default ConnectAccountConfirmation; diff --git a/test/e2e/page-objects/pages/dialog/snap-install-warning.ts b/test/e2e/page-objects/pages/dialog/snap-install-warning.ts new file mode 100644 index 000000000000..da2937e20eb9 --- /dev/null +++ b/test/e2e/page-objects/pages/dialog/snap-install-warning.ts @@ -0,0 +1,44 @@ +import { Driver } from '../../../webdriver/driver'; + +class SnapInstallWarning { + private driver: Driver; + + private readonly checkBoxPermission = '.mm-checkbox__input'; + + private readonly buttonConfirm = + '[data-testid="snap-install-warning-modal-confirm"]'; + + private readonly permissionConnect = '.permissions-connect'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.checkBoxPermission, + this.permissionConnect, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for snap install warning dialog to be loaded', + e, + ); + throw e; + } + console.log('Snap install warning dialog is loaded'); + } + + async clickCheckboxPermission() { + console.log('Click checkbox permission'); + await this.driver.clickElement(this.checkBoxPermission); + } + + async clickConfirmButton() { + console.log('Click confirm button'); + await this.driver.clickElement(this.buttonConfirm); + } +} + +export default SnapInstallWarning; diff --git a/test/e2e/page-objects/pages/dialog/snap-install.ts b/test/e2e/page-objects/pages/dialog/snap-install.ts new file mode 100644 index 000000000000..65b09ef73385 --- /dev/null +++ b/test/e2e/page-objects/pages/dialog/snap-install.ts @@ -0,0 +1,54 @@ +import { Driver } from '../../../webdriver/driver'; + +class SnapInstall { + private driver: Driver; + + private readonly nextPageButton = + '[data-testid="page-container-footer-next"]'; + + private readonly pageFooter = '.page-container__footer'; + + private readonly permissionConnect = '.permissions-connect'; + + private readonly scrollSnapInstall = '[data-testid="snap-install-scroll"]'; + + private readonly approveButton = '[data-testid="confirmation-submit-button"]'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.pageFooter, + this.permissionConnect, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for Snap install dialog to be loaded', + e, + ); + throw e; + } + console.log('Snap install dialog is loaded'); + } + + async clickNextButton() { + console.log('Click Confirm/Ok button'); + await this.driver.clickElement(this.nextPageButton); + } + + async clickConfirmButton() { + console.log('Scroll and click confirm button'); + await this.driver.clickElementSafe(this.scrollSnapInstall); + await this.driver.clickElement(this.nextPageButton); + } + + async clickApproveButton() { + console.log('Click approve button'); + await this.driver.clickElement(this.approveButton); + } +} + +export default SnapInstall; diff --git a/test/e2e/page-objects/pages/header-navbar.ts b/test/e2e/page-objects/pages/header-navbar.ts index 9957af1b1e86..1fa3a67643ee 100644 --- a/test/e2e/page-objects/pages/header-navbar.ts +++ b/test/e2e/page-objects/pages/header-navbar.ts @@ -27,6 +27,12 @@ class HeaderNavbar { private readonly networkPicker = '.mm-picker-network'; + private readonly notificationsButton = + '[data-testid="notifications-menu-item"]'; + + private readonly firstTimeTurnOnNotificationsButton = + '[data-testid="turn-on-notifications-button"]'; + constructor(driver: Driver) { this.driver = driver; } @@ -88,6 +94,18 @@ class HeaderNavbar { await this.driver.clickElement(this.switchNetworkDropDown); } + async enableNotifications(): Promise { + console.log('Enabling notifications for the first time'); + await this.openThreeDotMenu(); + await this.driver.clickElement(this.notificationsButton); + await this.driver.clickElement(this.firstTimeTurnOnNotificationsButton); + } + + async goToNotifications(): Promise { + console.log('Click notifications button'); + await this.driver.clickElement(this.notificationsButton); + } + async check_currentSelectedNetwork(networkName: string): Promise { console.log(`Validate the Switch network to ${networkName}`); await this.driver.waitForSelector( diff --git a/test/e2e/page-objects/pages/home/activity-list.ts b/test/e2e/page-objects/pages/home/activity-list.ts index 4c5d0989979a..4d98908e6439 100644 --- a/test/e2e/page-objects/pages/home/activity-list.ts +++ b/test/e2e/page-objects/pages/home/activity-list.ts @@ -19,10 +19,15 @@ class ActivityListPage { css: '.transaction-status-label--failed', }; + private readonly tooltip = '.tippy-tooltip-content'; + private readonly transactionAmountsInActivity = '[data-testid="transaction-list-item-primary-currency"]'; - private readonly tooltip = '.tippy-tooltip-content'; + private readonly viewTransactionOnExplorerButton = { + text: 'View on block explorer', + tag: 'a', + }; private readonly cancelTransactionButton = { text: 'Cancel', @@ -41,25 +46,28 @@ class ActivityListPage { } /** - * This function checks if the specified number of failed transactions are displayed in the activity list on homepage. - * It waits up to 10 seconds for the expected number of failed transactions to be visible. + * This function clicks on the activity at the specified index. + * Note: this function need to be called after check_completedTxNumberDisplayedInActivity to reduce flakiness. * - * @param expectedNumber - The number of failed transactions expected to be displayed in activity list. Defaults to 1. - * @returns A promise that resolves if the expected number of failed transactions is displayed within the timeout period. + * @param expectedNumber - The 1-based index of the activity to be clicked. */ - async check_failedTxNumberDisplayedInActivity( - expectedNumber: number = 1, - ): Promise { - console.log( - `Wait for ${expectedNumber} failed transactions to be displayed in activity list`, - ); - await this.driver.wait(async () => { - const failedTxs = await this.driver.findElements(this.failedTransactions); - return failedTxs.length === expectedNumber; - }, 10000); + async clickOnActivity(expectedNumber: number): Promise { + console.log(`Clicking on activity ${expectedNumber}`); + const activities = await this.driver.findElements(this.activityListAction); + await activities[expectedNumber - 1].click(); + } + + /** + * This function clicks on the "View on block explorer" button for the specified transaction. + * + * @param expectedNumber - The 1-based index of the transaction to be clicked. + */ + async viewTransactionOnExplorer(expectedNumber: number): Promise { console.log( - `${expectedNumber} failed transactions found in activity list on homepage`, + `Viewing transaction on explorer for transaction ${expectedNumber}`, ); + await this.clickOnActivity(expectedNumber); + await this.driver.clickElement(this.viewTransactionOnExplorerButton); } /** @@ -110,6 +118,28 @@ class ActivityListPage { ); } + /** + * This function checks if the specified number of failed transactions is displayed in the activity list on homepage. + * It waits up to 10 seconds for the expected number of failed transactions to be visible. + * + * @param expectedNumber - The number of failed transactions expected to be displayed in activity list. Defaults to 1. + * @returns A promise that resolves if the expected number of failed transactions is displayed within the timeout period. + */ + async check_failedTxNumberDisplayedInActivity( + expectedNumber: number = 1, + ): Promise { + console.log( + `Wait for ${expectedNumber} failed transactions to be displayed in activity list`, + ); + await this.driver.wait(async () => { + const failedTxs = await this.driver.findElements(this.failedTransactions); + return failedTxs.length === expectedNumber; + }, 10000); + console.log( + `${expectedNumber} failed transactions found in activity list on homepage`, + ); + } + async check_noTxInActivity(): Promise { await this.driver.assertElementNotPresent(this.completedTransactions); } diff --git a/test/e2e/page-objects/pages/home/homepage.ts b/test/e2e/page-objects/pages/home/homepage.ts index 6597c3f4577a..dceb7efadefd 100644 --- a/test/e2e/page-objects/pages/home/homepage.ts +++ b/test/e2e/page-objects/pages/home/homepage.ts @@ -212,7 +212,7 @@ class HomePage { await this.driver.wait(async () => { const uiState = await getCleanAppState(this.driver); return uiState.metamask.hasAccountSyncingSyncedAtLeastOnce === true; - }, 10000); + }, 30000); // Syncing can take some time so adding a longer timeout to reduce flakes } async check_ifBridgeButtonIsClickable(): Promise { diff --git a/test/e2e/page-objects/pages/mocked-page.ts b/test/e2e/page-objects/pages/mocked-page.ts new file mode 100644 index 000000000000..51383fad7f14 --- /dev/null +++ b/test/e2e/page-objects/pages/mocked-page.ts @@ -0,0 +1,24 @@ +import { Driver } from '../../webdriver/driver'; + +class MockedPage { + private driver: Driver; + + constructor(driver: Driver) { + this.driver = driver; + } + + /** + * This method checks if message is displayed on the mocked page. + * + * @param message - The message to check if it is displayed on the mocked page. + */ + async check_displayedMessage(message: string): Promise { + console.log('Checking if message is displayed on mocked page', message); + await this.driver.waitForSelector({ + text: message, + tag: 'body', + }); + } +} + +export default MockedPage; diff --git a/test/e2e/page-objects/pages/notifications-list-page.ts b/test/e2e/page-objects/pages/notifications-list-page.ts new file mode 100644 index 000000000000..e9c24974cffc --- /dev/null +++ b/test/e2e/page-objects/pages/notifications-list-page.ts @@ -0,0 +1,47 @@ +import { Driver } from '../../webdriver/driver'; + +class NotificationsListPage { + private driver: Driver; + + private readonly notificationsListPageTitle = { + text: 'Notifications', + tag: 'p', + }; + + private readonly notificationsSettingsButton = + '[data-testid="notifications-settings-button"]'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.notificationsListPageTitle, + this.notificationsSettingsButton, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for Notifications list page to be loaded', + e, + ); + throw e; + } + console.log('Notifications List page is loaded'); + } + + /** + * Navigates to the notifications settings page. + * + * This method clicks on the notifications settings button to navigate to the settings page. + */ + async goToNotificationsSettings(): Promise { + console.log( + `On notifications list page, navigating to notifications settings`, + ); + await this.driver.clickElement(this.notificationsSettingsButton); + } +} + +export default NotificationsListPage; diff --git a/test/e2e/page-objects/pages/settings/notifications-settings-page.ts b/test/e2e/page-objects/pages/settings/notifications-settings-page.ts new file mode 100644 index 000000000000..e05dccfa7de0 --- /dev/null +++ b/test/e2e/page-objects/pages/settings/notifications-settings-page.ts @@ -0,0 +1,168 @@ +import { toChecksumHexAddress } from '@metamask/controller-utils'; +import { Driver } from '../../../webdriver/driver'; +import { shortenAddress } from '../../../../../ui/helpers/utils/util'; + +class NotificationsSettingsPage { + private driver: Driver; + + private readonly notificationsSettingsPageTitle = { + text: 'Notifications', + tag: 'p', + }; + + private readonly allowNotificationsToggle = + '[data-testid="notifications-settings-allow-toggle-box"]'; + + private readonly allowNotificationsInput = + '[data-testid="notifications-settings-allow-toggle-input"]'; + + private readonly allowNotificationsAddressToggle = ( + address: string, + elementType: 'input' | 'box', + ) => { + const checksumAddress = toChecksumHexAddress(address.toLowerCase()); + return `[data-testid="${shortenAddress( + checksumAddress, + )}-notifications-settings-toggle-${elementType}"]`; + }; + + private readonly allowProductAnnouncementToggle = + '[data-testid="product-announcements-toggle-box"]'; + + private readonly allowProductAnnouncementInput = + '[data-testid="product-announcements-toggle-input"]'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.notificationsSettingsPageTitle, + this.allowNotificationsToggle, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for notifications settings page to be loaded', + e, + ); + throw e; + } + console.log('Notifications Settings page is loaded'); + } + + /** + * Validates the state of any notification toggle. + * + * @param options - Configuration object + * @param options.toggleType - The type of toggle to check ('general' | 'product' | 'address') + * @param options.address - The ethereum address (required only when toggleType is 'address') + * @param options.expectedState - The expected state of the toggle ('enabled' or 'disabled') + * @throws {Error} If toggle state doesn't match expected state or if the toggle element cannot be found + */ + async check_notificationState({ + toggleType, + address, + expectedState, + }: { + toggleType: 'general' | 'product' | 'address'; + address?: string; + expectedState: 'enabled' | 'disabled'; + }): Promise { + let selector: string; + const description = + toggleType === 'address' ? `for address ${address}` : ''; + + switch (toggleType) { + case 'general': + selector = this.allowNotificationsInput; + break; + case 'product': + selector = this.allowProductAnnouncementInput; + break; + case 'address': + if (!address) { + throw new Error( + 'Address is required when checking address notifications', + ); + } + selector = this.allowNotificationsAddressToggle(address, 'input'); + break; + default: + throw new Error(`Invalid toggle type: ${toggleType}`); + } + + console.log( + `Checking if ${toggleType} notifications ${description} are ${expectedState}`, + ); + const expectedValue = expectedState === 'enabled' ? 'true' : 'false'; + + try { + await this.driver.waitForElementToStopMoving(selector); + await this.driver.wait(async () => { + const toggle = await this.driver.findElement(selector); + return (await toggle.getAttribute('value')) === expectedValue; + }); + console.log( + `Successfully verified ${toggleType} notifications ${description} to be ${expectedState}`, + ); + } catch (error) { + throw new Error( + `Expected ${toggleType} notifications ${description} state to be: ${expectedState}`, + ); + } + } + + /** + * Clicks a notification toggle and verifies its new state. + * + * @param options - Configuration object + * @param options.toggleType - The type of toggle to click ('general' | 'product' | 'address') + * @param options.address - The ethereum address (required only when toggleType is 'address') + * @throws {Error} If toggle element cannot be found or if address is missing when required + */ + async clickNotificationToggle({ + toggleType, + address, + }: { + toggleType: 'general' | 'product' | 'address'; + address?: string; + }): Promise { + let selector: string; + + switch (toggleType) { + case 'general': + selector = this.allowNotificationsToggle; + console.log('Clicking general notifications toggle'); + break; + case 'product': + selector = this.allowProductAnnouncementToggle; + console.log('Clicking product announcement toggle'); + break; + case 'address': + if (!address) { + throw new Error( + 'Address is required when toggling address notifications', + ); + } + selector = this.allowNotificationsAddressToggle(address, 'box'); + console.log(`Clicking notifications toggle for address ${address}`); + break; + default: + throw new Error(`Invalid toggle type: ${toggleType}`); + } + + try { + await this.driver.waitForElementToStopMoving(selector); + await this.driver.clickElement(selector); + await this.driver.waitForElementToStopMoving(selector); + console.log(`Successfully clicked ${toggleType} notifications toggle`); + } catch (error) { + console.error(`Error clicking ${toggleType} notifications toggle`, error); + throw error; + } + } +} + +export default NotificationsSettingsPage; diff --git a/test/e2e/page-objects/pages/settings/settings-page.ts b/test/e2e/page-objects/pages/settings/settings-page.ts index 4444556a1b74..e149ee1eea1e 100644 --- a/test/e2e/page-objects/pages/settings/settings-page.ts +++ b/test/e2e/page-objects/pages/settings/settings-page.ts @@ -26,6 +26,11 @@ class SettingsPage { css: 'h3', }; + private readonly notificationsSettingsButton = { + text: 'Notifications', + css: '.tab-bar__tab__content__title', + }; + constructor(driver: Driver) { this.driver = driver; } @@ -88,6 +93,11 @@ class SettingsPage { console.log('Navigating to Privacy & Security Settings page'); await this.driver.clickElement(this.privacySettingsButton); } + + async goToNotificationsSettings(): Promise { + console.log('Navigating to Notifications Settings page'); + await this.driver.clickElement(this.notificationsSettingsButton); + } } export default SettingsPage; diff --git a/test/e2e/page-objects/pages/test-dapp.ts b/test/e2e/page-objects/pages/test-dapp.ts index c023ddfa41fd..c5153745848b 100644 --- a/test/e2e/page-objects/pages/test-dapp.ts +++ b/test/e2e/page-objects/pages/test-dapp.ts @@ -13,6 +13,8 @@ class TestDapp { tag: 'button', }; + private readonly addNetworkButton = '#addEthereumChain'; + private readonly approveTokensButton = '#approveTokens'; private readonly approveTokensButtonWithoutGas = '#approveTokensWithoutGas'; @@ -501,6 +503,14 @@ class TestDapp { await this.driver.clickElement(this.addTokensToWalletButton); } + async clickAddNetworkButton() { + await this.driver.clickElement(this.addNetworkButton); + } + + async clickConnectAccountButton() { + await this.driver.clickElement(this.connectAccountButton); + } + async clickApproveTokens() { await this.driver.clickElement(this.approveTokensButton); } @@ -624,7 +634,7 @@ class TestDapp { chainId?: string; }) { console.log('Connect account to test dapp'); - await this.driver.clickElement(this.connectAccountButton); + await this.clickConnectAccountButton(); if (connectAccountButtonEnabled) { await this.confirmConnectAccountModal(); } else { diff --git a/test/e2e/page-objects/pages/test-snaps.ts b/test/e2e/page-objects/pages/test-snaps.ts index bf149e58277a..c53d56570915 100644 --- a/test/e2e/page-objects/pages/test-snaps.ts +++ b/test/e2e/page-objects/pages/test-snaps.ts @@ -32,6 +32,48 @@ export class TestSnaps { private readonly connectHomePage = '#connecthomepage'; + private readonly connectBip32 = '#connectbip32'; + + private readonly connectBip44 = '#connectbip44'; + + private readonly reconnectButton = { + css: '#connectbip32', + text: 'Reconnect to BIP-32 Snap', + }; + + private readonly reconnectBip44Button = { + css: '#connectbip44', + text: 'Reconnect to BIP-44 Snap', + }; + + private readonly getPublicKeyButton = { + css: '#bip32GetPublic', + text: 'Get Public Key', + }; + + private readonly getCompressedKeyButton = { + css: '#bip32GetCompressedPublic', + text: 'Get Compressed Public Key', + }; + + private readonly publicKeyBip44Button = '#sendBip44Test'; + + private readonly inputMessageEd25519 = '#bip32Message-ed25519'; + + private readonly inputMessageEd25519Bip32 = '#bip32Message-ed25519Bip32'; + + private readonly inputMessageSecp256k1 = '#bip32Message-secp256k1'; + + private readonly buttonMessageSecp256k1 = '#sendBip32-secp256k1'; + + private readonly buttonSignEd25519Message = '#sendBip32-ed25519'; + + private readonly inputMessageBip44 = '#bip44Message'; + + private readonly buttonSignBip44Message = '#signBip44Message'; + + private readonly buttonSignEd25519Bip32Message = '#sendBip32-ed25519Bip32'; + constructor(driver: Driver) { this.driver = driver; } @@ -53,6 +95,44 @@ export class TestSnaps { await this.driver.clickElement(this.dialogsSnapConfirmationButton); } + async clickConnectBip32() { + console.log('Wait, scroll and click connect button'); + await this.driver.scrollToElement( + this.driver.findClickableElement(this.connectBip32), + ); + await this.driver.delay(largeDelayMs); + await this.driver.waitForSelector(this.connectBip32); + await this.driver.clickElement(this.connectBip32); + } + + async clickConnectBip44() { + console.log('Wait, scroll and click connect button'); + await this.driver.scrollToElement( + this.driver.findClickableElement(this.connectBip44), + ); + await this.driver.delay(largeDelayMs); + await this.driver.waitForSelector(this.connectBip44); + await this.driver.clickElement(this.connectBip44); + } + + async clickGetPublicKeyButton() { + console.log('Wait and click get public key button'); + await this.driver.waitForSelector(this.getPublicKeyButton); + await this.driver.clickElement(this.getPublicKeyButton); + } + + async clickPublicKeyBip44Button() { + console.log('Wait and click get public key button'); + await this.driver.waitForSelector(this.publicKeyBip44Button); + await this.driver.clickElement(this.publicKeyBip44Button); + } + + async clickGetCompressedPublicKeyButton() { + console.log('Wait and click get compressed public key button'); + await this.driver.waitForSelector(this.getCompressedKeyButton); + await this.driver.clickElement(this.getCompressedKeyButton); + } + async completeSnapInstallConfirmation() { await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); @@ -85,4 +165,51 @@ export class TestSnaps { await this.driver.waitForSelector(this.connectHomePage); await this.driver.clickElement(this.connectHomePage); } + + async fillMessageSecp256k1(message: string) { + console.log('Wait and fill message in secp256k1'); + await this.driver.fill(this.inputMessageSecp256k1, message); + await this.driver.clickElement(this.buttonMessageSecp256k1); + } + + async fillMessageEd25519(message: string) { + console.log('Wait and fill message in ed25519'); + await this.driver.waitForSelector(this.inputMessageEd25519); + await this.driver.fill(this.inputMessageEd25519, message); + await this.driver.clickElement(this.buttonSignEd25519Message); + } + + async fillMessageEd25519Bip32(message: string) { + console.log('Wait and fill message in ed25519 bip32'); + await this.driver.waitForSelector(this.inputMessageEd25519Bip32); + await this.driver.fill(this.inputMessageEd25519Bip32, message); + await this.driver.clickElement(this.buttonSignEd25519Bip32Message); + } + + async fillBip44MessageAndSign(message: string) { + console.log('Wait and enter bip44 message '); + await this.driver.pasteIntoField(this.inputMessageBip44, message); + const buttonSignBip44 = await this.driver.findElement( + this.buttonSignBip44Message, + ); + await this.driver.scrollToElement(buttonSignBip44); + await this.driver.waitForSelector(this.buttonSignBip44Message); + await this.driver.clickElement(this.buttonSignBip44Message); + } + + async scrollToSendEd25519() { + console.log('Scroll to send ed25519'); + const sendEd25519 = await this.driver.findElement(this.inputMessageEd25519); + await this.driver.scrollToElement(sendEd25519); + } + + async waitForReconnectButton() { + console.log('Wait for reconnect button'); + await this.driver.waitForSelector(this.reconnectButton); + } + + async waitForReconnectBip44Button() { + console.log('Wait for reconnect button'); + await this.driver.waitForSelector(this.reconnectBip44Button); + } } diff --git a/test/e2e/page-objects/pages/token-overview-page.ts b/test/e2e/page-objects/pages/token-overview-page.ts index 46e93ed490c7..94eddb3fd6c7 100644 --- a/test/e2e/page-objects/pages/token-overview-page.ts +++ b/test/e2e/page-objects/pages/token-overview-page.ts @@ -3,6 +3,8 @@ import { Driver } from '../../webdriver/driver'; class TokenOverviewPage { private driver: Driver; + private readonly assetOptionsButton = '[data-testid="asset-options__button"]'; + private readonly receiveButton = { text: 'Receive', css: '.icon-button', @@ -18,6 +20,11 @@ class TokenOverviewPage { css: '.icon-button', }; + private readonly viewAssetInExplorerButton = { + text: 'View Asset in explorer', + tag: 'div', + }; + constructor(driver: Driver) { this.driver = driver; } @@ -49,6 +56,17 @@ class TokenOverviewPage { async clickSwap(): Promise { await this.driver.clickElement(this.swapButton); } + + /** + * This method opens the asset in explorer. + */ + async viewAssetInExplorer(): Promise { + console.log('Viewing asset in explorer'); + await this.driver.clickElement(this.assetOptionsButton); + await this.driver.clickElementAndWaitToDisappear( + this.viewAssetInExplorerButton, + ); + } } export default TokenOverviewPage; diff --git a/test/e2e/run-openrpc-api-test-coverage.ts b/test/e2e/run-openrpc-api-test-coverage.ts index f192f6088954..60e52e3a4eab 100644 --- a/test/e2e/run-openrpc-api-test-coverage.ts +++ b/test/e2e/run-openrpc-api-test-coverage.ts @@ -324,6 +324,56 @@ async function main() { // }, ]; + const getProof = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_getProof', + ); + (getProof as MethodObject).examples = [ + { + name: 'getProofExample', + description: 'Example of a getProof request', + params: [ + { + name: 'address', + value: ACCOUNT_1, + }, + { + name: 'keys', + value: [ + '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + ], + }, + { + name: 'tag', + value: 'latest', + }, + ], + result: { + name: 'getProofResult', + value: { + address: ACCOUNT_1, + balance: '0x15af1d78b58c40000', + codeHash: + '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + nonce: '0x0', + storageHash: + '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + accountProof: [ + '0xf9017180a0ab8cdb808c8303bb61fb48e276217be9770fa83ecf3f90f2234d558885f5abf18080a0de26cb1b4fd99c4d3ed75d4a67931e3c252605c7d68e0148d5327f341bfd5283a0de86ea5531307567132648d5c7956cb6082d6803f3dbc9e16b2dd20b320ca93aa0c2c799b60a0cd6acd42c1015512872e86c186bcf196e85061e76842f3b7cf86080a04fa8b5b81f5814f27b3a3e2b6273792dc150c94bea8f90c4b4d3fb4f52cd80dea0c326f61dd1e74e037d4db73aede5642260bf92869081753bbace550a73989aeda06301b39b2ea8a44df8b0356120db64b788e71f52e1d7a6309d0d2e5b86fee7cb80a029087b3ba8c5129e161e2cb956640f4d8e31a35f3f133c19a1044993def98b61a0a5ac64bb99d260ef6b13a4f2040ed48a4936664ec13d400238b5004841a4d888a0a9e6cc0d5192cb036c2454c7cf19ff53abf1861b50043a7b3713bc003a5a7d88a0144540d36e30b250d25bd5c34d819538742dc54c2017c4eb1fabb8e45f72759180', + '0xf85180a0563305036bc8702a52ae6338bfbeca18e8f42fd5ee640e72e18f31455d3be5f880808080808080808080a02fb46956347985b9870156b5747712899d213b1636ad4fe553c63e33521d567a80808080', + '0xf872a020bf0de4df4861e4184def33fbb5c7e634b9c33718934bf717ec7b695ea08cb5b84ff84d8089015af1d78b58c40000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + ], + storageProof: [ + { + key: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + proof: [], + value: '0x0', + }, + ], + }, + }, + }, + ]; + const server = mockServer(port, openrpcDocument); server.start(); @@ -373,10 +423,11 @@ async function main() { ], skip: [ 'eth_coinbase', - // these 2 methods below are not supported by MetaMask extension yet and + // these methods below are not supported by MetaMask extension yet and // don't get passed through. See here: https://github.com/MetaMask/metamask-extension/issues/24225 'eth_getBlockReceipts', 'eth_maxPriorityFeePerGas', + 'wallet_swapAsset', ], rules: [ new JsonSchemaFakerRule({ diff --git a/test/e2e/snaps/test-snap-bip-32.spec.js b/test/e2e/snaps/test-snap-bip-32.spec.js deleted file mode 100644 index f6f01b8c2ee2..000000000000 --- a/test/e2e/snaps/test-snap-bip-32.spec.js +++ /dev/null @@ -1,157 +0,0 @@ -const { withFixtures, unlockWallet, WINDOW_TITLES } = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); -const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); - -describe('Test Snap bip-32', function () { - it('tests various functions of bip-32', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // navigate to test snaps page and connect - await driver.driver.get(TEST_SNAPS_WEBSITE_URL); - - // wait for page to load - await driver.waitForSelector({ - text: 'Installed Snaps', - tag: 'h2', - }); - - // find and scroll to the bip32 snap - const snapButton1 = await driver.findElement('#connectbip32'); - await driver.scrollToElement(snapButton1); - - // added delay for firefox (deflake) - await driver.delayFirefox(3000); - - // wait for and click connect to bip-32 - await driver.waitForSelector('#connectbip32'); - await driver.clickElement('#connectbip32'); - - // switch to metamask extension and click connect - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ - text: 'Connect', - tag: 'button', - }); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // wait for confirm to appear - await driver.waitForSelector({ text: 'Confirm' }); - - // click and dismiss possible scroll element - await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); - - // click confirm - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - - // wait for permissions popover, click checkboxes and confirm - await driver.waitForSelector('.mm-checkbox__input'); - await driver.clickElement('.mm-checkbox__input'); - await driver.waitForSelector( - '[data-testid="snap-install-warning-modal-confirm"]', - ); - await driver.clickElement( - '[data-testid="snap-install-warning-modal-confirm"]', - ); - - // wait for and click OK and wait for window to close - await driver.waitForSelector({ text: 'OK' }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'OK', - tag: 'button', - }); - - // switch back to test-snaps window - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - - // wait for npm installation success - await driver.waitForSelector({ - css: '#connectbip32', - text: 'Reconnect to BIP-32 Snap', - }); - - // scroll to and click get public key - await driver.waitForSelector({ text: 'Get Public Key' }); - await driver.clickElement('#bip32GetPublic'); - - // check for proper public key response using waitForSelector - await driver.waitForSelector({ - css: '#bip32PublicKeyResult', - text: '"0x043e98d696ae15caef75fa8dd204a7c5c08d1272b2218ba3c20feeb4c691eec366606ece56791c361a2320e7fad8bcbb130f66d51c591fc39767ab2856e93f8dfb', - }); - - // scroll to and click get compressed public key - await driver.waitForSelector({ text: 'Get Compressed Public Key' }); - await driver.clickElement('#bip32GetCompressedPublic'); - - // check for proper public key response using waitForSelector - await driver.waitForSelector({ - css: '#bip32PublicKeyResult', - text: '"0x033e98d696ae15caef75fa8dd204a7c5c08d1272b2218ba3c20feeb4c691eec366', - }); - - // wait then run SECP256K1 test - await driver.fill('#bip32Message-secp256k1', 'foo bar'); - await driver.clickElement('#sendBip32-secp256k1'); - - // hit 'approve' on the signature confirmation and wait for window to close - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Approve', - tag: 'button', - }); - - // switch back to the test-snaps window - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - - // check results of the secp256k1 signature with waitForSelector - await driver.waitForSelector({ - css: '#bip32MessageResult-secp256k1', - text: '"0x3045022100b3ade2992ea3e5eb58c7550e9bddad356e9554233c8b099ebc3cb418e9301ae2022064746e15ae024808f0ba5d860e44dc4c97e65c8cba6f5ef9ea2e8c819930d2dc', - }); - - // scroll further into messages section - const snapButton4 = await driver.findElement('#sendBip32-ed25519'); - await driver.scrollToElement(snapButton4); - - // wait then run ed25519 test - await driver.waitForSelector('#bip32Message-ed25519'); - await driver.fill('#bip32Message-ed25519', 'foo bar'); - await driver.clickElement('#sendBip32-ed25519'); - - // switch to dialog window - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and click 'approve' and wait for window to close - await driver.waitForSelector({ - text: 'Approve', - tag: 'button', - }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Approve', - tag: 'button', - }); - - // switch back to test-snaps window - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - - // check results of ed25519 signature with waitForSelector - await driver.waitForSelector({ - css: '#bip32MessageResult-ed25519', - text: '"0xf3215b4d6c59aac7e01b4ceef530d1e2abf4857926b85a81aaae3894505699243768a887b7da4a8c2e0f25196196ba290b6531050db8dc15c252bdd508532a0a"', - }); - }, - ); - }); -}); diff --git a/test/e2e/snaps/test-snap-bip-32.spec.ts b/test/e2e/snaps/test-snap-bip-32.spec.ts new file mode 100644 index 000000000000..112a2bace440 --- /dev/null +++ b/test/e2e/snaps/test-snap-bip-32.spec.ts @@ -0,0 +1,124 @@ +import { TestSnaps } from '../page-objects/pages/test-snaps'; +import { Driver } from '../webdriver/driver'; +import { loginWithoutBalanceValidation } from '../page-objects/flows/login.flow'; +import FixtureBuilder from '../fixture-builder'; +import { withFixtures, WINDOW_TITLES } from '../helpers'; +import SnapInstall from '../page-objects/pages/dialog/snap-install'; +import SnapInstallWarning from '../page-objects/pages/dialog/snap-install-warning'; + +describe('Test Snap bip-32', function () { + it('tests various functions of bip-32', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + title: this.test?.fullTitle(), + }, + async ({ driver }: { driver: Driver }) => { + await loginWithoutBalanceValidation(driver); + + const testSnaps = new TestSnaps(driver); + const snapInstall = new SnapInstall(driver); + const snapInstallWarning = new SnapInstallWarning(driver); + + // navigate to test snaps page and connect wait for page to load + await testSnaps.openPage(); + + // find, scroll and click connect to the bip32 snap + await testSnaps.clickConnectBip32(); + + // switch to metamask extension and click connect + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await snapInstall.check_pageIsLoaded(); + await snapInstall.clickNextButton(); + + // click confirm + await snapInstall.clickConfirmButton(); + + // wait for permissions popover, click checkboxes and confirm + await snapInstallWarning.check_pageIsLoaded(); + await snapInstallWarning.clickCheckboxPermission(); + await snapInstallWarning.clickConfirmButton(); + + // wait for and click OK and wait for window to close + await snapInstall.clickNextButton(); + + // switch back to test-snaps window + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // wait for npm installation success + await testSnaps.waitForReconnectButton(); + + // scroll to and click get public key + await testSnaps.clickGetPublicKeyButton(); + + // check for proper public key response using waitForSelector + await driver.waitForSelector({ + css: '#bip32PublicKeyResult', + text: '"0x043e98d696ae15caef75fa8dd204a7c5c08d1272b2218ba3c20feeb4c691eec366606ece56791c361a2320e7fad8bcbb130f66d51c591fc39767ab2856e93f8dfb', + }); + + // scroll to and click get compressed public key + await testSnaps.clickGetCompressedPublicKeyButton(); + + // check for proper public key response using waitForSelector + await driver.waitForSelector({ + css: '#bip32PublicKeyResult', + text: '"0x033e98d696ae15caef75fa8dd204a7c5c08d1272b2218ba3c20feeb4c691eec366', + }); + + // wait then run SECP256K1 test + await testSnaps.fillMessageSecp256k1('foo bar'); + + // hit 'approve' on the signature confirmation and wait for window to close + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await snapInstall.clickApproveButton(); + + // switch back to the test-snaps window + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // check results of the secp256k1 signature with waitForSelector + await driver.waitForSelector({ + css: '#bip32MessageResult-secp256k1', + text: '"0x3045022100b3ade2992ea3e5eb58c7550e9bddad356e9554233c8b099ebc3cb418e9301ae2022064746e15ae024808f0ba5d860e44dc4c97e65c8cba6f5ef9ea2e8c819930d2dc', + }); + + // scroll further into messages section + await testSnaps.scrollToSendEd25519(); + + // wait then run ed25519 test + await testSnaps.fillMessageEd25519('foo bar'); + + // switch to dialog window + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await snapInstall.clickApproveButton(); + + // switch back to test-snaps window + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // check results of ed25519 signature with waitForSelector + await driver.waitForSelector({ + css: '#bip32MessageResult-ed25519', + text: '"0xf3215b4d6c59aac7e01b4ceef530d1e2abf4857926b85a81aaae3894505699243768a887b7da4a8c2e0f25196196ba290b6531050db8dc15c252bdd508532a0a"', + }); + + // wait then run ed25519 test + await testSnaps.fillMessageEd25519Bip32('foo bar'); + + // switch to dialog window + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await snapInstall.clickApproveButton(); + + // switch back to test-snaps window + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // check results of ed25519 signature with waitForSelector + await driver.waitForSelector({ + css: '#bip32MessageResult-ed25519Bip32', + text: '"0xc279ee3e49f7e392a4e511136c39791e076f9be01d8648f3f1586ecf0f41def1739fa2978f90cfb2da4cf53ccb99405558cffcc4d190199b6949b03b1b8dae05"', + }); + }, + ); + }); +}); diff --git a/test/e2e/snaps/test-snap-bip-44.spec.js b/test/e2e/snaps/test-snap-bip-44.spec.js deleted file mode 100644 index 9846701be34d..000000000000 --- a/test/e2e/snaps/test-snap-bip-44.spec.js +++ /dev/null @@ -1,116 +0,0 @@ -const { withFixtures, unlockWallet, WINDOW_TITLES } = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); -const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); - -describe('Test Snap bip-44', function () { - it('can pop up bip-44 snap and get private key result', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // navigate to test snaps page and connect - await driver.driver.get(TEST_SNAPS_WEBSITE_URL); - - // wait for page to load - await driver.waitForSelector({ - text: 'Installed Snaps', - tag: 'h2', - }); - - // find and scroll to the bip44 snap - const snapButton1 = await driver.findElement('#connectbip44'); - await driver.scrollToElement(snapButton1); - - // added delay for firefox (deflake) - await driver.delayFirefox(1000); - - // wait for and click connect - await driver.waitForSelector('#connectbip44'); - await driver.clickElement('#connectbip44'); - - // switch to metamask extension and click connect and approve - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - await driver.waitForSelector({ text: 'Confirm' }); - - await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); - - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - - // deal with permissions popover - await driver.waitForSelector('.mm-checkbox__input'); - await driver.clickElement('.mm-checkbox__input'); - await driver.waitForSelector( - '[data-testid="snap-install-warning-modal-confirm"]', - ); - await driver.clickElement( - '[data-testid="snap-install-warning-modal-confirm"]', - ); - - // wait for and click ok and wait for window to close - await driver.waitForSelector({ text: 'OK' }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'OK', - tag: 'button', - }); - - // switch back to test-snaps window - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - - // wait for npm installation success - await driver.waitForSelector({ - css: '#connectbip44', - text: 'Reconnect to BIP-44 Snap', - }); - - // find and click bip44 test - await driver.clickElement('#sendBip44Test'); - - // check the results of the public key test using waitForSelector - await driver.waitForSelector({ - css: '#bip44Result', - text: '"0x86debb44fb3a984d93f326131d4c1db0bc39644f1a67b673b3ab45941a1cea6a385981755185ac4594b6521e4d1e08d1"', - }); - - // enter a message to sign - await driver.pasteIntoField('#bip44Message', '1234'); - const snapButton3 = await driver.findElement('#signBip44Message'); - await driver.scrollToElement(snapButton3); - await driver.waitForSelector('#signBip44Message'); - await driver.clickElement('#signBip44Message'); - - // Switch to approve signature message window - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and click approve and wait for window to close - await driver.waitForSelector({ - text: 'Approve', - tag: 'button', - }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Approve', - tag: 'button', - }); - - // switch back to test-snaps page - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - - // check the results of the message signature using waitForSelector - await driver.waitForSelector({ - css: '#bip44SignResult', - text: '"0xa41ab87ca50606eefd47525ad90294bbe44c883f6bc53655f1b8a55aa8e1e35df216f31be62e52c7a1faa519420e20810162e07dedb0fde2a4d997ff7180a78232ecd8ce2d6f4ba42ccacad33c5e9e54a8c4d41506bdffb2bb4c368581d8b086"', - }); - }, - ); - }); -}); diff --git a/test/e2e/snaps/test-snap-bip-44.spec.ts b/test/e2e/snaps/test-snap-bip-44.spec.ts new file mode 100644 index 000000000000..ddd905996c57 --- /dev/null +++ b/test/e2e/snaps/test-snap-bip-44.spec.ts @@ -0,0 +1,80 @@ +import { TestSnaps } from '../page-objects/pages/test-snaps'; +import { Driver } from '../webdriver/driver'; +import { loginWithoutBalanceValidation } from '../page-objects/flows/login.flow'; +import FixtureBuilder from '../fixture-builder'; +import { withFixtures, WINDOW_TITLES } from '../helpers'; +import SnapInstall from '../page-objects/pages/dialog/snap-install'; +import SnapInstallWarning from '../page-objects/pages/dialog/snap-install-warning'; + +describe('Test Snap bip-44', function () { + it('can pop up bip-44 snap and get private key result', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + title: this.test?.fullTitle(), + }, + async ({ driver }: { driver: Driver }) => { + await loginWithoutBalanceValidation(driver); + + const testSnaps = new TestSnaps(driver); + const snapInstall = new SnapInstall(driver); + const snapInstallWarning = new SnapInstallWarning(driver); + + // navigate to test snaps page and connect wait for page to load + await testSnaps.openPage(); + + // find and scroll to the bip44 snap + await testSnaps.clickConnectBip44(); + + // switch to metamask extension and click connect and approve + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await snapInstall.check_pageIsLoaded(); + await snapInstall.clickNextButton(); + + // click confirm + await snapInstall.clickConfirmButton(); + + // deal with permissions popover, click checkboxes and confirm + await snapInstallWarning.check_pageIsLoaded(); + await snapInstallWarning.clickCheckboxPermission(); + await snapInstallWarning.clickConfirmButton(); + + // wait for and click ok and wait for window to close + await snapInstall.clickNextButton(); + + // switch back to test-snaps window + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // wait for npm installation success + await testSnaps.waitForReconnectBip44Button(); + + // find and click bip44 test + await testSnaps.clickPublicKeyBip44Button(); + + // check the results of the public key test using waitForSelector + await driver.waitForSelector({ + css: '#bip44Result', + text: '"0x86debb44fb3a984d93f326131d4c1db0bc39644f1a67b673b3ab45941a1cea6a385981755185ac4594b6521e4d1e08d1"', + }); + + // enter a message to sign + await testSnaps.fillBip44MessageAndSign('1234'); + + // Switch to approve signature message window + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // wait for and click approve and wait for window to close + await snapInstall.clickApproveButton(); + + // switch back to test-snaps page + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // check the results of the message signature using waitForSelector + await driver.waitForSelector({ + css: '#bip44SignResult', + text: '"0xa41ab87ca50606eefd47525ad90294bbe44c883f6bc53655f1b8a55aa8e1e35df216f31be62e52c7a1faa519420e20810162e07dedb0fde2a4d997ff7180a78232ecd8ce2d6f4ba42ccacad33c5e9e54a8c4d41506bdffb2bb4c368581d8b086"', + }); + }, + ); + }); +}); diff --git a/test/e2e/tests/dapp-interactions/block-explorer.spec.js b/test/e2e/tests/dapp-interactions/block-explorer.spec.ts similarity index 51% rename from test/e2e/tests/dapp-interactions/block-explorer.spec.js rename to test/e2e/tests/dapp-interactions/block-explorer.spec.ts index ddfbd225d69f..6954fdae4785 100644 --- a/test/e2e/tests/dapp-interactions/block-explorer.spec.js +++ b/test/e2e/tests/dapp-interactions/block-explorer.spec.ts @@ -1,8 +1,16 @@ -const { mockNetworkStateOld } = require('../../../stub/networks'); - -const { withFixtures, unlockWallet } = require('../../helpers'); -const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); -const FixtureBuilder = require('../../fixture-builder'); +import { mockNetworkStateOld } from '../../../stub/networks'; +import { withFixtures } from '../../helpers'; +import { SMART_CONTRACTS } from '../../seeder/smart-contracts'; +import { DEFAULT_FIXTURE_ACCOUNT } from '../../constants'; +import FixtureBuilder from '../../fixture-builder'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import ActivityListPage from '../../page-objects/pages/home/activity-list'; +import AssetListPage from '../../page-objects/pages/home/asset-list'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import HomePage from '../../page-objects/pages/home/homepage'; +import MockedPage from '../../page-objects/pages/mocked-page'; +import TokenOverviewPage from '../../page-objects/pages/token-overview-page'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; describe('Block Explorer', function () { it('links to the users account on the explorer, ', async function () { @@ -19,30 +27,28 @@ describe('Block Explorer', function () { }), ) .build(), - title: this.test.fullTitle(), + title: this.test?.fullTitle(), }, async ({ driver }) => { - await unlockWallet(driver); + await loginWithBalanceValidation(driver); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.openAccountMenu(); // View account on explorer - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="account-list-item-menu-button"]', - ); - await driver.clickElement({ text: 'View on explorer', tag: 'p' }); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.viewAccountOnExplorer('Account 1'); // Switch to block explorer await driver.switchToWindowWithTitle('E2E Test Page'); // Verify block explorer await driver.waitForUrl({ - url: 'https://etherscan.io/address/0x5CfE73b6021E818B776b421B1c4Db2474086a7e1', - }); - - await driver.waitForSelector({ - text: 'Empty page by MetaMask', - tag: 'body', + url: `https://etherscan.io/address/${DEFAULT_FIXTURE_ACCOUNT}`, }); + await new MockedPage(driver).check_displayedMessage( + 'Empty page by MetaMask', + ); }, ); }); @@ -64,39 +70,30 @@ describe('Block Explorer', function () { .withTokensControllerERC20() .build(), smartContract: SMART_CONTRACTS.HST, - title: this.test.fullTitle(), + title: this.test?.fullTitle(), }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver, ganacheServer }) => { + await loginWithBalanceValidation(driver, ganacheServer); // View TST token in block explorer - await driver.clickElement( - '[data-testid="account-overview__asset-tab"]', - ); - - await driver.clickElement({ - text: 'TST', - tag: 'p', - }); + const assetListPage = new AssetListPage(driver); + await assetListPage.check_tokenItemNumber(2); + await assetListPage.clickOnAsset('TST'); - await driver.clickElement('[data-testid="asset-options__button"]'); - await driver.clickElement({ - text: 'View Asset in explorer', - tag: 'div', - }); + const tokenOverviewPage = new TokenOverviewPage(driver); + await tokenOverviewPage.check_pageIsLoaded(); + await tokenOverviewPage.viewAssetInExplorer(); // Switch to block explorer await driver.switchToWindowWithTitle('E2E Test Page'); // Verify block explorer await driver.waitForUrl({ - url: 'https://etherscan.io/token/0x581c3C1A2A4EBDE2A0Df29B5cf4c116E42945947', - }); - - await driver.waitForSelector({ - text: 'Empty page by MetaMask', - tag: 'body', + url: `https://etherscan.io/token/0x581c3C1A2A4EBDE2A0Df29B5cf4c116E42945947`, }); + await new MockedPage(driver).check_displayedMessage( + 'Empty page by MetaMask', + ); }, ); }); @@ -117,20 +114,16 @@ describe('Block Explorer', function () { }) .withTransactionControllerCompletedTransaction() .build(), - title: this.test.fullTitle(), + title: this.test?.fullTitle(), }, async ({ driver }) => { - await unlockWallet(driver); + await loginWithBalanceValidation(driver); // View transaction on block explorer - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - await driver.clickElement('[data-testid="activity-list-item-action"]'); - await driver.clickElement({ - text: 'View on block explorer', - tag: 'a', - }); + await new HomePage(driver).goToActivityList(); + const activityListPage = new ActivityListPage(driver); + await activityListPage.check_completedTxNumberDisplayedInActivity(1); + await activityListPage.viewTransactionOnExplorer(1); // Switch to block explorer await driver.switchToWindowWithTitle('E2E Test Page'); @@ -139,11 +132,9 @@ describe('Block Explorer', function () { await driver.waitForUrl({ url: 'https://etherscan.io/tx/0xe5e7b95690f584b8f66b33e31acc6184fea553fa6722d42486a59990d13d5fa2', }); - - await driver.waitForSelector({ - text: 'Empty page by MetaMask', - tag: 'body', - }); + await new MockedPage(driver).check_displayedMessage( + 'Empty page by MetaMask', + ); }, ); }); diff --git a/test/e2e/tests/dapp-interactions/dapp-interactions.spec.js b/test/e2e/tests/dapp-interactions/dapp-interactions.spec.js deleted file mode 100644 index 2d2afeeb01a2..000000000000 --- a/test/e2e/tests/dapp-interactions/dapp-interactions.spec.js +++ /dev/null @@ -1,97 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - openDapp, - DAPP_ONE_URL, - unlockWallet, - WINDOW_TITLES, - generateGanacheOptions, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Dapp interactions', function () { - it('should trigger the add chain confirmation despite MetaMask being locked', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().build(), - localNodeOptions: generateGanacheOptions({ - concurrent: [{ port: 8546, chainId: 1338 }], - }), - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await driver.navigate(); - await openDapp(driver); - - // Trigger Notification - await driver.clickElement('#addEthereumChain'); - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await unlockWallet(driver); - const notification = await driver.isElementPresent({ - text: 'Add Localhost 8546', - tag: 'h3', - }); - - assert.ok(notification, 'Dapp action does not appear in Metamask'); - }, - ); - }); - - it('should connect a second Dapp despite MetaMask being locked', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await driver.navigate(); - - // Connect to 2nd dapp => DAPP_ONE - await openDapp(driver, null, DAPP_ONE_URL); - await driver.clickElement({ text: 'Connect', tag: 'button' }); - await driver.waitUntilXWindowHandles(3); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await unlockWallet(driver, { - navigate: false, - }); - - await driver.clickElement({ text: 'Connect', tag: 'button' }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.waitForSelector({ - css: '#accounts', - text: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', - }); - // Assert Connection - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await unlockWallet(driver, { - navigate: false, - }); - await driver.clickElement( - '[data-testid ="account-options-menu-button"]', - ); - - await driver.clickElement({ text: 'All Permissions', tag: 'div' }); - const connectedDapp1 = await driver.isElementPresent({ - text: '127.0.0.1:8080', - tag: 'p', - }); - const connectedDapp2 = await driver.isElementPresent({ - text: '127.0.0.1:8081', - tag: 'p', - }); - assert.ok(connectedDapp1, 'Account not connected to Dapp1'); - assert.ok(connectedDapp2, 'Account not connected to Dapp2'); - }, - ); - }); -}); diff --git a/test/e2e/tests/dapp-interactions/dapp-interactions.spec.ts b/test/e2e/tests/dapp-interactions/dapp-interactions.spec.ts new file mode 100644 index 000000000000..4b36571b8f7d --- /dev/null +++ b/test/e2e/tests/dapp-interactions/dapp-interactions.spec.ts @@ -0,0 +1,99 @@ +import { + withFixtures, + WINDOW_TITLES, + generateGanacheOptions, +} from '../../helpers'; +import { + DAPP_ONE_ADDRESS, + DAPP_ONE_URL, + DAPP_HOST_ADDRESS, + DEFAULT_FIXTURE_ACCOUNT, +} from '../../constants'; +import FixtureBuilder from '../../fixture-builder'; +import AddNetworkConfirmation from '../../page-objects/pages/confirmations/redesign/add-network-confirmations'; +import ConnectAccountConfirmation from '../../page-objects/pages/confirmations/redesign/connect-account-confirmation'; +import Homepage from '../../page-objects/pages/home/homepage'; +import LoginPage from '../../page-objects/pages/login-page'; +import PermissionListPage from '../../page-objects/pages/permission/permission-list-page'; +import TestDapp from '../../page-objects/pages/test-dapp'; + +describe('Dapp interactions', function () { + it('should trigger the add chain confirmation despite MetaMask being locked', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().build(), + localNodeOptions: generateGanacheOptions({ + concurrent: [{ port: 8546, chainId: 1338 }], + }), + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await driver.navigate(); + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.check_pageIsLoaded(); + + // Trigger Notification + await testDapp.clickAddNetworkButton(); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const loginPage = new LoginPage(driver); + await loginPage.check_pageIsLoaded(); + await loginPage.loginToHomepage(); + await new AddNetworkConfirmation(driver).check_pageIsLoaded( + 'Localhost 8546', + ); + }, + ); + }); + + it('should connect a second Dapp despite MetaMask being locked', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + dappOptions: { numberOfDapps: 2 }, + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await driver.navigate(); + + // Connect to 2nd dapp => DAPP_ONE + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage({ url: DAPP_ONE_URL }); + await testDapp.check_pageIsLoaded(); + await testDapp.clickConnectAccountButton(); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const loginPage = new LoginPage(driver); + await loginPage.check_pageIsLoaded(); + await loginPage.loginToHomepage(); + const connectAccountConfirmation = new ConnectAccountConfirmation( + driver, + ); + await connectAccountConfirmation.check_pageIsLoaded(); + await connectAccountConfirmation.confirmConnect(); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await testDapp.check_connectedAccounts(DEFAULT_FIXTURE_ACCOUNT); + + // Login to homepage + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await loginPage.check_pageIsLoaded(); + await loginPage.loginToHomepage(); + const homepage = new Homepage(driver); + await homepage.check_pageIsLoaded(); + + // Assert Connection + await homepage.headerNavbar.openPermissionsPage(); + const permissionListPage = new PermissionListPage(driver); + await permissionListPage.check_pageIsLoaded(); + await permissionListPage.check_connectedToSite(DAPP_HOST_ADDRESS); + await permissionListPage.check_connectedToSite(DAPP_ONE_ADDRESS); + }, + ); + }); +}); diff --git a/test/e2e/tests/identity/account-syncing/mock-data.ts b/test/e2e/tests/identity/account-syncing/mock-data.ts index 1b39421526ec..20f44adc0c3e 100644 --- a/test/e2e/tests/identity/account-syncing/mock-data.ts +++ b/test/e2e/tests/identity/account-syncing/mock-data.ts @@ -4,6 +4,15 @@ import { IDENTITY_TEAM_STORAGE_KEY } from '../constants'; import { createEncryptedResponse } from '../../../helpers/identity/user-storage/generateEncryptedData'; import { UserStorageAccount } from './helpers'; +/** + * This array represents the accounts mock data before it is encrypted and sent to UserStorage. + * Each object within the array represents a UserStorageAccount, which includes properties such as: + * - v: The version of the User Storage. + * - a: The address of the account. + * - i: The id of the account. + * - n: The name of the account. + * - nlu: The name last updated timestamp of the account. + */ export const accountsToMockForAccountsSync: UserStorageAccount[] = [ { v: '1', diff --git a/test/e2e/tests/metrics/errors.spec.js b/test/e2e/tests/metrics/errors.spec.js index a9ca8cfd7ac3..cbe96504a50a 100644 --- a/test/e2e/tests/metrics/errors.spec.js +++ b/test/e2e/tests/metrics/errors.spec.js @@ -889,6 +889,9 @@ describe('Sentry errors', function () { showConfirmationAdvancedDetails: true, privacyMode: false, }, + balances: false, + accountsAssets: false, + assetsMetadata: false, smartTransactionsState: { fees: { approvalTxFees: true, // Initialized as undefined diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index cddc3c404309..2e84b6944a18 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -143,11 +143,6 @@ "metaMetricsDataDeletionId": null, "metaMetricsDataDeletionTimestamp": 0 }, - "MultichainAssetsController": { - "accountsAssets": "object", - "assetsMetadata": "object" - }, - "MultichainBalancesController": { "balances": "object" }, "MultichainRatesController": { "fiatCurrency": "usd", "rates": { diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index bad4742146ed..8446d437617f 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -157,7 +157,6 @@ "previousAppVersion": "", "previousMigrationVersion": 0, "currentMigrationVersion": "number", - "balances": "object", "selectedNetworkClientId": "string", "networksMetadata": { "networkConfigurationId": { @@ -250,9 +249,6 @@ "ignoredNfts": "object", "domains": "object", "logs": "object", - "methodData": "object", - "lastFetchedBlockNumbers": "object", - "submitHistory": "object", "fiatCurrency": "usd", "rates": { "btc": { "conversionDate": 0, "conversionRate": 0 }, @@ -292,8 +288,6 @@ "cacheTimestamp": "number", "accounts": "object", "accountsByChainId": "object", - "accountsAssets": "object", - "assetsMetadata": "object", "marketData": "object", "unapprovedDecryptMsgs": "object", "unapprovedDecryptMsgCount": 0, @@ -355,6 +349,9 @@ "pendingApprovalCount": "number", "approvalFlows": "object", "storageMetadata": {}, + "methodData": "object", + "lastFetchedBlockNumbers": "object", + "submitHistory": "object", "encryptionKey": "string", "encryptionSalt": "string" }, diff --git a/test/e2e/tests/notifications/enable-notifications-with-accounts-sync.spec.ts b/test/e2e/tests/notifications/enable-notifications-with-accounts-sync.spec.ts new file mode 100644 index 000000000000..5013650be99b --- /dev/null +++ b/test/e2e/tests/notifications/enable-notifications-with-accounts-sync.spec.ts @@ -0,0 +1,203 @@ +import { Mockttp } from 'mockttp'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/user-storage'; +import { withFixtures } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { mockIdentityServices } from '../identity/mocks'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import HomePage from '../../page-objects/pages/home/homepage'; +import { completeOnboardFlowIdentity } from '../identity/flows'; +import { UserStorageMockttpController } from '../../helpers/identity/user-storage/userStorageMockttpController'; +import { + getAccountsSyncMockResponse, + accountsToMockForAccountsSync as unencryptedMockAccounts, +} from '../identity/account-syncing/mock-data'; +import { IS_ACCOUNT_SYNCING_ENABLED } from '../identity/account-syncing/helpers'; +import NotificationsListPage from '../../page-objects/pages/notifications-list-page'; +import NotificationsSettingsPage from '../../page-objects/pages/settings/notifications-settings-page'; +import SettingsPage from '../../page-objects/pages/settings/settings-page'; +import { mockNotificationServices } from './mocks'; + +describe('Enable Notifications - With Accounts Syncing On', function () { + // Accounts Syncing only works on MV3 + if (!IS_ACCOUNT_SYNCING_ENABLED) { + return; + } + + describe('from inside MetaMask', function () { + /** + * Test notification settings persistence across sessions. + * + * Part 1: Initial Configuration + * - Complete onboarding with pre-synced accounts + * - Enable notifications and verify default state (all enabled) + * - Modify settings: + * → Disable second account notifications + * → Disable product notifications + * + * Part 2: Persistence Check + * - Start new session and complete onboarding + * - Re-enable general notifications (required for each new session) + * - Verify settings: + * → General notifications: requires manual re-enable + * → Product notifications: enabled (resets on new session) + * → First account: enabled + * → Second account: disabled (persisted from Part 1) + */ + it('syncs notification settings on next onboarding after enabling for the first time', async function () { + this.timeout(60000); // Multiple Syncing features can cause this test to take some time + const userStorageMockttpController = new UserStorageMockttpController(); + const mockedAccountsResponse = await getAccountsSyncMockResponse(); + + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }) + .withMetaMetricsController() + .build(), + title: this.test?.fullTitle(), + testSpecificMock: async (server: Mockttp) => { + // Using previously synced accounts to avoid having to add accounts manually, therefore, making the tests run quicker + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + { + getResponse: mockedAccountsResponse, + }, + ); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.notifications, + server, + ); + + return [ + await mockNotificationServices( + server, + userStorageMockttpController, + ), + await mockIdentityServices(server, userStorageMockttpController), + ]; + }, + }, + async ({ driver }) => { + await completeOnboardFlowIdentity(driver); + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_hasAccountSyncingSyncedAtLeastOnce(); + + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.enableNotifications(); + + // Navigate to notifications settings through global menu > notifications > settings button + const notificationsListPage = new NotificationsListPage(driver); + await notificationsListPage.check_pageIsLoaded(); + await notificationsListPage.goToNotificationsSettings(); + + const notificationsSettingsPage = new NotificationsSettingsPage( + driver, + ); + await notificationsSettingsPage.check_pageIsLoaded(); + + await notificationsSettingsPage.check_notificationState({ + toggleType: 'general', + expectedState: 'enabled', + }); + + await notificationsSettingsPage.check_notificationState({ + toggleType: 'product', + expectedState: 'enabled', + }); + + for (const { a: address } of unencryptedMockAccounts) { + await notificationsSettingsPage.check_notificationState({ + address, + toggleType: 'address', + expectedState: 'enabled', + }); + } + + // Switch off address 2 and product notifications toggle + await notificationsSettingsPage.clickNotificationToggle({ + address: unencryptedMockAccounts[1].a, + toggleType: 'address', + }); + + await notificationsSettingsPage.clickNotificationToggle({ + toggleType: 'product', + }); + }, + ); + + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }).build(), + title: this.test?.fullTitle(), + testSpecificMock: async (server: Mockttp) => { + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + ); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.notifications, + server, + ); + + return [ + await mockNotificationServices( + server, + userStorageMockttpController, + ), + await mockIdentityServices(server, userStorageMockttpController), + ]; + }, + }, + async ({ driver }) => { + await completeOnboardFlowIdentity(driver); + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_hasAccountSyncingSyncedAtLeastOnce(); + + // Navigate to notifications settings through global menu > settings > notifications settings + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.openSettingsPage(); + + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToNotificationsSettings(); + + const notificationsSettingsPage = new NotificationsSettingsPage( + driver, + ); + await notificationsSettingsPage.check_pageIsLoaded(); + await notificationsSettingsPage.clickNotificationToggle({ + toggleType: 'general', + }); + + await notificationsSettingsPage.check_notificationState({ + toggleType: 'general', + expectedState: 'enabled', + }); + + await notificationsSettingsPage.check_notificationState({ + toggleType: 'product', + expectedState: 'enabled', + }); + + const [{ a: account1 }, { a: account2 }] = unencryptedMockAccounts; + + await notificationsSettingsPage.check_notificationState({ + address: account1, + toggleType: 'address', + expectedState: 'enabled', + }); + + await notificationsSettingsPage.check_notificationState({ + address: account2, + toggleType: 'address', + expectedState: 'disabled', + }); + }, + ); + }); + }); +}); diff --git a/test/e2e/tests/notifications/enable-notifications.spec.ts b/test/e2e/tests/notifications/enable-notifications.spec.ts new file mode 100644 index 000000000000..27e07d1b8471 --- /dev/null +++ b/test/e2e/tests/notifications/enable-notifications.spec.ts @@ -0,0 +1,190 @@ +import { Mockttp } from 'mockttp'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/user-storage'; +import { withFixtures } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { mockIdentityServices } from '../identity/mocks'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import HomePage from '../../page-objects/pages/home/homepage'; +import { completeOnboardFlowIdentity } from '../identity/flows'; +import { UserStorageMockttpController } from '../../helpers/identity/user-storage/userStorageMockttpController'; +import NotificationsListPage from '../../page-objects/pages/notifications-list-page'; +import NotificationsSettingsPage from '../../page-objects/pages/settings/notifications-settings-page'; +import SettingsPage from '../../page-objects/pages/settings/settings-page'; +import { accountsToMockForAccountsSync as unencryptedMockAccounts } from '../identity/account-syncing/mock-data'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import { ACCOUNT_TYPE } from '../../constants'; +import { mockNotificationServices } from './mocks'; + +describe('Enable Notifications - Without Accounts Syncing', function () { + describe('from inside MetaMask', function () { + /** + * Test notification settings persistence across sessions. + * This specifically tests the scenario where accounts syncing is not on (i.e on Firefox or when user has not enabled this feature) + * + * Part 1: Initial Configuration + * - Complete onboarding + * - Adds some accounts + * - Enable notifications and verify default state (all enabled) + * - Modify settings: + * → Disable second account notifications + * → Disable product notifications + * + * Part 2: Persistence Check + * - Start new session and complete onboarding + * - Re-enable general notifications (required for each new session) + * - Add accounts again + * - Verify settings: + * → General notifications: requires manual re-enable + * → Product notifications: enabled (resets on new session) + * → First account: enabled + * → Second account: disabled (persisted from Part 1) + */ + it('syncs notification settings on next onboarding after enabling for the first time', async function () { + const userStorageMockttpController = new UserStorageMockttpController(); + + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }) + .withMetaMetricsController() + .build(), + title: this.test?.fullTitle(), + testSpecificMock: async (server: Mockttp) => { + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.notifications, + server, + ); + + await mockNotificationServices( + server, + userStorageMockttpController, + ); + }, + }, + async ({ driver }) => { + await completeOnboardFlowIdentity(driver); + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.openAccountMenu(); + + const accountListPage = new AccountListPage(driver); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + }); + + await headerNavbar.enableNotifications(); + + // Navigate to notifications settings through global menu > notifications > settings button + const notificationsListPage = new NotificationsListPage(driver); + await notificationsListPage.check_pageIsLoaded(); + await notificationsListPage.goToNotificationsSettings(); + + const notificationsSettingsPage = new NotificationsSettingsPage( + driver, + ); + await notificationsSettingsPage.check_pageIsLoaded(); + + await notificationsSettingsPage.check_notificationState({ + toggleType: 'general', + expectedState: 'enabled', + }); + + await notificationsSettingsPage.check_notificationState({ + toggleType: 'product', + expectedState: 'enabled', + }); + + // Switch off address 2 and product notifications toggle + await notificationsSettingsPage.clickNotificationToggle({ + address: unencryptedMockAccounts[1].a, + toggleType: 'address', + }); + + await notificationsSettingsPage.clickNotificationToggle({ + toggleType: 'product', + }); + }, + ); + + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }).build(), + title: this.test?.fullTitle(), + testSpecificMock: async (server: Mockttp) => { + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + ); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.notifications, + server, + ); + + return [ + await mockNotificationServices( + server, + userStorageMockttpController, + ), + await mockIdentityServices(server, userStorageMockttpController), + ]; + }, + }, + async ({ driver }) => { + await completeOnboardFlowIdentity(driver); + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.openAccountMenu(); + + const accountListPage = new AccountListPage(driver); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + }); + + // Navigate to notifications settings through global menu > settings > notifications settings + await headerNavbar.openSettingsPage(); + + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToNotificationsSettings(); + + const notificationsSettingsPage = new NotificationsSettingsPage( + driver, + ); + await notificationsSettingsPage.check_pageIsLoaded(); + await notificationsSettingsPage.clickNotificationToggle({ + toggleType: 'general', + }); + + await notificationsSettingsPage.check_notificationState({ + toggleType: 'general', + expectedState: 'enabled', + }); + + await notificationsSettingsPage.check_notificationState({ + toggleType: 'product', + expectedState: 'enabled', + }); + + const [{ a: account1 }, { a: account2 }] = unencryptedMockAccounts; + + await notificationsSettingsPage.check_notificationState({ + address: account1, + toggleType: 'address', + expectedState: 'enabled', + }); + + await notificationsSettingsPage.check_notificationState({ + address: account2, + toggleType: 'address', + expectedState: 'disabled', + }); + }, + ); + }); + }); +}); diff --git a/test/e2e/tests/notifications/mocks.ts b/test/e2e/tests/notifications/mocks.ts index c6f65d58dbb7..4412c0336074 100644 --- a/test/e2e/tests/notifications/mocks.ts +++ b/test/e2e/tests/notifications/mocks.ts @@ -4,10 +4,12 @@ import { NotificationServicesPushController, } from '@metamask/notification-services-controller'; import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; +import { AuthenticationController } from '@metamask/profile-sync-controller'; import { UserStorageMockttpController } from '../../helpers/identity/user-storage/userStorageMockttpController'; const NotificationMocks = NotificationServicesController.Mocks; const PushMocks = NotificationServicesPushController.Mocks; +const AuthMocks = AuthenticationController.Mocks; type MockResponse = { url: string | RegExp; @@ -37,6 +39,11 @@ export async function mockNotificationServices( ); } + // Auth + mockAPICall(server, AuthMocks.getMockAuthNonceResponse()); + mockAPICall(server, AuthMocks.getMockAuthLoginResponse()); + mockAPICall(server, AuthMocks.getMockAuthAccessTokenResponse()); + // Notifications mockAPICall(server, NotificationMocks.getMockFeatureAnnouncementResponse()); mockAPICall(server, NotificationMocks.getMockBatchCreateTriggersResponse()); diff --git a/test/e2e/tests/settings/account-token-list.spec.js b/test/e2e/tests/settings/account-token-list.spec.js index 19fdd817f33d..fcebbd715095 100644 --- a/test/e2e/tests/settings/account-token-list.spec.js +++ b/test/e2e/tests/settings/account-token-list.spec.js @@ -57,11 +57,10 @@ describe('Settings', function () { assert.equal(await tokenListAmount.getText(), tokenValue); await driver.clickElement('[data-testid="account-menu-icon"]'); - const accountTokenValue = await driver.waitForSelector( - '.multichain-account-list-item .multichain-account-list-item__avatar-currency .currency-display-component__text', - ); - - assert.equal(await accountTokenValue.getText(), '25', 'ETH'); + await driver.waitForSelector({ + css: '[data-testid="eth-overview__primary-currency"]', + text: '25 ETH', + }); }, ); }); @@ -105,11 +104,11 @@ describe('Settings', function () { await switchToNetworkFlow(driver, 'Sepolia'); await driver.clickElement('[data-testid="account-menu-icon"]'); - const accountTokenValue = await driver.waitForSelector( - '.multichain-account-list-item .multichain-account-list-item__asset', - ); - assert.equal(await accountTokenValue.getText(), '$42,500.00USD'); + await driver.waitForSelector({ + css: '[data-testid="eth-overview__primary-currency"]', + text: '$42,500.00USD', + }); }, ); }); @@ -141,11 +140,10 @@ describe('Settings', function () { assert.equal(await tokenListAmount.getText(), '25\nETH'); await driver.clickElement('[data-testid="account-menu-icon"]'); - const accountTokenValue = await driver.waitForSelector( - '.multichain-account-list-item .multichain-account-list-item__asset', - ); - - assert.equal(await accountTokenValue.getText(), '25ETH'); + await driver.waitForSelector({ + css: '[data-testid="eth-overview__primary-currency"]', + text: '25 ETH', + }); }, ); }); diff --git a/test/e2e/tests/tokens/add-token-using-search.ts b/test/e2e/tests/tokens/add-token-using-search.spec.ts similarity index 100% rename from test/e2e/tests/tokens/add-token-using-search.ts rename to test/e2e/tests/tokens/add-token-using-search.spec.ts diff --git a/test/e2e/tests/tokens/watch-asset-call-add-token.ts b/test/e2e/tests/tokens/watch-asset-call-add-token.spec.ts similarity index 100% rename from test/e2e/tests/tokens/watch-asset-call-add-token.ts rename to test/e2e/tests/tokens/watch-asset-call-add-token.spec.ts diff --git a/test/integration/notifications&auth/notifications-activation.test.tsx b/test/integration/notifications&auth/notifications-activation.test.tsx index ecfb18507984..c47befce5ed1 100644 --- a/test/integration/notifications&auth/notifications-activation.test.tsx +++ b/test/integration/notifications&auth/notifications-activation.test.tsx @@ -80,7 +80,7 @@ describe('Notifications Activation', () => { }); }; - it('should successfully activate notification for the first time', async () => { + it('should successfully activate notification for the first time and send correct metrics', async () => { const mockedState = getMockedNotificationsState(); await act(async () => { await integrationTestRender({ @@ -159,40 +159,4 @@ describe('Notifications Activation', () => { await trackNotificationsActivatedMetaMetricsEvent('dismissed', false); }); }); - - it('should successfully send correct metrics when notifications modal is dismissed', async () => { - const mockedState = getMockedNotificationsState(); - await act(async () => { - await integrationTestRender({ - preloadedState: { - ...mockedState, - isProfileSyncingEnabled: false, - isNotificationServicesEnabled: false, - isFeatureAnnouncementsEnabled: false, - isMetamaskNotificationsFeatureSeen: false, - }, - backgroundConnection: backgroundConnectionMocked, - }); - - await clickElement('account-options-menu-button'); - await waitForElement('notifications-menu-item'); - await clickElement('notifications-menu-item'); - - await waitFor(() => { - expect( - within(screen.getByRole('dialog')).getByText('Turn on'), - ).toBeInTheDocument(); - }); - - await act(async () => { - fireEvent.click( - within(screen.getByRole('dialog')).getByRole('button', { - name: 'Close', - }), - ); - }); - - await trackNotificationsActivatedMetaMetricsEvent('dismissed', false); - }); - }); }); diff --git a/test/integration/notifications&auth/notifications-toggle.test.tsx b/test/integration/notifications&auth/notifications-toggle.test.tsx index 9104729cb380..1c2b94ea9518 100644 --- a/test/integration/notifications&auth/notifications-toggle.test.tsx +++ b/test/integration/notifications&auth/notifications-toggle.test.tsx @@ -1,10 +1,4 @@ -import { - act, - fireEvent, - waitFor, - within, - screen, -} from '@testing-library/react'; +import { act, fireEvent, waitFor, screen } from '@testing-library/react'; import { integrationTestRender } from '../../lib/render-helpers'; import * as backgroundConnection from '../../../ui/store/background-connection'; import { createMockImplementation } from '../helpers'; @@ -36,6 +30,54 @@ const setupSubmitRequestToBackgroundMocks = ( ); }; +const selectors = { + accountOptionsMenuButton: 'account-options-menu-button', + notificationsMenuItem: 'notifications-menu-item', + notificationsSettingsButton: 'notifications-settings-button', + notificationsSettingsAllowToggleInput: + 'notifications-settings-allow-toggle-input', + productAnnouncementsToggleInput: 'product-announcements-toggle-input', +}; + +const clickElement = async (testId: string) => { + await act(async () => { + fireEvent.click(await screen.findByTestId(testId)); + }); +}; + +const waitForElement = async (testId: string) => { + expect(await screen.findByTestId(testId)).toBeInTheDocument(); +}; + +const verifyMetametricsEvent = async ( + expectedEvent: string, + expectedCategory: string, + expectedProperties: Record, +) => { + await waitFor(() => { + const metametrics = + mockedBackgroundConnection.submitRequestToBackground.mock.calls?.find( + (call) => + call[0] === 'trackMetaMetricsEvent' && + call[1]?.[0].category === expectedCategory, + ); + + expect(metametrics?.[0]).toBe('trackMetaMetricsEvent'); + + const [metricsEvent] = metametrics?.[1] as unknown as [ + { + event: string; + category: string; + properties: Record; + }, + ]; + + expect(metricsEvent?.event).toBe(expectedEvent); + expect(metricsEvent?.category).toBe(expectedCategory); + expect(metricsEvent?.properties).toMatchObject(expectedProperties); + }); +}; + describe('Notifications Toggle', () => { beforeEach(() => { jest.resetAllMocks(); @@ -46,16 +88,6 @@ describe('Notifications Toggle', () => { window.history.pushState({}, '', '/'); // return to homescreen }); - const clickElement = async (testId: string) => { - await act(async () => { - fireEvent.click(await screen.findByTestId(testId)); - }); - }; - - const waitForElement = async (testId: string) => { - expect(await screen.findByTestId(testId)).toBeInTheDocument(); - }; - it('disabling notifications from settings', async () => { const mockedState = getMockedNotificationsState(); await act(async () => { @@ -64,20 +96,13 @@ describe('Notifications Toggle', () => { backgroundConnection: backgroundConnectionMocked, }); - await clickElement('account-options-menu-button'); - await waitForElement('notifications-menu-item'); - await clickElement('notifications-menu-item'); - await waitForElement('notifications-settings-button'); - await clickElement('notifications-settings-button'); - await waitForElement('notifications-settings-allow-notifications'); - - const toggleSection = await screen.findByTestId( - 'notifications-settings-allow-notifications', - ); - - await act(async () => { - fireEvent.click(await within(toggleSection).findByRole('checkbox')); - }); + await clickElement(selectors.accountOptionsMenuButton); + await waitForElement(selectors.notificationsMenuItem); + await clickElement(selectors.notificationsMenuItem); + await waitForElement(selectors.notificationsSettingsButton); + await clickElement(selectors.notificationsSettingsButton); + await waitForElement(selectors.notificationsSettingsAllowToggleInput); + await clickElement(selectors.notificationsSettingsAllowToggleInput); await waitFor(() => { const disableNotificationsCall = @@ -99,44 +124,20 @@ describe('Notifications Toggle', () => { ); }); - await waitFor(() => { - const metametrics = - mockedBackgroundConnection.submitRequestToBackground.mock.calls?.find( - (call) => - call[0] === 'trackMetaMetricsEvent' && - call[1]?.[0].category === - MetaMetricsEventCategory.NotificationSettings, - ); - - expect(metametrics?.[0]).toBe('trackMetaMetricsEvent'); - - const [metricsEvent] = metametrics?.[1] as unknown as [ - { - event: string; - category: string; - properties: Record; - }, - ]; - - expect(metricsEvent?.event).toBe( - MetaMetricsEventName.NotificationsSettingsUpdated, - ); - - expect(metricsEvent?.category).toBe( - MetaMetricsEventCategory.NotificationSettings, - ); - - expect(metricsEvent?.properties).toMatchObject({ + await verifyMetametricsEvent( + MetaMetricsEventName.NotificationsSettingsUpdated, + MetaMetricsEventCategory.NotificationSettings, + { settings_type: 'notifications', was_profile_syncing_on: true, old_value: true, new_value: false, - }); - }); + }, + ); }); }); - it('enabling product announcments from settings', async () => { + it('enabling product announcements from settings', async () => { const mockedState = getMockedNotificationsState(); await act(async () => { await integrationTestRender({ @@ -150,18 +151,13 @@ describe('Notifications Toggle', () => { backgroundConnection: backgroundConnectionMocked, }); - await clickElement('account-options-menu-button'); - await waitForElement('notifications-menu-item'); - await clickElement('notifications-menu-item'); - await waitForElement('notifications-settings-button'); - await clickElement('notifications-settings-button'); - await waitForElement('notifications-settings-allow-notifications'); - - const allToggles = await screen.findAllByTestId('test-toggle'); - - await act(async () => { - fireEvent.click(allToggles[1]); - }); + await clickElement(selectors.accountOptionsMenuButton); + await waitForElement(selectors.notificationsMenuItem); + await clickElement(selectors.notificationsMenuItem); + await waitForElement(selectors.notificationsSettingsButton); + await clickElement(selectors.notificationsSettingsButton); + await waitForElement(selectors.productAnnouncementsToggleInput); + await clickElement(selectors.productAnnouncementsToggleInput); await waitFor(() => { const enableFeatureNotifications = @@ -184,39 +180,15 @@ describe('Notifications Toggle', () => { ); }); - await waitFor(() => { - const metametrics = - mockedBackgroundConnection.submitRequestToBackground.mock.calls?.find( - (call) => - call[0] === 'trackMetaMetricsEvent' && - call[1]?.[0].category === - MetaMetricsEventCategory.NotificationSettings, - ); - - expect(metametrics?.[0]).toBe('trackMetaMetricsEvent'); - - const [metricsEvent] = metametrics?.[1] as unknown as [ - { - event: string; - category: string; - properties: Record; - }, - ]; - - expect(metricsEvent?.event).toBe( - MetaMetricsEventName.NotificationsSettingsUpdated, - ); - - expect(metricsEvent?.category).toBe( - MetaMetricsEventCategory.NotificationSettings, - ); - - expect(metricsEvent?.properties).toMatchObject({ + await verifyMetametricsEvent( + MetaMetricsEventName.NotificationsSettingsUpdated, + MetaMetricsEventCategory.NotificationSettings, + { settings_type: 'product_announcements', old_value: false, new_value: true, - }); - }); + }, + ); }); }); }); diff --git a/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.stories.tsx b/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.stories.tsx new file mode 100644 index 000000000000..a98ef148d241 --- /dev/null +++ b/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.stories.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import { configureStore } from '@reduxjs/toolkit'; +import type { Meta, StoryObj } from '@storybook/react'; +import testData from '../../../../../.storybook/test-data'; +import UnconnectedAccountAlert from './unconnected-account-alert'; + +const mockState = { + ...testData, + metamask: { + ...testData.metamask, + unconnectedAccount: { + state: 'IDLE', + }, + }, + activeTab: { + id: 113, + title: 'E2E Test Dapp', + origin: 'https://metamask.github.io', + protocol: 'https:', + url: 'https://metamask.github.io/test-dapp/', + }, +}; + +const store = configureStore({ + reducer: (state = mockState) => state, + preloadedState: mockState, +}); + +const meta = { + title: 'Components/App/Alerts/UnconnectedAccountAlert', + component: UnconnectedAccountAlert, + decorators: [ + (Story) => ( + + + + ), + ], + parameters: { + docs: { + description: { + component: 'Alert shown when the selected account is not connected to the current dapp', + }, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const DefaultStory: Story = { + name: 'Default', +}; diff --git a/ui/components/app/assets/asset-list/asset-list.tsx b/ui/components/app/assets/asset-list/asset-list.tsx index 6badd38b66e3..624d21917fa8 100644 --- a/ui/components/app/assets/asset-list/asset-list.tsx +++ b/ui/components/app/assets/asset-list/asset-list.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useMemo } from 'react'; +import React, { useCallback, useContext } from 'react'; import { useSelector } from 'react-redux'; import TokenList from '../token-list'; import { getMultichainIsEvm } from '../../../../selectors/multichain'; @@ -8,23 +8,14 @@ import { MetaMetricsEventName, } from '../../../../../shared/constants/metametrics'; import DetectedToken from '../../detected-token/detected-token'; -import useAssetListTokenDetection from '../hooks/useAssetListTokenDetection'; -import usePrimaryCurrencyProperties from '../hooks/usePrimaryCurrencyProperties'; +import { + useAssetListTokenDetection, + usePrimaryCurrencyProperties, +} from '../hooks'; import AssetListControlBar from './asset-list-control-bar'; -import NativeToken from './native-token'; import AssetListFundingModals from './asset-list-funding-modals'; -export type TokenWithBalance = { - address: string; - symbol: string; - string?: string; - image: string; - secondary?: string; - tokenFiatAmount?: string; - isNative?: boolean; -}; - -export type AssetListProps = { +type AssetListProps = { onClickAsset: (chainId: string, address: string) => void; showTokensLinks?: boolean; }; @@ -33,7 +24,6 @@ const TokenListContainer = React.memo( ({ onClickAsset }: Pick) => { const trackEvent = useContext(MetaMetricsContext); const { primaryCurrencyProperties } = usePrimaryCurrencyProperties(); - const isEvm = useSelector(getMultichainIsEvm); const onTokenClick = useCallback( (chainId: string, tokenAddress: string) => { @@ -50,19 +40,7 @@ const TokenListContainer = React.memo( [], ); - const nativeToken = useMemo( - () => !isEvm && , - [isEvm, onClickAsset], - ); - - return ( - - ); + return ; }, ); diff --git a/ui/components/app/assets/asset-list/native-token/index.ts b/ui/components/app/assets/asset-list/native-token/index.ts deleted file mode 100644 index 6feb276bed54..000000000000 --- a/ui/components/app/assets/asset-list/native-token/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './native-token'; diff --git a/ui/components/app/assets/asset-list/native-token/native-token.tsx b/ui/components/app/assets/asset-list/native-token/native-token.tsx deleted file mode 100644 index 7c3b062e1fc3..000000000000 --- a/ui/components/app/assets/asset-list/native-token/native-token.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import { useSelector } from 'react-redux'; -import { - getMultichainCurrentNetwork, - getMultichainNativeCurrency, - getMultichainIsEvm, - getMultichainCurrencyImage, - getMultichainIsMainnet, - getMultichainSelectedAccountCachedBalance, -} from '../../../../../selectors/multichain'; -import { getPreferences } from '../../../../../selectors'; -import { TokenListItem } from '../../../../multichain'; -import { AssetListProps } from '../asset-list'; -import { useNativeTokenBalance } from './use-native-token-balance'; - -const NativeToken = ({ onClickAsset }: AssetListProps) => { - const nativeCurrency = useSelector(getMultichainNativeCurrency); - const isMainnet = useSelector(getMultichainIsMainnet); - const { chainId } = useSelector(getMultichainCurrentNetwork); - const { privacyMode } = useSelector(getPreferences); - const balance = useSelector(getMultichainSelectedAccountCachedBalance); - const balanceIsLoading = !balance; - - const { string, symbol, secondary } = useNativeTokenBalance(); - - const primaryTokenImage = useSelector(getMultichainCurrencyImage); - - const isEvm = useSelector(getMultichainIsEvm); - - const isStakeable = isMainnet && isEvm; - - return ( - onClickAsset(chainId, nativeCurrency)} - title={nativeCurrency} - primary={string} - tokenSymbol={symbol} - secondary={secondary} - tokenImage={balanceIsLoading ? null : primaryTokenImage} - isPrimaryTokenSymbolHidden={true} - isNativeCurrency - isStakeable={isStakeable} - showPercentage - privacyMode={privacyMode} - /> - ); -}; - -export default NativeToken; diff --git a/ui/components/app/assets/hooks/index.ts b/ui/components/app/assets/hooks/index.ts new file mode 100644 index 000000000000..21e2cbc94e01 --- /dev/null +++ b/ui/components/app/assets/hooks/index.ts @@ -0,0 +1,5 @@ +export { default as useAssetListTokenDetection } from './useAssetListTokenDetection'; +export { default as useNativeTokenBalance } from './useNativeTokenBalance'; +export { default as useNetworkFilter } from './useNetworkFilter'; +export { default as usePrimaryCurrencyProperties } from './usePrimaryCurrencyProperties'; +export { default as useTokenDisplayInfo } from './useTokenDisplayInfo'; diff --git a/ui/components/app/assets/asset-list/native-token/use-native-token-balance.ts b/ui/components/app/assets/hooks/useNativeTokenBalance.tsx similarity index 75% rename from ui/components/app/assets/asset-list/native-token/use-native-token-balance.ts rename to ui/components/app/assets/hooks/useNativeTokenBalance.tsx index 1aa86f3af337..0ce5936e019c 100644 --- a/ui/components/app/assets/asset-list/native-token/use-native-token-balance.ts +++ b/ui/components/app/assets/hooks/useNativeTokenBalance.tsx @@ -1,24 +1,24 @@ import currencyFormatter from 'currency-formatter'; import { useSelector } from 'react-redux'; - +import { Hex } from '@metamask/utils'; import { getMultichainCurrencyImage, getMultichainCurrentNetwork, getMultichainSelectedAccountCachedBalance, getMultichainShouldShowFiat, -} from '../../../../../selectors/multichain'; +} from '../../../../selectors/multichain'; import { getPreferences, getSelectedInternalAccount, -} from '../../../../../selectors'; -import { getCurrentCurrency } from '../../../../../ducks/metamask/metamask'; -import { useIsOriginalNativeTokenSymbol } from '../../../../../hooks/useIsOriginalNativeTokenSymbol'; -import { PRIMARY, SECONDARY } from '../../../../../helpers/constants/common'; -import { useUserPreferencedCurrency } from '../../../../../hooks/useUserPreferencedCurrency'; -import { useCurrencyDisplay } from '../../../../../hooks/useCurrencyDisplay'; -import { TokenWithBalance } from '../asset-list'; +} from '../../../../selectors'; +import { getCurrentCurrency } from '../../../../ducks/metamask/metamask'; +import { useIsOriginalNativeTokenSymbol } from '../../../../hooks/useIsOriginalNativeTokenSymbol'; +import { PRIMARY, SECONDARY } from '../../../../helpers/constants/common'; +import { useUserPreferencedCurrency } from '../../../../hooks/useUserPreferencedCurrency'; +import { useCurrencyDisplay } from '../../../../hooks/useCurrencyDisplay'; +import { TokenWithFiatAmount } from '../types'; -export const useNativeTokenBalance = () => { +const useNativeTokenBalance = () => { const showFiat = useSelector(getMultichainShouldShowFiat); const account = useSelector(getSelectedInternalAccount); const primaryTokenImage = useSelector(getMultichainCurrencyImage); @@ -81,21 +81,26 @@ export const useNativeTokenBalance = () => { // useCurrencyDisplay passes along the symbol and formatting into the value here // for sorting we need the raw value, without the currency and it should be decimal // this is the easiest way to do this without extensive refactoring of useCurrencyDisplay - const tokenFiatAmount = currencyFormatter - .unformat(unformattedTokenFiatAmount, { + const tokenFiatAmount = currencyFormatter.unformat( + unformattedTokenFiatAmount, + { code: currentCurrency.toUpperCase(), - }) - .toString(); + }, + ); - const nativeTokenWithBalance: TokenWithBalance = { - address: '', + const nativeTokenWithBalance: TokenWithFiatAmount = { + chainId: chainId as Hex, + address: '' as Hex, symbol: tokenSymbol ?? '', string: primaryBalance, image: primaryTokenImage, secondary: secondaryBalance, tokenFiatAmount, isNative: true, + decimals: 18, }; return nativeTokenWithBalance; }; + +export default useNativeTokenBalance; diff --git a/ui/components/app/assets/hooks/useShouldShowFiat.tsx b/ui/components/app/assets/hooks/useShouldShowFiat.tsx deleted file mode 100644 index 22b04d3c677b..000000000000 --- a/ui/components/app/assets/hooks/useShouldShowFiat.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useSelector } from 'react-redux'; -import { useMultichainSelector } from '../../../../hooks/useMultichainSelector'; -import { - getIsTestnet, - getSelectedAccount, - getShowFiatInTestnets, -} from '../../../../selectors'; -import { getMultichainShouldShowFiat } from '../../../../selectors/multichain'; - -const useShouldShowFiat = () => { - const isTestnet = useSelector(getIsTestnet); - const selectedAccount = useSelector(getSelectedAccount); - const shouldShowFiat = useMultichainSelector( - getMultichainShouldShowFiat, - selectedAccount, - ); - - const isMainnet = !isTestnet; - const showFiatInTestnets = useSelector(getShowFiatInTestnets); - - return shouldShowFiat && (isMainnet || (isTestnet && showFiatInTestnets)); -}; - -export default useShouldShowFiat; diff --git a/ui/components/app/assets/hooks/useSortedFilteredTokens.tsx b/ui/components/app/assets/hooks/useSortedFilteredTokens.tsx deleted file mode 100644 index 9f35a5a2d374..000000000000 --- a/ui/components/app/assets/hooks/useSortedFilteredTokens.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useMemo } from 'react'; -import { shallowEqual, useSelector } from 'react-redux'; -import { sortAssets } from '../util/sort'; -import { filterAssets } from '../util/filter'; -import { - getCurrentNetwork, - getNewTokensImported, - getPreferences, - getSelectedAccount, - getTokenBalancesEvm, - getTokenExchangeRates, -} from '../../../../selectors'; -import { getConversionRate } from '../../../../ducks/metamask/metamask'; -import { TokenWithFiatAmount } from '../types'; -import useNetworkFilter from './useNetworkFilter'; - -const useSortedFilteredTokens = () => { - const currentNetwork = useSelector(getCurrentNetwork); - const { tokenSortConfig } = useSelector(getPreferences); - const selectedAccount = useSelector(getSelectedAccount); - const conversionRate = useSelector(getConversionRate); - const contractExchangeRates = useSelector( - getTokenExchangeRates, - shallowEqual, - ); - const newTokensImported = useSelector(getNewTokensImported); - const evmBalances = useSelector(getTokenBalancesEvm); // TODO: Make this chain agnostic - - // network filter to determine which tokens to show in list - const { networkFilter } = useNetworkFilter(); - - return useMemo(() => { - const filteredAssets = filterAssets(evmBalances, [ - { - key: 'chainId', - opts: networkFilter, - filterCallback: 'inclusive', - }, - ]); - - const { nativeTokens, nonNativeTokens } = filteredAssets.reduce<{ - nativeTokens: TokenWithFiatAmount[]; - nonNativeTokens: TokenWithFiatAmount[]; - }>( - (acc, token) => { - if (token.isNative) { - acc.nativeTokens.push(token); - } else { - acc.nonNativeTokens.push(token); - } - return acc; - }, - { nativeTokens: [], nonNativeTokens: [] }, - ); - - const assets = [...nativeTokens, ...nonNativeTokens]; - return sortAssets(assets, tokenSortConfig); - }, [ - tokenSortConfig, - networkFilter, - conversionRate, - contractExchangeRates, - currentNetwork, - selectedAccount, - newTokensImported, - evmBalances, - ]); -}; - -export default useSortedFilteredTokens; diff --git a/ui/components/app/assets/hooks/useTokenDisplayInfo.tsx b/ui/components/app/assets/hooks/useTokenDisplayInfo.tsx new file mode 100644 index 000000000000..753dfdb0da96 --- /dev/null +++ b/ui/components/app/assets/hooks/useTokenDisplayInfo.tsx @@ -0,0 +1,121 @@ +import { useSelector } from 'react-redux'; +import { BigNumber } from 'bignumber.js'; +import { isEqualCaseInsensitive } from '@metamask/controller-utils'; +import { + getIsTestnet, + getSelectedAccount, + getShowFiatInTestnets, + getTokenList, + selectERC20TokensByChain, +} from '../../../../selectors'; +import { TokenDisplayInfo, TokenWithFiatAmount } from '../types'; +import { + getImageForChainId, + getMultichainIsEvm, + getMultichainShouldShowFiat, + isChainIdMainnet, +} from '../../../../selectors/multichain'; +import { formatWithThreshold } from '../util/formatWithThreshold'; +import { getIntlLocale } from '../../../../ducks/locale/locale'; +import { formatAmount } from '../../../../pages/confirmations/components/simulation-details/formatAmount'; +import { getCurrentCurrency } from '../../../../ducks/metamask/metamask'; +import { useMultichainSelector } from '../../../../hooks/useMultichainSelector'; + +type UseTokenDisplayInfoProps = { + token: TokenWithFiatAmount; +}; + +export const useTokenDisplayInfo = ({ + token, +}: UseTokenDisplayInfoProps): TokenDisplayInfo => { + const isEvm = useSelector(getMultichainIsEvm); + const tokenList = useSelector(getTokenList); + const erc20TokensByChain = useSelector(selectERC20TokensByChain); + const currentCurrency = useSelector(getCurrentCurrency); + const locale = useSelector(getIntlLocale); + const tokenChainImage = getImageForChainId(token.chainId); + const selectedAccount = useSelector(getSelectedAccount); + const showFiat = useMultichainSelector( + getMultichainShouldShowFiat, + selectedAccount, + ); + + const isTestnet = useSelector(getIsTestnet); + + const isMainnet = !isTestnet; + const showFiatInTestnets = useSelector(getShowFiatInTestnets); + + const shouldShowFiat = + showFiat && (isMainnet || (isTestnet && showFiatInTestnets)); + + const secondaryThreshold = 0.01; + + // Format for fiat balance with currency style + const secondary = + shouldShowFiat && token.tokenFiatAmount + ? formatWithThreshold( + Number(token.tokenFiatAmount), + secondaryThreshold, + locale, + { + style: 'currency', + currency: currentCurrency.toUpperCase(), + }, + ) + : undefined; + + const primary = formatAmount( + locale, + new BigNumber(Number(token.string) || '0', 10), + ); + + const isEvmMainnet = + token.chainId && isEvm ? isChainIdMainnet(token.chainId) : false; + + const isStakeable = isEvmMainnet && isEvm && token.isNative; + + if (isEvm) { + const tokenData = Object.values(tokenList).find( + (tokenToFind) => + isEqualCaseInsensitive(tokenToFind.symbol, token.symbol) && + isEqualCaseInsensitive(tokenToFind.address, token.address), + ); + + const title = + tokenData?.name || + (token.chainId === '0x1' && token.symbol === 'ETH' + ? 'Ethereum' + : token.chainId && + erc20TokensByChain?.[token.chainId]?.data?.[ + token.address.toLowerCase() + ]?.name) || + token.symbol; + + const tokenImage = + tokenData?.iconUrl || + (token.chainId && + erc20TokensByChain?.[token.chainId]?.data?.[token.address.toLowerCase()] + ?.iconUrl) || + token.image; + + return { + title, + tokenImage, + primary, + secondary, + isStakeable, + tokenChainImage: tokenChainImage as string, + }; + } + // TODO non-evm assets. this is only the native token + return { + title: token.symbol, + tokenImage: token.image, + primary: '', + secondary: token.secondary, + isStakeable: false, + tokenChainImage: token.image as string, + }; +}; + +export default useTokenDisplayInfo; diff --git a/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap b/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap index 3ec0face8295..c8a593fdc640 100644 --- a/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap +++ b/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap @@ -3,17 +3,15 @@ exports[`Token Cell should match snapshot 1`] = `
network logo
- $5.00 -

+ />
{ + const allNetworks = useSelector(getNetworkConfigurationsByChainId); + + return ( + + } + marginRight={4} + > + + + ); + }, + (prevProps, nextProps) => prevProps.token.chainId === nextProps.token.chainId, +); diff --git a/ui/components/app/assets/token-cell/cells/token-cell-percent-change.tsx b/ui/components/app/assets/token-cell/cells/token-cell-percent-change.tsx new file mode 100644 index 000000000000..5dc220e9ec93 --- /dev/null +++ b/ui/components/app/assets/token-cell/cells/token-cell-percent-change.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { getNativeTokenAddress } from '@metamask/assets-controllers'; +import { Hex } from '@metamask/utils'; +import { + TextColor, + TextVariant, +} from '../../../../../helpers/constants/design-system'; +import { Text } from '../../../../component-library'; +import { getMarketData } from '../../../../../selectors'; +import { getMultichainIsEvm } from '../../../../../selectors/multichain'; +import { useI18nContext } from '../../../../../hooks/useI18nContext'; +import { TokenFiatDisplayInfo } from '../../types'; +import { PercentageChange } from '../../../../multichain/token-list-item/price/percentage-change'; +import { + TranslateFunction, + networkTitleOverrides, +} from '../../util/networkTitleOverrides'; + +type TokenCellPercentChangeProps = { + token: TokenFiatDisplayInfo; +}; + +export const TokenCellPercentChange = React.memo( + ({ token }: TokenCellPercentChangeProps) => { + const isEvm = useSelector(getMultichainIsEvm); + const t = useI18nContext(); + const multiChainMarketData = useSelector(getMarketData); + + // We do not want to display any percentage with non-EVM since we don't have the data for this yet. + if (isEvm) { + const tokenPercentageChange = token.address + ? multiChainMarketData?.[token.chainId]?.[token.address] + ?.pricePercentChange1d + : null; + + return ( + + ); + } + + // fallback value (is this valid?) + return ( + + {networkTitleOverrides(t as TranslateFunction, token)} + + ); + }, + (prevProps, nextProps) => prevProps.token.address === nextProps.token.address, +); diff --git a/ui/components/app/assets/token-cell/cells/token-cell-primary-display.tsx b/ui/components/app/assets/token-cell/cells/token-cell-primary-display.tsx new file mode 100644 index 000000000000..2b706e0d5fc0 --- /dev/null +++ b/ui/components/app/assets/token-cell/cells/token-cell-primary-display.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { + TextAlign, + TextColor, + TextVariant, +} from '../../../../../helpers/constants/design-system'; +import { + SensitiveText, + SensitiveTextLength, +} from '../../../../component-library'; +import { TokenFiatDisplayInfo } from '../../types'; + +type TokenCellPrimaryDisplayProps = { + token: TokenFiatDisplayInfo; + privacyMode: boolean; +}; + +export const TokenCellPrimaryDisplay = React.memo( + ({ token, privacyMode }: TokenCellPrimaryDisplayProps) => { + return ( + + {token.primary} {token.symbol} + + ); + }, + (prevProps, nextProps) => + prevProps.token.primary === nextProps.token.primary && + prevProps.privacyMode === nextProps.privacyMode, +); diff --git a/ui/components/app/assets/token-cell/cells/token-cell-secondary-display.tsx b/ui/components/app/assets/token-cell/cells/token-cell-secondary-display.tsx new file mode 100644 index 000000000000..96fd4c8e22d7 --- /dev/null +++ b/ui/components/app/assets/token-cell/cells/token-cell-secondary-display.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { + BackgroundColor, + FontWeight, + IconColor, + TextAlign, + TextVariant, +} from '../../../../../helpers/constants/design-system'; +import { + ButtonIcon, + ButtonIconSize, + IconName, + SensitiveText, + SensitiveTextLength, +} from '../../../../component-library'; +import { getCurrencyRates } from '../../../../../selectors'; +import { getMultichainIsEvm } from '../../../../../selectors/multichain'; +import { TokenFiatDisplayInfo } from '../../types'; + +type TokenCellSecondaryDisplayProps = { + token: TokenFiatDisplayInfo; + handleScamWarningModal: (arg: boolean) => void; + privacyMode: boolean; +}; + +export const TokenCellSecondaryDisplay = React.memo( + ({ + token, + handleScamWarningModal, + privacyMode, + }: TokenCellSecondaryDisplayProps) => { + const isEvm = useSelector(getMultichainIsEvm); + const currencyRates = useSelector(getCurrencyRates); + + const isOriginalTokenSymbol = token.symbol && currencyRates[token.symbol]; + + const showScamWarning = token.isNative && !isOriginalTokenSymbol && isEvm; + + // show scam warning + if (showScamWarning) { + return ( + ) => { + e.preventDefault(); + e.stopPropagation(); + handleScamWarningModal(true); + }} + color={IconColor.errorDefault} + size={ButtonIconSize.Md} + backgroundColor={BackgroundColor.transparent} + data-testid="scam-warning" + ariaLabel="" + /> + ); + } + + // secondary display text + return ( + + {token.secondary} + + ); + }, + (prevProps, nextProps) => + prevProps.token.secondary === nextProps.token.secondary && + prevProps.privacyMode === nextProps.privacyMode, +); diff --git a/ui/components/app/assets/token-cell/cells/token-cell-title.tsx b/ui/components/app/assets/token-cell/cells/token-cell-title.tsx new file mode 100644 index 000000000000..0b311e71d731 --- /dev/null +++ b/ui/components/app/assets/token-cell/cells/token-cell-title.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { + Display, + FontWeight, + TextVariant, +} from '../../../../../helpers/constants/design-system'; +import { Text } from '../../../../component-library'; +import { TokenFiatDisplayInfo } from '../../types'; +import { StakeableLink } from '../../../../multichain/token-list-item/stakeable-link'; +import { + TranslateFunction, + networkTitleOverrides, +} from '../../util/networkTitleOverrides'; +import { useI18nContext } from '../../../../../hooks/useI18nContext'; +import Tooltip from '../../../../ui/tooltip'; + +type TokenCellTitleProps = { + token: TokenFiatDisplayInfo; +}; + +export const TokenCellTitle = React.memo( + ({ token }: TokenCellTitleProps) => { + const t = useI18nContext(); + + if (token.title.length > 12) { + return ( + + + {networkTitleOverrides(t as TranslateFunction, token)} + {token.isStakeable && ( + + )} + + + ); + } + + // non-ellipsized title + return ( + + {networkTitleOverrides(t as TranslateFunction, token)} + {token.isStakeable && ( + + )} + + ); + }, + (prevProps, nextProps) => prevProps.token.title === nextProps.token.title, // Only rerender if the title changes +); diff --git a/ui/components/app/assets/token-cell/token-cell.stories.js b/ui/components/app/assets/token-cell/token-cell.stories.js index fd22f43444ff..48b014fbd87e 100644 --- a/ui/components/app/assets/token-cell/token-cell.stories.js +++ b/ui/components/app/assets/token-cell/token-cell.stories.js @@ -1,5 +1,5 @@ import React from 'react'; -import TokenListItem from '.'; +import TokenCell from './token-cell'; export default { title: 'Components/App/TokenCell', @@ -21,14 +21,21 @@ export default { }, }, args: { - address: '0xAnotherToken', - symbol: 'TEST', - string: '5.000', - currentCurrency: 'usd', - isOriginalTokenSymbol: true, + token: { + address: '0xAnotherToken', + symbol: 'TEST', + string: '5.000', + currentCurrency: 'usd', + image: '', + chainId: '0x1', + tokenFiatAmount: 5, + aggregators: [], + decimals: 18, + isNative: false, + }, }, }; -export const DefaultStory = (args) => ; +export const DefaultStory = (args) => ; DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/assets/token-cell/token-cell.test.tsx b/ui/components/app/assets/token-cell/token-cell.test.tsx index d80996776c49..bd724f329560 100644 --- a/ui/components/app/assets/token-cell/token-cell.test.tsx +++ b/ui/components/app/assets/token-cell/token-cell.test.tsx @@ -3,6 +3,7 @@ import thunk from 'redux-thunk'; import configureMockStore from 'redux-mock-store'; import { fireEvent } from '@testing-library/react'; import { useSelector } from 'react-redux'; +import { Hex } from '@metamask/utils'; import { renderWithProvider } from '../../../../../test/lib/render-helpers'; import { useTokenFiatAmount } from '../../../../hooks/useTokenFiatAmount'; import { getCurrentCurrency } from '../../../../ducks/metamask/metamask'; @@ -85,24 +86,34 @@ describe('Token Cell', () => { const mockStore = configureMockStore([thunk])(mockState); const props = { - address: '0xAnotherToken', - symbol: 'TEST', - string: '5.000', - currentCurrency: 'usd', - image: '', - chainId: '0x1', - tokenFiatAmount: 5, + token: { + address: '0xAnotherToken' as Hex, + symbol: 'TEST', + string: '5.000', + currentCurrency: 'usd', + image: '', + chainId: '0x1' as Hex, + tokenFiatAmount: 5, + aggregators: [], + decimals: 18, + isNative: false, + }, onClick: jest.fn(), }; const propsLargeAmount = { - address: '0xAnotherToken', - symbol: 'TEST', - string: '5000000', - currentCurrency: 'usd', - image: '', - chainId: '0x1', - tokenFiatAmount: 5000000, + token: { + address: '0xAnotherToken' as Hex, + symbol: 'TEST', + string: '5000000', + currentCurrency: 'usd', + image: '', + chainId: '0x1' as Hex, + tokenFiatAmount: 5000000, + aggregators: [], + decimals: 18, + isNative: false, + }, onClick: jest.fn(), }; const useSelectorMock = useSelector; diff --git a/ui/components/app/assets/token-cell/token-cell.tsx b/ui/components/app/assets/token-cell/token-cell.tsx index c38a37609692..7b1ea9691595 100644 --- a/ui/components/app/assets/token-cell/token-cell.tsx +++ b/ui/components/app/assets/token-cell/token-cell.tsx @@ -1,133 +1,201 @@ -import React, { useCallback } from 'react'; -import { useSelector } from 'react-redux'; -import { BigNumber } from 'bignumber.js'; -import { getCurrentCurrency } from '../../../../ducks/metamask/metamask'; +import React, { useCallback, useContext, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; +import { useTokenDisplayInfo } from '../hooks'; import { - getTokenList, - selectERC20TokensByChain, - getNativeCurrencyForChain, -} from '../../../../selectors'; + BlockSize, + Display, + FlexDirection, + JustifyContent, +} from '../../../../helpers/constants/design-system'; import { - isChainIdMainnet, - getImageForChainId, - getMultichainIsEvm, -} from '../../../../selectors/multichain'; -import { TokenListItem } from '../../../multichain'; -import { isEqualCaseInsensitive } from '../../../../../shared/modules/string-utils'; -import { getIntlLocale } from '../../../../ducks/locale/locale'; -import { formatAmount } from '../../../../pages/confirmations/components/simulation-details/formatAmount'; + Box, + ButtonSecondary, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '../../../component-library'; +import { getMultichainIsEvm } from '../../../../selectors/multichain'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { MetaMetricsContext } from '../../../../contexts/metametrics'; +import { + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../../../shared/constants/metametrics'; +import { hexToDecimal } from '../../../../../shared/modules/conversion.utils'; +import { NETWORKS_ROUTE } from '../../../../helpers/constants/routes'; +import { setEditedNetwork } from '../../../../store/actions'; +import { + SafeChain, + useSafeChains, +} from '../../../../pages/settings/networks-tab/networks-form/use-safe-chains'; +import { TokenWithFiatAmount } from '../types'; +import { + TokenCellBadge, + TokenCellTitle, + TokenCellPercentChange, + TokenCellPrimaryDisplay, + TokenCellSecondaryDisplay, +} from './cells'; type TokenCellProps = { - address: string; - symbol: string; - string?: string; - chainId: string; - tokenFiatAmount: number | null; - image: string; - isNative?: boolean; + token: TokenWithFiatAmount; privacyMode?: boolean; onClick?: (chainId: string, address: string) => void; }; -export const formatWithThreshold = ( - amount: number | null, - threshold: number, - locale: string, - options: Intl.NumberFormatOptions, -): string => { - if (amount === null) { - return ''; - } - if (amount === 0) { - return new Intl.NumberFormat(locale, options).format(0); - } - return amount < threshold - ? `<${new Intl.NumberFormat(locale, options).format(threshold)}` - : new Intl.NumberFormat(locale, options).format(amount); -}; - export default function TokenCell({ - address, - image, - symbol, - chainId, - string, - tokenFiatAmount, - isNative, + token, privacyMode = false, onClick, }: TokenCellProps) { - const locale = useSelector(getIntlLocale); - const currentCurrency = useSelector(getCurrentCurrency); - const tokenList = useSelector(getTokenList); + const dispatch = useDispatch(); + const history = useHistory(); + const t = useI18nContext(); const isEvm = useSelector(getMultichainIsEvm); - const erc20TokensByChain = useSelector(selectERC20TokensByChain); - const isMainnet = chainId ? isChainIdMainnet(chainId) : false; - const tokenData = Object.values(tokenList).find( - (token) => - isEqualCaseInsensitive(token.symbol, symbol) && - isEqualCaseInsensitive(token.address, address), - ); + const trackEvent = useContext(MetaMetricsContext); + const { safeChains } = useSafeChains(); + const [showScamWarningModal, setShowScamWarningModal] = useState(false); - const title = - tokenData?.name || - (chainId === '0x1' && symbol === 'ETH' - ? 'Ethereum' - : chainId && - erc20TokensByChain?.[chainId]?.data?.[address.toLowerCase()]?.name) || - symbol; + const decimalChainId = isEvm && parseInt(hexToDecimal(token.chainId), 10); - const tokenImage = - tokenData?.iconUrl || - (chainId && - erc20TokensByChain?.[chainId]?.data?.[address.toLowerCase()]?.iconUrl) || - image; + const safeChainDetails: SafeChain | undefined = safeChains?.find((chain) => { + if (typeof decimalChainId === 'number') { + return chain.chainId === decimalChainId.toString(); + } + return undefined; + }); - const secondaryThreshold = 0.01; - // Format for fiat balance with currency style - const secondary = - tokenFiatAmount === null - ? undefined - : formatWithThreshold(tokenFiatAmount, secondaryThreshold, locale, { - style: 'currency', - currency: currentCurrency.toUpperCase(), - }); + const tokenDisplayInfo = useTokenDisplayInfo({ + token, + }); - const primary = formatAmount( - locale, - new BigNumber(Number(string) || '0', 10), - ); + const handleClick = useCallback( + (e?: React.MouseEvent) => { + e?.preventDefault(); - const isStakeable = isMainnet && isEvm && isNative; + // If the scam warning modal is open, do nothing + if (showScamWarningModal) { + return; + } - const handleOnClick = useCallback(() => { - if (!onClick || !chainId) { - return; - } - onClick(chainId, address); - }, [onClick, chainId, address]); + // Ensure token has a valid chainId before proceeding + if (!onClick || !token.chainId) { + return; + } + + // Call the onClick handler with chainId and address if needed + onClick(token.chainId, token.address); + + // Track the event + trackEvent({ + category: MetaMetricsEventCategory.Tokens, + event: MetaMetricsEventName.TokenDetailsOpened, + properties: { + location: 'Home', + chain_id: token.chainId, // FIXME: Ensure this is a number for EVM accounts + token_symbol: token.symbol, + }, + }); + }, + [onClick, token.chainId, token.address], + ); + + const handleScamWarningModal = (arg: boolean) => { + setShowScamWarningModal(arg); + }; - if (!chainId) { + if (!token.chainId) { return null; } - const tokenChainImage = getImageForChainId(chainId); - return ( - + + + + + + + + + + + + + + + + {/* scam warning modal, this should be higher up in the component tree */} + {isEvm && showScamWarningModal ? ( + setShowScamWarningModal(false)}> + + + setShowScamWarningModal(false)}> + {t('nativeTokenScamWarningTitle')} + + + {t('nativeTokenScamWarningDescription', [ + token.symbol, + safeChainDetails?.nativeCurrency?.symbol || + t('nativeTokenScamWarningDescriptionExpectedTokenFallback'), // never render "undefined" string value + ])} + + + { + dispatch(setEditedNetwork({ chainId: token.chainId })); + history.push(NETWORKS_ROUTE); + }} + block + > + {t('nativeTokenScamWarningConversion')} + + + + + ) : null} + ); } diff --git a/ui/components/app/assets/token-list/token-list.tsx b/ui/components/app/assets/token-list/token-list.tsx index 7d9ec917bf31..84a771fe0878 100644 --- a/ui/components/app/assets/token-list/token-list.tsx +++ b/ui/components/app/assets/token-list/token-list.tsx @@ -1,29 +1,67 @@ -import React, { ReactNode, useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { Hex } from '@metamask/utils'; import TokenCell from '../token-cell'; -import { getChainIdsToPoll, getPreferences } from '../../../../selectors'; +import { + getChainIdsToPoll, + getCurrentNetwork, + getNewTokensImported, + getPreferences, + getSelectedAccount, + getTokenBalancesEvm, +} from '../../../../selectors'; import { endTrace, TraceName } from '../../../../../shared/lib/trace'; import { useTokenBalances as pollAndUpdateEvmBalances } from '../../../../hooks/useTokenBalances'; -import useSortedFilteredTokens from '../hooks/useSortedFilteredTokens'; -import useShouldShowFiat from '../hooks/useShouldShowFiat'; +import { useNativeTokenBalance, useNetworkFilter } from '../hooks'; +import { TokenWithFiatAmount } from '../types'; +import { getMultichainIsEvm } from '../../../../selectors/multichain'; +import { filterAssets } from '../util/filter'; +import { sortAssets } from '../util/sort'; type TokenListProps = { onTokenClick: (chainId: string, address: string) => void; - nativeToken?: ReactNode; }; -function TokenList({ onTokenClick, nativeToken }: TokenListProps) { - const { privacyMode } = useSelector(getPreferences); +function TokenList({ onTokenClick }: TokenListProps) { + const isEvm = useSelector(getMultichainIsEvm); const chainIdsToPoll = useSelector(getChainIdsToPoll); + const newTokensImported = useSelector(getNewTokensImported); + const evmBalances = useSelector(getTokenBalancesEvm); // TODO: This is where we need to select non evm-assets from state, when isEvm is false + const currentNetwork = useSelector(getCurrentNetwork); + const { tokenSortConfig, privacyMode } = useSelector(getPreferences); + const selectedAccount = useSelector(getSelectedAccount); // EVM specific tokenBalance polling, updates state via polling loop per chainId pollAndUpdateEvmBalances({ chainIds: chainIdsToPoll as Hex[], }); - const sortedFilteredTokens = useSortedFilteredTokens(); - const shouldShowFiat = useShouldShowFiat(); + const nonEvmNativeToken = useNativeTokenBalance(); + + // network filter to determine which tokens to show in list + // on EVM we want to filter based on network filter controls, on non-evm we only want tokens from that chain identifier + const { networkFilter } = useNetworkFilter(); + + const sortedFilteredTokens = useMemo(() => { + const balances = isEvm ? evmBalances : [nonEvmNativeToken]; + const filteredAssets: TokenWithFiatAmount[] = filterAssets(balances, [ + { + key: 'chainId', + opts: isEvm ? networkFilter : { [nonEvmNativeToken.chainId]: true }, + filterCallback: 'inclusive', + }, + ]); + + // sort filtered tokens based on the tokenSortConfig in state + return sortAssets([...filteredAssets], tokenSortConfig); + }, [ + tokenSortConfig, + networkFilter, + currentNetwork, + selectedAccount, + newTokensImported, + evmBalances, + ]); useEffect(() => { if (sortedFilteredTokens) { @@ -31,28 +69,17 @@ function TokenList({ onTokenClick, nativeToken }: TokenListProps) { } }, [sortedFilteredTokens]); - // Displays nativeToken if provided - if (nativeToken) { - return React.cloneElement(nativeToken as React.ReactElement); - } - return ( -
- {sortedFilteredTokens.map((token) => ( + <> + {sortedFilteredTokens.map((token: TokenWithFiatAmount) => ( ))} -
+ ); } diff --git a/ui/components/app/assets/types.ts b/ui/components/app/assets/types.ts index a9bf642b3900..af832b4095c6 100644 --- a/ui/components/app/assets/types.ts +++ b/ui/components/app/assets/types.ts @@ -1,21 +1,54 @@ import { Hex } from '@metamask/utils'; -export type Token = { +// Common mixin for primary and secondary display values +export type TokenDisplayValues = { + primary?: string; + secondary?: string; + string?: string; +}; + +export type TokenBalanceValues = { + tokenFiatAmount?: number | null; + balance?: string; +}; + +// Base token type with common fields +export type BaseToken = { address: Hex; - aggregators: string[]; - chainId: Hex; - decimals: number; - isNative: boolean; symbol: string; image: string; + decimals: number; + chainId: Hex; + isNative?: boolean; }; -export type TokenWithFiatAmount = Token & { - tokenFiatAmount: number | null; - balance?: string; - string: string; // needed for backwards compatability TODO: fix this +// Token type with optional aggregators +export type Token = BaseToken & { + aggregators?: string[]; }; +// Token with balance and optional display values +export type TokenWithBalance = Omit & + TokenDisplayValues & + Omit; + +// Token display information (UI-related properties) +export type TokenDisplayInfo = TokenDisplayValues & { + title: string; + tokenImage: string; + isStakeable?: boolean; + tokenChainImage: string; +}; + +// Token type that includes fiat amount, balance, and display values +export type TokenWithFiatAmount = Token & + TokenDisplayValues & + TokenBalanceValues & { + isStakeable?: boolean; + }; + +export type TokenFiatDisplayInfo = TokenWithFiatAmount & TokenDisplayInfo; + export type AddressBalanceMapping = Record>; export type ChainAddressMarketData = Record< Hex, diff --git a/ui/components/app/assets/util/calculateTokenBalance.ts b/ui/components/app/assets/util/calculateTokenBalance.ts index dd19feaf5f6d..4227dd206134 100644 --- a/ui/components/app/assets/util/calculateTokenBalance.ts +++ b/ui/components/app/assets/util/calculateTokenBalance.ts @@ -5,7 +5,7 @@ import { hexToDecimal } from '../../../../../shared/modules/conversion.utils'; import { AddressBalanceMapping } from '../types'; type CalculateTokenBalanceParams = { - isNative: boolean; + isNative?: boolean; chainId: Hex; address: Hex; decimals: number; diff --git a/ui/components/app/assets/util/formatWithThreshold.ts b/ui/components/app/assets/util/formatWithThreshold.ts new file mode 100644 index 000000000000..b5ec83a82bab --- /dev/null +++ b/ui/components/app/assets/util/formatWithThreshold.ts @@ -0,0 +1,16 @@ +export const formatWithThreshold = ( + amount: number | null, + threshold: number, + locale: string, + options: Intl.NumberFormatOptions, +): string => { + if (amount === null) { + return ''; + } + if (amount === 0) { + return new Intl.NumberFormat(locale, options).format(0); + } + return amount < threshold + ? `<${new Intl.NumberFormat(locale, options).format(threshold)}` + : new Intl.NumberFormat(locale, options).format(amount); +}; diff --git a/ui/components/app/assets/util/networkTitleOverrides.ts b/ui/components/app/assets/util/networkTitleOverrides.ts new file mode 100644 index 000000000000..297fe3a525c1 --- /dev/null +++ b/ui/components/app/assets/util/networkTitleOverrides.ts @@ -0,0 +1,25 @@ +import { + CURRENCY_SYMBOLS, + NON_EVM_CURRENCY_SYMBOLS, +} from '../../../../../shared/constants/network'; +import { TokenFiatDisplayInfo } from '../types'; + +export type TranslateFunction = (arg: string) => string; + +// overrides certain token titles to display network name over token title +// usually in the case of native tokens for L1 networks +export const networkTitleOverrides = ( + t: TranslateFunction, // translate function from useI18nContext() hook + token: TokenFiatDisplayInfo, +) => { + switch (token.title) { + case CURRENCY_SYMBOLS.ETH: + return t('networkNameEthereum'); + case NON_EVM_CURRENCY_SYMBOLS.BTC: + return t('networkNameBitcoin'); + case NON_EVM_CURRENCY_SYMBOLS.SOL: + return t('networkNameSolana'); + default: + return token.title; + } +}; diff --git a/ui/components/app/assets/util/sort.ts b/ui/components/app/assets/util/sort.ts index b24a1c8e96a9..e6348cedf79e 100644 --- a/ui/components/app/assets/util/sort.ts +++ b/ui/components/app/assets/util/sort.ts @@ -20,11 +20,10 @@ export type SortingCallbacksT = { // All sortingCallbacks should be asc order, sortAssets function handles asc/dsc const sortingCallbacks: SortingCallbacksT = { numeric: (a: number, b: number) => a - b, - stringNumeric: (a: string, b: string) => { - return ( - parseFloat(parseFloat(a).toFixed(5)) - - parseFloat(parseFloat(b).toFixed(5)) - ); + stringNumeric: (a: string | null, b: string | null) => { + const numA = a ? parseFloat(parseFloat(a).toFixed(5)) : 0; + const numB = b ? parseFloat(parseFloat(b).toFixed(5)) : 0; + return numA - numB; }, alphaNumeric: (a: string, b: string) => a.localeCompare(b), date: (a: Date, b: Date) => a.getTime() - b.getTime(), diff --git a/ui/components/app/basic-configuration-modal/basic-configuration-modal.test.tsx b/ui/components/app/basic-configuration-modal/basic-configuration-modal.test.tsx new file mode 100644 index 000000000000..a87089f22bb6 --- /dev/null +++ b/ui/components/app/basic-configuration-modal/basic-configuration-modal.test.tsx @@ -0,0 +1,220 @@ +import * as React from 'react'; +import { fireEvent, waitFor } from '@testing-library/react'; +import { useLocation } from 'react-router-dom'; +import configureStore from '../../../store/store'; +import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import * as Actions from '../../../store/actions'; +import { + hideBasicFunctionalityModal, + onboardingToggleBasicFunctionalityOff, +} from '../../../ducks/app/app'; +import { ONBOARDING_PRIVACY_SETTINGS_ROUTE } from '../../../helpers/constants/routes'; +import { BasicConfigurationModal } from './basic-configuration-modal'; + +jest.mock('../../../store/actions', () => ({ + setDataCollectionForMarketing: jest.fn(), + setParticipateInMetaMetrics: jest.fn(), + toggleExternalServices: jest.fn(), +})); + +jest.mock('../../../ducks/app/app', () => ({ + hideBasicFunctionalityModal: jest.fn(), + onboardingToggleBasicFunctionalityOff: jest.fn(), +})); + +const mockDispatch = jest.fn(); + +jest.mock('react-redux', () => { + const actual = jest.requireActual('react-redux'); + return { + ...actual, + useDispatch: () => mockDispatch, + }; +}); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn(), +})); + +type StateOverrides = { + metamask: { + useExternalServices: T; + }; +}; + +type ArrangeMocksParams = { + isOnboarding?: boolean; + stateOverrides?: StateOverrides; +}; + +type ArrangeMocksReturn = { + toggleBasicFunctionalityButton: HTMLElement; + cancelButton: HTMLElement; + agreementCheckbox: T extends true ? HTMLElement : null; +}; + +const arrangeMocks = ({ + isOnboarding = false, + stateOverrides, +}: ArrangeMocksParams = {}): ArrangeMocksReturn => { + jest.clearAllMocks(); + + (useLocation as jest.Mock).mockReturnValue({ + pathname: isOnboarding + ? ONBOARDING_PRIVACY_SETTINGS_ROUTE + : '/any-other-path', + }); + + const store = configureStore({ + ...stateOverrides, + }); + const { getByTestId, getByTitle } = renderWithProvider( + , + store, + ); + + const agreementCheckbox = stateOverrides?.metamask.useExternalServices + ? getByTitle('basic-configuration-checkbox') + : null; + const toggleBasicFunctionalityButton = getByTestId( + 'basic-configuration-modal-toggle-button', + ); + const cancelButton = getByTestId('basic-configuration-modal-cancel-button'); + + return { + toggleBasicFunctionalityButton, + cancelButton, + agreementCheckbox, + } as ArrangeMocksReturn; +}; + +describe('BasicConfigurationModal', () => { + it('should call hideBasicFunctionalityModal when the cancel button is clicked', () => { + const { cancelButton } = arrangeMocks(); + + expect(cancelButton).toBeEnabled(); + + fireEvent.click(cancelButton); + + expect(hideBasicFunctionalityModal).toHaveBeenCalledTimes(1); + }); + + describe('during onboarding', () => { + it('should render the basic configuration modal', async () => { + const { + agreementCheckbox, + cancelButton, + toggleBasicFunctionalityButton, + } = arrangeMocks({ + isOnboarding: true, + stateOverrides: { + metamask: { + useExternalServices: true, + }, + }, + }); + + expect(agreementCheckbox).toBeInTheDocument(); + expect(cancelButton).toBeInTheDocument(); + expect(toggleBasicFunctionalityButton).toBeInTheDocument(); + }); + + it('should call appropriate actions when the turn off button is clicked', () => { + const { agreementCheckbox, toggleBasicFunctionalityButton } = + arrangeMocks({ + isOnboarding: true, + stateOverrides: { + metamask: { + useExternalServices: true, + }, + }, + }); + + fireEvent.click(agreementCheckbox, { + target: { checked: true }, + }); + + expect(toggleBasicFunctionalityButton).toBeEnabled(); + + fireEvent.click(toggleBasicFunctionalityButton); + + expect(hideBasicFunctionalityModal).toHaveBeenCalledTimes(1); + expect(onboardingToggleBasicFunctionalityOff).toHaveBeenCalledTimes(1); + expect(Actions.setParticipateInMetaMetrics).toHaveBeenCalledTimes(1); + expect(Actions.setParticipateInMetaMetrics).toHaveBeenCalledWith(false); + expect(Actions.setDataCollectionForMarketing).toHaveBeenCalledTimes(1); + expect(Actions.setDataCollectionForMarketing).toHaveBeenCalledWith(false); + }); + }); + + describe('outside onboarding', () => { + it('should render the basic configuration modal', async () => { + const { + agreementCheckbox, + cancelButton, + toggleBasicFunctionalityButton, + } = arrangeMocks({ + isOnboarding: false, + stateOverrides: { + metamask: { + useExternalServices: true, + }, + }, + }); + + expect(agreementCheckbox).toBeInTheDocument(); + expect(cancelButton).toBeInTheDocument(); + expect(toggleBasicFunctionalityButton).toBeInTheDocument(); + }); + + it('should call appropriate actions when the turn off button is clicked', () => { + const { agreementCheckbox, toggleBasicFunctionalityButton } = + arrangeMocks({ + stateOverrides: { + metamask: { + useExternalServices: true, + }, + }, + }); + + fireEvent.click(agreementCheckbox, { + target: { checked: true }, + }); + + waitFor(() => { + expect(toggleBasicFunctionalityButton).toBeEnabled(); + }); + + fireEvent.click(toggleBasicFunctionalityButton); + + expect(hideBasicFunctionalityModal).toHaveBeenCalledTimes(1); + expect(Actions.setParticipateInMetaMetrics).toHaveBeenCalledTimes(1); + expect(Actions.setParticipateInMetaMetrics).toHaveBeenCalledWith(false); + expect(Actions.setDataCollectionForMarketing).toHaveBeenCalledTimes(1); + expect(Actions.setDataCollectionForMarketing).toHaveBeenCalledWith(false); + expect(Actions.toggleExternalServices).toHaveBeenCalledTimes(1); + expect(Actions.toggleExternalServices).toHaveBeenCalledWith(false); + }); + + it('should call the appropriate actions when the turn on button is clicked', () => { + const { toggleBasicFunctionalityButton } = arrangeMocks({ + stateOverrides: { + metamask: { + useExternalServices: false, + }, + }, + }); + + expect(toggleBasicFunctionalityButton).toBeEnabled(); + + fireEvent.click(toggleBasicFunctionalityButton); + + expect(hideBasicFunctionalityModal).toHaveBeenCalledTimes(1); + expect(Actions.setParticipateInMetaMetrics).toHaveBeenCalledTimes(0); + expect(Actions.setDataCollectionForMarketing).toHaveBeenCalledTimes(0); + expect(Actions.toggleExternalServices).toHaveBeenCalledTimes(1); + expect(Actions.toggleExternalServices).toHaveBeenCalledWith(true); + }); + }); +}); diff --git a/ui/components/app/basic-configuration-modal/basic-configuration-modal.tsx b/ui/components/app/basic-configuration-modal/basic-configuration-modal.tsx index 52faaf0384af..652e333ef8aa 100644 --- a/ui/components/app/basic-configuration-modal/basic-configuration-modal.tsx +++ b/ui/components/app/basic-configuration-modal/basic-configuration-modal.tsx @@ -12,7 +12,11 @@ import { FontWeight, } from '../../../helpers/constants/design-system'; import { useI18nContext } from '../../../hooks/useI18nContext'; -import { toggleExternalServices } from '../../../store/actions'; +import { + setDataCollectionForMarketing, + setParticipateInMetaMetrics, + toggleExternalServices, +} from '../../../store/actions'; import { ModalOverlay, ModalContent, @@ -135,6 +139,7 @@ export function BasicConfigurationModal() { size={ButtonSize.Lg} width={BlockSize.Half} variant={ButtonVariant.Secondary} + data-testid="basic-configuration-modal-cancel-button" onClick={closeModal} > {t('cancel')} @@ -144,6 +149,7 @@ export function BasicConfigurationModal() { disabled={!hasAgreed && isExternalServicesEnabled} width={BlockSize.Half} variant={ButtonVariant.Primary} + data-testid="basic-configuration-modal-toggle-button" onClick={() => { const event = onboardingFlow ? { @@ -172,6 +178,11 @@ export function BasicConfigurationModal() { trackEvent(event); + if (isExternalServicesEnabled || onboardingFlow) { + dispatch(setParticipateInMetaMetrics(false)); + dispatch(setDataCollectionForMarketing(false)); + } + if (onboardingFlow) { dispatch(hideBasicFunctionalityModal()); dispatch(onboardingToggleBasicFunctionalityOff()); diff --git a/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx b/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx index 5a321c35cf0f..c890a4d1140a 100644 --- a/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx +++ b/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx @@ -26,7 +26,7 @@ import { Box, SensitiveText } from '../../component-library'; import { getCalculatedTokenAmount1dAgo } from '../../../helpers/utils/util'; import { useAccountTotalCrossChainFiatBalance } from '../../../hooks/useAccountTotalCrossChainFiatBalance'; import { useGetFormattedTokensPerChain } from '../../../hooks/useGetFormattedTokensPerChain'; -import { TokenWithBalance } from '../assets/asset-list/asset-list'; +import { TokenWithBalance } from '../assets/types'; export const AggregatedPercentageOverviewCrossChains = () => { const locale = useSelector(getIntlLocale); diff --git a/ui/components/component-library/avatar-account/avatar-account.test.tsx b/ui/components/component-library/avatar-account/avatar-account.test.tsx index 9279c1af93a0..e104c5db8682 100644 --- a/ui/components/component-library/avatar-account/avatar-account.test.tsx +++ b/ui/components/component-library/avatar-account/avatar-account.test.tsx @@ -1,8 +1,12 @@ /* eslint-disable jest/require-top-level-describe */ import { render } from '@testing-library/react'; import React from 'react'; -import { AvatarAccount, AvatarAccountSize, AvatarAccountVariant } from '.'; import 'jest-canvas-mock'; +import { AvatarAccount } from './avatar-account'; +import { + AvatarAccountSize, + AvatarAccountVariant, +} from './avatar-account.types'; describe('AvatarAccount', () => { it('should render correctly', () => { diff --git a/ui/components/component-library/avatar-base/avatar-base.stories.tsx b/ui/components/component-library/avatar-base/avatar-base.stories.tsx index a890f418a213..64ecc8b6b836 100644 --- a/ui/components/component-library/avatar-base/avatar-base.stories.tsx +++ b/ui/components/component-library/avatar-base/avatar-base.stories.tsx @@ -9,9 +9,11 @@ import { IconColor, } from '../../../helpers/constants/design-system'; -import { AvatarBase, Icon, IconName, Box } from '..'; import { AvatarBaseSize } from './avatar-base.types'; import README from './README.mdx'; +import { AvatarBase } from './avatar-base'; +import { Box } from '../box'; +import { Icon, IconName } from '../icon'; const marginSizeKnobOptions = [ 0, diff --git a/ui/components/component-library/avatar-favicon/avatar-favicon.stories.tsx b/ui/components/component-library/avatar-favicon/avatar-favicon.stories.tsx index 18b0248e3499..8b07e15cd459 100644 --- a/ui/components/component-library/avatar-favicon/avatar-favicon.stories.tsx +++ b/ui/components/component-library/avatar-favicon/avatar-favicon.stories.tsx @@ -9,7 +9,8 @@ import { import { Box } from '../box'; import README from './README.mdx'; -import { AvatarFavicon, AvatarFaviconSize } from '.'; +import { AvatarFavicon } from './avatar-favicon'; +import { AvatarFaviconSize } from './avatar-favicon.types'; export default { title: 'Components/ComponentLibrary/AvatarFavicon', diff --git a/ui/components/component-library/avatar-favicon/avatar-favicon.test.tsx b/ui/components/component-library/avatar-favicon/avatar-favicon.test.tsx index aa69e04127e5..cf679c88a665 100644 --- a/ui/components/component-library/avatar-favicon/avatar-favicon.test.tsx +++ b/ui/components/component-library/avatar-favicon/avatar-favicon.test.tsx @@ -2,9 +2,9 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import { IconName } from '..'; +import { IconName } from '../icon'; import { AvatarFaviconSize } from './avatar-favicon.types'; -import { AvatarFavicon } from '.'; +import { AvatarFavicon } from './avatar-favicon'; describe('AvatarFavicon', () => { const args = { diff --git a/ui/components/component-library/avatar-icon/avatar-icon.stories.tsx b/ui/components/component-library/avatar-icon/avatar-icon.stories.tsx index bcd97d8ccee8..8537ebb58e08 100644 --- a/ui/components/component-library/avatar-icon/avatar-icon.stories.tsx +++ b/ui/components/component-library/avatar-icon/avatar-icon.stories.tsx @@ -10,10 +10,10 @@ import { import { Box } from '../box'; -import { IconName } from '..'; - import README from './README.mdx'; -import { AvatarIcon, AvatarIconSize } from '.'; +import { AvatarIcon } from './avatar-icon'; +import { IconName } from '../icon'; +import { AvatarIconSize } from './avatar-icon.types'; export default { title: 'Components/ComponentLibrary/AvatarIcon', diff --git a/ui/components/component-library/avatar-icon/avatar-icon.test.tsx b/ui/components/component-library/avatar-icon/avatar-icon.test.tsx index 17ce35fe0dea..51f63ec3428e 100644 --- a/ui/components/component-library/avatar-icon/avatar-icon.test.tsx +++ b/ui/components/component-library/avatar-icon/avatar-icon.test.tsx @@ -2,12 +2,13 @@ import { render } from '@testing-library/react'; import React from 'react'; -import { IconName } from '..'; import { BackgroundColor, IconColor, } from '../../../helpers/constants/design-system'; -import { AvatarIcon, AvatarIconSize } from '.'; +import { IconName } from '../icon'; +import { AvatarIcon } from './avatar-icon'; +import { AvatarIconSize } from './avatar-icon.types'; describe('AvatarIcon', () => { it('should render correctly', () => { diff --git a/ui/components/component-library/avatar-network/avatar-network.stories.tsx b/ui/components/component-library/avatar-network/avatar-network.stories.tsx index 390ddc3e87cc..c878d322584b 100644 --- a/ui/components/component-library/avatar-network/avatar-network.stories.tsx +++ b/ui/components/component-library/avatar-network/avatar-network.stories.tsx @@ -7,11 +7,11 @@ import { BorderColor, AlignItems, } from '../../../helpers/constants/design-system'; -import { Box } from '..'; import { AvatarNetworkSize } from './avatar-network.types'; import README from './README.mdx'; import { AvatarNetwork } from './avatar-network'; +import { Box } from '../box'; export default { title: 'Components/ComponentLibrary/AvatarNetwork', diff --git a/ui/components/component-library/avatar-network/avatar-network.test.tsx b/ui/components/component-library/avatar-network/avatar-network.test.tsx index ce7e5574cf45..81819ad49276 100644 --- a/ui/components/component-library/avatar-network/avatar-network.test.tsx +++ b/ui/components/component-library/avatar-network/avatar-network.test.tsx @@ -7,8 +7,8 @@ import { BorderColor, TextColor, } from '../../../helpers/constants/design-system'; - -import { AvatarNetwork, AvatarNetworkSize } from '.'; +import { AvatarNetwork } from './avatar-network'; +import { AvatarNetworkSize } from './avatar-network.types'; describe('AvatarNetwork', () => { const args = { diff --git a/ui/components/component-library/avatar-token/avatar-token.stories.tsx b/ui/components/component-library/avatar-token/avatar-token.stories.tsx index dc7af5b453b3..0101dd74bb65 100644 --- a/ui/components/component-library/avatar-token/avatar-token.stories.tsx +++ b/ui/components/component-library/avatar-token/avatar-token.stories.tsx @@ -7,17 +7,14 @@ import { BackgroundColor, BorderColor, } from '../../../helpers/constants/design-system'; -import { - AvatarNetwork, - AvatarNetworkSize, - BadgeWrapper, - Box, - ButtonLink, - ButtonLinkSize, - Text, -} from '..'; import README from './README.mdx'; -import { AvatarToken, AvatarTokenSize } from '.'; +import { AvatarToken } from './avatar-token'; +import { Box } from '../box'; +import { AvatarTokenSize } from './avatar-token.types'; +import { ButtonLink, ButtonLinkSize } from '../button-link'; +import { BadgeWrapper } from '../badge-wrapper'; +import { AvatarNetwork, AvatarNetworkSize } from '../avatar-network'; +import { Text } from '../text'; export default { title: 'Components/ComponentLibrary/AvatarToken', diff --git a/ui/components/component-library/avatar-token/avatar-token.test.tsx b/ui/components/component-library/avatar-token/avatar-token.test.tsx index 90a714505fb3..d3a6674661b3 100644 --- a/ui/components/component-library/avatar-token/avatar-token.test.tsx +++ b/ui/components/component-library/avatar-token/avatar-token.test.tsx @@ -6,7 +6,8 @@ import { BorderColor, TextColor, } from '../../../helpers/constants/design-system'; -import { AvatarToken, AvatarTokenSize } from '.'; +import { AvatarToken } from './avatar-token'; +import { AvatarTokenSize } from './avatar-token.types'; describe('AvatarToken', () => { const args = { diff --git a/ui/components/component-library/badge-wrapper/badge-wrapper.stories.tsx b/ui/components/component-library/badge-wrapper/badge-wrapper.stories.tsx index 1be4c474f4d1..712261c713d6 100644 --- a/ui/components/component-library/badge-wrapper/badge-wrapper.stories.tsx +++ b/ui/components/component-library/badge-wrapper/badge-wrapper.stories.tsx @@ -11,18 +11,6 @@ import { IconColor, } from '../../../helpers/constants/design-system'; -import Box from '../../ui/box/box'; - -import { - AvatarAccount, - AvatarNetwork, - AvatarNetworkSize, - AvatarToken, - Icon, - IconName, - IconSize, - Tag, -} from '..'; import { BadgeWrapperAnchorElementShape, BadgeWrapperPosition, @@ -31,6 +19,13 @@ import { import README from './README.mdx'; import { BadgeWrapper } from './badge-wrapper'; +import { AvatarNetwork, AvatarNetworkSize } from '../avatar-network'; +import { AvatarAccount } from '../avatar-account'; +import { AvatarToken } from '../avatar-token'; + +import Box from '../../ui/box/box'; +import { Icon, IconName, IconSize } from '../icon'; +import { Tag } from '../tag'; export default { title: 'Components/ComponentLibrary/BadgeWrapper', diff --git a/ui/components/component-library/badge-wrapper/badge-wrapper.tsx b/ui/components/component-library/badge-wrapper/badge-wrapper.tsx index 5d046c6150bd..36026228724a 100644 --- a/ui/components/component-library/badge-wrapper/badge-wrapper.tsx +++ b/ui/components/component-library/badge-wrapper/badge-wrapper.tsx @@ -2,9 +2,8 @@ import React from 'react'; import classnames from 'classnames'; import { Display } from '../../../helpers/constants/design-system'; -import { Box } from '..'; -import type { BoxProps, PolymorphicRef } from '..'; +import { Box, type BoxProps, type PolymorphicRef } from '../box'; import { BadgeWrapperPosition, BadgeWrapperAnchorElementShape, diff --git a/ui/components/component-library/badge-wrapper/badge-wrapper.types.ts b/ui/components/component-library/badge-wrapper/badge-wrapper.types.ts index 4dd9a515106e..eac3e87f000e 100644 --- a/ui/components/component-library/badge-wrapper/badge-wrapper.types.ts +++ b/ui/components/component-library/badge-wrapper/badge-wrapper.types.ts @@ -1,10 +1,9 @@ import React from 'react'; - import type { - StyleUtilityProps, - PolymorphicComponentPropWithRef, BoxProps, -} from '..'; + PolymorphicComponentPropWithRef, + StyleUtilityProps, +} from '../box'; export enum BadgeWrapperPosition { topRight = 'top-right', diff --git a/ui/components/component-library/banner-alert/README.mdx b/ui/components/component-library/banner-alert/README.mdx index 45151a6e31be..81eb19a2ad07 100644 --- a/ui/components/component-library/banner-alert/README.mdx +++ b/ui/components/component-library/banner-alert/README.mdx @@ -1,6 +1,6 @@ import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; import { BannerAlert } from './banner-alert'; -import { BannerBase } from '..'; +import { BannerBase } from '../banner-base'; # BannerAlert diff --git a/ui/components/component-library/banner-alert/banner-alert.stories.tsx b/ui/components/component-library/banner-alert/banner-alert.stories.tsx index 0502ad5e2a9a..c0ad19ff73b8 100644 --- a/ui/components/component-library/banner-alert/banner-alert.stories.tsx +++ b/ui/components/component-library/banner-alert/banner-alert.stories.tsx @@ -5,11 +5,15 @@ import { Display, FlexDirection, } from '../../../helpers/constants/design-system'; -import { ButtonLink, ButtonPrimary, IconName, Box, ButtonLinkSize } from '..'; import README from './README.mdx'; -import { BannerAlert, BannerAlertSeverity } from '.'; +import { BannerAlert } from './banner-alert'; +import { BannerAlertSeverity } from './banner-alert.types'; +import { Box } from '../box'; +import { ButtonLink, ButtonLinkSize } from '../button-link'; +import { IconName } from '../icon'; +import { ButtonPrimary } from '../button-primary'; export default { title: 'Components/ComponentLibrary/BannerAlert', diff --git a/ui/components/component-library/banner-alert/banner-alert.test.tsx b/ui/components/component-library/banner-alert/banner-alert.test.tsx index b1b51fdb7fdc..3147a035825d 100644 --- a/ui/components/component-library/banner-alert/banner-alert.test.tsx +++ b/ui/components/component-library/banner-alert/banner-alert.test.tsx @@ -3,8 +3,8 @@ import { render } from '@testing-library/react'; import React from 'react'; import { renderWithUserEvent } from '../../../../test/lib/render-helpers'; - -import { BannerAlert, BannerAlertSeverity } from '.'; +import { BannerAlert } from './banner-alert'; +import { BannerAlertSeverity } from './banner-alert.types'; describe('BannerAlert', () => { it('should render BannerAlert element correctly', () => { diff --git a/ui/components/component-library/banner-alert/banner-alert.tsx b/ui/components/component-library/banner-alert/banner-alert.tsx index a79551959b1b..9e71caf7bd76 100644 --- a/ui/components/component-library/banner-alert/banner-alert.tsx +++ b/ui/components/component-library/banner-alert/banner-alert.tsx @@ -1,14 +1,13 @@ import React from 'react'; import classnames from 'classnames'; -import { BannerBase, Icon, IconName, IconSize } from '..'; - import { BackgroundColor, IconColor, } from '../../../helpers/constants/design-system'; import { PolymorphicRef } from '../box'; -import { BannerBaseProps } from '../banner-base'; +import { BannerBase, BannerBaseProps } from '../banner-base'; +import { Icon, IconName, IconSize } from '../icon'; import { BannerAlertComponent, BannerAlertProps, diff --git a/ui/components/component-library/banner-base/README.mdx b/ui/components/component-library/banner-base/README.mdx index e4ebea851072..bf01a7cd315f 100644 --- a/ui/components/component-library/banner-base/README.mdx +++ b/ui/components/component-library/banner-base/README.mdx @@ -1,5 +1,5 @@ import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; -import { BannerBase } from './banner-base'; +import { BannerBase } from '../banner-base'; ### This is a base component. It should not be used in your feature code directly but as a "base" for other UI components diff --git a/ui/components/component-library/banner-base/banner-base.stories.tsx b/ui/components/component-library/banner-base/banner-base.stories.tsx index f5df17ca395d..1a51d1d23171 100644 --- a/ui/components/component-library/banner-base/banner-base.stories.tsx +++ b/ui/components/component-library/banner-base/banner-base.stories.tsx @@ -1,16 +1,11 @@ import React from 'react'; import { Meta, StoryFn } from '@storybook/react'; import { useState } from '@storybook/addons'; -import { - ButtonLink, - ButtonLinkSize, - ButtonPrimary, - Icon, - IconName, - IconSize, -} from '..'; import { BannerBase } from './banner-base'; import README from './README.mdx'; +import { Icon, IconName, IconSize } from '../icon'; +import { ButtonLink, ButtonLinkSize } from '../button-link'; +import { ButtonPrimary } from '../button-primary'; export default { title: 'Components/ComponentLibrary/BannerBase', diff --git a/ui/components/component-library/banner-base/banner-base.test.tsx b/ui/components/component-library/banner-base/banner-base.test.tsx index 09eaf3943c30..6019c652f43b 100644 --- a/ui/components/component-library/banner-base/banner-base.test.tsx +++ b/ui/components/component-library/banner-base/banner-base.test.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { renderWithUserEvent } from '../../../../test/lib/render-helpers'; -import { Icon, IconName } from '..'; +import { Icon, IconName } from '../icon'; import { BannerBase } from './banner-base'; describe('BannerBase', () => { diff --git a/ui/components/component-library/banner-base/banner-base.tsx b/ui/components/component-library/banner-base/banner-base.tsx index 1dc0369b3025..12e4b7c717b7 100644 --- a/ui/components/component-library/banner-base/banner-base.tsx +++ b/ui/components/component-library/banner-base/banner-base.tsx @@ -10,16 +10,11 @@ import { TextVariant, } from '../../../helpers/constants/design-system'; -import { - ButtonLink, - IconName, - ButtonIcon, - Text, - Box, - ButtonLinkSize, - ButtonIconSize, -} from '..'; -import { BoxProps, PolymorphicRef } from '../box'; +import { Text } from '../text'; +import { Box, BoxProps, PolymorphicRef } from '../box'; +import { ButtonLink, ButtonLinkSize } from '../button-link'; +import { ButtonIcon, ButtonIconSize } from '../button-icon'; +import { IconName } from '../icon'; import { BannerBaseComponent, BannerBaseProps } from './banner-base.types'; export const BannerBase: BannerBaseComponent = React.forwardRef( diff --git a/ui/components/component-library/banner-tip/README.mdx b/ui/components/component-library/banner-tip/README.mdx index 49b567384c14..6c9955333833 100644 --- a/ui/components/component-library/banner-tip/README.mdx +++ b/ui/components/component-library/banner-tip/README.mdx @@ -1,6 +1,6 @@ import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; import { BannerTip } from './banner-tip'; -import { BannerBase } from '..'; +import { BannerBase } from '../banner-base'; # BannerTip diff --git a/ui/components/component-library/banner-tip/__snapshots__/banner-tip.test.tsx.snap b/ui/components/component-library/banner-tip/__snapshots__/banner-tip.test.tsx.snap index 0d2b8b210627..e8084246e379 100644 --- a/ui/components/component-library/banner-tip/__snapshots__/banner-tip.test.tsx.snap +++ b/ui/components/component-library/banner-tip/__snapshots__/banner-tip.test.tsx.snap @@ -10,9 +10,9 @@ exports[`BannerTip should render BannerTip element correctly 1`] = ` class="mm-box mm-box--display-flex mm-box--align-items-center" >
{ it('should render BannerTip element correctly', () => { diff --git a/ui/components/component-library/banner-tip/banner-tip.tsx b/ui/components/component-library/banner-tip/banner-tip.tsx index e7d67e60c345..64a089de5c2c 100644 --- a/ui/components/component-library/banner-tip/banner-tip.tsx +++ b/ui/components/component-library/banner-tip/banner-tip.tsx @@ -5,12 +5,11 @@ import { BorderColor, Display, } from '../../../helpers/constants/design-system'; -import { BannerBase, Box } from '..'; -import { BoxProps, PolymorphicRef } from '../box'; -import { BannerBaseProps } from '../banner-base'; +import { Box, BoxProps, PolymorphicRef } from '../box'; +import { BannerBase, BannerBaseProps } from '../banner-base'; import { BannerTipComponent, - BannerTipLogoType, + // BannerTipLogoType, BannerTipProps, } from './banner-tip.types'; @@ -19,7 +18,10 @@ export const BannerTip: BannerTipComponent = React.forwardRef( { children, className = '', - logoType = BannerTipLogoType.Greeting, + // TODO: Get new assets for greeting and chat based off + // of the new branding. If decision is to use the normal fox + // then remove enum and update stories to use the normal fox + // logoType = BannerTipLogoType.Greeting, logoWrapperProps, logoProps, startAccessory, @@ -38,8 +40,8 @@ export const BannerTip: BannerTipComponent = React.forwardRef( > )} className={classnames( 'mm-banner-tip--logo', diff --git a/ui/components/component-library/box/box.stories.tsx b/ui/components/component-library/box/box.stories.tsx index 8da07a5daada..522f190f78fd 100644 --- a/ui/components/component-library/box/box.stories.tsx +++ b/ui/components/component-library/box/box.stories.tsx @@ -16,7 +16,7 @@ import { FlexWrap, } from '../../../helpers/constants/design-system'; -import { Text } from '..'; +import { Text } from '../text'; import { Box } from './box'; diff --git a/ui/components/component-library/box/box.test.tsx b/ui/components/component-library/box/box.test.tsx index 7aa9125979df..39c58ee77fab 100644 --- a/ui/components/component-library/box/box.test.tsx +++ b/ui/components/component-library/box/box.test.tsx @@ -14,8 +14,7 @@ import { BackgroundColor, TextColor, } from '../../../helpers/constants/design-system'; - -import { Box } from '.'; +import { Box } from './box'; describe('Box', () => { it('should render the Box without crashing', () => { diff --git a/ui/components/component-library/button-base/button-base.stories.tsx b/ui/components/component-library/button-base/button-base.stories.tsx index 948ad6ea4890..871ba6290bd7 100644 --- a/ui/components/component-library/button-base/button-base.stories.tsx +++ b/ui/components/component-library/button-base/button-base.stories.tsx @@ -7,10 +7,12 @@ import { FlexDirection, TextColor, } from '../../../helpers/constants/design-system'; -import { Box, TextDirection, IconName } from '..'; import { ButtonBaseSize } from './button-base.types'; import { ButtonBase } from './button-base'; import README from './README.mdx'; +import { IconName } from '../icon'; +import { Box } from '../box'; +import { TextDirection } from '../text'; const marginSizeControlOptions = [ undefined, diff --git a/ui/components/component-library/button-base/button-base.test.tsx b/ui/components/component-library/button-base/button-base.test.tsx index 4708c0c1c5b0..d2eac4e40d5c 100644 --- a/ui/components/component-library/button-base/button-base.test.tsx +++ b/ui/components/component-library/button-base/button-base.test.tsx @@ -1,7 +1,7 @@ /* eslint-disable jest/require-top-level-describe */ import { render } from '@testing-library/react'; import React from 'react'; -import { IconName } from '..'; +import { IconName } from '../icon'; import { ButtonBaseSize } from './button-base.types'; import { ButtonBase } from './button-base'; diff --git a/ui/components/component-library/button-base/button-base.tsx b/ui/components/component-library/button-base/button-base.tsx index 2542c014e74d..7227722af9e3 100644 --- a/ui/components/component-library/button-base/button-base.tsx +++ b/ui/components/component-library/button-base/button-base.tsx @@ -1,6 +1,6 @@ import React from 'react'; import classnames from 'classnames'; -import { IconName, Icon, IconSize, Text } from '..'; +import { Text } from '../text'; import { AlignItems, Display, @@ -13,6 +13,7 @@ import { } from '../../../helpers/constants/design-system'; import type { PolymorphicRef } from '../box'; import type { TextProps } from '../text'; +import { Icon, IconName, IconSize } from '../icon'; import { ButtonBaseProps, ButtonBaseSize, diff --git a/ui/components/component-library/button-icon/button-icon.stories.tsx b/ui/components/component-library/button-icon/button-icon.stories.tsx index 271c944f4856..365861555617 100644 --- a/ui/components/component-library/button-icon/button-icon.stories.tsx +++ b/ui/components/component-library/button-icon/button-icon.stories.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { StoryFn, Meta } from '@storybook/react'; import { IconColor } from '../../../helpers/constants/design-system'; -import { IconName } from '..'; import { ButtonIconSize } from './button-icon.types'; import { ButtonIcon } from './button-icon'; import README from './README.mdx'; +import { IconName } from '../icon'; export default { title: 'Components/ComponentLibrary/ButtonIcon', diff --git a/ui/components/component-library/button-icon/button-icon.test.tsx b/ui/components/component-library/button-icon/button-icon.test.tsx index bf48c0d95e69..f60b2cb8aef0 100644 --- a/ui/components/component-library/button-icon/button-icon.test.tsx +++ b/ui/components/component-library/button-icon/button-icon.test.tsx @@ -2,7 +2,7 @@ import { render } from '@testing-library/react'; import React from 'react'; import { IconColor } from '../../../helpers/constants/design-system'; -import { IconName } from '..'; +import { IconName } from '../icon'; import { ButtonIconSize } from './button-icon.types'; import { ButtonIcon } from './button-icon'; diff --git a/ui/components/component-library/button-icon/button-icon.tsx b/ui/components/component-library/button-icon/button-icon.tsx index e118b9ac3de1..46fc08c4620c 100644 --- a/ui/components/component-library/button-icon/button-icon.tsx +++ b/ui/components/component-library/button-icon/button-icon.tsx @@ -10,9 +10,8 @@ import { JustifyContent, } from '../../../helpers/constants/design-system'; -import { Box, Icon } from '..'; -import { IconSize } from '../icon'; -import { BoxProps, PolymorphicRef } from '../box'; +import { Box, BoxProps, PolymorphicRef } from '../box'; +import { Icon, IconSize } from '../icon'; import { ButtonIconSize, ButtonIconProps, diff --git a/ui/components/component-library/button-link/button-link.stories.tsx b/ui/components/component-library/button-link/button-link.stories.tsx index 087843decd74..aba1659e7d8f 100644 --- a/ui/components/component-library/button-link/button-link.stories.tsx +++ b/ui/components/component-library/button-link/button-link.stories.tsx @@ -8,9 +8,11 @@ import { TextVariant, TextColor, } from '../../../helpers/constants/design-system'; -import { Box, Text } from '..'; +import { Box } from '../box'; +import { Text } from '../text'; import README from './README.mdx'; -import { ButtonLink, ButtonLinkSize } from '.'; +import { ButtonLink } from './button-link'; +import { ButtonLinkSize } from './button-link.types'; export default { title: 'Components/ComponentLibrary/ButtonLink', diff --git a/ui/components/component-library/button-link/button-link.test.tsx b/ui/components/component-library/button-link/button-link.test.tsx index 424b2aa9973f..89a06eaa42f8 100644 --- a/ui/components/component-library/button-link/button-link.test.tsx +++ b/ui/components/component-library/button-link/button-link.test.tsx @@ -1,8 +1,9 @@ /* eslint-disable jest/require-top-level-describe */ import { render } from '@testing-library/react'; import React from 'react'; -import { IconName } from '..'; -import { ButtonLink, ButtonLinkSize } from '.'; +import { IconName } from '../icon'; +import { ButtonLink } from './button-link'; +import { ButtonLinkSize } from './button-link.types'; describe('ButtonLink', () => { it('should render button element correctly', () => { diff --git a/ui/components/component-library/button-link/button-link.tsx b/ui/components/component-library/button-link/button-link.tsx index 958c1a0ee7ac..2ce9c3776c2e 100644 --- a/ui/components/component-library/button-link/button-link.tsx +++ b/ui/components/component-library/button-link/button-link.tsx @@ -1,12 +1,12 @@ import React from 'react'; import classnames from 'classnames'; -import { ButtonBase, IconSize } from '..'; import { BackgroundColor, Color, } from '../../../helpers/constants/design-system'; import type { PolymorphicRef } from '../box'; -import type { ButtonBaseProps } from '../button-base'; +import { ButtonBase, type ButtonBaseProps } from '../button-base'; +import { IconSize } from '../icon'; import type { ButtonLinkProps } from './button-link.types'; import { ButtonLinkSize, ButtonLinkComponent } from './button-link.types'; diff --git a/ui/components/component-library/button-primary/button-primary.stories.tsx b/ui/components/component-library/button-primary/button-primary.stories.tsx index b6ecb83bebdc..74e25887ac08 100644 --- a/ui/components/component-library/button-primary/button-primary.stories.tsx +++ b/ui/components/component-library/button-primary/button-primary.stories.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { StoryFn, Meta } from '@storybook/react'; import { AlignItems, Display } from '../../../helpers/constants/design-system'; -import { Box } from '..'; import README from './README.mdx'; -import { ButtonPrimary, ButtonPrimarySize } from '.'; +import { ButtonPrimary } from './button-primary'; +import { ButtonPrimarySize } from './button-primary.types'; +import { Box } from '../box'; export default { title: 'Components/ComponentLibrary/ButtonPrimary', diff --git a/ui/components/component-library/button-primary/button-primary.test.tsx b/ui/components/component-library/button-primary/button-primary.test.tsx index 95a51f79bb13..6b812807c5dc 100644 --- a/ui/components/component-library/button-primary/button-primary.test.tsx +++ b/ui/components/component-library/button-primary/button-primary.test.tsx @@ -1,8 +1,9 @@ /* eslint-disable jest/require-top-level-describe */ import { render } from '@testing-library/react'; import React from 'react'; -import { IconName } from '..'; -import { ButtonPrimary, ButtonPrimarySize } from '.'; +import { IconName } from '../icon'; +import { ButtonPrimary } from './button-primary'; +import { ButtonPrimarySize } from './button-primary.types'; describe('ButtonPrimary', () => { it('should render button element correctly', () => { diff --git a/ui/components/component-library/button-secondary/button-secondary.stories.tsx b/ui/components/component-library/button-secondary/button-secondary.stories.tsx index a7883213f999..6cb1bbbbe73a 100644 --- a/ui/components/component-library/button-secondary/button-secondary.stories.tsx +++ b/ui/components/component-library/button-secondary/button-secondary.stories.tsx @@ -1,9 +1,11 @@ import React from 'react'; import { StoryFn, Meta } from '@storybook/react'; import { AlignItems, Display } from '../../../helpers/constants/design-system'; -import { IconName, Box } from '..'; import README from './README.mdx'; -import { ButtonSecondary, ButtonSecondarySize } from '.'; +import { ButtonSecondary } from './button-secondary'; +import { IconName } from '../icon'; +import { ButtonSecondarySize } from './button-secondary.types'; +import { Box } from '../box'; const marginSizeControlOptions = [ undefined, diff --git a/ui/components/component-library/button-secondary/button-secondary.test.tsx b/ui/components/component-library/button-secondary/button-secondary.test.tsx index f8730d34af6c..c8ce5a265e40 100644 --- a/ui/components/component-library/button-secondary/button-secondary.test.tsx +++ b/ui/components/component-library/button-secondary/button-secondary.test.tsx @@ -1,8 +1,9 @@ /* eslint-disable jest/require-top-level-describe */ import { render } from '@testing-library/react'; import React from 'react'; -import { IconName } from '..'; -import { ButtonSecondary, ButtonSecondarySize } from '.'; +import { IconName } from '../icon'; +import { ButtonSecondary } from './button-secondary'; +import { ButtonSecondarySize } from './button-secondary.types'; describe('ButtonSecondary', () => { it('should render button element correctly', () => { diff --git a/ui/components/component-library/button/button.stories.tsx b/ui/components/component-library/button/button.stories.tsx index 421ee3a97dd7..0ee62b2d95f1 100644 --- a/ui/components/component-library/button/button.stories.tsx +++ b/ui/components/component-library/button/button.stories.tsx @@ -6,10 +6,12 @@ import { FlexDirection, TextVariant, } from '../../../helpers/constants/design-system'; -import { Box, IconName } from '..'; import { Text } from '../text'; import README from './README.mdx'; -import { Button, ButtonSize, ButtonVariant } from '.'; +import { Button } from './button'; +import { IconName } from '../icon'; +import { ButtonSize, ButtonVariant } from './button.types'; +import { Box } from '../box'; export default { title: 'Components/ComponentLibrary/Button', diff --git a/ui/components/component-library/button/button.test.tsx b/ui/components/component-library/button/button.test.tsx index 099158310f8f..b66511a97e60 100644 --- a/ui/components/component-library/button/button.test.tsx +++ b/ui/components/component-library/button/button.test.tsx @@ -1,9 +1,9 @@ /* eslint-disable jest/require-top-level-describe */ import { render } from '@testing-library/react'; import React from 'react'; -import { IconName } from '..'; +import { IconName } from '../icon'; import { Button } from './button'; -import { ButtonSize, ButtonVariant } from '.'; +import { ButtonSize, ButtonVariant } from './button.types'; describe('Button', () => { it('should render button element correctly', () => { diff --git a/ui/components/component-library/checkbox/checkbox.stories.tsx b/ui/components/component-library/checkbox/checkbox.stories.tsx index 6ee20f4f286c..c344d6195f28 100644 --- a/ui/components/component-library/checkbox/checkbox.stories.tsx +++ b/ui/components/component-library/checkbox/checkbox.stories.tsx @@ -2,7 +2,6 @@ import { StoryFn, Meta } from '@storybook/react'; import { useArgs } from '@storybook/client-api'; import React from 'react'; -import { Box } from '..'; import { BorderColor, Display, @@ -10,7 +9,8 @@ import { } from '../../../helpers/constants/design-system'; import README from './README.mdx'; -import { Checkbox } from '.'; +import { Checkbox } from './checkbox'; +import { Box } from '../box'; export default { title: 'Components/ComponentLibrary/Checkbox', diff --git a/ui/components/component-library/checkbox/checkbox.test.tsx b/ui/components/component-library/checkbox/checkbox.test.tsx index da9d62d756b2..a9fd1b6df888 100644 --- a/ui/components/component-library/checkbox/checkbox.test.tsx +++ b/ui/components/component-library/checkbox/checkbox.test.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { render, fireEvent } from '@testing-library/react'; import { BorderColor } from '../../../helpers/constants/design-system'; -import { IconName } from '..'; -import { Checkbox } from '.'; +import { IconName } from '../icon'; +import { Checkbox } from './checkbox'; describe('Checkbox', () => { it('should render the Checkbox without crashing', () => { diff --git a/ui/components/component-library/checkbox/checkbox.tsx b/ui/components/component-library/checkbox/checkbox.tsx index ff6435e5d0b3..c5739d20562b 100644 --- a/ui/components/component-library/checkbox/checkbox.tsx +++ b/ui/components/component-library/checkbox/checkbox.tsx @@ -10,10 +10,10 @@ import { AlignItems, } from '../../../helpers/constants/design-system'; -import type { PolymorphicRef, TextProps } from '..'; -import { Box, Icon, IconName, Text } from '..'; - -import { CheckboxProps, CheckboxComponent } from './checkbox.types'; +import { type PolymorphicRef, Box } from '../box'; +import { Text, type TextProps } from '../text'; +import { Icon, IconName } from '../icon'; +import type { CheckboxProps, CheckboxComponent } from './checkbox.types'; export const Checkbox: CheckboxComponent = React.forwardRef( ( diff --git a/ui/components/component-library/container/container.stories.tsx b/ui/components/component-library/container/container.stories.tsx index d6dc61512a14..75ac73409e62 100644 --- a/ui/components/component-library/container/container.stories.tsx +++ b/ui/components/component-library/container/container.stories.tsx @@ -9,7 +9,7 @@ import { import README from './README.mdx'; import { ContainerMaxWidth } from './container.types'; -import { Container } from '.'; +import { Container } from './container'; export default { title: 'Components/ComponentLibrary/Container', diff --git a/ui/components/component-library/container/container.test.tsx b/ui/components/component-library/container/container.test.tsx index 8cac49dec5c2..96ad42379bd7 100644 --- a/ui/components/component-library/container/container.test.tsx +++ b/ui/components/component-library/container/container.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { render } from '@testing-library/react'; import { ContainerMaxWidth } from './container.types'; -import { Container } from '.'; +import { Container } from './container'; describe('Container', () => { it('should render the Container without crashing', () => { diff --git a/ui/components/component-library/container/container.tsx b/ui/components/component-library/container/container.tsx index 17ea5eef23c9..40d131020327 100644 --- a/ui/components/component-library/container/container.tsx +++ b/ui/components/component-library/container/container.tsx @@ -1,7 +1,7 @@ import React from 'react'; import classnames from 'classnames'; import type { PolymorphicRef, BoxProps } from '../box'; -import { Box } from '..'; +import { Box } from '../box'; import { ContainerProps, ContainerComponent } from './container.types'; diff --git a/ui/components/component-library/form-text-field/form-text-field.stories.tsx b/ui/components/component-library/form-text-field/form-text-field.stories.tsx index 746d8acc5b01..a423f9c3e688 100644 --- a/ui/components/component-library/form-text-field/form-text-field.stories.tsx +++ b/ui/components/component-library/form-text-field/form-text-field.stories.tsx @@ -11,23 +11,18 @@ import { IconColor, } from '../../../helpers/constants/design-system'; -import { - Box, - ButtonLink, - ButtonPrimary, - ButtonSecondary, - HelpText, - Label, - Text, - TextFieldType, - Icon, - IconName, - IconSize, -} from '..'; - import { FormTextField } from './form-text-field'; import README from './README.mdx'; +import { Text } from '../text'; +import { Box } from '../box'; +import { ButtonSecondary } from '../button-secondary'; +import { ButtonPrimary } from '../button-primary'; +import { Icon, IconName, IconSize } from '../icon'; +import { Label } from '../label'; +import { ButtonLink } from '../button-link'; +import { HelpText } from '../help-text'; +import { TextFieldType } from '../text-field'; export default { title: 'Components/ComponentLibrary/FormTextField', diff --git a/ui/components/component-library/form-text-field/form-text-field.test.tsx b/ui/components/component-library/form-text-field/form-text-field.test.tsx index 63b0e1c06f2e..30caab71567d 100644 --- a/ui/components/component-library/form-text-field/form-text-field.test.tsx +++ b/ui/components/component-library/form-text-field/form-text-field.test.tsx @@ -5,7 +5,8 @@ import React from 'react'; import { fireEvent, render } from '@testing-library/react'; import { renderWithUserEvent } from '../../../../test/lib/render-helpers'; -import { FormTextField, FormTextFieldSize } from '.'; +import { FormTextField } from './form-text-field'; +import { FormTextFieldSize } from './form-text-field.types'; describe('FormTextField', () => { it('should render correctly', () => { diff --git a/ui/components/component-library/form-text-field/form-text-field.tsx b/ui/components/component-library/form-text-field/form-text-field.tsx index 9ee7b534a179..829505e27f7e 100644 --- a/ui/components/component-library/form-text-field/form-text-field.tsx +++ b/ui/components/component-library/form-text-field/form-text-field.tsx @@ -4,17 +4,12 @@ import { Display, FlexDirection, } from '../../../helpers/constants/design-system'; -import { - Box, - TextField, - HelpText, - HelpTextSeverity, - Label, - TextFieldSize, -} from '..'; -import { PolymorphicRef } from '../box'; +import { Box, PolymorphicRef } from '../box'; import type { BoxProps } from '../box'; -import { TextFieldProps } from '../text-field/text-field.types'; +import { TextFieldProps, TextFieldSize } from '../text-field/text-field.types'; +import { Label } from '../label'; +import { TextField } from '../text-field'; +import { HelpText, HelpTextSeverity } from '../help-text'; import { FormTextFieldSize, FormTextFieldProps, diff --git a/ui/components/component-library/header-base/header-base.stories.tsx b/ui/components/component-library/header-base/header-base.stories.tsx index 409a576f2df5..2edcd96d4c78 100644 --- a/ui/components/component-library/header-base/header-base.stories.tsx +++ b/ui/components/component-library/header-base/header-base.stories.tsx @@ -1,14 +1,6 @@ import React from 'react'; import { StoryFn, Meta } from '@storybook/react'; -import { - IconName, - Button, - ButtonSize, - ButtonIcon, - ButtonIconSize, - Text, - Box, -} from '..'; +import { Text } from '../text'; import { AlignItems, @@ -18,6 +10,10 @@ import { } from '../../../helpers/constants/design-system'; import { HeaderBase } from './header-base'; import README from './README.mdx'; +import { ButtonIcon, ButtonIconSize } from '../button-icon'; +import { IconName } from '../icon'; +import { Box } from '../box'; +import { Button, ButtonSize } from '../button'; export default { title: 'Components/ComponentLibrary/HeaderBase', diff --git a/ui/components/component-library/header-base/header-base.test.tsx b/ui/components/component-library/header-base/header-base.test.tsx index 00171ab95c11..61b6483bdbd5 100644 --- a/ui/components/component-library/header-base/header-base.test.tsx +++ b/ui/components/component-library/header-base/header-base.test.tsx @@ -1,7 +1,7 @@ /* eslint-disable jest/require-top-level-describe */ import { render } from '@testing-library/react'; import React from 'react'; -import { Icon, IconName } from '..'; +import { Icon, IconName } from '../icon'; import { HeaderBase } from './header-base'; describe('HeaderBase', () => { diff --git a/ui/components/component-library/header-base/header-base.tsx b/ui/components/component-library/header-base/header-base.tsx index d5b00ab362aa..669c21410971 100644 --- a/ui/components/component-library/header-base/header-base.tsx +++ b/ui/components/component-library/header-base/header-base.tsx @@ -4,7 +4,7 @@ import { Display, JustifyContent, } from '../../../helpers/constants/design-system'; -import { Box } from '..'; +import { Box } from '../box'; import type { PolymorphicRef, BoxProps } from '../box'; diff --git a/ui/components/component-library/help-text/README.mdx b/ui/components/component-library/help-text/README.mdx index 1e8c4e2d6a2a..3e6f0cb86bee 100644 --- a/ui/components/component-library/help-text/README.mdx +++ b/ui/components/component-library/help-text/README.mdx @@ -1,6 +1,6 @@ import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; -import { Text } from '..'; +import { Text } from '../text'; import { HelpText } from './help-text'; diff --git a/ui/components/component-library/help-text/help-text.stories.tsx b/ui/components/component-library/help-text/help-text.stories.tsx index da14d225a4d1..df49051b7de9 100644 --- a/ui/components/component-library/help-text/help-text.stories.tsx +++ b/ui/components/component-library/help-text/help-text.stories.tsx @@ -7,12 +7,12 @@ import { TextColor, } from '../../../helpers/constants/design-system'; -import { Box, Icon, IconName, IconSize } from '..'; - import { HelpText } from './help-text'; import { HelpTextSeverity } from './help-text.types'; import README from './README.mdx'; +import { Box } from '../box'; +import { Icon, IconName, IconSize } from '../icon'; export default { title: 'Components/ComponentLibrary/HelpText', diff --git a/ui/components/component-library/help-text/help-text.test.tsx b/ui/components/component-library/help-text/help-text.test.tsx index 29cd2e46a463..39a6084b2032 100644 --- a/ui/components/component-library/help-text/help-text.test.tsx +++ b/ui/components/component-library/help-text/help-text.test.tsx @@ -2,8 +2,9 @@ import { render } from '@testing-library/react'; import React from 'react'; import { TextColor } from '../../../helpers/constants/design-system'; -import { Icon, IconName } from '..'; -import { HelpText, HelpTextSeverity } from '.'; +import { Icon, IconName } from '../icon'; +import { HelpText } from './help-text'; +import { HelpTextSeverity } from './help-text.types'; describe('HelpText', () => { it('should render with text inside the HelpText', () => { diff --git a/ui/components/component-library/help-text/help-text.tsx b/ui/components/component-library/help-text/help-text.tsx index 4be242562b26..c181680e7acd 100644 --- a/ui/components/component-library/help-text/help-text.tsx +++ b/ui/components/component-library/help-text/help-text.tsx @@ -7,8 +7,11 @@ import { import { Text } from '../text'; import type { PolymorphicRef } from '../box'; import type { TextProps } from '../text'; -import type { HelpTextProps, HelpTextComponent } from './help-text.types'; -import { HelpTextSeverity } from '.'; +import { + type HelpTextProps, + type HelpTextComponent, + HelpTextSeverity, +} from './help-text.types'; export const HelpText: HelpTextComponent = forwardRef( ( diff --git a/ui/components/component-library/icon/icon.stories.tsx b/ui/components/component-library/icon/icon.stories.tsx index 023b8a1dbf96..48fe9c66bd9e 100644 --- a/ui/components/component-library/icon/icon.stories.tsx +++ b/ui/components/component-library/icon/icon.stories.tsx @@ -17,22 +17,17 @@ import { BorderRadius, } from '../../../helpers/constants/design-system'; -import { - ButtonIcon, - ButtonLink, - ButtonLinkSize, - Label, - Text, - TextField, - TextFieldSize, - TextFieldSearch, - ButtonIconSize, - Box, -} from '..'; +import { Text } from '../text'; import { Icon } from './icon'; import { IconName, IconSize } from './icon.types'; import README from './README.mdx'; +import { Box } from '../box'; +import { Label } from '../label'; +import { TextFieldSearch } from '../text-field-search'; +import { TextField, TextFieldSize } from '../text-field'; +import { ButtonIcon, ButtonIconSize } from '../button-icon'; +import { ButtonLink, ButtonLinkSize } from '../button-link'; export default { title: 'Components/ComponentLibrary/Icon', diff --git a/ui/components/component-library/input/input.stories.tsx b/ui/components/component-library/input/input.stories.tsx index ab4476f3aec0..370a140a96f7 100644 --- a/ui/components/component-library/input/input.stories.tsx +++ b/ui/components/component-library/input/input.stories.tsx @@ -8,12 +8,12 @@ import { TextVariant, } from '../../../helpers/constants/design-system'; -import { Button, Box, ButtonVariant } from '..'; - import { InputType } from './input.types'; import { Input } from './input'; import README from './README.mdx'; +import { Box } from '../box'; +import { Button, ButtonVariant } from '../button'; const marginSizeControlOptions = [ undefined, diff --git a/ui/components/component-library/label/README.mdx b/ui/components/component-library/label/README.mdx index ef32640eed48..6962ef2c5dfe 100644 --- a/ui/components/component-library/label/README.mdx +++ b/ui/components/component-library/label/README.mdx @@ -1,6 +1,6 @@ import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; -import { Text } from '..'; +import { Text } from '../text'; import { Label } from './label'; diff --git a/ui/components/component-library/label/label.stories.tsx b/ui/components/component-library/label/label.stories.tsx index 2a5b157b7197..e1408ba8e4ef 100644 --- a/ui/components/component-library/label/label.stories.tsx +++ b/ui/components/component-library/label/label.stories.tsx @@ -7,10 +7,12 @@ import { IconColor, } from '../../../helpers/constants/design-system'; -import { Box, Icon, IconName, IconSize, TextField } from '..'; import { Label } from './label'; import README from './README.mdx'; +import { Icon, IconName, IconSize } from '../icon'; +import { Box } from '../box'; +import { TextField } from '../text-field'; export default { title: 'Components/ComponentLibrary/Label', diff --git a/ui/components/component-library/label/label.test.tsx b/ui/components/component-library/label/label.test.tsx index b2f3a4e222bd..fc5e7db52a0b 100644 --- a/ui/components/component-library/label/label.test.tsx +++ b/ui/components/component-library/label/label.test.tsx @@ -1,8 +1,9 @@ /* eslint-disable jest/require-top-level-describe */ import { fireEvent, render } from '@testing-library/react'; import React from 'react'; -import { Icon, IconName, TextField } from '..'; +import { Icon, IconName } from '../icon'; +import { TextField } from '../text-field'; import { Label } from './label'; describe('label', () => { diff --git a/ui/components/component-library/modal-body/modal-body.stories.tsx b/ui/components/component-library/modal-body/modal-body.stories.tsx index c958d0860fd4..4bf80240edb1 100644 --- a/ui/components/component-library/modal-body/modal-body.stories.tsx +++ b/ui/components/component-library/modal-body/modal-body.stories.tsx @@ -5,7 +5,7 @@ import { Display, FlexDirection, } from '../../../helpers/constants/design-system'; -import { Text } from '..'; +import { Text } from '../text'; import README from './README.mdx'; import { ModalBody } from './modal-body'; diff --git a/ui/components/component-library/modal-body/modal-body.tsx b/ui/components/component-library/modal-body/modal-body.tsx index c658b6cb7d6a..e45832adb74e 100644 --- a/ui/components/component-library/modal-body/modal-body.tsx +++ b/ui/components/component-library/modal-body/modal-body.tsx @@ -1,7 +1,7 @@ import React from 'react'; import classnames from 'classnames'; -import { Box } from '..'; +import { Box } from '../box'; import type { PolymorphicRef, BoxProps } from '../box'; import { ModalBodyProps, ModalBodyComponent } from './modal-body.types'; diff --git a/ui/components/component-library/modal-content/deprecated/modal-content.tsx b/ui/components/component-library/modal-content/deprecated/modal-content.tsx index dda7dc7d681e..a3e39128011e 100644 --- a/ui/components/component-library/modal-content/deprecated/modal-content.tsx +++ b/ui/components/component-library/modal-content/deprecated/modal-content.tsx @@ -10,15 +10,15 @@ import { AlignItems, } from '../../../../helpers/constants/design-system'; -import { Box, ModalFocus, useModalContext } from '../..'; - -import { BoxProps } from '../../box'; +import { Box, BoxProps } from '../../box'; import type { PolymorphicRef } from '../../box'; import { ModalContentProps, ModalContentSize, ModalContentComponent, } from '../modal-content.types'; +import { useModalContext } from '../../modal/modal.context'; +import { ModalFocus } from '../../modal-focus'; /** * @deprecated This version of `ModalContent` is deprecated. Please use the version from the component-library in ui/components/component-library/modal-content/modal-content.tsx diff --git a/ui/components/component-library/modal-content/modal-content.scss b/ui/components/component-library/modal-content/modal-content.scss index f08dc1c730f9..6ecc79083da5 100644 --- a/ui/components/component-library/modal-content/modal-content.scss +++ b/ui/components/component-library/modal-content/modal-content.scss @@ -35,6 +35,17 @@ max-height: 100%; box-shadow: var(--shadow-size-lg) var(--color-shadow-default); + // Animate for users who have no reduced motion preferences + @media (prefers-reduced-motion: no-preference) { + animation: modal-dialog-slide-up 400ms cubic-bezier(0.3, 0.8, 0.3, 1) forwards; + } + + // Don't animate for users who have reduced motion preferences + @media (prefers-reduced-motion: reduce) { + opacity: 1; + transform: translateY(0); + } + &--size-sm { --size: 360px; @@ -54,3 +65,17 @@ } } } + +@media (prefers-reduced-motion: no-preference) { + @keyframes modal-dialog-slide-up { + from { + transform: translateY(24px); + opacity: 0; + } + + to { + transform: translateY(0); + opacity: 1; + } + } +} diff --git a/ui/components/component-library/modal-content/modal-content.stories.tsx b/ui/components/component-library/modal-content/modal-content.stories.tsx index 90eab15c3aae..4dfb10455e86 100644 --- a/ui/components/component-library/modal-content/modal-content.stories.tsx +++ b/ui/components/component-library/modal-content/modal-content.stories.tsx @@ -3,21 +3,18 @@ import { StoryFn, Meta } from '@storybook/react'; import { Display, FlexWrap } from '../../../helpers/constants/design-system'; -import { - Box, - ButtonVariant, - Button, - Text, - Modal, - ModalHeader, - ModalBody, - ModalFooter, -} from '..'; +import { Text } from '../text'; import { ModalContent } from './modal-content'; import { ModalContentSize } from './modal-content.types'; import README from './README.mdx'; +import { Button, ButtonVariant } from '../button'; +import { Modal } from '../modal'; +import { ModalHeader } from '../modal-header'; +import { ModalBody } from '../modal-body'; +import { ModalFooter } from '../modal-footer'; +import { Box } from '../box'; export default { title: 'Components/ComponentLibrary/ModalContent', diff --git a/ui/components/component-library/modal-content/modal-content.test.tsx b/ui/components/component-library/modal-content/modal-content.test.tsx index 99cadb63a81f..7197d7ae7cac 100644 --- a/ui/components/component-library/modal-content/modal-content.test.tsx +++ b/ui/components/component-library/modal-content/modal-content.test.tsx @@ -1,7 +1,7 @@ /* eslint-disable jest/require-top-level-describe */ import { render, fireEvent } from '@testing-library/react'; import React from 'react'; -import { Modal } from '..'; +import { Modal } from '../modal'; import { ModalContent } from './modal-content'; import { ModalContentSize } from './modal-content.types'; diff --git a/ui/components/component-library/modal-content/modal-content.tsx b/ui/components/component-library/modal-content/modal-content.tsx index 39f25b084cf8..dc59ea7aa2f9 100644 --- a/ui/components/component-library/modal-content/modal-content.tsx +++ b/ui/components/component-library/modal-content/modal-content.tsx @@ -11,10 +11,10 @@ import { FlexDirection, } from '../../../helpers/constants/design-system'; -import { Box, ModalFocus, useModalContext } from '..'; - -import { BoxProps } from '../box'; +import { Box, BoxProps } from '../box'; import type { PolymorphicRef } from '../box'; +import { useModalContext } from '../modal/modal.context'; +import { ModalFocus } from '../modal-focus'; import { ModalContentProps, ModalContentSize, diff --git a/ui/components/component-library/modal-footer/modal-footer.stories.tsx b/ui/components/component-library/modal-footer/modal-footer.stories.tsx index 4b7f43189658..b2aefc730b6f 100644 --- a/ui/components/component-library/modal-footer/modal-footer.stories.tsx +++ b/ui/components/component-library/modal-footer/modal-footer.stories.tsx @@ -1,7 +1,6 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; -import { Box, Container, Checkbox, ContainerMaxWidth } from '..'; import { BackgroundColor, Display, @@ -10,6 +9,9 @@ import { import { ModalFooter } from './modal-footer'; import README from './README.mdx'; +import { Box } from '../box'; +import { Container, ContainerMaxWidth } from '../container'; +import { Checkbox } from '../checkbox'; const meta: Meta = { title: 'Components/ComponentLibrary/ModalFooter', diff --git a/ui/components/component-library/modal-footer/modal-footer.tsx b/ui/components/component-library/modal-footer/modal-footer.tsx index 053d69878f72..64e830e5e2c4 100644 --- a/ui/components/component-library/modal-footer/modal-footer.tsx +++ b/ui/components/component-library/modal-footer/modal-footer.tsx @@ -6,16 +6,9 @@ import { Display, FlexWrap, } from '../../../helpers/constants/design-system'; -import type { PolymorphicRef, BoxProps } from '../box'; -import type { ButtonProps } from '../button'; -import { - Box, - Button, - ButtonSize, - ButtonVariant, - Container, - ContainerMaxWidth, -} from '..'; +import { type PolymorphicRef, type BoxProps, Box } from '../box'; +import { Button, ButtonSize, ButtonVariant, type ButtonProps } from '../button'; +import { Container, ContainerMaxWidth } from '../container'; import { ModalFooterProps, ModalFooterComponent } from './modal-footer.types'; export const ModalFooter: ModalFooterComponent = React.forwardRef( diff --git a/ui/components/component-library/modal-header/deprecated/modal-header.tsx b/ui/components/component-library/modal-header/deprecated/modal-header.tsx index 612cac792c43..c91c498c45e4 100644 --- a/ui/components/component-library/modal-header/deprecated/modal-header.tsx +++ b/ui/components/component-library/modal-header/deprecated/modal-header.tsx @@ -1,12 +1,15 @@ import React from 'react'; import classnames from 'classnames'; -import { HeaderBase, Text, ButtonIcon, ButtonIconSize, IconName } from '../..'; +import { Text } from '../../text'; import { TextVariant, TextAlign, } from '../../../../helpers/constants/design-system'; import { useI18nContext } from '../../../../hooks/useI18nContext'; -import { ModalHeaderProps } from '.'; +import { ModalHeaderProps } from '../modal-header.types'; +import { HeaderBase } from '../../header-base'; +import { ButtonIcon, ButtonIconSize } from '../../button-icon'; +import { IconName } from '../../icon'; /** * @deprecated This version of `ModalHeader` is deprecated. Please use the version from the component-library in ui/components/component-library/modal-header/modal-header.tsx diff --git a/ui/components/component-library/modal-header/modal-header.stories.tsx b/ui/components/component-library/modal-header/modal-header.stories.tsx index 676cf7d09172..b8b165947b8e 100644 --- a/ui/components/component-library/modal-header/modal-header.stories.tsx +++ b/ui/components/component-library/modal-header/modal-header.stories.tsx @@ -10,10 +10,11 @@ import { JustifyContent, } from '../../../helpers/constants/design-system'; -import { AvatarAccount, ButtonSize, Button, Text } from '..'; - import { ModalHeader } from './modal-header'; +import { Text } from '../text'; import README from './README.mdx'; +import { AvatarAccount } from '../avatar-account'; +import { Button, ButtonSize } from '../button'; export default { title: 'Components/ComponentLibrary/ModalHeader', diff --git a/ui/components/component-library/modal-header/modal-header.test.tsx b/ui/components/component-library/modal-header/modal-header.test.tsx index a6fb8bfd08a2..c2e276cecb2d 100644 --- a/ui/components/component-library/modal-header/modal-header.test.tsx +++ b/ui/components/component-library/modal-header/modal-header.test.tsx @@ -1,7 +1,7 @@ /* eslint-disable jest/require-top-level-describe */ import { render, fireEvent } from '@testing-library/react'; import React from 'react'; -import { IconName } from '..'; +import { IconName } from '../icon'; import { ModalHeader } from './modal-header'; describe('ModalHeader', () => { diff --git a/ui/components/component-library/modal-header/modal-header.tsx b/ui/components/component-library/modal-header/modal-header.tsx index 0e3bbbdefcdc..506969c739cc 100644 --- a/ui/components/component-library/modal-header/modal-header.tsx +++ b/ui/components/component-library/modal-header/modal-header.tsx @@ -1,13 +1,16 @@ import React from 'react'; import classnames from 'classnames'; -import { HeaderBase, Text, ButtonIcon, ButtonIconSize, IconName } from '..'; +import { Text } from '../text'; import { TextVariant, TextAlign, BlockSize, } from '../../../helpers/constants/design-system'; import { useI18nContext } from '../../../hooks/useI18nContext'; -import { ModalHeaderProps } from '.'; +import { ButtonIcon, ButtonIconSize } from '../button-icon'; +import { IconName } from '../icon'; +import { HeaderBase } from '../header-base'; +import { ModalHeaderProps } from './modal-header.types'; export const ModalHeader: React.FC = ({ children, diff --git a/ui/components/component-library/modal-overlay/modal-overlay.scss b/ui/components/component-library/modal-overlay/modal-overlay.scss index 33d2ea7f9fdc..f8545983a4c8 100644 --- a/ui/components/component-library/modal-overlay/modal-overlay.scss +++ b/ui/components/component-library/modal-overlay/modal-overlay.scss @@ -7,4 +7,22 @@ right: 0; bottom: 0; z-index: design-system.$modal-z-index; + opacity: 1; + + // Don't animate for users who have reduced motion preferences + @media (prefers-reduced-motion: no-preference) { + animation: modal-overlay-fade-in 250ms linear forwards; + } +} + +@media (prefers-reduced-motion: no-preference) { + @keyframes modal-overlay-fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } + } } diff --git a/ui/components/component-library/modal/modal.stories.tsx b/ui/components/component-library/modal/modal.stories.tsx index 5e33799393c5..a7592e08d3ae 100644 --- a/ui/components/component-library/modal/modal.stories.tsx +++ b/ui/components/component-library/modal/modal.stories.tsx @@ -4,23 +4,20 @@ import { StoryFn, Meta } from '@storybook/react'; import { BlockSize, Display } from '../../../helpers/constants/design-system'; -import { - ModalOverlay, - ModalContent, - ModalHeader, - ModalBody, - ModalFooter, - Text, - Button, - ButtonLink, - ButtonLinkSize, - TextFieldSearch, - IconName, - Box, -} from '..'; +import { Text } from '../text'; import { Modal } from './modal'; import README from './README.mdx'; +import { ButtonLink, ButtonLinkSize } from '../button-link'; +import { Box } from '../box'; +import { Button } from '../button'; +import { IconName } from '../icon'; +import { ModalOverlay } from '../modal-overlay'; +import { ModalContent } from '../modal-content'; +import { ModalHeader } from '../modal-header'; +import { ModalBody } from '../modal-body'; +import { ModalFooter } from '../modal-footer'; +import { TextFieldSearch } from '../text-field-search'; export default { title: 'Components/ComponentLibrary/Modal', diff --git a/ui/components/component-library/picker-network/picker-network.stories.tsx b/ui/components/component-library/picker-network/picker-network.stories.tsx index ea35b98f397c..5809f26946ec 100644 --- a/ui/components/component-library/picker-network/picker-network.stories.tsx +++ b/ui/components/component-library/picker-network/picker-network.stories.tsx @@ -6,7 +6,7 @@ import { BlockSize, } from '../../../helpers/constants/design-system'; -import { Box } from '..'; +import { Box } from '../box'; import README from './README.mdx'; import { PickerNetwork } from './picker-network'; import { diff --git a/ui/components/component-library/picker-network/picker-network.test.tsx b/ui/components/component-library/picker-network/picker-network.test.tsx index d05e4a52caf9..f96fd1708cde 100644 --- a/ui/components/component-library/picker-network/picker-network.test.tsx +++ b/ui/components/component-library/picker-network/picker-network.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import { IconName } from '..'; +import { IconName } from '../icon'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; import configureStore from '../../../store/store'; import { AvatarType } from '../../multichain/avatar-group/avatar-group.types'; diff --git a/ui/components/component-library/picker-network/picker-network.tsx b/ui/components/component-library/picker-network/picker-network.tsx index f92ad174beed..29f6a1c8c57c 100644 --- a/ui/components/component-library/picker-network/picker-network.tsx +++ b/ui/components/component-library/picker-network/picker-network.tsx @@ -8,17 +8,11 @@ import { BackgroundColor, Display, } from '../../../helpers/constants/design-system'; -import { - AvatarNetwork, - AvatarNetworkSize, - Box, - IconName, - Icon, - IconSize, - Text, -} from '..'; -import { BoxProps, PolymorphicRef } from '../box'; +import { Text } from '../text'; +import { Box, BoxProps, PolymorphicRef } from '../box'; import { AvatarGroup } from '../../multichain/avatar-group'; +import { AvatarNetwork, AvatarNetworkSize } from '../avatar-network'; +import { Icon, IconName, IconSize } from '../icon'; import { PickerNetworkComponent, PickerNetworkProps, diff --git a/ui/components/component-library/popover-header/popover-header.stories.tsx b/ui/components/component-library/popover-header/popover-header.stories.tsx index 9cdeaf10a1bf..eefb79be09bf 100644 --- a/ui/components/component-library/popover-header/popover-header.stories.tsx +++ b/ui/components/component-library/popover-header/popover-header.stories.tsx @@ -10,10 +10,12 @@ import { JustifyContent, } from '../../../helpers/constants/design-system'; -import { AvatarAccount, ButtonSize, Button, Text } from '..'; +import { Text } from '../text'; import { PopoverHeader } from './popover-header'; import README from './README.mdx'; +import { AvatarAccount } from '../avatar-account'; +import { Button, ButtonSize } from '../button'; export default { title: 'Components/ComponentLibrary/PopoverHeader', diff --git a/ui/components/component-library/popover-header/popover-header.test.tsx b/ui/components/component-library/popover-header/popover-header.test.tsx index 6b027543fd8e..7572efa27449 100644 --- a/ui/components/component-library/popover-header/popover-header.test.tsx +++ b/ui/components/component-library/popover-header/popover-header.test.tsx @@ -1,7 +1,7 @@ /* eslint-disable jest/require-top-level-describe */ import { render, fireEvent } from '@testing-library/react'; import React from 'react'; -import { IconName } from '..'; +import { IconName } from '../icon'; import { PopoverHeader } from './popover-header'; describe('PopoverHeader', () => { diff --git a/ui/components/component-library/popover-header/popover-header.tsx b/ui/components/component-library/popover-header/popover-header.tsx index 1bd004bef583..89ed53a60040 100644 --- a/ui/components/component-library/popover-header/popover-header.tsx +++ b/ui/components/component-library/popover-header/popover-header.tsx @@ -1,6 +1,6 @@ import React from 'react'; import classnames from 'classnames'; -import { HeaderBase, ButtonIcon, ButtonIconSize, IconName, Text } from '..'; +import { Text } from '../text'; import { IconColor, TextVariant, @@ -8,7 +8,10 @@ import { TextColor, } from '../../../helpers/constants/design-system'; import { useI18nContext } from '../../../hooks/useI18nContext'; -import { PopoverHeaderProps } from '.'; +import { HeaderBase } from '../header-base'; +import { ButtonIcon, ButtonIconSize } from '../button-icon'; +import { IconName } from '../icon'; +import type { PopoverHeaderProps } from './popover-header.types'; export const PopoverHeader: React.FC = ({ children, diff --git a/ui/components/component-library/popover/popover.stories.tsx b/ui/components/component-library/popover/popover.stories.tsx index caaf343ecf8e..58a35e9afc6c 100644 --- a/ui/components/component-library/popover/popover.stories.tsx +++ b/ui/components/component-library/popover/popover.stories.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { StoryFn, Meta } from '@storybook/react'; -import { Box, Icon, IconName, IconSize, PopoverHeader, Text } from '..'; +import { Text } from '../text'; import { AlignItems, BackgroundColor, @@ -13,7 +13,11 @@ import { } from '../../../helpers/constants/design-system'; import README from './README.mdx'; -import { Popover, PopoverPosition, PopoverRole } from '.'; +import { Popover } from './popover'; +import { PopoverPosition, PopoverRole } from './popover.types'; +import { Box } from '../box'; +import { PopoverHeader } from '../popover-header'; +import { Icon, IconName, IconSize } from '../icon'; export default { title: 'Components/ComponentLibrary/Popover', diff --git a/ui/components/component-library/popover/popover.tsx b/ui/components/component-library/popover/popover.tsx index 253bacef4855..eeee65274fe7 100644 --- a/ui/components/component-library/popover/popover.tsx +++ b/ui/components/component-library/popover/popover.tsx @@ -11,7 +11,7 @@ import { JustifyContent, } from '../../../helpers/constants/design-system'; -import { Box } from '..'; +import { Box } from '../box'; import type { BoxProps, PolymorphicRef } from '../box'; import { diff --git a/ui/components/component-library/select-button/select-button.stories.tsx b/ui/components/component-library/select-button/select-button.stories.tsx index 5cb74487c90a..b434a8686a82 100644 --- a/ui/components/component-library/select-button/select-button.stories.tsx +++ b/ui/components/component-library/select-button/select-button.stories.tsx @@ -1,15 +1,6 @@ import { StoryFn, Meta } from '@storybook/react'; import React from 'react'; -import { - AvatarBase, - AvatarBaseSize, - AvatarAccount, - AvatarAccountSize, - Box, - SelectWrapper, - SelectOption, - Text, -} from '..'; +import { Text } from '../text'; import { Display, TextColor, @@ -18,7 +9,12 @@ import { import README from './README.mdx'; import { SelectButtonSize } from './select-button.types'; -import { SelectButton } from '.'; +import { SelectButton } from './select-button'; +import { AvatarBase, AvatarBaseSize } from '../avatar-base'; +import { SelectWrapper } from '../select-wrapper'; +import { Box } from '../box'; +import { AvatarAccount, AvatarAccountSize } from '../avatar-account'; +import { SelectOption } from '../select-option'; export default { title: 'Components/ComponentLibrary/SelectButton', diff --git a/ui/components/component-library/select-button/select-button.test.tsx b/ui/components/component-library/select-button/select-button.test.tsx index 599a7e9390e7..28c14ffbdb60 100644 --- a/ui/components/component-library/select-button/select-button.test.tsx +++ b/ui/components/component-library/select-button/select-button.test.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { render, fireEvent } from '@testing-library/react'; -import { AvatarAccount, AvatarAccountSize, SelectWrapper } from '..'; -import { SelectButton, SelectButtonSize } from '.'; +import { AvatarAccount, AvatarAccountSize } from '../avatar-account'; +import { SelectWrapper } from '../select-wrapper'; +import { SelectButton } from './select-button'; +import { SelectButtonSize } from './select-button.types'; describe('SelectButton', () => { it('renders without crashing', () => { diff --git a/ui/components/component-library/select-button/select-button.tsx b/ui/components/component-library/select-button/select-button.tsx index a4c6dac5a568..5546694f87f2 100644 --- a/ui/components/component-library/select-button/select-button.tsx +++ b/ui/components/component-library/select-button/select-button.tsx @@ -1,8 +1,8 @@ import React, { useContext } from 'react'; import classnames from 'classnames'; import { SelectContext } from '../select-wrapper'; -import type { PolymorphicRef } from '../box'; -import { Box, Icon, IconName, IconSize, Label, Text } from '..'; +import { Box, type PolymorphicRef } from '../box'; +import { Text } from '../text'; import type { TextProps } from '../text'; import { AlignItems, @@ -16,6 +16,8 @@ import { TextColor, TextVariant, } from '../../../helpers/constants/design-system'; +import { Label } from '../label'; +import { Icon, IconName, IconSize } from '../icon'; import { SelectButtonProps, SelectButtonComponent, diff --git a/ui/components/component-library/select-option/select-option.tsx b/ui/components/component-library/select-option/select-option.tsx index 3df4e741f996..a1ebeba44b5f 100644 --- a/ui/components/component-library/select-option/select-option.tsx +++ b/ui/components/component-library/select-option/select-option.tsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react'; import classnames from 'classnames'; import type { PolymorphicRef, BoxProps } from '../box'; -import { Box } from '..'; +import { Box } from '../box'; import { SelectContext } from '../select-wrapper'; import { Display } from '../../../helpers/constants/design-system'; import { diff --git a/ui/components/component-library/select-wrapper/select-wrapper.stories.tsx b/ui/components/component-library/select-wrapper/select-wrapper.stories.tsx index af07e7f1249a..6e836d00a7f1 100644 --- a/ui/components/component-library/select-wrapper/select-wrapper.stories.tsx +++ b/ui/components/component-library/select-wrapper/select-wrapper.stories.tsx @@ -7,8 +7,8 @@ import { Button } from '../button'; import { Text } from '../text'; import { BackgroundColor } from '../../../helpers/constants/design-system'; import README from './README.mdx'; - -import { SelectWrapper, useSelectContext } from '.'; +import { SelectWrapper } from './select-wrapper'; +import { useSelectContext } from './select-wrapper.context'; export default { title: 'Components/ComponentLibrary/SelectWrapper', diff --git a/ui/components/component-library/select-wrapper/select-wrapper.test.tsx b/ui/components/component-library/select-wrapper/select-wrapper.test.tsx index a90566206e6f..f97f0fb8cf13 100644 --- a/ui/components/component-library/select-wrapper/select-wrapper.test.tsx +++ b/ui/components/component-library/select-wrapper/select-wrapper.test.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { render, fireEvent, act } from '@testing-library/react'; -import { Button, type ButtonProps } from '..'; +import { Button, type ButtonProps } from '../button'; import { SelectButton } from '../select-button'; import { SelectOption } from '../select-option'; -import { SelectWrapper, useSelectContext } from '.'; +import { SelectWrapper } from './select-wrapper'; +import { useSelectContext } from './select-wrapper.context'; describe('SelectWrapper', () => { it('should render the SelectWrapper without crashing', () => { diff --git a/ui/components/component-library/select-wrapper/select-wrapper.tsx b/ui/components/component-library/select-wrapper/select-wrapper.tsx index 054cdd032c69..43211c438866 100644 --- a/ui/components/component-library/select-wrapper/select-wrapper.tsx +++ b/ui/components/component-library/select-wrapper/select-wrapper.tsx @@ -1,7 +1,7 @@ import React, { useState, useRef } from 'react'; import classnames from 'classnames'; -import type { PolymorphicRef, BoxProps } from '../box'; -import { Box, Popover, PopoverPosition } from '..'; +import { Box, type PolymorphicRef, BoxProps } from '../box'; +import { Popover, PopoverPosition } from '../popover'; import { SelectWrapperComponent, SelectWrapperProps, diff --git a/ui/components/component-library/sensitive-text/sensitive-text.stories.tsx b/ui/components/component-library/sensitive-text/sensitive-text.stories.tsx index 142def9118b5..4f1e1a2936c3 100644 --- a/ui/components/component-library/sensitive-text/sensitive-text.stories.tsx +++ b/ui/components/component-library/sensitive-text/sensitive-text.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react'; import React from 'react'; -import { SensitiveText } from '.'; +import { SensitiveText } from './sensitive-text'; import { SensitiveTextLength } from './sensitive-text.types'; import README from './README.mdx'; import { Box } from '../box'; @@ -34,18 +34,14 @@ export const Children: Story = { args: { children: 'Sensitive information', }, - render: (args) => ( - - ), + render: (args) => , }; export const IsHidden: Story = { args: { isHidden: true, }, - render: (args) => ( - - ), + render: (args) => , }; export const Length: Story = { diff --git a/ui/components/component-library/skeleton/skeleton.stories.tsx b/ui/components/component-library/skeleton/skeleton.stories.tsx index 874c675e725b..2f7e0a38cda9 100644 --- a/ui/components/component-library/skeleton/skeleton.stories.tsx +++ b/ui/components/component-library/skeleton/skeleton.stories.tsx @@ -9,9 +9,11 @@ import { TextVariant, AlignItems, } from '../../../helpers/constants/design-system'; -import { Box, Button, ButtonVariant, Text } from '..'; +import { Text } from '../text'; import README from './README.mdx'; import { Skeleton } from './skeleton'; +import { Box } from '../box'; +import { Button, ButtonVariant } from '../button'; const meta: Meta = { title: 'Components/ComponentLibrary/Skeleton', diff --git a/ui/components/component-library/skeleton/skeleton.tsx b/ui/components/component-library/skeleton/skeleton.tsx index 760083a0c4ca..9c82945858ac 100644 --- a/ui/components/component-library/skeleton/skeleton.tsx +++ b/ui/components/component-library/skeleton/skeleton.tsx @@ -5,7 +5,7 @@ import { BackgroundColor, BorderRadius, } from '../../../helpers/constants/design-system'; -import { Box } from '..'; +import { Box } from '../box'; import type { PolymorphicRef, BoxProps } from '../box'; import { SkeletonProps, SkeletonComponent } from './skeleton.types'; diff --git a/ui/components/component-library/tag-url/tag-url.stories.tsx b/ui/components/component-library/tag-url/tag-url.stories.tsx index aa81632818c0..a33698a24bcc 100644 --- a/ui/components/component-library/tag-url/tag-url.stories.tsx +++ b/ui/components/component-library/tag-url/tag-url.stories.tsx @@ -4,7 +4,7 @@ import { Display, FlexDirection, } from '../../../helpers/constants/design-system'; -import { Box } from '..'; +import { Box } from '../box'; import README from './README.mdx'; import { TagUrl } from './tag-url'; diff --git a/ui/components/component-library/tag-url/tag-url.tsx b/ui/components/component-library/tag-url/tag-url.tsx index 15e3d3dbcef6..89555a3f461f 100644 --- a/ui/components/component-library/tag-url/tag-url.tsx +++ b/ui/components/component-library/tag-url/tag-url.tsx @@ -9,17 +9,11 @@ import { IconColor, TextVariant, } from '../../../helpers/constants/design-system'; -import { - AvatarFavicon, - ButtonLink, - Box, - IconName, - Icon, - IconSize, - Text, - ButtonLinkSize, -} from '..'; -import { BoxProps, PolymorphicRef } from '../box'; +import { Text } from '../text'; +import { Box, BoxProps, PolymorphicRef } from '../box'; +import { ButtonLink, ButtonLinkSize } from '../button-link'; +import { AvatarFavicon } from '../avatar-favicon'; +import { Icon, IconName, IconSize } from '../icon'; import { TagUrlComponent, TagUrlProps } from './tag-url.types'; export const TagUrl: TagUrlComponent = React.forwardRef( diff --git a/ui/components/component-library/tag/tag.tsx b/ui/components/component-library/tag/tag.tsx index 14548f969438..e2ffd73d7df9 100644 --- a/ui/components/component-library/tag/tag.tsx +++ b/ui/components/component-library/tag/tag.tsx @@ -1,7 +1,7 @@ import React from 'react'; import classnames from 'classnames'; -import { Box, Icon, IconSize, Text } from '..'; -import type { BoxProps, PolymorphicRef } from '../box'; +import { Text } from '../text'; +import { Box, type BoxProps, type PolymorphicRef } from '../box'; import { AlignItems, @@ -12,6 +12,7 @@ import { TextVariant, } from '../../../helpers/constants/design-system'; +import { Icon, IconSize } from '../icon'; import { TagComponent, TagProps } from './tag.types'; export const Tag: TagComponent = React.forwardRef( diff --git a/ui/components/component-library/text-field-search/README.mdx b/ui/components/component-library/text-field-search/README.mdx index 24bd69e036a3..6d8e0c9154cc 100644 --- a/ui/components/component-library/text-field-search/README.mdx +++ b/ui/components/component-library/text-field-search/README.mdx @@ -1,6 +1,6 @@ import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; -import { TextField } from '..'; +import { TextField } from '../text-field'; import { TextFieldSearch } from './text-field-search'; diff --git a/ui/components/component-library/text-field-search/text-field-search.tsx b/ui/components/component-library/text-field-search/text-field-search.tsx index 736f30a55859..a63b989164a0 100644 --- a/ui/components/component-library/text-field-search/text-field-search.tsx +++ b/ui/components/component-library/text-field-search/text-field-search.tsx @@ -1,17 +1,11 @@ import React from 'react'; import classnames from 'classnames'; -import { - ButtonIcon, - ButtonIconSize, - Icon, - IconName, - IconSize, - TextField, - TextFieldType, -} from '..'; import { useI18nContext } from '../../../hooks/useI18nContext'; -import { TextFieldProps } from '../text-field/text-field.types'; +import { TextFieldProps, TextFieldType } from '../text-field/text-field.types'; import { PolymorphicRef } from '../box'; +import { TextField } from '../text-field'; +import { ButtonIcon, ButtonIconSize } from '../button-icon'; +import { Icon, IconName, IconSize } from '../icon'; import { TextFieldSearchProps, TextFieldSearchComponent, diff --git a/ui/components/component-library/text-field/text-field.stories.tsx b/ui/components/component-library/text-field/text-field.stories.tsx index 4de3e956fb61..fafa90f3ecb0 100644 --- a/ui/components/component-library/text-field/text-field.stories.tsx +++ b/ui/components/component-library/text-field/text-field.stories.tsx @@ -12,28 +12,20 @@ import { TextColor, Size, } from '../../../helpers/constants/design-system'; +import { AvatarToken, AvatarTokenSize } from '../avatar-token'; -import { - AvatarAccount, - AvatarAccountSize, - AvatarToken, - Button, - ButtonIcon, - Box, - Text, - IconName, - Icon, - IconSize, - AvatarTokenSize, - Input, -} from '..'; +import { Text } from '../text'; -import { PolymorphicRef } from '../box'; -import { InputProps, InputComponent } from '../input'; +import { Box, PolymorphicRef } from '../box'; +import { InputProps, InputComponent, Input } from '../input'; import { TextFieldSize, TextFieldType } from './text-field.types'; import { TextField } from './text-field'; +import { ButtonIcon } from '../button-icon'; import README from './README.mdx'; +import { Icon, IconName, IconSize } from '../icon'; +import { AvatarAccount, AvatarAccountSize } from '../avatar-account'; +import { Button } from '../button'; export default { title: 'Components/ComponentLibrary/TextField', diff --git a/ui/components/component-library/text-field/text-field.tsx b/ui/components/component-library/text-field/text-field.tsx index 6b8a0f1fe53e..0ab0f95cf43a 100644 --- a/ui/components/component-library/text-field/text-field.tsx +++ b/ui/components/component-library/text-field/text-field.tsx @@ -8,10 +8,8 @@ import { BackgroundColor, } from '../../../helpers/constants/design-system'; -import { Box, Input } from '..'; - -import { BoxProps, PolymorphicRef } from '../box'; -import { InputProps } from '../input'; +import { Box, BoxProps, PolymorphicRef } from '../box'; +import { Input, InputProps } from '../input'; import { TextFieldComponent, TextFieldProps, diff --git a/ui/components/component-library/text/text.stories.tsx b/ui/components/component-library/text/text.stories.tsx index ecdc6e2f1b53..fcb8b26e475f 100644 --- a/ui/components/component-library/text/text.stories.tsx +++ b/ui/components/component-library/text/text.stories.tsx @@ -16,7 +16,7 @@ import { Color, } from '../../../helpers/constants/design-system'; -import { Box } from '..'; +import { Box } from '../box'; import README from './README.mdx'; import { Text } from './text'; diff --git a/ui/components/component-library/text/text.tsx b/ui/components/component-library/text/text.tsx index 02d5d4c89121..3b370347622a 100644 --- a/ui/components/component-library/text/text.tsx +++ b/ui/components/component-library/text/text.tsx @@ -6,8 +6,7 @@ import { TextColor, } from '../../../helpers/constants/design-system'; -import { Box } from '..'; - +import { Box } from '../box'; import type { PolymorphicRef, BoxProps } from '../box'; import { TextProps, TextComponent } from './text.types'; diff --git a/ui/components/component-library/textarea/textarea.stories.tsx b/ui/components/component-library/textarea/textarea.stories.tsx index e059fc7b5a0a..b7d78db9af0d 100644 --- a/ui/components/component-library/textarea/textarea.stories.tsx +++ b/ui/components/component-library/textarea/textarea.stories.tsx @@ -6,7 +6,7 @@ import { FlexDirection, Display, } from '../../../helpers/constants/design-system'; -import { Box } from '..'; +import { Box } from '../box'; import { TextareaResize } from './textarea.types'; import { Textarea } from './textarea'; diff --git a/ui/components/component-library/textarea/textarea.tsx b/ui/components/component-library/textarea/textarea.tsx index dca3a8025f0d..44e9cf938507 100644 --- a/ui/components/component-library/textarea/textarea.tsx +++ b/ui/components/component-library/textarea/textarea.tsx @@ -7,8 +7,7 @@ import { BorderColor, } from '../../../helpers/constants/design-system'; -import { Text } from '..'; -import { TextProps } from '../text'; +import { Text, TextProps } from '../text'; import { PolymorphicRef } from '../box'; import { diff --git a/ui/components/multichain/app-header/__snapshots__/app-header.test.js.snap b/ui/components/multichain/app-header/__snapshots__/app-header.test.js.snap index 500dc9476532..88e6842f422e 100644 --- a/ui/components/multichain/app-header/__snapshots__/app-header.test.js.snap +++ b/ui/components/multichain/app-header/__snapshots__/app-header.test.js.snap @@ -37,185 +37,16 @@ exports[`App Header locked state matches snapshot: locked 1`] = ` > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + { const history = useHistory(); + const theme = useTheme(); return ( { data-testid="app-header-logo" justifyContent={JustifyContent.center} > - history.push(DEFAULT_ROUTE)} /> + history.push(DEFAULT_ROUTE)} + theme={theme} + /> ); }; diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/Asset.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/Asset.tsx index e247409f848e..bf29d5669333 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/Asset.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/Asset.tsx @@ -11,8 +11,8 @@ import { TokenListItem } from '../../token-list-item'; import { isEqualCaseInsensitive } from '../../../../../shared/modules/string-utils'; import { formatAmount } from '../../../../pages/confirmations/components/simulation-details/formatAmount'; import { getIntlLocale } from '../../../../ducks/locale/locale'; -import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../../shared/constants/network'; import { formatCurrency } from '../../../../helpers/utils/confirm-tx.util'; +import { getImageForChainId } from '../../../../selectors/multichain'; import { AssetWithDisplayData, ERC20Asset, NativeAsset } from './types'; type AssetProps = AssetWithDisplayData & { @@ -78,11 +78,7 @@ export default function Asset({ primary={isTokenChainIdInWallet ? primaryAmountToUse : undefined} title={title} tooltipText={tooltipText} - tokenChainImage={ - CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[ - chainId as keyof typeof CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP - ] - } + tokenChainImage={getImageForChainId(chainId)} isPrimaryTokenSymbolHidden {...assetItemProps} /> diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.tsx index afbaae178f2c..5cafc73018cd 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.tsx @@ -27,7 +27,7 @@ import { import { TokenListItem } from '../..'; import LoadingScreen from '../../../ui/loading-screen'; import { useI18nContext } from '../../../../hooks/useI18nContext'; -import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../../shared/constants/network'; +import { getImageForChainId } from '../../../../selectors/multichain'; import AssetComponent from './Asset'; import { AssetWithDisplayData, ERC20Asset, NFT, NativeAsset } from './types'; @@ -157,11 +157,7 @@ export default function AssetList({ secondary={secondaryCurrencyValue} tokenImage={token.image} isPrimaryTokenSymbolHidden - tokenChainImage={ - CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[ - token.chainId as keyof typeof CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP - ] - } + tokenChainImage={getImageForChainId(token.chainId)} {...assetItemProps} /> ) : ( diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.stories.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.stories.tsx new file mode 100644 index 000000000000..489649727826 --- /dev/null +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.stories.tsx @@ -0,0 +1,121 @@ +import React, { useState } from 'react'; +import { Provider } from 'react-redux'; +import { RpcEndpointType, NetworkConfiguration } from '@metamask/network-controller'; +import { CHAIN_IDS } from '@metamask/transaction-controller'; +import configureStore from '../../../../store/store'; +import mockState from '../../../../../test/data/mock-state.json'; +import { AssetPickerModalNetwork } from './asset-picker-modal-network'; +import { Meta, StoryFn } from '@storybook/react'; +import { Button } from '../../../component-library'; + +const networks: NetworkConfiguration[] = [ + { + chainId: CHAIN_IDS.MAINNET, + nativeCurrency: 'ETH', + defaultBlockExplorerUrlIndex: 0, + blockExplorerUrls: ['https://explorerurl'], + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + networkClientId: 'test1', + url: 'https://rpcurl', + type: RpcEndpointType.Custom as const, + }, + ], + name: 'Ethereum', + }, + { + chainId: CHAIN_IDS.OPTIMISM, + nativeCurrency: 'ETH', + defaultBlockExplorerUrlIndex: 0, + blockExplorerUrls: ['https://explorerurl'], + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + networkClientId: 'test2', + url: 'https://rpcurl', + type: RpcEndpointType.Custom as const, + }, + ], + name: 'OP Mainnet', + }, + { + chainId: CHAIN_IDS.BSC, + nativeCurrency: 'BNB', + defaultBlockExplorerUrlIndex: 0, + blockExplorerUrls: ['https://explorerurl'], + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + networkClientId: 'test3', + url: 'https://rpcurl', + type: RpcEndpointType.Custom as const, + }, + ], + name: 'BNB Smart Chain', + }, +]; + +function store() { + return configureStore(mockState); +} + +const AssetPickerModalNetworkWithButton: StoryFn = (args) => { + const [isOpen, setIsOpen] = useState(false); + + return ( + + + setIsOpen(false)} + /> + + ); +}; + +const meta: Meta = { + title: 'Components/Multichain/AssetPickerModalNetwork', + component: AssetPickerModalNetwork, + render: AssetPickerModalNetworkWithButton, + args: { + onNetworkChange: () => {}, + onBack: () => {}, + }, +}; + +export default meta; +const Story = { + args: {} as Partial>, +}; +type StoryType = typeof Story & { args: Partial> }; + +export const Default: StoryType = { + args: { + networks, + }, +}; + +export const WithSelectedNetwork: StoryType = { + args: { + networks, + network: networks[0], + }, +}; + +export const WithMultiSelect: StoryType = { + args: { + networks, + isMultiselectEnabled: true, + selectedChainIds: [CHAIN_IDS.MAINNET, CHAIN_IDS.OPTIMISM], + onMultiselectSubmit: (chainIds) => console.log('Selected chains:', chainIds), + }, +}; + +export const WithCustomHeader: StoryType = { + args: { + networks, + header: 'Select Source Network', + }, +}; diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.tsx index a682c85a2cd9..c87af818700f 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.tsx @@ -26,7 +26,6 @@ import { Text, AvatarNetworkSize, } from '../../../component-library'; -import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../../shared/constants/network'; ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) import { useI18nContext } from '../../../../hooks/useI18nContext'; ///: END:ONLY_INCLUDE_IF @@ -36,6 +35,7 @@ import { getCurrentCurrency } from '../../../../ducks/metamask/metamask'; import { formatCurrency } from '../../../../helpers/utils/confirm-tx.util'; import { useMultichainBalances } from '../../../../hooks/useMultichainBalances'; import { NETWORK_TO_SHORT_NETWORK_NAME_MAP } from '../../../../../shared/constants/bridge'; +import { getImageForChainId } from '../../../../selectors/multichain'; /** * AssetPickerModalNetwork component displays a modal for selecting a network in the asset picker. @@ -244,11 +244,7 @@ export const AssetPickerModalNetwork = ({ onNetworkChange(networkConfig); onBack(); }} - iconSrc={ - CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[ - chainId as keyof typeof CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP - ] - } + iconSrc={getImageForChainId(chainId)} iconSize={AvatarNetworkSize.Sm} focus={false} disabled={shouldDisableNetwork?.(networkConfig)} diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx index b9c212da8ea0..9da156bc4a0d 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx @@ -53,15 +53,13 @@ import { useTokenTracker } from '../../../../hooks/useTokenTracker'; import { getRenderableTokenData } from '../../../../hooks/useTokensToSearch'; import { getSwapsBlockedTokens } from '../../../../ducks/send'; import { isEqualCaseInsensitive } from '../../../../../shared/modules/string-utils'; -import { - CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP, - NETWORK_TO_NAME_MAP, -} from '../../../../../shared/constants/network'; +import { NETWORK_TO_NAME_MAP } from '../../../../../shared/constants/network'; import { useMultichainBalances } from '../../../../hooks/useMultichainBalances'; import { AvatarType } from '../../avatar-group/avatar-group.types'; import { NETWORK_TO_SHORT_NETWORK_NAME_MAP } from '../../../../../shared/constants/bridge'; import { useAsyncResult } from '../../../../hooks/useAsyncResult'; import { fetchTopAssetsList } from '../../../../pages/swaps/swaps.util'; +import { getImageForChainId } from '../../../../selectors/multichain'; import { ERC20Asset, NativeAsset, @@ -412,11 +410,6 @@ export function AssetPickerModal({ selectedNetwork, ]); - const getNetworkImageUrl = (networkChainId: string) => - CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[ - networkChainId as keyof typeof CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP - ]; - const getNetworkPickerLabel = () => { if (!isMultiselectEnabled) { return ( @@ -477,7 +470,7 @@ export function AssetPickerModal({ label={getNetworkPickerLabel()} src={ selectedNetwork?.chainId - ? getNetworkImageUrl(selectedNetwork.chainId) + ? getImageForChainId(selectedNetwork.chainId) : undefined } avatarGroupProps={ @@ -485,7 +478,7 @@ export function AssetPickerModal({ ? { limit: 2, members: selectedChainIds.map((c) => ({ - avatarValue: getNetworkImageUrl(c), + avatarValue: getImageForChainId(c) ?? '', symbol: NETWORK_TO_SHORT_NETWORK_NAME_MAP[ c as keyof typeof NETWORK_TO_SHORT_NETWORK_NAME_MAP diff --git a/ui/components/multichain/asset-picker-amount/asset-picker/asset-picker.tsx b/ui/components/multichain/asset-picker-amount/asset-picker/asset-picker.tsx index a7592bbd79ce..e8da53856811 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker/asset-picker.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker/asset-picker.tsx @@ -43,11 +43,11 @@ import { import { TabName } from '../asset-picker-modal/asset-picker-modal-tabs'; import { AssetPickerModalNetwork } from '../asset-picker-modal/asset-picker-modal-network'; import { - CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP, GOERLI_DISPLAY_NAME, SEPOLIA_DISPLAY_NAME, } from '../../../../../shared/constants/network'; import { useMultichainBalances } from '../../../../hooks/useMultichainBalances'; +import { getImageForChainId } from '../../../../selectors/multichain'; const ELLIPSIFY_LENGTH = 13; // 6 (start) + 4 (end) + 3 (...) @@ -163,11 +163,9 @@ export function AssetPicker({ return undefined; }; - const networkImageSrc = - selectedNetwork?.chainId && - CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[ - selectedNetwork.chainId as keyof typeof CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP - ]; + const networkImageSrc = selectedNetwork?.chainId + ? getImageForChainId(selectedNetwork.chainId) + : undefined; const handleButtonClick = () => { if (networkProps && !networkProps.network) { diff --git a/ui/components/multichain/avatar-group/avatar-group.tsx b/ui/components/multichain/avatar-group/avatar-group.tsx index 39af437980d9..98457ac7bb10 100644 --- a/ui/components/multichain/avatar-group/avatar-group.tsx +++ b/ui/components/multichain/avatar-group/avatar-group.tsx @@ -2,19 +2,7 @@ import * as React from 'react'; import classnames from 'classnames'; import { useSelector } from 'react-redux'; import { getUseBlockie } from '../../../selectors'; -import { - Text, - Box, - AvatarToken, - AvatarTokenSize, - AvatarAccount, - AvatarAccountSize, - AvatarAccountVariant, - AvatarNetwork, - AvatarNetworkSize, - AvatarBase, - AvatarBaseSize, -} from '../../component-library'; +import { Text } from '../../component-library/text'; import { AlignItems, BackgroundColor, @@ -24,6 +12,24 @@ import { TextColor, TextVariant, } from '../../../helpers/constants/design-system'; +import { + AvatarTokenSize, + AvatarToken, +} from '../../component-library/avatar-token'; +import { Box } from '../../component-library/box'; +import { + AvatarAccount, + AvatarAccountSize, + AvatarAccountVariant, +} from '../../component-library/avatar-account'; +import { + AvatarNetwork, + AvatarNetworkSize, +} from '../../component-library/avatar-network'; +import { + AvatarBase, + AvatarBaseSize, +} from '../../component-library/avatar-base'; import { AvatarGroupProps, AvatarType } from './avatar-group.types'; export const AvatarGroup: React.FC = ({ diff --git a/ui/components/multichain/avatar-group/avatar-group.types.tsx b/ui/components/multichain/avatar-group/avatar-group.types.tsx index b5c52b1ed9f5..0c859d8b0a77 100644 --- a/ui/components/multichain/avatar-group/avatar-group.types.tsx +++ b/ui/components/multichain/avatar-group/avatar-group.types.tsx @@ -1,5 +1,5 @@ import { BorderColor } from '../../../helpers/constants/design-system'; -import { AvatarTokenSize } from '../../component-library'; +import { AvatarTokenSize } from '../../component-library/avatar-token/avatar-token.types'; import type { StyleUtilityProps } from '../../component-library/box'; export type AvatarGroupProps = StyleUtilityProps & { diff --git a/ui/components/multichain/notifications-settings-box/notifications-settings-box.test.tsx b/ui/components/multichain/notifications-settings-box/notifications-settings-box.test.tsx index 6adaf44fc9a8..68a0f332ec11 100644 --- a/ui/components/multichain/notifications-settings-box/notifications-settings-box.test.tsx +++ b/ui/components/multichain/notifications-settings-box/notifications-settings-box.test.tsx @@ -23,6 +23,7 @@ describe('NotificationsSettingsBox', () => { onToggle={() => { console.log('Toggled'); }} + dataTestId="test-id" >
{testMessage}
@@ -34,15 +35,21 @@ describe('NotificationsSettingsBox', () => { it('toggles value on click', () => { const onToggleMock = jest.fn(); - render( + const testId = 'test-id'; + const { container } = render( - +
Toggle Test
, ); + console.log(container.innerHTML); - fireEvent.click(screen.getByTestId('test-toggle')); + fireEvent.click(screen.getByTestId(`${testId}-toggle-input`)); expect(onToggleMock).toHaveBeenCalledTimes(1); }); }); diff --git a/ui/components/multichain/notifications-settings-box/notifications-settings-box.tsx b/ui/components/multichain/notifications-settings-box/notifications-settings-box.tsx index 8edbaf5d2143..397237d750dc 100644 --- a/ui/components/multichain/notifications-settings-box/notifications-settings-box.tsx +++ b/ui/components/multichain/notifications-settings-box/notifications-settings-box.tsx @@ -19,6 +19,7 @@ export type NotificationsSettingsBoxProps = { loading?: boolean; disabled?: boolean; error?: string | null; + dataTestId: string; onToggle: () => void; }; @@ -28,6 +29,7 @@ export function NotificationsSettingsBox({ loading = false, disabled = false, error = null, + dataTestId, onToggle, }: NotificationsSettingsBoxProps) { const t = useI18nContext(); @@ -44,7 +46,10 @@ export function NotificationsSettingsBox({ className="notifications-settings-box" > {children} - + {loading ? ( @@ -54,7 +59,7 @@ export function NotificationsSettingsBox({ value={value} onToggle={onToggle} disabled={disabled} - dataTestId="test-toggle" + dataTestId={`${dataTestId}-toggle-input`} className="notifications-settings-box__toggle" /> )} diff --git a/ui/components/multichain/token-list-item/stakeable-link.tsx b/ui/components/multichain/token-list-item/stakeable-link.tsx new file mode 100644 index 000000000000..39ca098f3330 --- /dev/null +++ b/ui/components/multichain/token-list-item/stakeable-link.tsx @@ -0,0 +1,85 @@ +import React, { useContext } from 'react'; +import { useSelector } from 'react-redux'; +import { + BackgroundColor, + FontWeight, + IconColor, + TextColor, +} from '../../../helpers/constants/design-system'; +import { Box, Icon, IconName, IconSize, Text } from '../../component-library'; +import { + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../../shared/constants/metametrics'; +import { getPortfolioUrl } from '../../../helpers/utils/portfolio'; +import { + getDataCollectionForMarketing, + getMetaMetricsId, + getParticipateInMetaMetrics, +} from '../../../selectors'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { useI18nContext } from '../../../hooks/useI18nContext'; + +type StakeableLinkProps = { + chainId: string; + symbol?: string; +}; + +export const StakeableLink = ({ chainId, symbol }: StakeableLinkProps) => { + const t = useI18nContext(); + const trackEvent = useContext(MetaMetricsContext); + const metaMetricsId = useSelector(getMetaMetricsId); + const isMetaMetricsEnabled = useSelector(getParticipateInMetaMetrics); + const isMarketingEnabled = useSelector(getDataCollectionForMarketing); + return ( + ) => { + e.preventDefault(); + e.stopPropagation(); + const url = getPortfolioUrl( + 'stake', + 'ext_stake_button', + metaMetricsId, + isMetaMetricsEnabled, + isMarketingEnabled, + ); + global.platform.openTab({ url }); + trackEvent({ + event: MetaMetricsEventName.StakingEntryPointClicked, + category: MetaMetricsEventCategory.Tokens, + properties: { + location: 'Token List Item', + text: 'Stake', + // FIXME: This might not be a number for non-EVM accounts + chain_id: chainId, + token_symbol: symbol, + }, + }); + }} + > + + + {t('stake')} + + + + ); +}; diff --git a/ui/components/multichain/token-list-item/token-list-item.tsx b/ui/components/multichain/token-list-item/token-list-item.tsx index 3efb3dc95523..87b95b04ffd6 100644 --- a/ui/components/multichain/token-list-item/token-list-item.tsx +++ b/ui/components/multichain/token-list-item/token-list-item.tsx @@ -25,9 +25,7 @@ import { ButtonIcon, ButtonIconSize, ButtonSecondary, - Icon, IconName, - IconSize, Modal, ModalBody, ModalContent, @@ -38,13 +36,7 @@ import { SensitiveTextLength, Text, } from '../../component-library'; -import { - getMetaMetricsId, - getParticipateInMetaMetrics, - getDataCollectionForMarketing, - getMarketData, - getCurrencyRates, -} from '../../../selectors'; +import { getMarketData, getCurrencyRates } from '../../../selectors'; import { getMultichainIsEvm } from '../../../selectors/multichain'; import Tooltip from '../../ui/tooltip'; import { useI18nContext } from '../../../hooks/useI18nContext'; @@ -58,10 +50,8 @@ import { NON_EVM_CURRENCY_SYMBOLS, } from '../../../../shared/constants/network'; import { hexToDecimal } from '../../../../shared/modules/conversion.utils'; - import { NETWORKS_ROUTE } from '../../../helpers/constants/routes'; import { setEditedNetwork } from '../../../store/actions'; -import { getPortfolioUrl } from '../../../helpers/utils/portfolio'; import { SafeChain, useSafeChains, @@ -69,6 +59,7 @@ import { import { NETWORK_TO_SHORT_NETWORK_NAME_MAP } from '../../../../shared/constants/bridge'; import { getNetworkConfigurationsByChainId } from '../../../../shared/modules/selectors/networks'; import { PercentageChange } from './price/percentage-change/percentage-change'; +import { StakeableLink } from './stakeable-link'; type TokenListItemProps = { className?: string; @@ -114,9 +105,6 @@ export const TokenListItemComponent = ({ const t = useI18nContext(); const isEvm = useSelector(getMultichainIsEvm); const trackEvent = useContext(MetaMetricsContext); - const metaMetricsId = useSelector(getMetaMetricsId); - const isMetaMetricsEnabled = useSelector(getParticipateInMetaMetrics); - const isMarketingEnabled = useSelector(getDataCollectionForMarketing); const { safeChains } = useSafeChains(); const currencyRates = useSelector(getCurrencyRates); @@ -174,57 +162,6 @@ export const TokenListItemComponent = ({ const tokenMainTitleToDisplay = shouldShowPercentage && !isTitleNetworkName ? tokenTitle : tokenSymbol; - const stakeableTitle = ( - ) => { - e.preventDefault(); - e.stopPropagation(); - const url = getPortfolioUrl( - 'stake', - 'ext_stake_button', - metaMetricsId, - isMetaMetricsEnabled, - isMarketingEnabled, - ); - global.platform.openTab({ url }); - trackEvent({ - event: MetaMetricsEventName.StakingEntryPointClicked, - category: MetaMetricsEventCategory.Tokens, - properties: { - location: 'Token List Item', - text: 'Stake', - // FIXME: This might not be a number for non-EVM accounts - chain_id: chainId, - token_symbol: tokenSymbol, - }, - }); - }} - > - - - {t('stake')} - - - - ); // Used for badge icon const allNetworks = useSelector(getNetworkConfigurationsByChainId); @@ -320,7 +257,9 @@ export const TokenListItemComponent = ({ ellipsis > {tokenMainTitleToDisplay} - {isStakeable && stakeableTitle} + {isStakeable && ( + + )} ) : ( @@ -330,7 +269,9 @@ export const TokenListItemComponent = ({ ellipsis > {tokenMainTitleToDisplay} - {isStakeable && stakeableTitle} + {isStakeable && ( + + )} )} diff --git a/ui/components/ui/mascot/mascot.component.js b/ui/components/ui/mascot/mascot.component.js index a339352ec8b0..02a494312383 100644 --- a/ui/components/ui/mascot/mascot.component.js +++ b/ui/components/ui/mascot/mascot.component.js @@ -46,6 +46,9 @@ export default class Mascot extends Component { width, height, meshJson: getBuildSpecificAsset('foxMeshJson'), + verticalFieldOfView: Math.PI / 37.5, + near: 100, + far: 340, }); this.mascotContainer = createRef(); diff --git a/ui/components/ui/metafox-logo/horizontal-logo.js b/ui/components/ui/metafox-logo/horizontal-logo.js index aa82eac93565..923042c3d1c4 100644 --- a/ui/components/ui/metafox-logo/horizontal-logo.js +++ b/ui/components/ui/metafox-logo/horizontal-logo.js @@ -5,818 +5,40 @@ import { ThemeType } from '../../../../shared/constants/preferences'; const LOGO_WIDTH = 162; const LOGO_HEIGHT = 30; -const TEXT_COLOR = 'var(--color-text-default)'; -const FLASK_PILL_BACKGROUND = 'var(--color-overlay-alternative)'; -const FLASK_PILL_TEXT = 'var(--color-overlay-inverse)'; -const BETA_PILL_BACKGROUND = 'var(--color-primary-default)'; -const BETA_PIL_TEXT = 'var(--color-primary-inverse)'; export default function MetaFoxHorizontalLogo({ theme: themeProps, className, }) { - const [setTheme] = useState(() => + const [theme, setTheme] = useState(() => themeProps === undefined ? document.documentElement.getAttribute('data-theme') : themeProps, ); + const fill = theme === 'dark' ? 'rgb(255,255,255)' : 'rgb(22,22,22)'; + useEffect(() => { if (themeProps !== undefined) { setTheme(themeProps); } - }, [themeProps]); + }, [themeProps, setTheme]); - switch (process.env.METAMASK_BUILD_TYPE) { - case 'beta': - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); - case 'flask': - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); - default: - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); - } + return ( + + + + ); } MetaFoxHorizontalLogo.propTypes = { diff --git a/ui/components/ui/metafox-logo/metafox-logo.component.js b/ui/components/ui/metafox-logo/metafox-logo.component.js index 8bce0486cc89..85a8d8cf07cc 100644 --- a/ui/components/ui/metafox-logo/metafox-logo.component.js +++ b/ui/components/ui/metafox-logo/metafox-logo.component.js @@ -13,10 +13,17 @@ export default class MetaFoxLogo extends PureComponent { ///: BEGIN:ONLY_INCLUDE_IF(build-flask) src: PropTypes.string, ///: END:ONLY_INCLUDE_IF + theme: PropTypes.string, }; static defaultProps = { onClick: undefined, + unsetIconHeight: false, + isOnboarding: false, + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + src: undefined, + ///: END:ONLY_INCLUDE_IF + theme: undefined, }; render() { @@ -27,13 +34,16 @@ export default class MetaFoxLogo extends PureComponent { ///: BEGIN:ONLY_INCLUDE_IF(build-flask) src, ///: END:ONLY_INCLUDE_IF + theme, } = this.props; + const iconProps = unsetIconHeight ? {} : { height: 42, width: 42 }; iconProps.src = './images/logo/metamask-fox.svg'; let renderHorizontalLogo = () => (
- T + static-logo
static-logo
TEST +

@@ -448,17 +470,15 @@ exports[`AssetPage should render an ERC20 asset without prices 1`] = ` Your balance
static-logo
- $0.00 -

+ />
static-logo
- $0.00 -

+ />
{t('yourBalance')} - + {[AssetType.token, AssetType.native].includes(type) && ( + + )} { @@ -130,13 +130,9 @@ export const BridgeQuoteCard = () => { @@ -152,13 +148,9 @@ export const BridgeQuoteCard = () => { diff --git a/ui/pages/bridge/transaction-details/transaction-details.tsx b/ui/pages/bridge/transaction-details/transaction-details.tsx index 3e2a8900717d..cea276dbad1e 100644 --- a/ui/pages/bridge/transaction-details/transaction-details.tsx +++ b/ui/pages/bridge/transaction-details/transaction-details.tsx @@ -47,7 +47,6 @@ import { import { formatDate } from '../../../helpers/utils/util'; import { ConfirmInfoRowDivider as Divider } from '../../../components/app/confirm/info/row'; import { useI18nContext } from '../../../hooks/useI18nContext'; -import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../shared/constants/network'; import { selectedAddressTxListSelector } from '../../../selectors'; import { MetaMetricsContextProp, @@ -63,6 +62,7 @@ import { NETWORK_TO_SHORT_NETWORK_NAME_MAP, AllowedBridgeChainIds, } from '../../../../shared/constants/bridge'; +import { getImageForChainId } from '../../../selectors/multichain'; import TransactionDetailRow from './transaction-detail-row'; import BridgeExplorerLinks from './bridge-explorer-links'; import BridgeStepList from './bridge-step-list'; @@ -202,15 +202,11 @@ const CrossChainSwapTxDetails = () => { : StatusTypes.PENDING; const srcChainIconUrl = srcNetwork - ? CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[ - srcNetwork.chainId as keyof typeof CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP - ] + ? getImageForChainId(srcNetwork.chainId) : undefined; const destChainIconUrl = destNetwork - ? CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[ - destNetwork.chainId as keyof typeof CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP - ] + ? getImageForChainId(destNetwork.chainId) : undefined; const srcNetworkName = diff --git a/ui/pages/confirmations/components/confirm/footer/footer.test.tsx b/ui/pages/confirmations/components/confirm/footer/footer.test.tsx index b931ec782f65..506d3b9af9f4 100644 --- a/ui/pages/confirmations/components/confirm/footer/footer.test.tsx +++ b/ui/pages/confirmations/components/confirm/footer/footer.test.tsx @@ -136,20 +136,8 @@ describe('ConfirmFooter', () => { // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any .mockImplementation(() => ({} as any)); - const updateCustomNonceSpy = jest - .spyOn(Actions, 'updateCustomNonce') - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .mockImplementation(() => ({} as any)); - const setNextNonceSpy = jest - .spyOn(Actions, 'setNextNonce') - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .mockImplementation(() => ({} as any)); fireEvent.click(cancelButton); expect(rejectSpy).toHaveBeenCalled(); - expect(updateCustomNonceSpy).toHaveBeenCalledWith(''); - expect(setNextNonceSpy).toHaveBeenCalledWith(''); }); it('invoke required actions when submit button is clicked', () => { @@ -160,20 +148,8 @@ describe('ConfirmFooter', () => { // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any .mockImplementation(() => ({} as any)); - const updateCustomNonceSpy = jest - .spyOn(Actions, 'updateCustomNonce') - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .mockImplementation(() => ({} as any)); - const setNextNonceSpy = jest - .spyOn(Actions, 'setNextNonce') - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .mockImplementation(() => ({} as any)); fireEvent.click(submitButton); expect(resolveSpy).toHaveBeenCalled(); - expect(updateCustomNonceSpy).toHaveBeenCalledWith(''); - expect(setNextNonceSpy).toHaveBeenCalledWith(''); }); it('displays a danger "Confirm" button there are danger alerts', async () => { diff --git a/ui/pages/confirmations/components/confirm/footer/footer.tsx b/ui/pages/confirmations/components/confirm/footer/footer.tsx index 095caaee88c3..9e20f22a15d3 100644 --- a/ui/pages/confirmations/components/confirm/footer/footer.tsx +++ b/ui/pages/confirmations/components/confirm/footer/footer.tsx @@ -20,11 +20,9 @@ import useAlerts from '../../../../../hooks/useAlerts'; import { rejectPendingApproval, resolvePendingApproval, - setNextNonce, ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) updateAndApproveTx, ///: END:ONLY_INCLUDE_IF - updateCustomNonce, } from '../../../../../store/actions'; import { isSignatureTransactionType } from '../../../utils'; import { useConfirmContext } from '../../../context/confirm'; @@ -190,8 +188,6 @@ const Footer = () => { dispatch( rejectPendingApproval(currentConfirmation.id, serializeError(error)), ); - dispatch(updateCustomNonce('')); - dispatch(setNextNonce('')); }, [currentConfirmation], ); @@ -223,8 +219,6 @@ const Footer = () => { } else { dispatch(resolvePendingApproval(currentConfirmation.id, undefined)); } - dispatch(updateCustomNonce('')); - dispatch(setNextNonce('')); }, [currentConfirmation, customNonceValue]); const handleFooterCancel = useCallback(() => { diff --git a/ui/pages/notifications-settings/notifications-settings-allow-notifications.test.tsx b/ui/pages/notifications-settings/notifications-settings-allow-notifications.test.tsx index 7d1197f64acd..0b459f24fdf7 100644 --- a/ui/pages/notifications-settings/notifications-settings-allow-notifications.test.tsx +++ b/ui/pages/notifications-settings/notifications-settings-allow-notifications.test.tsx @@ -16,6 +16,7 @@ const store = mockStore({ describe('NotificationsSettingsAllowNotifications', () => { it('renders correctly', () => { + const testId = 'notifications-settings-allow'; const { getByTestId } = render( @@ -25,13 +26,13 @@ describe('NotificationsSettingsAllowNotifications', () => { setLoading={() => { return null; }} + dataTestId={testId} /> , ); - expect( - getByTestId('notifications-settings-allow-notifications'), - ).toBeInTheDocument(); + expect(getByTestId(`${testId}-toggle-box`)).toBeInTheDocument(); + expect(getByTestId(`${testId}-toggle-input`)).toBeInTheDocument(); }); }); diff --git a/ui/pages/notifications-settings/notifications-settings-allow-notifications.tsx b/ui/pages/notifications-settings/notifications-settings-allow-notifications.tsx index fdfaeb1109cb..13b595f23e73 100644 --- a/ui/pages/notifications-settings/notifications-settings-allow-notifications.tsx +++ b/ui/pages/notifications-settings/notifications-settings-allow-notifications.tsx @@ -40,10 +40,12 @@ export function NotificationsSettingsAllowNotifications({ loading, setLoading, disabled, + dataTestId, }: { loading: boolean; setLoading: (loading: boolean) => void; disabled: boolean; + dataTestId: string; }) { const t = useI18nContext(); const trackEvent = useContext(MetaMetricsContext); @@ -142,13 +144,13 @@ export function NotificationsSettingsAllowNotifications({ paddingLeft={8} paddingRight={8} paddingBottom={8} - data-testid="notifications-settings-allow-notifications" > diff --git a/ui/pages/notifications-settings/notifications-settings-per-account.tsx b/ui/pages/notifications-settings/notifications-settings-per-account.tsx index 4e5afa6cb5f1..c1c8b32a118f 100644 --- a/ui/pages/notifications-settings/notifications-settings-per-account.tsx +++ b/ui/pages/notifications-settings/notifications-settings-per-account.tsx @@ -1,4 +1,5 @@ import React, { useState, useCallback, useContext } from 'react'; +import { toChecksumHexAddress } from '@metamask/controller-utils'; import { MetaMetricsContext } from '../../contexts/metametrics'; import { MetaMetricsEventCategory, @@ -10,6 +11,7 @@ import { NotificationsSettingsAccount, } from '../../components/multichain'; import { useListNotifications } from '../../hooks/metamask-notifications/useNotifications'; +import { shortenAddress } from '../../helpers/utils/util'; type NotificationsSettingsPerAccountProps = { address: string; @@ -82,6 +84,9 @@ export const NotificationsSettingsPerAccount = ({ await toggleAccount(!isEnabled); }, [address, isEnabled, toggleAccount, trackEvent]); + const checksumAddress = toChecksumHexAddress(address); + const shortenedAddress = shortenAddress(checksumAddress); + return ( <> diff --git a/ui/pages/notifications-settings/notifications-settings-types.tsx b/ui/pages/notifications-settings/notifications-settings-types.tsx index 5232b43121f8..65ce6c8dd51d 100644 --- a/ui/pages/notifications-settings/notifications-settings-types.tsx +++ b/ui/pages/notifications-settings/notifications-settings-types.tsx @@ -101,7 +101,7 @@ export function NotificationsSettingsTypes({ onToggle={onToggleFeatureAnnouncements} error={errorFeatureAnnouncements} disabled={disabled} - data-testid="product-announcements-toggle" + dataTestId="product-announcements" > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + {t('appName')} @@ -189,238 +189,96 @@ export default function OnboardingPinBillboard() { d="M742 257.875C741.367 257.875 740.875 258.391 740.875 259C740.875 259.633 741.367 260.125 742 260.125C742.609 260.125 743.125 259.633 743.125 259C743.125 258.391 742.609 257.875 742 257.875ZM740.875 255.438C740.875 256.07 741.367 256.562 742 256.562C742.609 256.562 743.125 256.07 743.125 255.438C743.125 254.828 742.609 254.312 742 254.312C741.367 254.312 740.875 254.828 740.875 255.438ZM740.875 262.562C740.875 263.195 741.367 263.688 742 263.688C742.609 263.688 743.125 263.195 743.125 262.562C743.125 261.953 742.609 261.438 742 261.438C741.367 261.438 740.875 261.953 740.875 262.562Z" fill="#BBC0C5" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + +
@@ -154,10 +154,13 @@ export default function OnboardingWelcome() {
@@ -175,10 +178,13 @@ export default function OnboardingWelcome() {
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 0eea2598ccb9..310fd4d9c0f2 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 @@ -1424,18 +1424,16 @@ exports[`Security Tab should match snapshot 1`] = ` >
Participate in MetaMetrics
Participate in MetaMetrics to help us make MetaMask better
@@ -1445,7 +1443,7 @@ exports[`Security Tab should match snapshot 1`] = ` data-testid="participate-in-meta-metrics-toggle" >