diff --git a/.circleci/config.yml b/.circleci/config.yml index 49df23bc9c28..81cc863d1bbe 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,6 +67,18 @@ aliases: git checkout -B "$CIRCLE_BRANCH" "$CIRCLE_SHA1" fi + # Check if MMI Optional tests should run + - &check-mmi-optional + name: Check if MMI Optional tests should run + command: | + RUN_MMI_OPTIONAL=$(cat ./RUN_MMI_OPTIONAL) + if [[ "${CIRCLE_BRANCH}" == "develop" || "${RUN_MMI_OPTIONAL}" == "true" ]]; then + echo "Running MMI Optional tests" + else + echo "Skipping MMI Optional tests" + circleci step halt + fi + workflows: test_and_release: jobs: @@ -77,6 +89,7 @@ workflows: - trigger-beta-build: requires: - prep-deps + - check-pr-tag - prep-deps - test-deps-audit: requires: @@ -132,6 +145,7 @@ workflows: - prep-build-test-mmi-playwright: requires: - prep-deps + - check-pr-tag - prep-build-storybook: requires: - prep-deps @@ -373,6 +387,68 @@ jobs: name: Create GitHub Pull Request for version command: .circleci/scripts/release-create-release-pr.sh + check-pr-tag: + docker: + - image: cimg/base:stable + steps: + - run: + name: Check for MMI Team Tag + command: | + #!/bin/bash + + # GitHub Personal Access Token for API Authentication + GITHUB_TOKEN="${GITHUB_TOKEN}" + BRANCH="${CIRCLE_BRANCH}" + + # Fetch the PRs associated with the current branch and check the response + PR_RESPONSE=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ + "https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pulls?state=open&head=${CIRCLE_PROJECT_USERNAME}:${BRANCH}") + echo "https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pulls?state=open&head=${CIRCLE_PROJECT_USERNAME}:${BRANCH}" + + # Check if the response contains valid JSON + if ! echo "$PR_RESPONSE" | jq empty; then + echo "Failed to parse JSON response." + echo "$PR_RESPONSE" + exit 1 + fi + + # Check if we received an array of PRs + if ! echo "$PR_RESPONSE" | jq -e '. | type == "array"'; then + echo "$PR_RESPONSE" + echo "Expected an array of PRs, got something else." + exit 1 + fi + + # Check if the array of PRs is empty + PR_COUNT=$(echo "$PR_RESPONSE" | jq '. | length') + + # If no PRs are found, exit gracefully + if [ "$PR_COUNT" -eq 0 ]; then + echo "No open PRs found. Exiting." + echo "false" > ./RUN_MMI_OPTIONAL + exit 0 + fi + + # Extract label names from the PR_RESPONSE + LABEL_NAMES=$(echo "$PR_RESPONSE" | jq -r '.[0].labels[].name') + + echo "Labels found: $LABEL_NAMES" + + # Check if "team-mmi" label is present + if echo "$LABEL_NAMES" | grep -qw "team-mmi"; then + echo "team-mmi tag found." + # assign the RUN_MMI_OPTIONAL variable to true + echo "true" > ./RUN_MMI_OPTIONAL + else + echo "team-mmi tag not found." + # assign the RUN_MMI_OPTIONAL variable to false + echo "false" > ./RUN_MMI_OPTIONAL + fi + - persist_to_workspace: + root: . + paths: + - RUN_MMI_OPTIONAL + prep-deps: executor: node-browsers-medium steps: @@ -640,6 +716,7 @@ jobs: - run: *shallow-git-clone - attach_workspace: at: . + - run: *check-mmi-optional - run: name: Build MMI extension for Playwright e2e command: | @@ -654,6 +731,7 @@ jobs: - persist_to_workspace: root: . paths: + - RUN_MMI_OPTIONAL - dist-test-mmi-playwright - builds-test-mmi-playwright - store_artifacts: @@ -1121,6 +1199,7 @@ jobs: - run: *shallow-git-clone - attach_workspace: at: . + - run: *check-mmi-optional - run: name: Move test build to dist command: mv ./dist-test-mmi-playwright ./dist diff --git a/.github/scripts/check-pr-has-required-labels.ts b/.github/scripts/check-pr-has-required-labels.ts index 15ef77022ceb..354dc2c2aa7d 100644 --- a/.github/scripts/check-pr-has-required-labels.ts +++ b/.github/scripts/check-pr-has-required-labels.ts @@ -73,7 +73,7 @@ async function main(): Promise { if (!hasTeamLabel) { errorMessage += 'No team labels found on the PR. '; } - errorMessage += `Please make sure the PR is appropriately labeled before merging it.\n\nSee labeling guidelines for more detail: https://github.com/MetaMask/metamask-extension/blob/develop/.github/LABELING_GUIDELINES.md`; + errorMessage += `Please make sure the PR is appropriately labeled before merging it.\n\nSee labeling guidelines for more detail: https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md`; core.setFailed(errorMessage); process.exit(1); } diff --git a/.yarn/patches/@metamask-assets-controllers-npm-26.0.0-17c0e9432c.patch b/.yarn/patches/@metamask-assets-controllers-npm-26.0.0-17c0e9432c.patch new file mode 100644 index 000000000000..a84784cded69 --- /dev/null +++ b/.yarn/patches/@metamask-assets-controllers-npm-26.0.0-17c0e9432c.patch @@ -0,0 +1,24 @@ +diff --git a/dist/TokenDetectionController.js b/dist/TokenDetectionController.js +index 9aa09140d47424217eac118aebca9031e5d2a236..8100c432e7e01dbefcb5f53db3c58e51f120a51d 100644 +--- a/dist/TokenDetectionController.js ++++ b/dist/TokenDetectionController.js +@@ -230,8 +230,7 @@ _TokenDetectionController_intervalId = new WeakMap(), _TokenDetectionController_ + const isDetectionChangedFromPreferences = __classPrivateFieldGet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, "f") !== useTokenDetection; + __classPrivateFieldSet(this, _TokenDetectionController_selectedAddress, newSelectedAddress, "f"); + __classPrivateFieldSet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, useTokenDetection, "f"); +- if (useTokenDetection && +- (isSelectedAddressChanged || isDetectionChangedFromPreferences)) { ++ if (isSelectedAddressChanged || isDetectionChangedFromPreferences) { + yield __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, { + selectedAddress: __classPrivateFieldGet(this, _TokenDetectionController_selectedAddress, "f"), + }); +@@ -239,8 +238,7 @@ _TokenDetectionController_intervalId = new WeakMap(), _TokenDetectionController_ + })); + this.messagingSystem.subscribe('AccountsController:selectedAccountChange', ({ address: newSelectedAddress }) => __awaiter(this, void 0, void 0, function* () { + const isSelectedAddressChanged = __classPrivateFieldGet(this, _TokenDetectionController_selectedAddress, "f") !== newSelectedAddress; +- if (isSelectedAddressChanged && +- __classPrivateFieldGet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, "f")) { ++ if (isSelectedAddressChanged) { + __classPrivateFieldSet(this, _TokenDetectionController_selectedAddress, newSelectedAddress, "f"); + yield __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, { + selectedAddress: __classPrivateFieldGet(this, _TokenDetectionController_selectedAddress, "f"), diff --git a/.yarn/patches/@metamask-keyring-controller-npm-13.0.0-d94816a680.patch b/.yarn/patches/@metamask-keyring-controller-npm-13.0.0-d94816a680.patch new file mode 100644 index 000000000000..254cf55f3f5f --- /dev/null +++ b/.yarn/patches/@metamask-keyring-controller-npm-13.0.0-d94816a680.patch @@ -0,0 +1,21 @@ +diff --git a/dist/KeyringController.js b/dist/KeyringController.js +index fc649ea6fc97b905d811b236de638172fb10b548..beab676ab85e5e372eda7846e98b7d34af6317f5 100644 +--- a/dist/KeyringController.js ++++ b/dist/KeyringController.js +@@ -1092,9 +1092,13 @@ _KeyringController_keyringBuilders = new WeakMap(), _KeyringController_keyrings + }, _KeyringController_addQRKeyring = function _KeyringController_addQRKeyring() { + return __awaiter(this, void 0, void 0, function* () { + // QRKeyring is not yet compatible with Keyring type from @metamask/utils +- const qrKeyring = (yield __classPrivateFieldGet(this, _KeyringController_instances, "m", _KeyringController_newKeyring).call(this, KeyringTypes.qr, { +- accounts: [], +- })); ++ /** ++ * Patch for @metamask/keyring-controller v13.0.0 ++ * Below code change will fix the issue 23804, The intial code added a empty accounts as argument when creating a new QR keyring. ++ * cause the new Keystone MetamaskKeyring default properties all are undefined during deserialise() process. ++ * Please refer to PR 23903 for detail. ++ */ ++ const qrKeyring = (yield __classPrivateFieldGet(this, _KeyringController_instances, "m", _KeyringController_newKeyring).call(this, KeyringTypes.qr)); + const accounts = yield qrKeyring.getAccounts(); + yield __classPrivateFieldGet(this, _KeyringController_instances, "m", _KeyringController_checkForDuplicate).call(this, KeyringTypes.qr, accounts); + __classPrivateFieldGet(this, _KeyringController_keyrings, "f").push(qrKeyring); diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 963d101ee522..4a6835316343 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -2562,21 +2562,6 @@ "notificationsMarkAllAsRead": { "message": "Alle als gelesen markieren" }, - "notificationsOpenBetaSnapsActionText": { - "message": "Mehr erfahren" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "🎉 Wir freuen uns sehr, die Open-Beta-Version von MetaMask Snaps zu präsentieren!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "Personalisieren Sie Ihre Wallet mit Snaps, die von der Entwickler-Community erstellt wurden!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Snaps helfen Ihnen dabei, mehr aus MetaMask zu machen – wie z. B. Verbindungen mit anderen Netzwerken herzustellen, Transaktionseinblicke zu erlangen und benutzerdefinierte Benachrichtigungen zu erhalten." - }, - "notificationsOpenBetaSnapsTitle": { - "message": "Wir stellen vor: MetaMask Snaps" - }, "numberOfNewTokensDetectedPlural": { "message": "$1 neue Tokens in diesem Konto gefunden.", "description": "$1 is the number of new tokens detected" @@ -3267,6 +3252,10 @@ "securityAndPrivacy": { "message": "Sicherheit und Datenschutz" }, + "securityProviderPoweredBy": { + "message": "Powered by $1", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "Details anzeigen" }, @@ -3491,12 +3480,6 @@ "smartContracts": { "message": "Smart Contracts" }, - "smartSwapsAreHere": { - "message": "Die Smart Swaps sind da!" - }, - "smartSwapsDescription": { - "message": "MetaMask Swaps ist jetzt wesentlich intelligenter! Die Aktivierung von Smart Swaps wird es MetaMask erlauben, Ihre Swaps programmatisch zu optimieren, um zu helfen:" - }, "smartSwapsErrorNotEnoughFunds": { "message": "Nicht genügend Gelder für einen Smart Swap." }, @@ -3776,18 +3759,6 @@ "strong": { "message": "Stark" }, - "stxBenefit1": { - "message": "Transaktionskosten minimieren" - }, - "stxBenefit2": { - "message": "Transaktionsausfälle reduzieren" - }, - "stxBenefit3": { - "message": "Steckengebliebene Transaktionen eliminieren" - }, - "stxBenefit4": { - "message": "Front-Running verhindern" - }, "stxCancelled": { "message": "Swap wäre gescheitert" }, @@ -4718,7 +4689,7 @@ }, "viewOnCustomBlockExplorer": { "message": "Zeige $1 bei $2", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "$1 auf Etherscan anzeigen", diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index c60a82396912..330fcbfbdebc 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -2562,21 +2562,6 @@ "notificationsMarkAllAsRead": { "message": "Επισήμανση όλων ως αναγνωσμένων" }, - "notificationsOpenBetaSnapsActionText": { - "message": "Μάθετε περισσότερα" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "🎉 Είμαστε ενθουσιασμένοι που ανακοινώνουμε την δοκιμαστική έναρξη του MetaMask Snaps!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "Εξατομικεύστε το πορτοφόλι σας με snaps που δημιουργήθηκαν από την κοινότητα προγραμματιστών!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Τα Snaps σάς βοηθούν να κάνετε περισσότερα με το MetaMask — όπως τη σύνδεση σε περισσότερα δίκτυα, τη προβολή πληροφοριών συναλλαγών και τη λήψη προσαρμοσμένων ειδοποιήσεων." - }, - "notificationsOpenBetaSnapsTitle": { - "message": "Παρουσιάζουμε τα MetaMask Snaps" - }, "numberOfNewTokensDetectedPlural": { "message": "$1 νέα tokens βρέθηκαν σε αυτόν τον λογαριασμό", "description": "$1 is the number of new tokens detected" @@ -3267,6 +3252,10 @@ "securityAndPrivacy": { "message": "Ασφάλεια και απόρρητο" }, + "securityProviderPoweredBy": { + "message": "Με την υποστήριξη του $1", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "Δείτε λεπτομέρειες" }, @@ -3491,12 +3480,6 @@ "smartContracts": { "message": "Έξυπνα συμβόλαια" }, - "smartSwapsAreHere": { - "message": "Οι Έξυπνες Ανταλλαγές είναι εδώ!" - }, - "smartSwapsDescription": { - "message": "Οι Ανταλλαγές στο MetaMask μόλις έγιναν πολύ πιο έξυπνες! Η ενεργοποίηση των Έξυπνων Ανταλλαγών θα επιτρέψει στο MetaMask να βελτιστοποιήσει προγραμματιστικά τις Ανταλλαγές σας, ώστε να σας βοηθήσει:" - }, "smartSwapsErrorNotEnoughFunds": { "message": "Δεν υπάρχουν αρκετά κεφάλαια για έξυπνες ανταλλαγές." }, @@ -3776,18 +3759,6 @@ "strong": { "message": "Ισχυρό" }, - "stxBenefit1": { - "message": "Ελαχιστοποίηση του κόστους συναλλαγών" - }, - "stxBenefit2": { - "message": "Μείωση των αποτυχημένων συναλλαγών" - }, - "stxBenefit3": { - "message": "Εξάλειψη των εμπλοκών στις συναλλαγές" - }, - "stxBenefit4": { - "message": "Αποτροπή των προπορευόμενων συναλλαγών (front-running)" - }, "stxCancelled": { "message": "Η ανταλλαγή θα είχε αποτύχει" }, @@ -4718,7 +4689,7 @@ }, "viewOnCustomBlockExplorer": { "message": "Προβολή $1 στο $2", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "Προβολή $1 στο Etherscan", diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 1d20250bc6d3..92ed0a123348 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -634,7 +634,7 @@ "message": "If you approve this request, a third party known for scams will take all your assets." }, "blockaidMessage": { - "message": "Privacy preserving - no data is shared with third parties. Available on Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon and Sepolia." + "message": "Privacy preserving - no data is shared with third parties. Available on Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon, Base and Sepolia." }, "blockaidTitleDeceptive": { "message": "This is a deceptive request" @@ -759,6 +759,9 @@ "close": { "message": "Close" }, + "closeExtension": { + "message": "Close extension" + }, "coingecko": { "message": "CoinGecko" }, @@ -914,9 +917,6 @@ "connectedWith": { "message": "Connected with" }, - "connectedaccountsTabKey": { - "message": "Connected accounts" - }, "connecting": { "message": "Connecting..." }, @@ -1577,14 +1577,17 @@ "editSpeedUpEditGasFeeModalTitle": { "message": "Edit speed up gas fee" }, + "enable": { + "message": "Enable" + }, "enableAutoDetect": { "message": " Enable autodetect" }, "enableFromSettings": { "message": " Enable it from Settings." }, - "enableSmartSwaps": { - "message": "Enable Smart Swaps" + "enableSmartTransactions": { + "message": "Enable Smart Transactions" }, "enableSnap": { "message": "Enable" @@ -1756,9 +1759,6 @@ "failureMessage": { "message": "Something went wrong, and we were unable to complete the action" }, - "faqAndRiskDisclosures": { - "message": "FAQ and Risk Disclosures" - }, "fast": { "message": "Fast" }, @@ -1825,6 +1825,9 @@ "fromTokenLists": { "message": "From token lists: $1" }, + "function": { + "message": "Function: $1" + }, "functionApprove": { "message": "Function: Approve" }, @@ -2235,6 +2238,9 @@ "interactingWith": { "message": "Interacting with" }, + "introducingSmartTransactions": { + "message": "Introducing Smart Transactions" + }, "invalidAddress": { "message": "Invalid address" }, @@ -2382,6 +2388,9 @@ "learnMoreUpperCase": { "message": "Learn more" }, + "learnMoreUpperCaseWithDot": { + "message": "Learn more." + }, "learnScamRisk": { "message": "scams and security risks." }, @@ -2524,9 +2533,6 @@ "message": "Make sure nobody is looking", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, - "manageInSettings": { - "message": "Manage in settings" - }, "max": { "message": "Max" }, @@ -2977,6 +2983,9 @@ "notEnoughGas": { "message": "Not enough gas" }, + "notRightNow": { + "message": "Not right now" + }, "note": { "message": "Note" }, @@ -3051,7 +3060,7 @@ "message": "Got it" }, "notificationsBlockaidDefaultDescriptionOne": { - "message": "Steer clear of known scams while still preserving your privacy with security alerts powered by Blockaid. This feature is available on Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon and Sepolia." + "message": "Steer clear of known scams while still preserving your privacy with security alerts powered by Blockaid. This feature is available on Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon, Base and Sepolia." }, "notificationsBlockaidDefaultDescriptionTwo": { "message": "Always do your own due diligence before approving requests." @@ -3059,15 +3068,6 @@ "notificationsBlockaidDefaultTitle": { "message": "Stay safe with security alerts" }, - "notificationsBuySellActionText": { - "message": "Try it on MetaMask Portfolio" - }, - "notificationsBuySellDescription": { - "message": "Introducing the ‘Buy & Sell’ button, giving you easy access to our latest feature: Sell. When clicked, you’ll be redirected to MetaMask Portfolio, where you’ll be able to convert your crypto to cash in a flash. Initially available for ETH on mainnet in the US (state restrictions apply), UK, and parts of Europe." - }, - "notificationsBuySellTitle": { - "message": "Sell your crypto, get cash" - }, "notificationsDropLedgerFirefoxDescription": { "message": "Firefox no longer supports U2F, so Ledger won't work with MetaMask on Firefox. Try MetaMask on Google Chrome instead.", "description": "Description of a notification in the 'See What's New' popup. Describes that ledger will not longer be supported for firefox users and they should use MetaMask on chrome for ledger support instead." @@ -3097,21 +3097,6 @@ "notificationsMarkAllAsRead": { "message": "Mark all as read" }, - "notificationsOpenBetaSnapsActionText": { - "message": "Learn more" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "🎉 We're excited to announce the Open Beta of MetaMask Snaps!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "Personalize your wallet with snaps built by the developer community!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Snaps help you do more with MetaMask — like connect to more networks, see transaction insights, and get custom notifications." - }, - "notificationsOpenBetaSnapsTitle": { - "message": "Introducing MetaMask Snaps" - }, "notificationsPetnamesActionText": { "message": "Got it" }, @@ -3879,6 +3864,9 @@ "removeNFT": { "message": "Remove NFT" }, + "removeNftErrorMessage": { + "message": "We could not remove this NFT." + }, "removeNftMessage": { "message": "NFT was successfully removed!" }, @@ -4103,6 +4091,10 @@ "securityAndPrivacy": { "message": "Security & privacy" }, + "securityProviderPoweredBy": { + "message": "Powered by $1", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "See details" }, @@ -4384,7 +4376,7 @@ "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. " + "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" }, "simulationsSettingSubHeader": { "message": "Estimate balance changes" @@ -4407,28 +4399,58 @@ "smartContracts": { "message": "Smart contracts" }, - "smartSwaps": { - "message": "Smart Swaps" - }, - "smartSwapsAreHere": { - "message": "Smart Swaps are here!" - }, - "smartSwapsDescription": { - "message": "MetaMask Swaps just got a whole lot smarter! Enabling Smart Swaps will allow MetaMask to programmatically optimize your Swap to help:" - }, - "smartSwapsDescription2": { - "message": "*Smart Swaps will submit your transaction privately. You can opt-out in advanced settings at any time. To learn more about Smart Swaps, read our $1.", - "description": "$1 is an external link to FAQ and Risk Disclosures" - }, "smartSwapsErrorNotEnoughFunds": { "message": "Not enough funds for a smart swap." }, "smartSwapsErrorUnavailable": { "message": "Smart Swaps are temporarily unavailable." }, - "smartSwapsTooltip": { - "message": "Simulate transactions before submitting to decrease transaction costs and reduce failures. To learn more, read our $1", - "description": "$1 is an external link to FAQ and Risk Disclosures" + "smartTransactionCancelled": { + "message": "Your transaction was canceled" + }, + "smartTransactionCancelledDescription": { + "message": "Your transaction couldn't be completed, so it was canceled to save you from paying unnecessary gas fees." + }, + "smartTransactionError": { + "message": "Your transaction failed" + }, + "smartTransactionErrorDescription": { + "message": "Sudden market changes can cause failures. If the problem continues, reach out to MetaMask customer support." + }, + "smartTransactionPending": { + "message": "Submitting your transaction" + }, + "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" + }, + "smartTransactionsBenefit1": { + "message": "82% fewer failed transactions" + }, + "smartTransactionsBenefit2": { + "message": "Transaction protection" + }, + "smartTransactionsBenefit3": { + "message": "Real-time updates" + }, + "smartTransactionsDescription": { + "message": "Unlock the safest, most reliable, and easiest transaction experience - a smarter way to navigate web3." + }, + "smartTransactionsDescription2": { + "message": "Millions of dollars are lost every month due to failed transactions & frontrunning. Smart transactions fixes this." + }, + "smartTransactionsDescription3": { + "message": "Right now, Smart Transactions are only available on ETH Mainnet. You can turn them off at any time in settings. $1", + "description": "$1 is an external link to learn more about Smart Transactions" }, "snapAccountCreated": { "message": "Account created" @@ -4796,18 +4818,6 @@ "strong": { "message": "Strong" }, - "stxBenefit1": { - "message": "Minimize transaction costs" - }, - "stxBenefit2": { - "message": "Reduce transaction failures" - }, - "stxBenefit3": { - "message": "Eliminate stuck transactions" - }, - "stxBenefit4": { - "message": "Prevent front-running" - }, "stxCancelled": { "message": "Swap would have failed" }, @@ -4817,6 +4827,10 @@ "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" }, @@ -4824,6 +4838,9 @@ "message": "Sudden market changes can cause failures. If the problem persists, please reach out to $1.", "description": "This message is shown to a user if their swap fails. The $1 will be replaced by support.metamask.io" }, + "stxOptInDescription": { + "message": "Turn on smart transactions for more reliable and secure transactions, and adjustable fees on ETH Mainnet. $1" + }, "stxPendingPrivatelySubmittingSwap": { "message": "Privately submitting your Swap..." }, @@ -5781,6 +5798,9 @@ "view": { "message": "View" }, + "viewActivity": { + "message": "View activity" + }, "viewAllDetails": { "message": "View all details" }, @@ -5804,7 +5824,7 @@ }, "viewOnCustomBlockExplorer": { "message": "View $1 at $2", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "View $1 on Etherscan", @@ -5816,6 +5836,9 @@ "viewOnOpensea": { "message": "View on Opensea" }, + "viewTransaction": { + "message": "View transaction" + }, "viewinCustodianApp": { "message": "View in custodian app" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 239e4e5bbb7b..d880225a4ffd 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -2562,21 +2562,6 @@ "notificationsMarkAllAsRead": { "message": "Marcar todo como leído" }, - "notificationsOpenBetaSnapsActionText": { - "message": "Conozca más" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "🎉 ¡Nos complace anunciar la versión Beta abierta de MetaMask Snaps!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "¡Personalice su monedero con snaps creados por la comunidad de desarrolladores!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Los snaps lo ayudan a hacer más con MetaMask, como conectarlo a más redes, ver información sobre transacciones y recibir notificaciones personalizadas." - }, - "notificationsOpenBetaSnapsTitle": { - "message": "Presentamos MetaMask Snaps" - }, "numberOfNewTokensDetectedPlural": { "message": "$1 nuevos tokens encontrados en esta cuenta", "description": "$1 is the number of new tokens detected" @@ -3267,6 +3252,10 @@ "securityAndPrivacy": { "message": "Seguridad y privacidad" }, + "securityProviderPoweredBy": { + "message": "Impulsado por $1", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "Ver detalles" }, @@ -3491,12 +3480,6 @@ "smartContracts": { "message": "Contratos inteligentes" }, - "smartSwapsAreHere": { - "message": "¡Los intercambios inteligentes ya están aquí!" - }, - "smartSwapsDescription": { - "message": "¡La función Intercambios de MetaMask ahora es mucho más inteligente! Habilitar Intercambios inteligentes permitirá que MetaMask optimice mediante programación su intercambio para ayudar a:" - }, "smartSwapsErrorNotEnoughFunds": { "message": "No hay suficientes fondos para un intercambio inteligente." }, @@ -3776,18 +3759,6 @@ "strong": { "message": "Fuerte" }, - "stxBenefit1": { - "message": "Minimizar los costos de transacción" - }, - "stxBenefit2": { - "message": "Reducir las fallas en las transacciones" - }, - "stxBenefit3": { - "message": "Eliminar las transacciones atascadas" - }, - "stxBenefit4": { - "message": "Prevenir la inversión ventajista" - }, "stxCancelled": { "message": "El intercambio habría fallado" }, @@ -4718,7 +4689,7 @@ }, "viewOnCustomBlockExplorer": { "message": "Ver $1 en $2", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "Ver $1 en Etherscan", diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 370c94563e9d..7ed07f835b8b 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -2543,7 +2543,7 @@ }, "viewOnCustomBlockExplorer": { "message": "Ver $1 en $2", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "Ver $1 en Etherscan", diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 3c97a6a7e33f..a05b9ce014b5 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -2565,21 +2565,6 @@ "notificationsMarkAllAsRead": { "message": "Marquer tout comme lu" }, - "notificationsOpenBetaSnapsActionText": { - "message": "En savoir plus" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "🎉 Nous sommes ravis d’annoncer le lancement de la version bêta publique de MetaMask Snaps !" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "Personnalisez votre portefeuille avec des snaps conçus par la communauté de développeurs !" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Les snaps vous aident à profiter pleinement de MetaMask, par exemple vous connecter à plus de réseaux, voir un aperçu des transactions et recevoir des notifications personnalisées." - }, - "notificationsOpenBetaSnapsTitle": { - "message": "Présentation de MetaMask Snaps" - }, "numberOfNewTokensDetectedPlural": { "message": "$1 nouveaux jetons trouvés dans ce compte", "description": "$1 is the number of new tokens detected" @@ -3270,6 +3255,10 @@ "securityAndPrivacy": { "message": "Sécurité et confidentialité" }, + "securityProviderPoweredBy": { + "message": "Service fourni par $1", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "Voir les détails" }, @@ -3494,12 +3483,6 @@ "smartContracts": { "message": "Contrats intelligents" }, - "smartSwapsAreHere": { - "message": "Les contrats de swap intelligents sont enfin arrivés !" - }, - "smartSwapsDescription": { - "message": "Les swaps sont devenus beaucoup plus intelligents sur MetaMask ! L’activation des contrats de swap intelligents permettra à MetaMask d’optimiser programmatiquement le processus contractuel pour vous aider à :" - }, "smartSwapsErrorNotEnoughFunds": { "message": "Fonds insuffisants pour souscrire un contrat de swap intelligent." }, @@ -3779,18 +3762,6 @@ "strong": { "message": "Robuste" }, - "stxBenefit1": { - "message": "Minimise les frais de transaction" - }, - "stxBenefit2": { - "message": "Réduit les échecs de transaction" - }, - "stxBenefit3": { - "message": "Élimine les blocages de transaction" - }, - "stxBenefit4": { - "message": "Empêcher le favoritisme" - }, "stxCancelled": { "message": "Le swap aurait échoué" }, @@ -4721,7 +4692,7 @@ }, "viewOnCustomBlockExplorer": { "message": "Afficher $1 à $2", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "Afficher $1 sur Etherscan", diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 9de035fa0577..e55d1f11604b 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -2562,21 +2562,6 @@ "notificationsMarkAllAsRead": { "message": "सभी को पढ़ा हुआ चिन्हित करें" }, - "notificationsOpenBetaSnapsActionText": { - "message": "ज्यादा जानें।" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "🎉 MetaMask Snaps के Open Beta की घोषणा करते हुए हमें खुशी का अनुभव हो रहा है!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "डेवलेपर कम्युनिटी द्वारा तैयार किए गए Snaps के साथ अपने वॉलेट को निजीकृत करें!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Snaps की मदद से आप MetaMask के साथ और भी बहुत कुछ कर सकते हैं - जैसे, ज़्यादा नेटवर्कों से जुड़ना, ट्रांजेक्शन से संबंधित जानकारी देखना और कस्टम नोटिफ़िकेशन्स पाना।" - }, - "notificationsOpenBetaSnapsTitle": { - "message": "पेश हैं MetaMask Snaps" - }, "numberOfNewTokensDetectedPlural": { "message": "इस अकाउंट में $1 के नए टोकन पाए गए", "description": "$1 is the number of new tokens detected" @@ -3267,6 +3252,10 @@ "securityAndPrivacy": { "message": "सुरक्षा और गोपनीयता" }, + "securityProviderPoweredBy": { + "message": "$1 द्वारा पावर्ड", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "ब्यौरा देखें" }, @@ -3491,12 +3480,6 @@ "smartContracts": { "message": "स्मार्ट कॉन्ट्रैक्ट्स" }, - "smartSwapsAreHere": { - "message": "स्मार्ट स्वैप यहां हैं!" - }, - "smartSwapsDescription": { - "message": "MetaMask के स्वैप अब और अधिक स्मार्ट हो गए हैं! इन हेतु सहायता के लिए स्मार्ट स्वैप को इनेबल करने से MetaMask आपके स्वैप को प्रोग्रामेटिक रूप से ऑप्टिमाइज कर पाएगा:" - }, "smartSwapsErrorNotEnoughFunds": { "message": "स्मार्ट स्वैप के लिए पर्याप्त फंड नहीं है।" }, @@ -3776,18 +3759,6 @@ "strong": { "message": "मजबूत" }, - "stxBenefit1": { - "message": "ट्रांसेक्शन लागतें मिनिमाइज़ करें" - }, - "stxBenefit2": { - "message": "ट्रांसेक्शन विफलताएं कम करें" - }, - "stxBenefit3": { - "message": "अटके हुए ट्रांसेक्शन को हटा दें" - }, - "stxBenefit4": { - "message": "फ़्रंट-रनिंग को रोकें" - }, "stxCancelled": { "message": "स्वैप विफल हो सकता था" }, @@ -4718,7 +4689,7 @@ }, "viewOnCustomBlockExplorer": { "message": "$1 को $2 पर देखें", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "Etherscan पर $1 देखें", diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index ef2cfabd1a53..e01944cd0a67 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -2562,21 +2562,6 @@ "notificationsMarkAllAsRead": { "message": "Tandai semua telah dibaca" }, - "notificationsOpenBetaSnapsActionText": { - "message": "Selengkapnya" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "🎉 Kami sangat gembira mengumumkan versi Open Beta dari MetaMask Snap!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "Personalisasikan dompet Anda dengan snap yang dibuat oleh komunitas pengembang!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Snap membantu Anda melakukan lebih banyak hal dengan MetaMask — seperti terhubung ke lebih banyak jaringan, melihat wawasan transaksi, dan mendapatkan notifikasi khusus." - }, - "notificationsOpenBetaSnapsTitle": { - "message": "Memperkenalkan MetaMask Snap" - }, "numberOfNewTokensDetectedPlural": { "message": "$1 token baru ditemukan di akun ini", "description": "$1 is the number of new tokens detected" @@ -3267,6 +3252,10 @@ "securityAndPrivacy": { "message": "Keamanan & privasi" }, + "securityProviderPoweredBy": { + "message": "Didukung oleh $1", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "Lihat detailnya" }, @@ -3491,12 +3480,6 @@ "smartContracts": { "message": "Kontrak cerdas" }, - "smartSwapsAreHere": { - "message": "Smart Swap telah hadir!" - }, - "smartSwapsDescription": { - "message": "MetaMask Swaps kini semakin pintar! Mengaktifkan Smart Swap akan mengizinkan MetaMask mengoptimalkan Swap secara terprogram untuk membantu:" - }, "smartSwapsErrorNotEnoughFunds": { "message": "Dana tidak cukup untuk pertukaran cerdas." }, @@ -3776,18 +3759,6 @@ "strong": { "message": "Kuat" }, - "stxBenefit1": { - "message": "Meminimalkan biaya transaksi" - }, - "stxBenefit2": { - "message": "Kurangi potensi kegagalan transaksi \t" - }, - "stxBenefit3": { - "message": "Hapus transaksi yang macet" - }, - "stxBenefit4": { - "message": "Cegah perilaku front running \t" - }, "stxCancelled": { "message": "Pertukaran akan gagal" }, @@ -4718,7 +4689,7 @@ }, "viewOnCustomBlockExplorer": { "message": "Lihat $1 di $2", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "Lihat $1 di Etherscan", diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 440314e5cef2..671eae4c90d7 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -2562,21 +2562,6 @@ "notificationsMarkAllAsRead": { "message": "すべて既読にする" }, - "notificationsOpenBetaSnapsActionText": { - "message": "詳細" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "🎉 MetaMask Snapsのオープンベータについてお知らせします!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "開発者コミュニティによって開発されたsnapで、ウォレットをカスタマイズできます!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Snapsを使えば、他のネットワークへの接続やトランザクションインサイトの表示、カスタム通知の取得など、MetaMaskでより多くのことができるようになります。" - }, - "notificationsOpenBetaSnapsTitle": { - "message": "MetaMask Snapsのご紹介" - }, "numberOfNewTokensDetectedPlural": { "message": "$1種類の新しいトークンがこのアカウントで見つかりました", "description": "$1 is the number of new tokens detected" @@ -3267,6 +3252,10 @@ "securityAndPrivacy": { "message": "セキュリティとプライバシー" }, + "securityProviderPoweredBy": { + "message": "データソース: $1", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "詳細を表示" }, @@ -3491,12 +3480,6 @@ "smartContracts": { "message": "スマートコントラクト" }, - "smartSwapsAreHere": { - "message": "スマートスワップの登場です!" - }, - "smartSwapsDescription": { - "message": "MetaMask Swapsがはるかに賢くなりました!スマートスワップを有効にすると、MetaMaskがプログラムに従ってスワップを最適化できるようになるため、以下のようなメリットがあります。" - }, "smartSwapsErrorNotEnoughFunds": { "message": "スマートスワップに必要な資金が不足しています。" }, @@ -3776,18 +3759,6 @@ "strong": { "message": "強" }, - "stxBenefit1": { - "message": "トランザクションコストを最小化" - }, - "stxBenefit2": { - "message": "トランザクションの失敗数を低減" - }, - "stxBenefit3": { - "message": "トランザクションの停滞を解消" - }, - "stxBenefit4": { - "message": "フロントランニングを防止" - }, "stxCancelled": { "message": "スワップが失敗するところでした" }, @@ -4718,7 +4689,7 @@ }, "viewOnCustomBlockExplorer": { "message": "$1を$2で表示", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "$1をEtherscanで表示", diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 42a6a46f54b2..02e18ddd1538 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -2562,21 +2562,6 @@ "notificationsMarkAllAsRead": { "message": "모두 읽음으로 표시" }, - "notificationsOpenBetaSnapsActionText": { - "message": "자세히 알아보기" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "🎉 MetaMask Snaps의 오픈 베타가 시작되었습니다!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "개발자 커뮤니티가 구축한 스냅으로 지갑을 개별 맞춤하세요!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "스냅은 추가 네트워크 연결, 트랜잭션 인사이트 확인, 커스텀 알림 받기 등 사용자가 MetaMask에서 더욱 다양한 기능을 사용할 수 있도록 합니다." - }, - "notificationsOpenBetaSnapsTitle": { - "message": "MetaMask Snaps 소개" - }, "numberOfNewTokensDetectedPlural": { "message": "계정에서 $1개의 새로운 토큰이 발견됨", "description": "$1 is the number of new tokens detected" @@ -3267,6 +3252,10 @@ "securityAndPrivacy": { "message": "보안 및 프라이버시" }, + "securityProviderPoweredBy": { + "message": "$1 제공", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "세부 정보 보기" }, @@ -3491,12 +3480,6 @@ "smartContracts": { "message": "스마트 계약" }, - "smartSwapsAreHere": { - "message": "스마트 스왑이 시작되었습니다!" - }, - "smartSwapsDescription": { - "message": "MetaMask 스왑이 더욱 스마트해졌습니다! 스마트 스왑을 활성화하면 MetaMask가 프로그램을 통해 스왑을 최적화하여 다음과 같은 활동에 도움을 드립니다." - }, "smartSwapsErrorNotEnoughFunds": { "message": "스마트 스왑 자금 부족" }, @@ -3776,18 +3759,6 @@ "strong": { "message": "강함" }, - "stxBenefit1": { - "message": "트랜잭션 비용 최소화하기" - }, - "stxBenefit2": { - "message": "트랜잭션 실패 줄이기" - }, - "stxBenefit3": { - "message": "중단된 트랜잭션 제거하기" - }, - "stxBenefit4": { - "message": "프런트 러닝 방지" - }, "stxCancelled": { "message": "스왑이 실패했을 것입니다" }, @@ -4718,7 +4689,7 @@ }, "viewOnCustomBlockExplorer": { "message": "$2에서 $1 보기", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "Etherscan에서 $1 보기", diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index efd2c1598c91..cc46fcce77e9 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -2562,21 +2562,6 @@ "notificationsMarkAllAsRead": { "message": "Marcar todas como lidas" }, - "notificationsOpenBetaSnapsActionText": { - "message": "Saiba mais" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "🎉 Estamos empolgados de anunciar o Beta Aberto do MetaMask Snaps!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "Personalize sua carteira com snaps criados pela comunidade de desenvolvedores!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Os snaps ajudam a fazer mais com a MetaMask, como conectar-se a mais redes, ver insights de transações e receber notificações personalizadas." - }, - "notificationsOpenBetaSnapsTitle": { - "message": "Apresentamos o MetaMask Snaps" - }, "numberOfNewTokensDetectedPlural": { "message": "$1 novos tokens encontrados nesta conta", "description": "$1 is the number of new tokens detected" @@ -3271,6 +3256,10 @@ "securityAndPrivacy": { "message": "Segurança e Privacidade" }, + "securityProviderPoweredBy": { + "message": "Com tecnologia da $1", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "Ver detalhes" }, @@ -3495,12 +3484,6 @@ "smartContracts": { "message": "Contratos inteligentes" }, - "smartSwapsAreHere": { - "message": "As trocas inteligentes chegaram!" - }, - "smartSwapsDescription": { - "message": "As trocas na MetaMask ficaram muito mais inteligentes! Ativar as trocas inteligentes permitirá que a MetaMask otimize programaticamente sua troca para ajudar:" - }, "smartSwapsErrorNotEnoughFunds": { "message": "Fundos insuficientes para uma troca inteligente." }, @@ -3780,18 +3763,6 @@ "strong": { "message": "Forte" }, - "stxBenefit1": { - "message": "Minimize os custos das transações" - }, - "stxBenefit2": { - "message": "Reduza as falhas nas transações" - }, - "stxBenefit3": { - "message": "Elimine transações travadas" - }, - "stxBenefit4": { - "message": "Previna o front-running (uso de informações privilegiadas para negociações)" - }, "stxCancelled": { "message": "A troca teria falhado" }, @@ -4722,7 +4693,7 @@ }, "viewOnCustomBlockExplorer": { "message": "Ver $1 em $2", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "Ver $1 no Etherscan", diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index c2fe68724a82..1cb18170dda1 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -2547,7 +2547,7 @@ }, "viewOnCustomBlockExplorer": { "message": "Ver $1 em $2", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "Ver $1 no Etherscan", diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 5f547423e67c..892a68f3ec20 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -2562,21 +2562,6 @@ "notificationsMarkAllAsRead": { "message": "Отметить все как прочитанные" }, - "notificationsOpenBetaSnapsActionText": { - "message": "Подробнее" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "🎉 Мы рады объявить об открытой бета-версии MetaMask Snaps!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "Персонализируйте свой кошелек с помощью snaps, созданных сообществом разработчиков!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Snaps помогают вам делать больше с MetaMask — например, подключаться к большему количеству сетей, просматривать статистику транзакций и получать настраиваемые уведомления." - }, - "notificationsOpenBetaSnapsTitle": { - "message": "Представляем MetaMask Snaps" - }, "numberOfNewTokensDetectedPlural": { "message": "$1 новых токена(-ов) найдены в этом счете", "description": "$1 is the number of new tokens detected" @@ -3267,6 +3252,10 @@ "securityAndPrivacy": { "message": "Безопасность и конфиденциальность" }, + "securityProviderPoweredBy": { + "message": "На основе $1", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "См. подробности" }, @@ -3491,12 +3480,6 @@ "smartContracts": { "message": "Смарт-контракты" }, - "smartSwapsAreHere": { - "message": "Появились смарт-свопы!" - }, - "smartSwapsDescription": { - "message": "Свопы MetaMask стали намного умнее! Включение смарт-свопов позволит MetaMask программно оптимизировать ваш своп, чтобы помочь:" - }, "smartSwapsErrorNotEnoughFunds": { "message": "Недостаточно средств для смарт-свопа." }, @@ -3776,18 +3759,6 @@ "strong": { "message": "Сильный" }, - "stxBenefit1": { - "message": "Минимизируйте транзакционные издержки" - }, - "stxBenefit2": { - "message": "Уменьшите количество сбоев транзакций" - }, - "stxBenefit3": { - "message": "Устраните зависание транзакций" - }, - "stxBenefit4": { - "message": "Предотвратите опережение" - }, "stxCancelled": { "message": "Своп бы не удался" }, @@ -4718,7 +4689,7 @@ }, "viewOnCustomBlockExplorer": { "message": "Смотреть $1 в $2", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "Смотреть 1$ на Etherscan", diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 29f9aad4a139..b56219510f25 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -2562,21 +2562,6 @@ "notificationsMarkAllAsRead": { "message": "Markahan ang lahat bilang nabasa na" }, - "notificationsOpenBetaSnapsActionText": { - "message": "Matuto pa" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "🎉 Nasasabik kaming ianunsiyo ang Open Beta ng MetaMask Snaps!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "Ipersonalisa ang iyong wallet gamit ang mga snap na binuo ng developer community!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Ang Snaps ay makakatulong sa iyo na maraming magawa sa MetaMask -- tulad ng kumonekta sa maraming network, tingnan ang mga insight sa transaksyon, at makakuha ng custom na notipikasyon." - }, - "notificationsOpenBetaSnapsTitle": { - "message": "Ipinakikilala ang MetaMask Snaps" - }, "numberOfNewTokensDetectedPlural": { "message": "$1 (na) bagong token ang nakita sa account na ito", "description": "$1 is the number of new tokens detected" @@ -3267,6 +3252,10 @@ "securityAndPrivacy": { "message": "Seguridad at pagkapribado" }, + "securityProviderPoweredBy": { + "message": "Pinapagana ng $1", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "Tingnan ang mga detalye" }, @@ -3491,12 +3480,6 @@ "smartContracts": { "message": "Mga smart na kontrata" }, - "smartSwapsAreHere": { - "message": "Nandito na ang mga Smart Swap!" - }, - "smartSwapsDescription": { - "message": "Mas humusay pa ang mga MetaMask Swap! Ang pag-enable sa mga Smart Swap ay magbibigay-daan sa MetaMask na i-optimize ang iyong Swap gamit ang program para makatulong na:" - }, "smartSwapsErrorNotEnoughFunds": { "message": "Hindi sapat ang pondo para sa smart swap." }, @@ -3776,18 +3759,6 @@ "strong": { "message": "Mahirap" }, - "stxBenefit1": { - "message": "Bawasan ang mga gastos sa transaksyon" - }, - "stxBenefit2": { - "message": "Bawasan ang mga nabigong transaksyon" - }, - "stxBenefit3": { - "message": "Alisin ang mga hindi umuusad na transaksyon" - }, - "stxBenefit4": { - "message": "Pigilan ang front-running" - }, "stxCancelled": { "message": "Nabigo sana ang pag-swap kung" }, @@ -4718,7 +4689,7 @@ }, "viewOnCustomBlockExplorer": { "message": "Tingnan ang $1 sa $2", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "Tingnan ang $1 sa Etherscan", diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 85e13fffacd8..13163c4a498f 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -2562,21 +2562,6 @@ "notificationsMarkAllAsRead": { "message": "Tümünü okundu olarak işaretle" }, - "notificationsOpenBetaSnapsActionText": { - "message": "Daha fazla bilgi edinin" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "MetaMask Snaps'in Açık Beta sürümünü duyurmaktan heyecan duyuyoruz!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "Geliştirici topluluğu tarafından oluşturulan snap'lerle cüzdanınızı kişiselleştirin!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Snap'ler MetaMask ile daha fazla ağa bağlanma, işlem içgörülerini görme ve kişisel bildirimler alma gibi daha çok şey yapmanıza yardımcı olur." - }, - "notificationsOpenBetaSnapsTitle": { - "message": "MetaMask Snaps tanıtımı" - }, "numberOfNewTokensDetectedPlural": { "message": "Bu hesapta $1 yeni token bulundu", "description": "$1 is the number of new tokens detected" @@ -3267,6 +3252,10 @@ "securityAndPrivacy": { "message": "Güvenlik ve gizlilik" }, + "securityProviderPoweredBy": { + "message": "$1 hizmetidir", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "Ayrıntılara bakın" }, @@ -3491,12 +3480,6 @@ "smartContracts": { "message": "Akıllı sözleşmeler" }, - "smartSwapsAreHere": { - "message": "Akıllı Swap'lar burada!" - }, - "smartSwapsDescription": { - "message": "MetaMask Swap işlemleri artık çok daha akıllı! Akıllı Swap'ları etkinleştirmek, MetaMask'in aşağıdakilere yardımcı olmak için Swap'ini programlı olarak optimize etmesine olanak tanır:" - }, "smartSwapsErrorNotEnoughFunds": { "message": "Akıllı swap için yeterli para yok." }, @@ -3776,18 +3759,6 @@ "strong": { "message": "Güçlü" }, - "stxBenefit1": { - "message": "İşlem maliyetlerini en aza indir" - }, - "stxBenefit2": { - "message": "İşlem hatalarını azalt" - }, - "stxBenefit3": { - "message": "Sıkışmış işlemleri ortadan kaldır" - }, - "stxBenefit4": { - "message": "Önden çalıştırmayı engelle" - }, "stxCancelled": { "message": "Swap işlemi başarısız olurdu" }, @@ -4718,7 +4689,7 @@ }, "viewOnCustomBlockExplorer": { "message": "$1 ögesini $2 üzerinde görüntüle", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "Etherscan'de $1 görüntüle", diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 82903c7710df..fd6df9be7c59 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -2562,21 +2562,6 @@ "notificationsMarkAllAsRead": { "message": "Đánh dấu đã đọc tất cả" }, - "notificationsOpenBetaSnapsActionText": { - "message": "Tìm hiểu thêm" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "Chúng tôi vui mừng thông báo về phiên bản Open Beta của MetaMask Snaps!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "Cá nhân hóa ví của bạn bằng các snap do cộng đồng nhà lập trình xây dựng!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Snap giúp bạn làm được nhiều việc hơn với MetaMask — chẳng hạn như kết nối với nhiều mạng hơn, xem thông tin chuyên sâu về giao dịch và nhận thông báo tùy chỉnh." - }, - "notificationsOpenBetaSnapsTitle": { - "message": "Giới thiệu MetaMask Snaps" - }, "numberOfNewTokensDetectedPlural": { "message": "Tìm thấy $1 token mới trong tài khoản này", "description": "$1 is the number of new tokens detected" @@ -3267,6 +3252,10 @@ "securityAndPrivacy": { "message": "Bảo mật và quyền riêng tư" }, + "securityProviderPoweredBy": { + "message": "Được cung cấp bởi $1", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "Xem chi tiết" }, @@ -3491,12 +3480,6 @@ "smartContracts": { "message": "Hợp đồng thông minh" }, - "smartSwapsAreHere": { - "message": "Hoán đổi thông minh đã ra mắt!" - }, - "smartSwapsDescription": { - "message": "Tính năng Hoán đổi của MetaMask nay đã thông minh hơn rất nhiều! Kích hoạt Hoán đổi thông minh sẽ cho phép MetaMask tối ưu quy trình Hoán đổi để giúp bạn:" - }, "smartSwapsErrorNotEnoughFunds": { "message": "Không có đủ tiền để thực hiện hoán đổi thông minh." }, @@ -3776,18 +3759,6 @@ "strong": { "message": "Mạnh" }, - "stxBenefit1": { - "message": "Giảm thiểu chi phí giao dịch" - }, - "stxBenefit2": { - "message": "Giảm tỷ lệ thất bại khi giao dịch" - }, - "stxBenefit3": { - "message": "Loại bỏ các giao dịch bị mắc kẹt" - }, - "stxBenefit4": { - "message": "Ngăn chặn giao dịch chạy trước" - }, "stxCancelled": { "message": "Hoán đổi sẽ thất bại" }, @@ -4718,7 +4689,7 @@ }, "viewOnCustomBlockExplorer": { "message": "Xem $1 tại $2", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "Xem $1 trên Etherscan", diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 788e699e4126..303dad1975f3 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -2562,21 +2562,6 @@ "notificationsMarkAllAsRead": { "message": "将所有标记为已读" }, - "notificationsOpenBetaSnapsActionText": { - "message": "了解详情" - }, - "notificationsOpenBetaSnapsDescriptionOne": { - "message": "🎉 我们很高兴宣布推出 MetaMask Snaps 公开测试版!" - }, - "notificationsOpenBetaSnapsDescriptionThree": { - "message": "使用开发者社群构建的 snap,个性化您的钱包!" - }, - "notificationsOpenBetaSnapsDescriptionTwo": { - "message": "Snaps 可以帮助您利用 MetaMask 做更多事情,例如连接到更多网络、查看交易见解,以及获取自定义通知。" - }, - "notificationsOpenBetaSnapsTitle": { - "message": "介绍 MetaMask Snaps" - }, "numberOfNewTokensDetectedPlural": { "message": "在此账户中找到$1枚新代币", "description": "$1 is the number of new tokens detected" @@ -3267,6 +3252,10 @@ "securityAndPrivacy": { "message": "安全和隐私" }, + "securityProviderPoweredBy": { + "message": "由 $1 提供支持", + "description": "The security provider that is providing data" + }, "seeDetails": { "message": "查看详情" }, @@ -3491,12 +3480,6 @@ "smartContracts": { "message": "智能合约" }, - "smartSwapsAreHere": { - "message": "智能兑换已推出!" - }, - "smartSwapsDescription": { - "message": "MetaMask Swaps 变得更加智能!启用智能兑换使得 MetaMask 在编程方面让您的兑换体验更加优化,有助于:" - }, "smartSwapsErrorNotEnoughFunds": { "message": "没有足够的资金进行智能兑换。" }, @@ -3776,18 +3759,6 @@ "strong": { "message": "强" }, - "stxBenefit1": { - "message": "将交易成本减至最低" - }, - "stxBenefit2": { - "message": "减少交易失败" - }, - "stxBenefit3": { - "message": "消除卡住的交易" - }, - "stxBenefit4": { - "message": "防止抢先交易" - }, "stxCancelled": { "message": "交换就会失败" }, @@ -4718,7 +4689,7 @@ }, "viewOnCustomBlockExplorer": { "message": "在 $2 上查看 $1", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "在 Etherscan 上查看 $1", diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index ce0fc3d38a96..57c40475e47b 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -1483,7 +1483,7 @@ }, "viewOnCustomBlockExplorer": { "message": "在 $1 瀏覽", - "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL" + "description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Explorer URL" }, "viewOnEtherscan": { "message": "在 Etherscan 上瀏覽", diff --git a/app/images/arbitrum.svg b/app/images/arbitrum.svg index 8863afe882c8..9c19a48f2d72 100644 --- a/app/images/arbitrum.svg +++ b/app/images/arbitrum.svg @@ -1,12 +1,40 @@ - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/images/introducing-snaps.svg b/app/images/introducing-snaps.svg deleted file mode 100644 index 84b0d5918f10..000000000000 --- a/app/images/introducing-snaps.svg +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/images/logo/metamask-smart-transactions.png b/app/images/logo/metamask-smart-transactions.png deleted file mode 100644 index 6eed753120bf..000000000000 Binary files a/app/images/logo/metamask-smart-transactions.png and /dev/null differ diff --git a/app/images/sell_button_whatsnew.png b/app/images/sell_button_whatsnew.png deleted file mode 100644 index fea8c079cf7a..000000000000 Binary files a/app/images/sell_button_whatsnew.png and /dev/null differ diff --git a/app/manifest/v2/_base.json b/app/manifest/v2/_base.json index 68ed57a4e27c..4197c535972e 100644 --- a/app/manifest/v2/_base.json +++ b/app/manifest/v2/_base.json @@ -67,6 +67,7 @@ "http://localhost:8545/", "https://*.infura.io/", "https://*.codefi.network/", + "https://*.cx.metamask.io/", "https://chainid.network/chains.json", "https://lattice.gridplus.io/*", "activeTab", diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index ef959ce919ed..92ebef3cebfa 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -70,6 +70,7 @@ export default class AppStateController extends EventEmitter { // States used for displaying the changed network toast switchedNetworkDetails: null, switchedNetworkNeverShowMessage: false, + currentExtensionPopupId: 0, }); this.timer = null; @@ -405,7 +406,17 @@ export default class AppStateController extends EventEmitter { } /** - * Track which network MetaMask has just automatically switched to, or call this with `null` to clear that state. + * Sets a unique ID for the current extension popup + * + * @param currentExtensionPopupId + */ + setCurrentExtensionPopupId(currentExtensionPopupId) { + this.store.updateState({ currentExtensionPopupId }); + } + + /** + * Sets an object with networkName and appName + * or `null` if the message is meant to be cleared * * @param {{ origin: string, networkClientId: string } | null} switchedNetworkDetails - Details about the network that MetaMask just switched to. */ diff --git a/app/scripts/controllers/authentication/authentication-controller.ts b/app/scripts/controllers/authentication/authentication-controller.ts index 585538086d9c..a8a3bb15c7bf 100644 --- a/app/scripts/controllers/authentication/authentication-controller.ts +++ b/app/scripts/controllers/authentication/authentication-controller.ts @@ -26,25 +26,22 @@ type SessionProfile = { metametricsId: string; }; +type SessionData = { + /** profile - anonymous profile data for the given logged in user */ + profile: SessionProfile; + /** accessToken - used to make requests authorized endpoints */ + accessToken: string; + /** expiresIn - string date to determine if new access token is required */ + expiresIn: string; +}; + export type AuthenticationControllerState = { /** * Global isSignedIn state. - * Can be used to determine if "Profile Syncing" is enabled or not. + * Can be used to determine if "Profile Syncing" is enabled. */ isSignedIn: boolean; - - /** - * These tokens & session data will expire every 30 mins. - * - * @property profile - anonymous profile data for the given logged in user - * @property accessToken - used to make requests authorized endpoints - * @property expiresIn - string date to determine if new access token is required - */ - sessionData?: { - profile: SessionProfile; - accessToken: string; - expiresIn: string; - }; + sessionData?: SessionData; }; const defaultState: AuthenticationControllerState = { isSignedIn: false }; const metadata: StateMetadata = { @@ -130,7 +127,7 @@ export default class AuthenticationController extends BaseController< public async getBearerToken(): Promise { this.#assertLoggedIn(); - if (this.#hasValidAuthTokens(this.state.sessionData)) { + if (this.#hasValidSession(this.state.sessionData)) { return this.state.sessionData.accessToken; } @@ -139,14 +136,15 @@ export default class AuthenticationController extends BaseController< } /** - * NOTE this will be changed to use profileId in future task. + * Will return a session profile. + * Throws if a user is not logged in. * - * @returns the identifier id + * @returns profile for the session. */ public async getSessionProfile(): Promise { this.#assertLoggedIn(); - if (this.#hasValidAuthTokens(this.state.sessionData)) { + if (this.#hasValidSession(this.state.sessionData)) { return this.state.sessionData.profile; } @@ -219,16 +217,14 @@ export default class AuthenticationController extends BaseController< } } - #hasValidAuthTokens( - ephemeralTokens: AuthenticationControllerState['sessionData'], - ): ephemeralTokens is NonNullable< - AuthenticationControllerState['sessionData'] - > { - if (!ephemeralTokens) { + #hasValidSession( + sessionData: SessionData | undefined, + ): sessionData is SessionData { + if (!sessionData) { return false; } - const prevDate = Date.parse(ephemeralTokens.expiresIn); + const prevDate = Date.parse(sessionData.expiresIn); if (isNaN(prevDate)) { return false; } diff --git a/app/scripts/controllers/authentication/services.ts b/app/scripts/controllers/authentication/services.ts index 272bffc51339..345302e905cd 100644 --- a/app/scripts/controllers/authentication/services.ts +++ b/app/scripts/controllers/authentication/services.ts @@ -1,6 +1,6 @@ const AUTH_ENDPOINT = process.env.AUTH_API || ''; export const AUTH_NONCE_ENDPOINT = `${AUTH_ENDPOINT}/api/v2/nonce`; -export const AUTH_LOGIN_ENDPOINT = `${AUTH_ENDPOINT}/api/v2/snaps/login`; +export const AUTH_LOGIN_ENDPOINT = `${AUTH_ENDPOINT}/api/v2/srp/login`; const OIDC_ENDPOINT = process.env.OIDC_API || ''; export const OIDC_TOKENS_ENDPOINT = `${OIDC_ENDPOINT}/oauth2/token`; diff --git a/app/scripts/controllers/onboarding.test.ts b/app/scripts/controllers/onboarding.test.ts new file mode 100644 index 000000000000..61b9cf8de589 --- /dev/null +++ b/app/scripts/controllers/onboarding.test.ts @@ -0,0 +1,57 @@ +import { FirstTimeFlowType } from '../../../shared/constants/onboarding'; +import OnboardingController, { OnboardingControllerState } from './onboarding'; + +describe('OnboardingController', () => { + let onboardingController: OnboardingController; + + beforeEach(() => { + onboardingController = new OnboardingController({ + initState: { + seedPhraseBackedUp: null, + firstTimeFlowType: null, + completedOnboarding: false, + onboardingTabs: {}, + }, + }); + }); + + it('should set the seedPhraseBackedUp property', () => { + const newSeedPhraseBackUpState = true; + onboardingController.setSeedPhraseBackedUp(newSeedPhraseBackUpState); + const state: OnboardingControllerState = + onboardingController.store.getState(); + expect(state.seedPhraseBackedUp).toBe(newSeedPhraseBackUpState); + }); + + it('should set the firstTimeFlowType property', () => { + const type: FirstTimeFlowType = FirstTimeFlowType.create; + onboardingController.setFirstTimeFlowType(type); + const state: OnboardingControllerState = + onboardingController.store.getState(); + expect(state.firstTimeFlowType).toBe(type); + }); + + it('should register a site for onboarding', async () => { + const location = 'example.com'; + const tabId = '123'; + await onboardingController.registerOnboarding(location, tabId); + const state: OnboardingControllerState = + onboardingController.store.getState(); + expect(state.onboardingTabs?.[location]).toBe(tabId); + }); + + it('should skip update state if the location is already onboard', async () => { + const location = 'example.com'; + const tabId = '123'; + await onboardingController.registerOnboarding(location, tabId); + const state: OnboardingControllerState = + onboardingController.store.getState(); + const updateStateSpy = jest.spyOn( + onboardingController.store, + 'updateState', + ); + + expect(state.onboardingTabs?.[location]).toBe(tabId); + expect(updateStateSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index e01cc5129af4..01404fe9f743 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -91,6 +91,7 @@ export default class PreferencesController { showExtensionInFullSizeView: false, showFiatInTestnets: false, showTestNetworks: false, + smartTransactionsOptInStatus: null, // null means we will show the Smart Transactions opt-in modal to a user if they are eligible useNativeCurrencyAsPrimaryCurrency: true, hideZeroBalanceTokens: false, petnamesEnabled: true, @@ -119,7 +120,6 @@ export default class PreferencesController { this.store = new ObservableStore(initState); this.store.setMaxListeners(13); - this.tokenListController = opts.tokenListController; opts.onKeyringStateChange((state) => { const accounts = new Set(); @@ -218,13 +218,6 @@ export default class PreferencesController { */ setUseTokenDetection(val) { this.store.updateState({ useTokenDetection: val }); - this.tokenListController.updatePreventPollingOnNetworkRestart(!val); - if (val) { - this.tokenListController.start(); - } else { - this.tokenListController.clearingTokenListData(); - this.tokenListController.stop(); - } } /** diff --git a/app/scripts/lib/backup.test.js b/app/scripts/lib/backup.test.js index ceab30f28188..850e6a719467 100644 --- a/app/scripts/lib/backup.test.js +++ b/app/scripts/lib/backup.test.js @@ -161,6 +161,7 @@ const jsonData = JSON.stringify({ showExtensionInFullSizeView: false, showFiatInTestnets: false, showTestNetworks: true, + smartTransactionsOptInStatus: false, useNativeCurrencyAsPrimaryCurrency: true, }, ipfsGateway: 'dweb.link', diff --git a/app/scripts/lib/encryptor-factory.test.ts b/app/scripts/lib/encryptor-factory.test.ts new file mode 100644 index 000000000000..729162ff2ecb --- /dev/null +++ b/app/scripts/lib/encryptor-factory.test.ts @@ -0,0 +1,174 @@ +import * as browserPassworder from '@metamask/browser-passworder'; +import { encryptorFactory } from './encryptor-factory'; + +jest.mock('@metamask/browser-passworder'); + +const mockIterations = 100; +const mockPassword = 'password'; +const mockData = 'data'; + +describe('encryptorFactory', () => { + afterEach(async () => { + jest.resetAllMocks(); + }); + + const mockBrowserPassworder = browserPassworder as jest.Mocked< + typeof browserPassworder + >; + + it('should return an object with browser passworder methods', () => { + const encryptor = encryptorFactory(mockIterations); + + [ + 'encrypt', + 'encryptWithDetail', + 'encryptWithKey', + 'decrypt', + 'decryptWithDetail', + 'decryptWithKey', + 'keyFromPassword', + 'importKey', + 'isVaultUpdated', + ].forEach((method) => { + expect(encryptor).toHaveProperty(method); + }); + }); + + describe('encrypt', () => { + it('should call browser-passworder.encrypt with the given password, data, and iterations', async () => { + const encryptor = encryptorFactory(mockIterations); + + await encryptor.encrypt(mockPassword, mockData); + + expect(mockBrowserPassworder.encrypt).toHaveBeenCalledWith( + mockPassword, + mockData, + undefined, + undefined, + { + algorithm: 'PBKDF2', + params: { + iterations: mockIterations, + }, + }, + ); + }); + + it('should return the result of browser-passworder.encrypt', async () => { + const encryptor = encryptorFactory(mockIterations); + const mockResult = 'result'; + mockBrowserPassworder.encrypt.mockResolvedValue(mockResult); + + expect(await encryptor.encrypt(mockPassword, mockData)).toBe(mockResult); + }); + }); + + describe('encryptWithDetail', () => { + it('should call browser-passworder.encryptWithDetail with the given password, object, and iterations', async () => { + const encryptor = encryptorFactory(mockIterations); + + await encryptor.encryptWithDetail(mockPassword, { foo: 'bar' }, 'salt'); + + expect(mockBrowserPassworder.encryptWithDetail).toHaveBeenCalledWith( + mockPassword, + { foo: 'bar' }, + 'salt', + { + algorithm: 'PBKDF2', + params: { + iterations: mockIterations, + }, + }, + ); + }); + + it('should return the result of browser-passworder.encryptWithDetail', async () => { + const encryptor = encryptorFactory(mockIterations); + const mockResult = { + vault: 'vault', + exportedKeyString: 'salt', + }; + mockBrowserPassworder.encryptWithDetail.mockResolvedValue(mockResult); + + expect( + await encryptor.encryptWithDetail(mockPassword, { foo: 'bar' }, 'salt'), + ).toBe(mockResult); + }); + }); + + describe('decrypt', () => { + it('should call browser-passworder.decrypt with the given password, data, and iterations', async () => { + const encryptor = encryptorFactory(mockIterations); + + await encryptor.decrypt(mockPassword, mockData); + + expect(mockBrowserPassworder.decrypt).toHaveBeenCalledWith( + mockPassword, + mockData, + ); + }); + + it('should return the result of browser-passworder.decrypt', async () => { + const encryptor = encryptorFactory(mockIterations); + const mockResult = 'result'; + mockBrowserPassworder.decrypt.mockResolvedValue(mockResult); + + expect(await encryptor.decrypt(mockPassword, mockData)).toBe(mockResult); + }); + }); + + describe('decryptWithDetail', () => { + it('should call browser-passworder.decryptWithDetail with the given password and object', async () => { + const encryptor = encryptorFactory(mockIterations); + + await encryptor.decryptWithDetail(mockPassword, mockData); + + expect(mockBrowserPassworder.decryptWithDetail).toHaveBeenCalledWith( + mockPassword, + mockData, + ); + }); + + it('should return the result of browser-passworder.decryptWithDetail', async () => { + const encryptor = encryptorFactory(mockIterations); + const mockResult = { + exportedKeyString: 'key', + vault: 'data', + salt: 'salt', + }; + mockBrowserPassworder.decryptWithDetail.mockResolvedValue(mockResult); + + expect(await encryptor.decryptWithDetail(mockPassword, mockData)).toBe( + mockResult, + ); + }); + }); + + describe('isVaultUpdated', () => { + it('should call browser-passworder.isVaultUpdated with the given vault and iterations', () => { + const encryptor = encryptorFactory(mockIterations); + const mockVault = 'vault'; + + encryptor.isVaultUpdated(mockVault); + + expect(mockBrowserPassworder.isVaultUpdated).toHaveBeenCalledWith( + mockVault, + { + algorithm: 'PBKDF2', + params: { + iterations: mockIterations, + }, + }, + ); + }); + + it('should return the result of browser-passworder.isVaultUpdated', () => { + const encryptor = encryptorFactory(mockIterations); + const mockResult = false; + const mockVault = 'vault'; + mockBrowserPassworder.isVaultUpdated.mockReturnValue(mockResult); + + expect(encryptor.isVaultUpdated(mockVault)).toBe(mockResult); + }); + }); +}); diff --git a/app/scripts/lib/encryptor-factory.ts b/app/scripts/lib/encryptor-factory.ts index 68f8ee42307e..b4eac5e0e6eb 100644 --- a/app/scripts/lib/encryptor-factory.ts +++ b/app/scripts/lib/encryptor-factory.ts @@ -57,7 +57,7 @@ const encryptWithDetailFactory = * @param iterations - The number of iterations to use for the PBKDF2 algorithm. * @returns A function that checks if the vault was encrypted with the given number of iterations. */ -const isVaultUpdatedFactory = (iterations: number) => async (vault: string) => +const isVaultUpdatedFactory = (iterations: number) => (vault: string) => isVaultUpdated(vault, { algorithm: 'PBKDF2', params: { diff --git a/app/scripts/lib/ppom/ppom-middleware.ts b/app/scripts/lib/ppom/ppom-middleware.ts index 54e31f6704aa..8992b3a37c07 100644 --- a/app/scripts/lib/ppom/ppom-middleware.ts +++ b/app/scripts/lib/ppom/ppom-middleware.ts @@ -31,7 +31,7 @@ const CONFIRMATION_METHODS = Object.freeze([ export const SUPPORTED_CHAIN_IDS: Hex[] = [ CHAIN_IDS.ARBITRUM, CHAIN_IDS.AVALANCHE, - // CHAIN_IDS.BASE, + CHAIN_IDS.BASE, CHAIN_IDS.BSC, CHAIN_IDS.LINEA_MAINNET, CHAIN_IDS.MAINNET, diff --git a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-open-add-hardware-wallet.js b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-open-add-hardware-wallet.js index 2518a326a9ef..61af6eb43fe8 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-open-add-hardware-wallet.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-open-add-hardware-wallet.js @@ -1,4 +1,4 @@ -import { RPC_ALLOWED_ORIGINS } from '@metamask-institutional/rpc-allowlist'; +import { isAllowedRPCOrigin } from '@metamask-institutional/rpc-allowlist'; import { MESSAGE_TYPE } from '../../../../../../shared/constants/app'; const mmiOpenAddHardwareWallet = { @@ -30,14 +30,8 @@ async function mmiOpenAddHardwareWalletHandler( { handleMmiOpenAddHardwareWallet }, ) { try { - let validUrl = false; - // if (!RPC_ALLOWED_ORIGINS[MESSAGE_TYPE.MMI_PORTFOLIO].includes(req.origin)) { - RPC_ALLOWED_ORIGINS[MESSAGE_TYPE.MMI_PORTFOLIO].forEach((regexp) => { - // eslint-disable-next-line require-unicode-regexp - if (regexp.test(req.origin)) { - validUrl = true; - } - }); + const validUrl = isAllowedRPCOrigin(MESSAGE_TYPE.MMI_PORTFOLIO, req.origin); + // eslint-disable-next-line no-negated-condition if (!validUrl) { throw new Error('Unauthorized'); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-portfolio.js b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-portfolio.js index e52599ee9736..cbe96127682f 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-portfolio.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-portfolio.js @@ -1,4 +1,4 @@ -import { RPC_ALLOWED_ORIGINS } from '@metamask-institutional/rpc-allowlist'; +import { isAllowedRPCOrigin } from '@metamask-institutional/rpc-allowlist'; import { MESSAGE_TYPE } from '../../../../../../shared/constants/app'; const mmiPortfolio = { @@ -36,13 +36,8 @@ async function mmiPortfolioHandler( { handleMmiDashboardData }, ) { try { - let validUrl = false; - RPC_ALLOWED_ORIGINS[MESSAGE_TYPE.MMI_PORTFOLIO].forEach((regexp) => { - // eslint-disable-next-line require-unicode-regexp - if (regexp.test(req.origin)) { - validUrl = true; - } - }); + const validUrl = isAllowedRPCOrigin(MESSAGE_TYPE.MMI_PORTFOLIO, req.origin); + // eslint-disable-next-line no-negated-condition if (!validUrl) { throw new Error('Unauthorized'); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-set-account-and-network.js b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-set-account-and-network.js index db880691536e..9dca692601a3 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-set-account-and-network.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-set-account-and-network.js @@ -1,5 +1,5 @@ import { ethErrors } from 'eth-rpc-errors'; -import { RPC_ALLOWED_ORIGINS } from '@metamask-institutional/rpc-allowlist'; +import { isAllowedRPCOrigin } from '@metamask-institutional/rpc-allowlist'; import { MESSAGE_TYPE } from '../../../../../../shared/constants/app'; const mmiSetAccountAndNetwork = { @@ -37,13 +37,8 @@ async function mmiSetAccountAndNetworkHandler( { handleMmiSetAccountAndNetwork }, ) { try { - let validUrl = false; - RPC_ALLOWED_ORIGINS[MESSAGE_TYPE.MMI_PORTFOLIO].forEach((regexp) => { - // eslint-disable-next-line require-unicode-regexp - if (regexp.test(req.origin)) { - validUrl = true; - } - }); + const validUrl = isAllowedRPCOrigin(MESSAGE_TYPE.MMI_PORTFOLIO, req.origin); + // eslint-disable-next-line no-negated-condition if (!validUrl) { throw new Error('Unauthorized'); diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 4430ed40b7e2..0a2d65a09a7b 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -86,6 +86,7 @@ export const SENTRY_BACKGROUND_STATE = { browserEnvironment: true, connectedStatusPopoverHasBeenShown: true, currentPopupId: false, + currentExtensionPopupId: false, defaultHomeActiveTabName: true, fullScreenGasPollTokens: true, hadAdvancedGasFeesSetPriorToMigration92_3: true, @@ -221,6 +222,7 @@ export const SENTRY_BACKGROUND_STATE = { showExtensionInFullSizeView: true, showFiatInTestnets: true, showTestNetworks: true, + smartTransactionsOptInStatus: true, useNativeCurrencyAsPrimaryCurrency: true, petnamesEnabled: true, }, diff --git a/app/scripts/lib/transaction/metrics.test.ts b/app/scripts/lib/transaction/metrics.test.ts index 9163feefb291..d1d3f5e88091 100644 --- a/app/scripts/lib/transaction/metrics.test.ts +++ b/app/scripts/lib/transaction/metrics.test.ts @@ -72,6 +72,8 @@ const mockTransactionMetricsRequest = { // eslint-disable-next-line @typescript-eslint/no-explicit-any snapAndHardwareMessenger: jest.fn() as any, trackEvent: jest.fn(), + getIsSmartTransaction: jest.fn(), + getSmartTransactionByMinedTxHash: jest.fn(), } as TransactionMetricsRequest; describe('Transaction metrics', () => { diff --git a/app/scripts/lib/transaction/metrics.ts b/app/scripts/lib/transaction/metrics.ts index 085f542b6ee7..fa0a1469578e 100644 --- a/app/scripts/lib/transaction/metrics.ts +++ b/app/scripts/lib/transaction/metrics.ts @@ -7,6 +7,7 @@ import { TransactionMeta, TransactionType, } from '@metamask/transaction-controller'; +import { SmartTransaction } from '@metamask/smart-transactions-controller/dist/types'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import { determineTransactionAssetType, @@ -38,6 +39,7 @@ import { ///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { getBlockaidMetricsProps } from '../../../../ui/helpers/utils/metrics'; ///: END:ONLY_INCLUDE_IF +import { getSmartTransactionMetricsProperties } from '../../../../shared/modules/metametrics'; import { getSnapAndHardwareInfoForMetrics, type SnapAndHardwareMessenger, @@ -86,6 +88,10 @@ export type TransactionMetricsRequest = { // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any trackEvent: (payload: any) => void; + getIsSmartTransaction: () => boolean; + getSmartTransactionByMinedTxHash: ( + txhash: string | undefined, + ) => SmartTransaction; }; export const METRICS_STATUS_FAILED = 'failed on-chain'; @@ -869,7 +875,6 @@ async function buildEventFragmentProperties({ TransactionType.tokenMethodSetApprovalForAll, TransactionType.tokenMethodTransfer, TransactionType.tokenMethodTransferFrom, - TransactionType.smart, TransactionType.swap, TransactionType.swapApproval, ].includes(type); @@ -968,6 +973,12 @@ async function buildEventFragmentProperties({ uiCustomizations.push(MetaMetricsEventUiCustomization.GasEstimationFailed); } + const smartTransactionMetricsProperties = + getSmartTransactionMetricsProperties( + transactionMetricsRequest, + transactionMeta, + ); + /** The transaction status property is not considered sensitive and is now included in the non-anonymous event */ let properties = { chain_id: chainId, @@ -994,6 +1005,7 @@ async function buildEventFragmentProperties({ ///: END:ONLY_INCLUDE_IF // ui_customizations must come after ...blockaidProperties ui_customizations: uiCustomizations.length > 0 ? uiCustomizations : null, + ...smartTransactionMetricsProperties, // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any } as Record; diff --git a/app/scripts/lib/transaction/smart-transactions.test.ts b/app/scripts/lib/transaction/smart-transactions.test.ts new file mode 100644 index 000000000000..35f533f7c882 --- /dev/null +++ b/app/scripts/lib/transaction/smart-transactions.test.ts @@ -0,0 +1,348 @@ +import EventEmitter from 'events'; +import { + TransactionType, + TransactionStatus, + TransactionController, +} from '@metamask/transaction-controller'; +import SmartTransactionsController from '@metamask/smart-transactions-controller'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; +import { submitSmartTransactionHook } from './smart-transactions'; +import type { + SubmitSmartTransactionRequest, + SmartTransactionsControllerMessenger, +} from './smart-transactions'; + +const addressFrom = '0xabce7847fd3661a9b7c86aaf1daea08d9da5750e'; +const txHash = + '0x0302b75dfb9fd9eb34056af031efcaee2a8cbd799ea054a85966165cd82a7356'; +const uuid = 'uuid'; +const txId = '1'; + +let addRequestCallback: () => void; + +type SubmitSmartTransactionRequestMocked = SubmitSmartTransactionRequest & { + smartTransactionsController: jest.Mocked; + transactionController: jest.Mocked; +}; + +const createSignedTransaction = () => { + return '0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a02b79f322a625d623a2bb2911e0c6b3e7eaf741a7c7c5d2e8c67ef3ff4acf146ca01ae168fea63dc3391b75b586c8a7c0cb55cdf3b8e2e4d8e097957a3a56c6f2c5'; +}; + +const createTransactionControllerMock = () => { + return { + approveTransactionsWithSameNonce: jest.fn((transactions = []) => { + return transactions.length === 0 ? [] : [createSignedTransaction()]; + }), + state: { transactions: [] }, + } as unknown as jest.Mocked; +}; + +const createSmartTransactionsControllerMessengerMock = () => { + return { + call: jest.fn((type) => { + if (type === 'ApprovalController:addRequest') { + return { + then: (callback: () => void) => { + addRequestCallback = callback; + }, + }; + } + return Promise.resolve({ id: 'approvalId' }); + }), + } as unknown as jest.Mocked; +}; + +const createSmartTransactionsControllerMock = () => { + return { + getFees: jest.fn(async () => { + return { + tradeTxFees: { + cancelFees: [], + feeEstimate: 42000000000000, + fees: [ + { maxFeePerGas: 12843636951, maxPriorityFeePerGas: 2853145236 }, + ], + gasLimit: 21000, + gasUsed: 21000, + }, + }; + }), + submitSignedTransactions: jest.fn(async () => { + return { + uuid, + txHash, + }; + }), + eventEmitter: new EventEmitter(), + } as unknown as jest.Mocked; +}; + +describe('submitSmartTransactionHook', () => { + const createRequest = () => { + return { + transactionMeta: { + hash: txHash, + status: TransactionStatus.signed, + id: '1', + txParams: { + from: addressFrom, + to: '0x1678a085c290ebd122dc42cba69373b5953b831d', + gasPrice: '0x77359400', + gas: '0x7b0d', + nonce: '0x4b', + }, + type: TransactionType.simpleSend, + chainId: CHAIN_IDS.MAINNET, + time: 1624408066355, + defaultGasEstimates: { + gas: '0x7b0d', + gasPrice: '0x77359400', + }, + error: { + name: 'Error', + message: 'Details of the error', + }, + securityProviderResponse: { + flagAsDangerous: 0, + }, + }, + smartTransactionsController: createSmartTransactionsControllerMock(), + transactionController: createTransactionControllerMock(), + isSmartTransaction: true, + controllerMessenger: createSmartTransactionsControllerMessengerMock(), + featureFlags: { + extensionActive: true, + mobileActive: false, + smartTransactions: { + expectedDeadline: 45, + maxDeadline: 150, + returnTxHashAsap: false, + }, + }, + }; + }; + + beforeEach(() => { + addRequestCallback = () => undefined; + }); + + it('does not submit a transaction that is not a smart transaction', async () => { + const request: SubmitSmartTransactionRequestMocked = createRequest(); + request.isSmartTransaction = false; + const result = await submitSmartTransactionHook(request); + expect(result).toEqual({ transactionHash: undefined }); + }); + + it('returns a txHash asap if the feature flag requires it', async () => { + const request: SubmitSmartTransactionRequestMocked = createRequest(); + request.featureFlags.smartTransactions.returnTxHashAsap = true; + const result = await submitSmartTransactionHook(request); + expect(result).toEqual({ transactionHash: txHash }); + }); + + it('throws an error if there is no uuid', async () => { + const request: SubmitSmartTransactionRequestMocked = createRequest(); + request.smartTransactionsController.submitSignedTransactions = jest.fn( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async ({ signedTransactions, signedCanceledTransactions }) => { + return { uuid: undefined }; + }, + ); + await expect(submitSmartTransactionHook(request)).rejects.toThrow( + 'No smart transaction UUID', + ); + }); + + it('throws an error if there is no transaction hash', async () => { + const request: SubmitSmartTransactionRequestMocked = createRequest(); + setImmediate(() => { + request.smartTransactionsController.eventEmitter.emit( + `uuid:smartTransaction`, + { + status: 'cancelled', + statusMetadata: { + minedHash: '', + }, + }, + ); + }); + await expect(submitSmartTransactionHook(request)).rejects.toThrow( + 'Transaction does not have a transaction hash, there was a problem', + ); + }); + + it('submits a smart transaction', async () => { + const request: SubmitSmartTransactionRequestMocked = createRequest(); + setImmediate(() => { + request.smartTransactionsController.eventEmitter.emit( + `uuid:smartTransaction`, + { + status: 'pending', + statusMetadata: { + minedHash: '', + }, + }, + ); + request.smartTransactionsController.eventEmitter.emit( + `uuid:smartTransaction`, + { + status: 'success', + statusMetadata: { + minedHash: txHash, + }, + }, + ); + }); + const result = await submitSmartTransactionHook(request); + expect(result).toEqual({ transactionHash: txHash }); + const { txParams, chainId } = request.transactionMeta; + expect( + request.transactionController.approveTransactionsWithSameNonce, + ).toHaveBeenCalledWith( + [ + { + ...txParams, + maxFeePerGas: '0x2fd8a58d7', + maxPriorityFeePerGas: '0xaa0f8a94', + chainId, + }, + ], + { hasNonce: true }, + ); + expect( + request.smartTransactionsController.submitSignedTransactions, + ).toHaveBeenCalledWith({ + signedTransactions: [createSignedTransaction()], + signedCanceledTransactions: [], + txParams, + transactionMeta: request.transactionMeta, + }); + addRequestCallback(); + expect(request.controllerMessenger.call).toHaveBeenCalledTimes(4); + expect(request.controllerMessenger.call).toHaveBeenCalledWith( + 'ApprovalController:startFlow', + ); + expect(request.controllerMessenger.call).toHaveBeenCalledWith( + 'ApprovalController:addRequest', + { + id: 'approvalId', + origin: 'http://localhost', + type: 'smartTransaction:showSmartTransactionStatusPage', + requestState: { + smartTransaction: { + status: 'pending', + uuid, + creationTime: expect.any(Number), + }, + isDapp: true, + txId, + }, + }, + true, + ); + expect(request.controllerMessenger.call).toHaveBeenCalledWith( + 'ApprovalController:updateRequestState', + { + id: 'approvalId', + requestState: { + smartTransaction: { + status: 'success', + statusMetadata: { + minedHash: + '0x0302b75dfb9fd9eb34056af031efcaee2a8cbd799ea054a85966165cd82a7356', + }, + }, + isDapp: true, + txId, + }, + }, + ); + + expect(request.controllerMessenger.call).toHaveBeenCalledWith( + 'ApprovalController:endFlow', + { + id: 'approvalId', + }, + ); + }); + + it('submits a smart transaction and does not update approval request if approval was already approved or rejected', async () => { + const request: SubmitSmartTransactionRequestMocked = createRequest(); + setImmediate(() => { + request.smartTransactionsController.eventEmitter.emit( + `uuid:smartTransaction`, + { + status: 'pending', + uuid, + statusMetadata: { + minedHash: '', + }, + }, + ); + addRequestCallback(); + request.smartTransactionsController.eventEmitter.emit( + `uuid:smartTransaction`, + { + status: 'success', + uuid, + statusMetadata: { + minedHash: txHash, + }, + }, + ); + }); + const result = await submitSmartTransactionHook(request); + expect(result).toEqual({ transactionHash: txHash }); + const { txParams, chainId } = request.transactionMeta; + expect( + request.transactionController.approveTransactionsWithSameNonce, + ).toHaveBeenCalledWith( + [ + { + ...txParams, + maxFeePerGas: '0x2fd8a58d7', + maxPriorityFeePerGas: '0xaa0f8a94', + chainId, + }, + ], + { hasNonce: true }, + ); + expect( + request.smartTransactionsController.submitSignedTransactions, + ).toHaveBeenCalledWith({ + signedTransactions: [createSignedTransaction()], + signedCanceledTransactions: [], + txParams, + transactionMeta: request.transactionMeta, + }); + expect(request.controllerMessenger.call).toHaveBeenCalledTimes(3); + expect(request.controllerMessenger.call).toHaveBeenCalledWith( + 'ApprovalController:startFlow', + ); + expect(request.controllerMessenger.call).toHaveBeenCalledWith( + 'ApprovalController:addRequest', + { + id: 'approvalId', + origin: 'http://localhost', + type: 'smartTransaction:showSmartTransactionStatusPage', + requestState: { + smartTransaction: { + status: 'pending', + uuid, + creationTime: expect.any(Number), + }, + isDapp: true, + txId, + }, + }, + true, + ); + expect(request.controllerMessenger.call).toHaveBeenCalledWith( + 'ApprovalController:endFlow', + { + id: 'approvalId', + }, + ); + }); +}); diff --git a/app/scripts/lib/transaction/smart-transactions.ts b/app/scripts/lib/transaction/smart-transactions.ts new file mode 100644 index 000000000000..df9fc82e33f5 --- /dev/null +++ b/app/scripts/lib/transaction/smart-transactions.ts @@ -0,0 +1,328 @@ +import SmartTransactionsController from '@metamask/smart-transactions-controller'; +import { + Fee, + Fees, + SmartTransactionStatuses, + SmartTransaction, +} from '@metamask/smart-transactions-controller/dist/types'; +import type { Hex } from '@metamask/utils'; +import { + TransactionController, + TransactionMeta, + TransactionParams, +} from '@metamask/transaction-controller'; +import log from 'loglevel'; +import { + RestrictedControllerMessenger, + EventConstraint, +} from '@metamask/base-controller'; +import { + AddApprovalRequest, + UpdateRequestState, + StartFlow, + EndFlow, +} from '@metamask/approval-controller'; + +import { decimalToHex } from '../../../../shared/modules/conversion.utils'; +import { CANCEL_GAS_LIMIT_DEC } from '../../../../shared/constants/smartTransactions'; +import { + SMART_TRANSACTION_CONFIRMATION_TYPES, + ORIGIN_METAMASK, +} from '../../../../shared/constants/app'; + +const namespace = 'SmartTransactions'; + +type AllowedActions = + | AddApprovalRequest + | UpdateRequestState + | StartFlow + | EndFlow; + +export type SmartTransactionsControllerMessenger = + RestrictedControllerMessenger< + typeof namespace, + AllowedActions, + EventConstraint, + AllowedActions['type'], + never + >; + +export type FeatureFlags = { + extensionActive: boolean; + mobileActive: boolean; + smartTransactions: { + expectedDeadline?: number; + maxDeadline?: number; + returnTxHashAsap?: boolean; + }; +}; + +export type SubmitSmartTransactionRequest = { + transactionMeta: TransactionMeta; + smartTransactionsController: SmartTransactionsController; + transactionController: TransactionController; + isSmartTransaction: boolean; + controllerMessenger: SmartTransactionsControllerMessenger; + featureFlags: FeatureFlags; +}; + +class SmartTransactionHook { + #approvalFlowEnded: boolean; + + #approvalFlowId: string; + + #chainId: Hex; + + #controllerMessenger: SmartTransactionsControllerMessenger; + + #featureFlags: { + extensionActive: boolean; + mobileActive: boolean; + smartTransactions: { + expectedDeadline?: number; + maxDeadline?: number; + returnTxHashAsap?: boolean; + }; + }; + + #isDapp: boolean; + + #isSmartTransaction: boolean; + + #smartTransactionsController: SmartTransactionsController; + + #transactionController: TransactionController; + + #transactionMeta: TransactionMeta; + + #txParams: TransactionParams; + + constructor(request: SubmitSmartTransactionRequest) { + const { + transactionMeta, + smartTransactionsController, + transactionController, + isSmartTransaction, + controllerMessenger, + featureFlags, + } = request; + this.#approvalFlowId = ''; + this.#approvalFlowEnded = false; + this.#transactionMeta = transactionMeta; + this.#smartTransactionsController = smartTransactionsController; + this.#transactionController = transactionController; + this.#isSmartTransaction = isSmartTransaction; + this.#controllerMessenger = controllerMessenger; + this.#featureFlags = featureFlags; + this.#isDapp = transactionMeta.origin !== ORIGIN_METAMASK; + this.#chainId = transactionMeta.chainId; + this.#txParams = transactionMeta.txParams; + } + + async submit() { + // Will cause TransactionController to publish to the RPC provider as normal. + const useRegularTransactionSubmit = { transactionHash: undefined }; + if (!this.#isSmartTransaction) { + return useRegularTransactionSubmit; + } + const { id: approvalFlowId } = await this.#controllerMessenger.call( + 'ApprovalController:startFlow', + ); + this.#approvalFlowId = approvalFlowId; + try { + const getFeesResponse = await this.#smartTransactionsController.getFees( + { ...this.#txParams, chainId: this.#chainId }, + undefined, + ); + const submitTransactionResponse = await this.#signAndSubmitTransactions({ + getFeesResponse, + }); + const uuid = submitTransactionResponse?.uuid; + if (!uuid) { + throw new Error('No smart transaction UUID'); + } + const returnTxHashAsap = + this.#featureFlags?.smartTransactions?.returnTxHashAsap; + this.#addApprovalRequest({ + uuid, + }); + this.#addListenerToUpdateStatusPage({ + uuid, + }); + let transactionHash: string | undefined | null; + if (returnTxHashAsap && submitTransactionResponse?.txHash) { + transactionHash = submitTransactionResponse.txHash; + } else { + transactionHash = await this.#waitForTransactionHash({ + uuid, + }); + } + if (transactionHash === null) { + throw new Error( + 'Transaction does not have a transaction hash, there was a problem', + ); + } + return { transactionHash }; + } catch (error) { + log.error('Error in smart transaction publish hook', error); + this.#onApproveOrReject(); + throw error; + } + } + + #onApproveOrReject() { + if (this.#approvalFlowEnded) { + return; + } + this.#approvalFlowEnded = true; + this.#controllerMessenger.call('ApprovalController:endFlow', { + id: this.#approvalFlowId, + }); + } + + #addApprovalRequest({ uuid }: { uuid: string }) { + const onApproveOrRejectWrapper = () => { + this.#onApproveOrReject(); + }; + this.#controllerMessenger + .call( + 'ApprovalController:addRequest', + { + id: this.#approvalFlowId, + origin, + type: SMART_TRANSACTION_CONFIRMATION_TYPES.showSmartTransactionStatusPage, + requestState: { + smartTransaction: { + status: SmartTransactionStatuses.PENDING, + creationTime: Date.now(), + uuid, + }, + isDapp: this.#isDapp, + txId: this.#transactionMeta.id, + }, + }, + true, + ) + .then(onApproveOrRejectWrapper, onApproveOrRejectWrapper); + } + + async #updateApprovalRequest({ + smartTransaction, + }: { + smartTransaction: SmartTransaction; + }) { + return await this.#controllerMessenger.call( + 'ApprovalController:updateRequestState', + { + id: this.#approvalFlowId, + requestState: { + smartTransaction, + isDapp: this.#isDapp, + txId: this.#transactionMeta.id, + }, + }, + ); + } + + async #addListenerToUpdateStatusPage({ uuid }: { uuid: string }) { + this.#smartTransactionsController.eventEmitter.on( + `${uuid}:smartTransaction`, + async (smartTransaction: SmartTransaction) => { + const { status } = smartTransaction; + if (!status || status === SmartTransactionStatuses.PENDING) { + return; + } + if (!this.#approvalFlowEnded) { + await this.#updateApprovalRequest({ + smartTransaction, + }); + } + }, + ); + } + + #waitForTransactionHash({ uuid }: { uuid: string }): Promise { + return new Promise((resolve) => { + this.#smartTransactionsController.eventEmitter.on( + `${uuid}:smartTransaction`, + async (smartTransaction: SmartTransaction) => { + const { status, statusMetadata } = smartTransaction; + if (!status || status === SmartTransactionStatuses.PENDING) { + return; + } + log.debug('Smart Transaction: ', smartTransaction); + if (statusMetadata?.minedHash) { + log.debug( + 'Smart Transaction - Received tx hash: ', + statusMetadata?.minedHash, + ); + resolve(statusMetadata.minedHash); + } else { + resolve(null); + } + }, + ); + }); + } + + async #signAndSubmitTransactions({ + getFeesResponse, + }: { + getFeesResponse: Fees; + }) { + const signedTransactions = await this.#createSignedTransactions( + getFeesResponse.tradeTxFees?.fees ?? [], + false, + ); + const signedCanceledTransactions = await this.#createSignedTransactions( + getFeesResponse.tradeTxFees?.cancelFees || [], + true, + ); + return await this.#smartTransactionsController.submitSignedTransactions({ + signedTransactions, + signedCanceledTransactions, + txParams: this.#txParams, + transactionMeta: this.#transactionMeta, + }); + } + + #applyFeeToTransaction(fee: Fee, isCancel: boolean): TransactionParams { + const unsignedTransaction = { + ...this.#txParams, + maxFeePerGas: `0x${decimalToHex(fee.maxFeePerGas)}`, + maxPriorityFeePerGas: `0x${decimalToHex(fee.maxPriorityFeePerGas)}`, + gas: isCancel + ? `0x${decimalToHex(CANCEL_GAS_LIMIT_DEC)}` // It has to be 21000 for cancel transactions, otherwise the API would reject it. + : this.#txParams.gas, + }; + if (isCancel) { + unsignedTransaction.to = unsignedTransaction.from; + unsignedTransaction.data = '0x'; + } + return unsignedTransaction; + } + + async #createSignedTransactions( + fees: Fee[], + isCancel: boolean, + ): Promise { + const unsignedTransactions = fees.map((fee) => { + return this.#applyFeeToTransaction(fee, isCancel); + }); + const transactionsWithChainId = unsignedTransactions.map((tx) => ({ + ...tx, + chainId: tx.chainId || this.#chainId, + })); + return (await this.#transactionController.approveTransactionsWithSameNonce( + transactionsWithChainId, + { hasNonce: true }, + )) as string[]; + } +} + +export const submitSmartTransactionHook = ( + request: SubmitSmartTransactionRequest, +) => { + const smartTransactionHook = new SmartTransactionHook(request); + return smartTransactionHook.submit(); +}; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 398ee8343ddd..79532adc3ca9 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -171,6 +171,7 @@ import { TEST_NETWORK_TICKER_MAP, NetworkStatus, } from '../../shared/constants/network'; +import { ALLOWED_SMART_TRANSACTIONS_CHAIN_IDS } from '../../shared/constants/smartTransactions'; import { HardwareDeviceNames, LedgerTransportTypes, @@ -211,6 +212,12 @@ import { STATIC_MAINNET_TOKEN_LIST } from '../../shared/constants/tokens'; import { getTokenValueParam } from '../../shared/lib/metamask-controller-utils'; import { isManifestV3 } from '../../shared/modules/mv3.utils'; import { convertNetworkId } from '../../shared/modules/network.utils'; +import { + getIsSmartTransaction, + getFeatureFlagsByChainId, + getSmartTransactionsOptInStatus, + getCurrentChainSupportsSmartTransactions, +} from '../../shared/modules/selectors'; import { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) handleMMITransactionUpdate, @@ -234,6 +241,7 @@ import { getAdditionalSignArguments as getAdditionalSignArgumentsMMI, } from './lib/transaction/mmi-hooks'; ///: END:ONLY_INCLUDE_IF +import { submitSmartTransactionHook } from './lib/transaction/smart-transactions'; ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) import { keyringSnapPermissionsBuilder } from './lib/keyring-snaps-permissions'; ///: END:ONLY_INCLUDE_IF @@ -508,15 +516,14 @@ export default class MetamaskController extends EventEmitter { const preferencesMessenger = this.controllerMessenger.getRestricted({ name: 'PreferencesController', - allowedActions: ['PreferencesController:getState'], - allowedEvents: ['PreferencesController:stateChange'], + allowedActions: [], + allowedEvents: [], }); this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, initLangCode: opts.initLangCode, messenger: preferencesMessenger, - tokenListController: this.tokenListController, provider: this.provider, networkConfigurations: this.networkController.state.networkConfigurations, onKeyringStateChange: (listener) => @@ -978,6 +985,7 @@ export default class MetamaskController extends EventEmitter { additionalKeyrings.push( mmiKeyringBuilderFactory(CUSTODIAN_TYPES[custodianType].keyringClass, { mmiConfigurationController: this.mmiConfigurationController, + captureException, }), ); } @@ -1603,6 +1611,15 @@ export default class MetamaskController extends EventEmitter { () => listener(), ); }, + pendingTransactions: { + isResubmitEnabled: () => { + const state = this._getMetaMaskState(); + return !( + getSmartTransactionsOptInStatus(state) && + getCurrentChainSupportsSmartTransactions(state) + ); + }, + }, provider: this.provider, hooks: { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) @@ -1620,6 +1637,7 @@ export default class MetamaskController extends EventEmitter { beforePublish: beforeTransactionPublishMMI.bind(this), getAdditionalSignArguments: getAdditionalSignArgumentsMMI.bind(this), ///: END:ONLY_INCLUDE_IF + publish: this._publishSmartTransactionHook.bind(this), }, sign: (...args) => this.keyringController.signTransaction(...args), state: initState.TransactionController, @@ -1808,18 +1826,19 @@ export default class MetamaskController extends EventEmitter { networkControllerMessenger, 'NetworkController:stateChange', ), - getNonceLock: this.txController.nonceTracker.getNonceLock.bind( - this.txController.nonceTracker, - ), + getNonceLock: this.txController.getNonceLock.bind(this.txController), confirmExternalTransaction: this.txController.confirmExternalTransaction.bind(this.txController), + getTransactions: this.txController.getTransactions.bind( + this.txController, + ), provider: this.provider, trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind( this.metaMetricsController, ), }, { - supportedChainIds: [CHAIN_IDS.MAINNET, CHAIN_IDS.GOERLI], + supportedChainIds: ALLOWED_SMART_TRANSACTIONS_CHAIN_IDS, }, initState.SmartTransactionsController, ); @@ -2216,12 +2235,20 @@ export default class MetamaskController extends EventEmitter { triggerNetworkrequests() { this.accountTracker.start(); this.txController.startIncomingTransactionPolling(); - if (this.preferencesController.store.getState().useCurrencyRateCheck) { + this.tokenDetectionController.enable(); + + const preferencesControllerState = + this.preferencesController.store.getState(); + + const { useCurrencyRateCheck } = preferencesControllerState; + + if (useCurrencyRateCheck) { this.currencyRateController.startPollingByNetworkClientId( this.networkController.state.selectedNetworkClientId, ); } - if (this.preferencesController.store.getState().useTokenDetection) { + + if (this.#isTokenListPollingRequired(preferencesControllerState)) { this.tokenListController.start(); } } @@ -2229,10 +2256,18 @@ export default class MetamaskController extends EventEmitter { stopNetworkRequests() { this.accountTracker.stop(); this.txController.stopIncomingTransactionPolling(); - if (this.preferencesController.store.getState().useCurrencyRateCheck) { + this.tokenDetectionController.disable(); + + const preferencesControllerState = + this.preferencesController.store.getState(); + + const { useCurrencyRateCheck } = preferencesControllerState; + + if (useCurrencyRateCheck) { this.currencyRateController.stopAllPolling(); } - if (this.preferencesController.store.getState().useTokenDetection) { + + if (this.#isTokenListPollingRequired(preferencesControllerState)) { this.tokenListController.stop(); this.tokenRatesController.stop(); } @@ -2493,24 +2528,11 @@ export default class MetamaskController extends EventEmitter { setupControllerEventSubscriptions() { let lastSelectedAddress; - this.preferencesController.store.subscribe(async (state) => { - const { currentLocale } = state; - - const { chainId } = this.networkController.state.providerConfig; - await updateCurrentLocale(currentLocale); - - if (state.incomingTransactionsPreferences?.[chainId]) { - this.txController.startIncomingTransactionPolling(); - } else { - this.txController.stopIncomingTransactionPolling(); - } - - // TODO: Remove once the preferences controller has been replaced with the core monorepo implementation - this.controllerMessenger.publish( - `${this.preferencesController.name}:stateChange`, - [state, []], - ); - }); + this.preferencesController.store.subscribe( + previousValueComparator((prevState, currState) => { + this.#onPreferencesControllerStateChange(currState, prevState); + }, this.preferencesController.store.getState()), + ); this.controllerMessenger.subscribe( `${this.accountsController.name}:selectedAccountChange`, @@ -2983,6 +3005,12 @@ export default class MetamaskController extends EventEmitter { setActiveNetwork: (networkConfigurationId) => { return this.networkController.setActiveNetwork(networkConfigurationId); }, + setNetworkClientIdForDomain: (origin, networkClientId) => { + return this.selectedNetworkController.setNetworkClientIdForDomain( + origin, + networkClientId, + ); + }, rollbackToPreviousProvider: networkController.rollbackToPreviousProvider.bind(networkController), removeNetworkConfiguration: @@ -3098,6 +3126,8 @@ export default class MetamaskController extends EventEmitter { // AppStateController setLastActiveTime: appStateController.setLastActiveTime.bind(appStateController), + setCurrentExtensionPopupId: + appStateController.setCurrentExtensionPopupId.bind(appStateController), setDefaultHomeActiveTabName: appStateController.setDefaultHomeActiveTabName.bind(appStateController), setConnectedStatusPopoverHasBeenShown: @@ -3423,10 +3453,6 @@ export default class MetamaskController extends EventEmitter { swapsController.setSwapsQuotesPollingLimitEnabled.bind(swapsController), // Smart Transactions - setSmartTransactionsOptInStatus: - smartTransactionsController.setOptInState.bind( - smartTransactionsController, - ), fetchSmartTransactionFees: smartTransactionsController.getFees.bind( smartTransactionsController, ), @@ -3721,6 +3747,8 @@ export default class MetamaskController extends EventEmitter { this.txController.clearUnapprovedTransactions(); + this.tokenDetectionController.enable(); + // create new vault const vault = await this.keyringController.createNewVaultAndRestore( password, @@ -5560,6 +5588,14 @@ export default class MetamaskController extends EventEmitter { getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this), getTransaction: (id) => this.txController.state.transactions.find((tx) => tx.id === id), + getIsSmartTransaction: () => { + return getIsSmartTransaction(this._getMetaMaskState()); + }, + getSmartTransactionByMinedTxHash: (txHash) => { + return this.smartTransactionsController.getSmartTransactionByMinedTxHash( + txHash, + ); + }, }; return { ...controllerActions, @@ -5644,11 +5680,6 @@ export default class MetamaskController extends EventEmitter { */ set isClientOpen(open) { this._isClientOpen = open; - if (open) { - this.tokenDetectionController.enable(); - } else { - this.tokenDetectionController.disable(); - } } /* eslint-enable accessor-pairs */ @@ -6012,4 +6043,79 @@ export default class MetamaskController extends EventEmitter { { updatedTransactionMeta }, ); } + + _publishSmartTransactionHook(transactionMeta) { + const state = this._getMetaMaskState(); + const isSmartTransaction = getIsSmartTransaction(state); + if (!isSmartTransaction) { + // Will cause TransactionController to publish to the RPC provider as normal. + return { transactionHash: undefined }; + } + const featureFlags = getFeatureFlagsByChainId(state); + return submitSmartTransactionHook({ + transactionMeta, + transactionController: this.txController, + smartTransactionsController: this.smartTransactionsController, + controllerMessenger: this.controllerMessenger, + isSmartTransaction, + featureFlags, + }); + } + + _getMetaMaskState() { + return { + metamask: this.getState(), + }; + } + + async #onPreferencesControllerStateChange(currentState, previousState) { + const { currentLocale } = currentState; + const { chainId } = this.networkController.state.providerConfig; + + await updateCurrentLocale(currentLocale); + + if (currentState.incomingTransactionsPreferences?.[chainId]) { + this.txController.startIncomingTransactionPolling(); + } else { + this.txController.stopIncomingTransactionPolling(); + } + + this.#checkTokenListPolling(currentState, previousState); + + // TODO: Remove once the preferences controller has been replaced with the core monorepo implementation + this.controllerMessenger.publish( + 'PreferencesController:stateChange', + currentState, + [], + ); + } + + #checkTokenListPolling(currentState, previousState) { + const previousEnabled = this.#isTokenListPollingRequired(previousState); + const newEnabled = this.#isTokenListPollingRequired(currentState); + + if (previousEnabled === newEnabled) { + return; + } + + this.tokenListController.updatePreventPollingOnNetworkRestart(!newEnabled); + + if (newEnabled) { + log.debug('Started token list controller polling'); + this.tokenListController.start(); + } else { + log.debug('Stopped token list controller polling'); + this.tokenListController.clearingTokenListData(); + this.tokenListController.stop(); + } + } + + #isTokenListPollingRequired(preferencesControllerState) { + const { useTokenDetection, useTransactionSimulations, preferences } = + preferencesControllerState ?? {}; + + const { petnamesEnabled } = preferences ?? {}; + + return useTokenDetection || petnamesEnabled || useTransactionSimulations; + } } diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 766ea73417e0..749d3ce66e70 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -19,6 +19,7 @@ import { NetworkType } from '@metamask/controller-utils'; import { ControllerMessenger } from '@metamask/base-controller'; import { LoggingController, LogType } from '@metamask/logging-controller'; import { TransactionController } from '@metamask/transaction-controller'; +import { TokenListController } from '@metamask/assets-controllers'; import { NETWORK_TYPES } from '../../shared/constants/network'; import { createTestProviderTools } from '../../test/stub/provider'; import { HardwareDeviceNames } from '../../shared/constants/hardware-wallets'; @@ -26,6 +27,7 @@ import { KeyringType } from '../../shared/constants/keyring'; import { LOG_EVENT } from '../../shared/constants/logs'; import mockEncryptor from '../../test/lib/mock-encryptor'; import * as tokenUtils from '../../shared/lib/token-util'; +import { flushPromises } from '../../test/lib/timer-helpers'; import { deferredPromise } from './lib/util'; import MetaMaskController from './metamask-controller'; @@ -294,6 +296,14 @@ describe('MetaMaskController', () => { describe('MetaMaskController Behaviour', () => { let metamaskController; + async function simulatePreferencesChange(preferences) { + metamaskController.preferencesController.store.subscribe.mock.lastCall[0]( + preferences, + ); + + await flushPromises(); + } + beforeEach(() => { jest.spyOn(MetaMaskController.prototype, 'resetStates'); @@ -316,6 +326,9 @@ describe('MetaMaskController', () => { .mockReturnValue(); jest.spyOn(ControllerMessenger.prototype, 'subscribe'); + jest.spyOn(TokenListController.prototype, 'start'); + jest.spyOn(TokenListController.prototype, 'stop'); + jest.spyOn(TokenListController.prototype, 'clearingTokenListData'); metamaskController = new MetaMaskController({ showUserConfirmation: noop, @@ -1779,13 +1792,11 @@ describe('MetaMaskController', () => { TransactionController.prototype.startIncomingTransactionPolling, ).not.toHaveBeenCalled(); - await metamaskController.preferencesController.store.subscribe.mock.lastCall[0]( - { - incomingTransactionsPreferences: { - [MAINNET_CHAIN_ID]: true, - }, + await simulatePreferencesChange({ + incomingTransactionsPreferences: { + [MAINNET_CHAIN_ID]: true, }, - ); + }); expect( TransactionController.prototype.startIncomingTransactionPolling, @@ -1797,13 +1808,11 @@ describe('MetaMaskController', () => { TransactionController.prototype.stopIncomingTransactionPolling, ).not.toHaveBeenCalled(); - await metamaskController.preferencesController.store.subscribe.mock.lastCall[0]( - { - incomingTransactionsPreferences: { - [MAINNET_CHAIN_ID]: false, - }, + await simulatePreferencesChange({ + incomingTransactionsPreferences: { + [MAINNET_CHAIN_ID]: false, }, - ); + }); expect( TransactionController.prototype.stopIncomingTransactionPolling, @@ -1839,6 +1848,115 @@ describe('MetaMaskController', () => { ).toHaveBeenCalledTimes(1); }); }); + + describe('token list controller', () => { + it('stops polling if petnames, simulations, and token detection disabled', async () => { + expect(TokenListController.prototype.stop).not.toHaveBeenCalled(); + + expect( + TokenListController.prototype.clearingTokenListData, + ).not.toHaveBeenCalled(); + + await simulatePreferencesChange({ + useTransactionSimulations: false, + useTokenDetection: false, + preferences: { + petnamesEnabled: false, + }, + }); + + expect(TokenListController.prototype.stop).toHaveBeenCalledTimes(1); + + expect( + TokenListController.prototype.clearingTokenListData, + ).toHaveBeenCalledTimes(1); + }); + + it.each([ + [ + 'petnames', + { + preferences: { petnamesEnabled: false }, + useTokenDetection: true, + useTransactionSimulations: true, + }, + ], + [ + 'simulations', + { + preferences: { petnamesEnabled: true }, + useTokenDetection: true, + useTransactionSimulations: false, + }, + ], + [ + 'token detection', + { + preferences: { petnamesEnabled: true }, + useTokenDetection: false, + useTransactionSimulations: true, + }, + ], + ])( + 'does not stop polling if only %s disabled', + async (_, preferences) => { + expect(TokenListController.prototype.stop).not.toHaveBeenCalled(); + + expect( + TokenListController.prototype.clearingTokenListData, + ).not.toHaveBeenCalled(); + + await simulatePreferencesChange(preferences); + + expect(TokenListController.prototype.stop).not.toHaveBeenCalled(); + + expect( + TokenListController.prototype.clearingTokenListData, + ).not.toHaveBeenCalled(); + }, + ); + + it.each([ + [ + 'petnames', + { + preferences: { petnamesEnabled: true }, + useTokenDetection: false, + useTransactionSimulations: false, + }, + ], + [ + 'simulations', + { + preferences: { petnamesEnabled: false }, + useTokenDetection: false, + useTransactionSimulations: true, + }, + ], + [ + 'token detection', + { + preferences: { petnamesEnabled: false }, + useTokenDetection: true, + useTransactionSimulations: false, + }, + ], + ])('starts polling if only %s enabled', async (_, preferences) => { + expect(TokenListController.prototype.start).not.toHaveBeenCalled(); + + await simulatePreferencesChange({ + useTransactionSimulations: false, + useTokenDetection: false, + preferences: { + petnamesEnabled: false, + }, + }); + + await simulatePreferencesChange(preferences); + + expect(TokenListController.prototype.start).toHaveBeenCalledTimes(1); + }); + }); }); describe('MV3 Specific behaviour', () => { diff --git a/app/scripts/snaps/preinstalled-snaps.ts b/app/scripts/snaps/preinstalled-snaps.ts index 67144b87177f..0a014c350c21 100644 --- a/app/scripts/snaps/preinstalled-snaps.ts +++ b/app/scripts/snaps/preinstalled-snaps.ts @@ -1,55 +1,8 @@ import type { PreinstalledSnap } from '@metamask/snaps-controllers'; +import MessageSigningSnap from '@metamask/message-signing-snap/dist/preinstalled-snap.json'; -// Preinstalled snaps requires fs and require to read and use any installed snaps. -// We can switch `require` to `import`, but then this file gets transpiled to ESM & won't have access to `require.resolve`. -// Work is needed to create the require object in ESM - specifically the import url. See snippet -// import { createRequire } from 'node:module' -// const require = createRequire(import.meta.url) -// ^ The 'import.meta' meta-property is not allowed in files which will build into CommonJS output -/* eslint-disable-next-line @typescript-eslint/no-require-imports,@typescript-eslint/no-var-requires */ -const fs = require('fs'); - -const PREINSTALLED_SNAPS: PreinstalledSnap[] = []; - -///: BEGIN:ONLY_INCLUDE_IF(snaps) -PREINSTALLED_SNAPS.push( - getPreinstalledSnap( - '@metamask/message-signing-snap', - fs.readFileSync( - require.resolve('@metamask/message-signing-snap/snap.manifest.json'), - 'utf-8', - ), - [ - { - path: 'images/icon.svg', - value: fs.readFileSync( - require.resolve('@metamask/message-signing-snap/images/icon.svg'), - ), - }, - { - path: 'dist/bundle.js', - value: fs.readFileSync( - require.resolve('@metamask/message-signing-snap/dist/bundle.js'), - ), - }, - ], - ), -); - -function getPreinstalledSnap( - npmPackage: string, - manifest: PreinstalledSnap['manifest'], - files: PreinstalledSnap['files'], -): PreinstalledSnap { - return { - snapId: `npm:${npmPackage}` as PreinstalledSnap['snapId'], - manifest: JSON.parse(manifest), - files, - removable: false, - }; -} -///: END:ONLY_INCLUDE_IF - -Object.freeze(PREINSTALLED_SNAPS); +const PREINSTALLED_SNAPS: readonly PreinstalledSnap[] = Object.freeze([ + MessageSigningSnap as PreinstalledSnap, +]); export default PREINSTALLED_SNAPS; diff --git a/builds.yml b/builds.yml index 8a6c5f15f556..446ce72139ec 100644 --- a/builds.yml +++ b/builds.yml @@ -103,6 +103,7 @@ buildTypes: env: - INFURA_MMI_PROJECT_ID - SEGMENT_MMI_WRITE_KEY + - SENTRY_MMI_DSN - INFURA_ENV_KEY_REF: INFURA_MMI_PROJECT_ID - SEGMENT_WRITE_KEY_REF: SEGMENT_MMI_WRITE_KEY - ALLOW_LOCAL_SNAPS: false @@ -201,7 +202,7 @@ env: ### - AUTH_API: https://authentication.api.cx.metamask.io - OIDC_API: https://oidc.api.cx.metamask.io - - OIDC_CLIENT_ID: 77285a0b-a0c6-4681-ba36-cc7d3a21aece + - OIDC_CLIENT_ID: 1132f10a-b4e5-4390-a5f2-d9c6022db564 - OIDC_GRANT_TYPE: urn:ietf:params:oauth:grant-type:jwt-bearer ### diff --git a/development/build/config.js b/development/build/config.js index 4e4ee1ea9c8a..030b9e04dad0 100644 --- a/development/build/config.js +++ b/development/build/config.js @@ -15,7 +15,7 @@ const VARIABLES_REQUIRED_IN_PRODUCTION = { 'INFURA_MMI_PROJECT_ID', 'MMI_CONFIGURATION_SERVICE_URL', 'SEGMENT_MMI_WRITE_KEY', - 'SENTRY_DSN', + 'SENTRY_MMI_DSN', ], }; diff --git a/jest.config.js b/jest.config.js index dd42be4196a1..bd8d58f867ed 100644 --- a/jest.config.js +++ b/jest.config.js @@ -45,6 +45,7 @@ module.exports = { '/app/scripts/controllers/transactions/etherscan.test.ts', '/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts', '/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts', + '/app/scripts/controllers/onboarding.test.ts', '/app/scripts/controllers/mmi-controller.test.ts', '/app/scripts/controllers/permissions/**/*.test.js', '/app/scripts/controllers/preferences.test.js', diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index ba9b06f8c333..ac8d7ff3639b 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -1405,24 +1405,24 @@ "uuid": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller": { + "@metamask/gas-fee-controller>@metamask/base-controller": { "globals": { - "clearTimeout": true, - "console.error": true, "setTimeout": true }, "packages": { - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "immer": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": { + "@metamask/gas-fee-controller>@metamask/polling-controller": { "globals": { + "clearTimeout": true, + "console.error": true, "setTimeout": true }, "packages": { - "immer": true + "@metamask/gas-fee-controller>@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/jazzicon": { @@ -1928,21 +1928,57 @@ }, "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/bignumber": true, - "@ethersproject/providers": true, - "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, + "@metamask/eth-query": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true, "@metamask/smart-transactions-controller>@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller": true, "@metamask/smart-transactions-controller>bignumber.js": true, + "browserify>buffer": true, "fast-json-patch": true, - "lodash": true + "lodash": true, + "webpack>events": true } }, - "@metamask/smart-transactions-controller>@metamask/base-controller": { + "@metamask/smart-transactions-controller>@babel/runtime": { "globals": { - "setTimeout": true + "regeneratorRuntime": "write" + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx": { + "packages": { + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": { + "packages": { + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/util": { + "globals": { + "console.warn": true, + "fetch": true }, "packages": { - "immer": true + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@ethereumjs/util>@ethereumjs/rlp": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/util>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true } }, "@metamask/smart-transactions-controller>@metamask/controller-utils": { @@ -1954,18 +1990,26 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/smart-transactions-controller>@metamask/controller-utils>ethjs-unit": true, + "@metamask/ethjs>@metamask/ethjs-unit": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@ethereumjs/util": true, "@metamask/utils": true, + "bn.js": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true + "eth-ens-namehash": true } }, - "@metamask/smart-transactions-controller>@metamask/controller-utils>ethjs-unit": { + "@metamask/smart-transactions-controller>@metamask/controller-utils>@ethereumjs/util": { + "globals": { + "console.warn": true + }, "packages": { - "@metamask/ethjs>ethjs-abi>number-to-bn": true, - "bn.js": true + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "webpack>events": true } }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { @@ -1973,6 +2017,137 @@ "crypto.getRandomValues": true } }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@ethereumjs/common": true, + "@ethersproject/abi": true, + "@metamask/eth-query": true, + "@metamask/gas-fee-controller": true, + "@metamask/metamask-eth-abis": true, + "@metamask/name-controller>async-mutex": true, + "@metamask/network-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": true, + "@metamask/transaction-controller>nonce-tracker": true, + "@metamask/utils": true, + "bn.js": true, + "fast-json-patch": true, + "lodash": true, + "uuid": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": { + "packages": { + "@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": { + "globals": { + "console.warn": true + }, + "packages": { + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/ethjs>@metamask/ethjs-unit": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "@metamask/utils": true, + "bn.js": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": { + "packages": { + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": { + "packages": { + "@metamask/ethjs>ethjs-abi": true, + "@metamask/ethjs>js-sha3": true, + "@metamask/smart-transactions-controller>@babel/runtime": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true, + "promise-to-callback": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": { + "globals": { + "clearInterval": true, + "setInterval": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "browserify>buffer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query": { + "globals": { + "console": true + }, + "packages": { + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": true, + "promise-to-callback": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": { + "packages": { + "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/number-to-bn": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": { + "packages": { + "promise-to-callback": true + } + }, "@metamask/smart-transactions-controller>bignumber.js": { "globals": { "crypto": true, @@ -2207,10 +2382,10 @@ "@metamask/controller-utils": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/providers>@metamask/rpc-errors": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/base-controller": true, + "@metamask/user-operation-controller>@metamask/polling-controller": true, "@metamask/utils": true, "bn.js": true, "lodash": true, @@ -2227,6 +2402,18 @@ "immer": true } }, + "@metamask/user-operation-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/user-operation-controller>@metamask/base-controller": true, + "uuid": true + } + }, "@metamask/utils": { "globals": { "TextDecoder": true, diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index 83d4a3fb44a4..d8da5ff974fd 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -1490,24 +1490,24 @@ "uuid": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller": { + "@metamask/gas-fee-controller>@metamask/base-controller": { "globals": { - "clearTimeout": true, - "console.error": true, "setTimeout": true }, "packages": { - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "immer": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": { + "@metamask/gas-fee-controller>@metamask/polling-controller": { "globals": { + "clearTimeout": true, + "console.error": true, "setTimeout": true }, "packages": { - "immer": true + "@metamask/gas-fee-controller>@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/jazzicon": { @@ -2112,21 +2112,57 @@ }, "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/bignumber": true, - "@ethersproject/providers": true, - "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, + "@metamask/eth-query": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true, "@metamask/smart-transactions-controller>@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller": true, "@metamask/smart-transactions-controller>bignumber.js": true, + "browserify>buffer": true, "fast-json-patch": true, - "lodash": true + "lodash": true, + "webpack>events": true } }, - "@metamask/smart-transactions-controller>@metamask/base-controller": { + "@metamask/smart-transactions-controller>@babel/runtime": { "globals": { - "setTimeout": true + "regeneratorRuntime": "write" + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx": { + "packages": { + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": { + "packages": { + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/util": { + "globals": { + "console.warn": true, + "fetch": true }, "packages": { - "immer": true + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@ethereumjs/util>@ethereumjs/rlp": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/util>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true } }, "@metamask/smart-transactions-controller>@metamask/controller-utils": { @@ -2138,18 +2174,26 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/smart-transactions-controller>@metamask/controller-utils>ethjs-unit": true, + "@metamask/ethjs>@metamask/ethjs-unit": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@ethereumjs/util": true, "@metamask/utils": true, + "bn.js": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true + "eth-ens-namehash": true } }, - "@metamask/smart-transactions-controller>@metamask/controller-utils>ethjs-unit": { + "@metamask/smart-transactions-controller>@metamask/controller-utils>@ethereumjs/util": { + "globals": { + "console.warn": true + }, "packages": { - "@metamask/ethjs>ethjs-abi>number-to-bn": true, - "bn.js": true + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "webpack>events": true } }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { @@ -2157,6 +2201,137 @@ "crypto.getRandomValues": true } }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@ethereumjs/common": true, + "@ethersproject/abi": true, + "@metamask/eth-query": true, + "@metamask/gas-fee-controller": true, + "@metamask/metamask-eth-abis": true, + "@metamask/name-controller>async-mutex": true, + "@metamask/network-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": true, + "@metamask/transaction-controller>nonce-tracker": true, + "@metamask/utils": true, + "bn.js": true, + "fast-json-patch": true, + "lodash": true, + "uuid": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": { + "packages": { + "@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": { + "globals": { + "console.warn": true + }, + "packages": { + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/ethjs>@metamask/ethjs-unit": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "@metamask/utils": true, + "bn.js": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": { + "packages": { + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": { + "packages": { + "@metamask/ethjs>ethjs-abi": true, + "@metamask/ethjs>js-sha3": true, + "@metamask/smart-transactions-controller>@babel/runtime": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true, + "promise-to-callback": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": { + "globals": { + "clearInterval": true, + "setInterval": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "browserify>buffer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query": { + "globals": { + "console": true + }, + "packages": { + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": true, + "promise-to-callback": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": { + "packages": { + "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/number-to-bn": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": { + "packages": { + "promise-to-callback": true + } + }, "@metamask/smart-transactions-controller>bignumber.js": { "globals": { "crypto": true, @@ -2520,10 +2695,10 @@ "@metamask/controller-utils": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/providers>@metamask/rpc-errors": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/base-controller": true, + "@metamask/user-operation-controller>@metamask/polling-controller": true, "@metamask/utils": true, "bn.js": true, "lodash": true, @@ -2540,6 +2715,18 @@ "immer": true } }, + "@metamask/user-operation-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/user-operation-controller>@metamask/base-controller": true, + "uuid": true + } + }, "@metamask/utils": { "globals": { "TextDecoder": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 226e4f49bc9a..aaccb621f4da 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -1490,24 +1490,24 @@ "uuid": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller": { + "@metamask/gas-fee-controller>@metamask/base-controller": { "globals": { - "clearTimeout": true, - "console.error": true, "setTimeout": true }, "packages": { - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "immer": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": { + "@metamask/gas-fee-controller>@metamask/polling-controller": { "globals": { + "clearTimeout": true, + "console.error": true, "setTimeout": true }, "packages": { - "immer": true + "@metamask/gas-fee-controller>@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/jazzicon": { @@ -2164,21 +2164,57 @@ }, "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/bignumber": true, - "@ethersproject/providers": true, - "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, + "@metamask/eth-query": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true, "@metamask/smart-transactions-controller>@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller": true, "@metamask/smart-transactions-controller>bignumber.js": true, + "browserify>buffer": true, "fast-json-patch": true, - "lodash": true + "lodash": true, + "webpack>events": true } }, - "@metamask/smart-transactions-controller>@metamask/base-controller": { + "@metamask/smart-transactions-controller>@babel/runtime": { "globals": { - "setTimeout": true + "regeneratorRuntime": "write" + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx": { + "packages": { + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": { + "packages": { + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/util": { + "globals": { + "console.warn": true, + "fetch": true }, "packages": { - "immer": true + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@ethereumjs/util>@ethereumjs/rlp": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/util>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true } }, "@metamask/smart-transactions-controller>@metamask/controller-utils": { @@ -2190,18 +2226,26 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/smart-transactions-controller>@metamask/controller-utils>ethjs-unit": true, + "@metamask/ethjs>@metamask/ethjs-unit": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@ethereumjs/util": true, "@metamask/utils": true, + "bn.js": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true + "eth-ens-namehash": true } }, - "@metamask/smart-transactions-controller>@metamask/controller-utils>ethjs-unit": { + "@metamask/smart-transactions-controller>@metamask/controller-utils>@ethereumjs/util": { + "globals": { + "console.warn": true + }, "packages": { - "@metamask/ethjs>ethjs-abi>number-to-bn": true, - "bn.js": true + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "webpack>events": true } }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { @@ -2209,6 +2253,137 @@ "crypto.getRandomValues": true } }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@ethereumjs/common": true, + "@ethersproject/abi": true, + "@metamask/eth-query": true, + "@metamask/gas-fee-controller": true, + "@metamask/metamask-eth-abis": true, + "@metamask/name-controller>async-mutex": true, + "@metamask/network-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": true, + "@metamask/transaction-controller>nonce-tracker": true, + "@metamask/utils": true, + "bn.js": true, + "fast-json-patch": true, + "lodash": true, + "uuid": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": { + "packages": { + "@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": { + "globals": { + "console.warn": true + }, + "packages": { + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/ethjs>@metamask/ethjs-unit": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "@metamask/utils": true, + "bn.js": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": { + "packages": { + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": { + "packages": { + "@metamask/ethjs>ethjs-abi": true, + "@metamask/ethjs>js-sha3": true, + "@metamask/smart-transactions-controller>@babel/runtime": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true, + "promise-to-callback": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": { + "globals": { + "clearInterval": true, + "setInterval": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "browserify>buffer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query": { + "globals": { + "console": true + }, + "packages": { + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": true, + "promise-to-callback": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": { + "packages": { + "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/number-to-bn": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": { + "packages": { + "promise-to-callback": true + } + }, "@metamask/smart-transactions-controller>bignumber.js": { "globals": { "crypto": true, @@ -2572,10 +2747,10 @@ "@metamask/controller-utils": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/providers>@metamask/rpc-errors": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/base-controller": true, + "@metamask/user-operation-controller>@metamask/polling-controller": true, "@metamask/utils": true, "bn.js": true, "lodash": true, @@ -2592,6 +2767,18 @@ "immer": true } }, + "@metamask/user-operation-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/user-operation-controller>@metamask/base-controller": true, + "uuid": true + } + }, "@metamask/utils": { "globals": { "TextDecoder": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 4140c011d7a1..19dd813bb86b 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -1405,24 +1405,24 @@ "uuid": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller": { + "@metamask/gas-fee-controller>@metamask/base-controller": { "globals": { - "clearTimeout": true, - "console.error": true, "setTimeout": true }, "packages": { - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "immer": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": { + "@metamask/gas-fee-controller>@metamask/polling-controller": { "globals": { + "clearTimeout": true, + "console.error": true, "setTimeout": true }, "packages": { - "immer": true + "@metamask/gas-fee-controller>@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/jazzicon": { @@ -2079,21 +2079,57 @@ }, "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/bignumber": true, - "@ethersproject/providers": true, - "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, + "@metamask/eth-query": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true, "@metamask/smart-transactions-controller>@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller": true, "@metamask/smart-transactions-controller>bignumber.js": true, + "browserify>buffer": true, "fast-json-patch": true, - "lodash": true + "lodash": true, + "webpack>events": true } }, - "@metamask/smart-transactions-controller>@metamask/base-controller": { + "@metamask/smart-transactions-controller>@babel/runtime": { "globals": { - "setTimeout": true + "regeneratorRuntime": "write" + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx": { + "packages": { + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": { + "packages": { + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/util": { + "globals": { + "console.warn": true, + "fetch": true }, "packages": { - "immer": true + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@ethereumjs/util>@ethereumjs/rlp": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/util>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true } }, "@metamask/smart-transactions-controller>@metamask/controller-utils": { @@ -2105,18 +2141,26 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/smart-transactions-controller>@metamask/controller-utils>ethjs-unit": true, + "@metamask/ethjs>@metamask/ethjs-unit": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@ethereumjs/util": true, "@metamask/utils": true, + "bn.js": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true + "eth-ens-namehash": true } }, - "@metamask/smart-transactions-controller>@metamask/controller-utils>ethjs-unit": { + "@metamask/smart-transactions-controller>@metamask/controller-utils>@ethereumjs/util": { + "globals": { + "console.warn": true + }, "packages": { - "@metamask/ethjs>ethjs-abi>number-to-bn": true, - "bn.js": true + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "webpack>events": true } }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { @@ -2124,6 +2168,137 @@ "crypto.getRandomValues": true } }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@ethereumjs/common": true, + "@ethersproject/abi": true, + "@metamask/eth-query": true, + "@metamask/gas-fee-controller": true, + "@metamask/metamask-eth-abis": true, + "@metamask/name-controller>async-mutex": true, + "@metamask/network-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": true, + "@metamask/transaction-controller>nonce-tracker": true, + "@metamask/utils": true, + "bn.js": true, + "fast-json-patch": true, + "lodash": true, + "uuid": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": { + "packages": { + "@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": { + "globals": { + "console.warn": true + }, + "packages": { + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/ethjs>@metamask/ethjs-unit": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "@metamask/utils": true, + "bn.js": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": { + "packages": { + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": { + "packages": { + "@metamask/ethjs>ethjs-abi": true, + "@metamask/ethjs>js-sha3": true, + "@metamask/smart-transactions-controller>@babel/runtime": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true, + "promise-to-callback": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": { + "globals": { + "clearInterval": true, + "setInterval": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "browserify>buffer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query": { + "globals": { + "console": true + }, + "packages": { + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": true, + "promise-to-callback": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": { + "packages": { + "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/number-to-bn": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": { + "packages": { + "promise-to-callback": true + } + }, "@metamask/smart-transactions-controller>bignumber.js": { "globals": { "crypto": true, @@ -2487,10 +2662,10 @@ "@metamask/controller-utils": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/providers>@metamask/rpc-errors": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/base-controller": true, + "@metamask/user-operation-controller>@metamask/polling-controller": true, "@metamask/utils": true, "bn.js": true, "lodash": true, @@ -2507,6 +2682,18 @@ "immer": true } }, + "@metamask/user-operation-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/user-operation-controller>@metamask/base-controller": true, + "uuid": true + } + }, "@metamask/utils": { "globals": { "TextDecoder": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index c8ba0a32e567..270461a22154 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -718,6 +718,7 @@ }, "@metamask-institutional/custody-keyring": { "globals": { + "console.error": true, "console.log": true, "console.warn": true }, @@ -761,6 +762,11 @@ "fetch": true } }, + "@metamask-institutional/rpc-allowlist": { + "globals": { + "URL": true + } + }, "@metamask-institutional/sdk": { "globals": { "URLSearchParams": true, @@ -1538,24 +1544,24 @@ "uuid": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller": { + "@metamask/gas-fee-controller>@metamask/base-controller": { "globals": { - "clearTimeout": true, - "console.error": true, "setTimeout": true }, "packages": { - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "uuid": true + "immer": true } }, - "@metamask/gas-fee-controller>@metamask/polling-controller>@metamask/base-controller": { + "@metamask/gas-fee-controller>@metamask/polling-controller": { "globals": { + "clearTimeout": true, + "console.error": true, "setTimeout": true }, "packages": { - "immer": true + "@metamask/gas-fee-controller>@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/jazzicon": { @@ -2212,21 +2218,57 @@ }, "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/bignumber": true, - "@ethersproject/providers": true, - "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, + "@metamask/eth-query": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true, "@metamask/smart-transactions-controller>@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller": true, "@metamask/smart-transactions-controller>bignumber.js": true, + "browserify>buffer": true, "fast-json-patch": true, - "lodash": true + "lodash": true, + "webpack>events": true } }, - "@metamask/smart-transactions-controller>@metamask/base-controller": { + "@metamask/smart-transactions-controller>@babel/runtime": { "globals": { - "setTimeout": true + "regeneratorRuntime": "write" + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx": { + "packages": { + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": true, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": true, + "@metamask/smart-transactions-controller>@ethereumjs/util": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/common": { + "packages": { + "@metamask/smart-transactions-controller>@ethereumjs/util": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/tx>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/util": { + "globals": { + "console.warn": true, + "fetch": true }, "packages": { - "immer": true + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@ethereumjs/util>@ethereumjs/rlp": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@ethereumjs/util>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true } }, "@metamask/smart-transactions-controller>@metamask/controller-utils": { @@ -2238,18 +2280,26 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/smart-transactions-controller>@metamask/controller-utils>ethjs-unit": true, + "@metamask/ethjs>@metamask/ethjs-unit": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@ethereumjs/util": true, "@metamask/utils": true, + "bn.js": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true + "eth-ens-namehash": true } }, - "@metamask/smart-transactions-controller>@metamask/controller-utils>ethjs-unit": { + "@metamask/smart-transactions-controller>@metamask/controller-utils>@ethereumjs/util": { + "globals": { + "console.warn": true + }, "packages": { - "@metamask/ethjs>ethjs-abi>number-to-bn": true, - "bn.js": true + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "webpack>events": true } }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { @@ -2257,6 +2307,137 @@ "crypto.getRandomValues": true } }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@ethereumjs/common": true, + "@ethersproject/abi": true, + "@metamask/eth-query": true, + "@metamask/gas-fee-controller": true, + "@metamask/metamask-eth-abis": true, + "@metamask/name-controller>async-mutex": true, + "@metamask/network-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": true, + "@metamask/transaction-controller>nonce-tracker": true, + "@metamask/utils": true, + "bn.js": true, + "fast-json-patch": true, + "lodash": true, + "uuid": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": { + "packages": { + "@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": { + "globals": { + "console.warn": true + }, + "packages": { + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "webpack>events": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/ethjs>@metamask/ethjs-unit": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, + "@metamask/utils": true, + "bn.js": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": { + "packages": { + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": { + "packages": { + "@metamask/ethjs>ethjs-abi": true, + "@metamask/ethjs>js-sha3": true, + "@metamask/smart-transactions-controller>@babel/runtime": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true, + "promise-to-callback": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-filter": { + "globals": { + "clearInterval": true, + "setInterval": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "browserify>buffer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query": { + "globals": { + "console": true + }, + "packages": { + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": true, + "promise-to-callback": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-format": { + "packages": { + "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/number-to-bn": true, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract>@metamask/ethjs-util": true + } + }, + "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-query>@metamask/ethjs-rpc": { + "packages": { + "promise-to-callback": true + } + }, "@metamask/smart-transactions-controller>bignumber.js": { "globals": { "crypto": true, @@ -2620,10 +2801,10 @@ "@metamask/controller-utils": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/providers>@metamask/rpc-errors": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/base-controller": true, + "@metamask/user-operation-controller>@metamask/polling-controller": true, "@metamask/utils": true, "bn.js": true, "lodash": true, @@ -2640,6 +2821,18 @@ "immer": true } }, + "@metamask/user-operation-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/user-operation-controller>@metamask/base-controller": true, + "uuid": true + } + }, "@metamask/utils": { "globals": { "TextDecoder": true, diff --git a/package.json b/package.json index 82f9bde79e1e..65be185d9d45 100644 --- a/package.json +++ b/package.json @@ -237,7 +237,8 @@ "@metamask/network-controller@npm:^17.0.0": "patch:@metamask/network-controller@npm%3A18.0.1#~/.yarn/patches/@metamask-network-controller-npm-18.0.1-c4d0cfaecd.patch", "@metamask/network-controller@npm:^15.0.0": "patch:@metamask/network-controller@npm%3A18.0.1#~/.yarn/patches/@metamask-network-controller-npm-18.0.1-c4d0cfaecd.patch", "@metamask/network-controller@npm:^18.0.1": "patch:@metamask/network-controller@npm%3A18.0.1#~/.yarn/patches/@metamask-network-controller-npm-18.0.1-c4d0cfaecd.patch", - "@metamask/network-controller@npm:^17.2.1": "patch:@metamask/network-controller@npm%3A18.0.1#~/.yarn/patches/@metamask-network-controller-npm-18.0.1-c4d0cfaecd.patch" + "@metamask/network-controller@npm:^17.2.1": "patch:@metamask/network-controller@npm%3A18.0.1#~/.yarn/patches/@metamask-network-controller-npm-18.0.1-c4d0cfaecd.patch", + "@metamask/keyring-controller@npm:^13.0.0": "patch:@metamask/keyring-controller@npm%3A13.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-13.0.0-d94816a680.patch" }, "dependencies": { "@babel/runtime": "patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch", @@ -256,23 +257,23 @@ "@lavamoat/lavadome-react": "0.0.17", "@lavamoat/snow": "^2.0.1", "@material-ui/core": "^4.11.0", - "@metamask-institutional/custody-controller": "^0.2.22", - "@metamask-institutional/custody-keyring": "^1.0.10", - "@metamask-institutional/extension": "^0.3.18", - "@metamask-institutional/institutional-features": "^1.2.11", + "@metamask-institutional/custody-controller": "^0.2.24", + "@metamask-institutional/custody-keyring": "^1.0.12", + "@metamask-institutional/extension": "^0.3.20", + "@metamask-institutional/institutional-features": "^1.2.15", "@metamask-institutional/portfolio-dashboard": "^1.4.0", - "@metamask-institutional/rpc-allowlist": "^1.0.1", - "@metamask-institutional/sdk": "^0.1.23", - "@metamask-institutional/transaction-update": "^0.1.32", + "@metamask-institutional/rpc-allowlist": "^1.0.2", + "@metamask-institutional/sdk": "^0.1.25", + "@metamask-institutional/transaction-update": "^0.1.38", "@metamask/abi-utils": "^2.0.2", "@metamask/accounts-controller": "^11.0.0", "@metamask/address-book-controller": "^3.1.7", "@metamask/announcement-controller": "^5.0.1", "@metamask/approval-controller": "^6.0.0", - "@metamask/assets-controllers": "^26.0.0", + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A26.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-26.0.0-17c0e9432c.patch", "@metamask/base-controller": "^4.1.0", "@metamask/browser-passworder": "^4.3.0", - "@metamask/contract-metadata": "^2.3.1", + "@metamask/contract-metadata": "^2.5.0", "@metamask/controller-utils": "^9.0.2", "@metamask/design-tokens": "^2.1.1", "@metamask/desktop": "^0.3.0", @@ -293,11 +294,11 @@ "@metamask/gas-fee-controller": "^14.0.0", "@metamask/jazzicon": "^2.0.0", "@metamask/keyring-api": "^3.0.0", - "@metamask/keyring-controller": "^13.0.0", + "@metamask/keyring-controller": "patch:@metamask/keyring-controller@npm%3A13.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-13.0.0-d94816a680.patch", "@metamask/logging-controller": "^2.0.2", "@metamask/logo": "^3.1.2", "@metamask/message-manager": "^7.3.0", - "@metamask/message-signing-snap": "^0.3.1", + "@metamask/message-signing-snap": "^0.3.3", "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/name-controller": "^4.2.0", "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A18.0.1#~/.yarn/patches/@metamask-network-controller-npm-18.0.1-c4d0cfaecd.patch", @@ -315,13 +316,13 @@ "@metamask/scure-bip39": "^2.0.3", "@metamask/selected-network-controller": "^9.0.0", "@metamask/signature-controller": "^12.0.0", - "@metamask/smart-transactions-controller": "^6.2.2", + "@metamask/smart-transactions-controller": "^8.1.0", "@metamask/snaps-controllers": "^6.0.4", "@metamask/snaps-execution-environments": "^5.0.4", "@metamask/snaps-rpc-methods": "^7.0.2", "@metamask/snaps-sdk": "^3.2.0", "@metamask/snaps-utils": "^7.0.4", - "@metamask/transaction-controller": "^26.0.0", + "@metamask/transaction-controller": "^27.0.1", "@metamask/user-operation-controller": "^6.0.0", "@metamask/utils": "^8.2.1", "@ngraveio/bc-ur": "^1.1.12", @@ -340,7 +341,7 @@ "base32-encode": "^1.2.0", "base64-js": "^1.5.1", "bignumber.js": "^4.1.0", - "blo": "1.1.1", + "blo": "1.2.0", "bn.js": "^5.2.1", "bowser": "^2.11.0", "classnames": "^2.2.6", diff --git a/shared/constants/app.ts b/shared/constants/app.ts index e2c3412496ed..3329c165dfe7 100644 --- a/shared/constants/app.ts +++ b/shared/constants/app.ts @@ -81,6 +81,11 @@ export const SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES = { }; ///: END:ONLY_INCLUDE_IF +export const SMART_TRANSACTION_CONFIRMATION_TYPES = { + showSmartTransactionStatusPage: + 'smartTransaction:showSmartTransactionStatusPage', +}; + /** * Custom messages to send and be received by the extension */ diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index cd5a89e8aadf..9017381f7419 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -610,6 +610,7 @@ export enum MetaMetricsEventName { TokenImportButtonClicked = 'Import Token Button Clicked', TokenScreenOpened = 'Token Screen Opened', TokenAdded = 'Token Added', + NFTRemoved = 'NFT Removed', TokenDetected = 'Token Detected', TokenHidden = 'Token Hidden', TokenImportCanceled = 'Token Import Canceled', diff --git a/shared/constants/network.test.ts b/shared/constants/network.test.ts index 0c4f50b87b7f..ac427914fbaa 100644 --- a/shared/constants/network.test.ts +++ b/shared/constants/network.test.ts @@ -3,6 +3,7 @@ import { join } from 'path'; import { CHAIN_IDS, CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP, + FEATURED_RPCS, NETWORK_TO_NAME_MAP, } from './network'; @@ -24,4 +25,70 @@ describe('NetworkConstants', () => { expect(NETWORK_TO_NAME_MAP[CHAIN_IDS.OPTIMISM]).toBe('OP Mainnet'); expect(NETWORK_TO_NAME_MAP[CHAIN_IDS.POLYGON]).toBe('Polygon'); }); + describe('popularNetwork', () => { + it('should have correct chainIds for all popular network', () => { + const expectedChainIds: { [key: string]: string } = { + 'Arbitrum One': CHAIN_IDS.ARBITRUM, + 'Avalanche Network C-Chain': CHAIN_IDS.AVALANCHE, + 'BNB Chain': CHAIN_IDS.BSC, + 'OP Mainnet': CHAIN_IDS.OPTIMISM, + 'Polygon Mainnet': CHAIN_IDS.POLYGON, + 'zkSync Era Mainnet': CHAIN_IDS.ZKSYNC_ERA, + 'Base Mainnet': CHAIN_IDS.BASE, + }; + + FEATURED_RPCS.forEach((rpc) => { + expect(rpc.chainId).toBe(expectedChainIds[rpc.nickname]); + }); + }); + }); + + describe('FEATURED_RPCS Infura Usage Tests', () => { + it('arbitrum entry should use Infura', () => { + const arbitrumRpc = FEATURED_RPCS.find( + (rpc) => rpc.chainId === CHAIN_IDS.ARBITRUM, + ); + expect(arbitrumRpc?.rpcUrl).toContain('infura.io'); + }); + + it('avalanche entry should use Infura', () => { + const avalancheRpc = FEATURED_RPCS.find( + (rpc) => rpc.chainId === CHAIN_IDS.AVALANCHE, + ); + expect(avalancheRpc?.rpcUrl).toContain('infura.io'); + }); + + it('bsc entry should not use Infura', () => { + const bscRpc = FEATURED_RPCS.find((rpc) => rpc.chainId === CHAIN_IDS.BSC); + expect(bscRpc?.rpcUrl).not.toContain('infura.io'); + }); + + it('optimism entry should use Infura', () => { + const optimismRpc = FEATURED_RPCS.find( + (rpc) => rpc.chainId === CHAIN_IDS.OPTIMISM, + ); + expect(optimismRpc?.rpcUrl).toContain('infura.io'); + }); + + it('polygon entry should use Infura', () => { + const polygonRpc = FEATURED_RPCS.find( + (rpc) => rpc.chainId === CHAIN_IDS.POLYGON, + ); + expect(polygonRpc?.rpcUrl).toContain('infura.io'); + }); + + it('zkSync Era entry should not use Infura', () => { + const zksyncEraRpc = FEATURED_RPCS.find( + (rpc) => rpc.chainId === CHAIN_IDS.ZKSYNC_ERA, + ); + expect(zksyncEraRpc?.rpcUrl).not.toContain('infura.io'); + }); + + it('base entry should not use Infura', () => { + const baseRpc = FEATURED_RPCS.find( + (rpc) => rpc.chainId === CHAIN_IDS.BASE, + ); + expect(baseRpc?.rpcUrl).not.toContain('infura.io'); + }); + }); }); diff --git a/shared/constants/network.ts b/shared/constants/network.ts index 1181a63338ef..3b2908cea942 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -213,6 +213,7 @@ export const DEPRECATED_NETWORKS = [ CHAIN_IDS.GOERLI, CHAIN_IDS.ARBITRUM_GOERLI, CHAIN_IDS.OPTIMISM_GOERLI, + CHAIN_IDS.POLYGON_TESTNET, CHAIN_IDS.LINEA_GOERLI, ]; @@ -1005,26 +1006,6 @@ export const FEATURED_RPCS: RPCDefinition[] = [ imageUrl: MATIC_TOKEN_IMAGE_URL, }, }, - { - chainId: CHAIN_IDS.CELO, - nickname: CELO_DISPLAY_NAME, - rpcUrl: `https://celo-mainnet.infura.io/v3/${infuraProjectId}`, - ticker: CURRENCY_SYMBOLS.CELO, - rpcPrefs: { - blockExplorerUrl: 'https://celoscan.io', - imageUrl: CELO_TOKEN_IMAGE_URL, - }, - }, - { - chainId: CHAIN_IDS.GNOSIS, - nickname: GNOSIS_DISPLAY_NAME, - rpcUrl: `https://rpc.gnosischain.com`, - ticker: CURRENCY_SYMBOLS.GNOSIS, - rpcPrefs: { - blockExplorerUrl: 'https://gnosisscan.io', - imageUrl: GNOSIS_TOKEN_IMAGE_URL, - }, - }, { chainId: CHAIN_IDS.ZKSYNC_ERA, nickname: ZK_SYNC_ERA_DISPLAY_NAME, diff --git a/shared/constants/security-provider.ts b/shared/constants/security-provider.ts index aeb339c1670a..537d0bd41e5c 100644 --- a/shared/constants/security-provider.ts +++ b/shared/constants/security-provider.ts @@ -2,6 +2,23 @@ export enum SecurityProvider { Blockaid = 'blockaid', } +type SecurityProviderConfig = Record< + SecurityProvider, + { + /** translation key for security provider name */ + readonly tKeyName: string; + /** URL to securty provider website */ + readonly url: string; + } +>; + +export const SECURITY_PROVIDER_CONFIG: Readonly = { + [SecurityProvider.Blockaid]: { + tKeyName: 'blockaid', + url: 'https://blockaid.io/', + }, +}; + /** The reason, also referred to as the attack type, provided in the PPOM Response */ export enum BlockaidReason { /** Approval for a malicious spender */ diff --git a/shared/constants/smartTransactions.js b/shared/constants/smartTransactions.js deleted file mode 100644 index a77d993c7085..000000000000 --- a/shared/constants/smartTransactions.js +++ /dev/null @@ -1,5 +0,0 @@ -import { SECOND } from './time'; - -export const FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME = SECOND * 10; -export const FALLBACK_SMART_TRANSACTIONS_DEADLINE = 180; -export const FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER = 2; diff --git a/shared/constants/smartTransactions.ts b/shared/constants/smartTransactions.ts new file mode 100644 index 000000000000..4e648d2c9cbf --- /dev/null +++ b/shared/constants/smartTransactions.ts @@ -0,0 +1,21 @@ +import { SECOND } from './time'; + +import { CHAIN_IDS } from './network'; + +export const FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME: number = SECOND * 10; +export const FALLBACK_SMART_TRANSACTIONS_DEADLINE: number = 180; +export const FALLBACK_SMART_TRANSACTIONS_EXPECTED_DEADLINE = 45; +export const FALLBACK_SMART_TRANSACTIONS_MAX_DEADLINE = 150; +export const FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER: number = 2; + +export const ALLOWED_SMART_TRANSACTIONS_CHAIN_IDS: string[] = [ + CHAIN_IDS.MAINNET, + CHAIN_IDS.SEPOLIA, +]; + +export const SKIP_STX_RPC_URL_CHECK_CHAIN_IDS: string[] = [CHAIN_IDS.SEPOLIA]; + +export const CANCEL_GAS_LIMIT_DEC = 21000; + +export const SMART_TRANSACTIONS_LEARN_MORE_URL = + 'https://support.metamask.io/hc/en-us/articles/9184393821211'; diff --git a/shared/constants/swaps.ts b/shared/constants/swaps.ts index b861c571c181..b2882b88d07c 100644 --- a/shared/constants/swaps.ts +++ b/shared/constants/swaps.ts @@ -49,6 +49,10 @@ export type SwapsTokenObject = { iconUrl: string; }; +type BlockExplorerUrlMap = { + [key: string]: string; +}; + export const ETH_SWAPS_TOKEN_OBJECT: SwapsTokenObject = { symbol: CURRENCY_SYMBOLS.ETH, name: 'Ether', @@ -180,9 +184,6 @@ const ZKSYNC_DEFAULT_BLOCK_EXPLORER_URL = 'https://explorer.zksync.io/'; const LINEA_DEFAULT_BLOCK_EXPLORER_URL = 'https://lineascan.build/'; const BASE_DEFAULT_BLOCK_EXPLORER_URL = 'https://basescan.org/'; -export const SMART_SWAPS_FAQ_AND_RISK_DISCLOSURES_URL = - 'https://support.metamask.io/hc/articles/9184393821211'; - export const ALLOWED_PROD_SWAPS_CHAIN_IDS = [ CHAIN_IDS.MAINNET, SWAPS_TESTNET_CHAIN_ID, @@ -296,18 +297,19 @@ export const SWAPS_CHAINID_DEFAULT_TOKEN_MAP = { [CHAIN_IDS.BASE]: BASE_SWAPS_TOKEN_OBJECT, } as const; -export const SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP = { - [CHAIN_IDS.BSC]: BSC_DEFAULT_BLOCK_EXPLORER_URL, - [CHAIN_IDS.MAINNET]: MAINNET_DEFAULT_BLOCK_EXPLORER_URL, - [CHAIN_IDS.POLYGON]: POLYGON_DEFAULT_BLOCK_EXPLORER_URL, - [CHAIN_IDS.GOERLI]: GOERLI_DEFAULT_BLOCK_EXPLORER_URL, - [CHAIN_IDS.AVALANCHE]: AVALANCHE_DEFAULT_BLOCK_EXPLORER_URL, - [CHAIN_IDS.OPTIMISM]: OPTIMISM_DEFAULT_BLOCK_EXPLORER_URL, - [CHAIN_IDS.ARBITRUM]: ARBITRUM_DEFAULT_BLOCK_EXPLORER_URL, - [CHAIN_IDS.ZKSYNC_ERA]: ZKSYNC_DEFAULT_BLOCK_EXPLORER_URL, - [CHAIN_IDS.LINEA_MAINNET]: LINEA_DEFAULT_BLOCK_EXPLORER_URL, - [CHAIN_IDS.BASE]: BASE_DEFAULT_BLOCK_EXPLORER_URL, -} as const; +export const SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP: BlockExplorerUrlMap = + { + [CHAIN_IDS.BSC]: BSC_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.MAINNET]: MAINNET_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.POLYGON]: POLYGON_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.GOERLI]: GOERLI_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.AVALANCHE]: AVALANCHE_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.OPTIMISM]: OPTIMISM_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.ARBITRUM]: ARBITRUM_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.ZKSYNC_ERA]: ZKSYNC_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.LINEA_MAINNET]: LINEA_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.BASE]: BASE_DEFAULT_BLOCK_EXPLORER_URL, + } as const; export const ETHEREUM = 'ethereum'; export const POLYGON = 'polygon'; diff --git a/shared/constants/urls.ts b/shared/constants/urls.ts new file mode 100644 index 000000000000..a44d7d2573c8 --- /dev/null +++ b/shared/constants/urls.ts @@ -0,0 +1,3 @@ +export enum BaseUrl { + Portfolio = 'https://portfolio.metamask.io', +} diff --git a/shared/lib/metamask-controller-utils.js b/shared/lib/metamask-controller-utils.js index dcc76476fdb8..c10e17c37763 100644 --- a/shared/lib/metamask-controller-utils.js +++ b/shared/lib/metamask-controller-utils.js @@ -1,3 +1,8 @@ +import { TransactionType } from '@metamask/transaction-controller'; + export function getTokenValueParam(tokenData = {}) { + if (tokenData?.name === TransactionType.tokenMethodIncreaseAllowance) { + return tokenData?.args?.increment?.toString(); + } return tokenData?.args?._value?.toString(); } diff --git a/shared/lib/ui-utils.js b/shared/lib/ui-utils.js index f4c768ff7b81..773538d3b269 100644 --- a/shared/lib/ui-utils.js +++ b/shared/lib/ui-utils.js @@ -15,3 +15,6 @@ export const CONSENSYS_TERMS_OF_USE = 'https://consensys.io/terms-of-use'; export const SECURITY_ALERTS_LEARN_MORE_LINK = 'https://support.metamask.io/hc/en-us/articles/19878220833947'; + +export const TRANSACTION_SIMULATIONS_LEARN_MORE_LINK = + 'https://support.metamask.io/transactions-and-gas/transactions/simulations/'; diff --git a/shared/modules/feature-flags.ts b/shared/modules/feature-flags.ts new file mode 100644 index 000000000000..4c05c1919922 --- /dev/null +++ b/shared/modules/feature-flags.ts @@ -0,0 +1,41 @@ +import { CHAIN_IDS } from '../constants/network'; + +enum NetworkName { + Ethereum = 'ethereum', + Polygon = 'polygon', + Bsc = 'bsc', + Avalanche = 'avalanche', + Optimism = 'optimism', + Arbitrum = 'arbitrum', + ZkSyncEra = 'zksync', + Linea = 'linea', +} + +/** + * @param chainId + * @returns string e.g. ethereum, bsc or polygon + */ +export const getNetworkNameByChainId = (chainId: string): string => { + switch (chainId) { + case CHAIN_IDS.MAINNET: + case CHAIN_IDS.GOERLI: + case CHAIN_IDS.SEPOLIA: + return NetworkName.Ethereum; + case CHAIN_IDS.BSC: + return NetworkName.Bsc; + case CHAIN_IDS.POLYGON: + return NetworkName.Polygon; + case CHAIN_IDS.AVALANCHE: + return NetworkName.Avalanche; + case CHAIN_IDS.OPTIMISM: + return NetworkName.Optimism; + case CHAIN_IDS.ARBITRUM: + return NetworkName.Arbitrum; + case CHAIN_IDS.ZKSYNC_ERA: + return NetworkName.ZkSyncEra; + case CHAIN_IDS.LINEA_MAINNET: + return NetworkName.Linea; + default: + return ''; + } +}; diff --git a/shared/modules/metametrics.test.ts b/shared/modules/metametrics.test.ts new file mode 100644 index 000000000000..1227be64db6f --- /dev/null +++ b/shared/modules/metametrics.test.ts @@ -0,0 +1,151 @@ +import { Provider } from '@metamask/network-controller'; +import { + TransactionStatus, + TransactionType, +} from '@metamask/transaction-controller'; +import { createTestProviderTools } from '../../test/stub/provider'; +import { TransactionMetricsRequest } from '../../app/scripts/lib/transaction/metrics'; +import { CHAIN_IDS } from '../constants/network'; +import { getSmartTransactionMetricsProperties } from './metametrics'; + +const txHash = + '0x0302b75dfb9fd9eb34056af031efcaee2a8cbd799ea054a85966165cd82a7356'; +const address = '0x1678a085c290ebd122dc42cba69373b5953b831d'; +const providerResultStub = { + eth_getCode: '0x123', +}; +const { provider } = createTestProviderTools({ + scaffold: providerResultStub, + chainId: CHAIN_IDS.MAINNET, +}); + +const createTransactionMetricsRequest = (customProps = {}) => { + return { + createEventFragment: jest.fn(), + finalizeEventFragment: jest.fn(), + getEventFragmentById: jest.fn(), + updateEventFragment: jest.fn(), + getAccountType: jest.fn(), + getDeviceModel: jest.fn(), + getEIP1559GasFeeEstimates: jest.fn(), + getSelectedAddress: jest.fn(), + getParticipateInMetrics: jest.fn(), + getTokenStandardAndDetails: jest.fn(), + getTransaction: jest.fn(), + provider: provider as Provider, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + snapAndHardwareMessenger: jest.fn() as any, + trackEvent: jest.fn(), + getIsSmartTransaction: jest.fn(), + getSmartTransactionByMinedTxHash: jest.fn(), + ...customProps, + } as TransactionMetricsRequest; +}; + +const createTransactionMeta = () => { + return { + id: '1', + status: TransactionStatus.unapproved, + txParams: { + from: address, + to: address, + gasPrice: '0x77359400', + gas: '0x7b0d', + nonce: '0x4b', + }, + type: TransactionType.simpleSend, + chainId: CHAIN_IDS.MAINNET, + time: 1624408066355, + defaultGasEstimates: { + gas: '0x7b0d', + gasPrice: '0x77359400', + }, + securityProviderResponse: { + flagAsDangerous: 0, + }, + hash: txHash, + error: null, + }; +}; + +describe('getSmartTransactionMetricsProperties', () => { + it('returns all smart transaction properties', () => { + const transactionMetricsRequest = createTransactionMetricsRequest({ + getIsSmartTransaction: () => true, + getSmartTransactionByMinedTxHash: () => { + return { + uuid: 'uuid', + status: 'success', + cancellable: false, + statusMetadata: { + cancellationFeeWei: 36777567771000, + cancellationReason: 'not_cancelled', + deadlineRatio: 0.6400288486480713, + minedHash: txHash, + duplicated: true, + timedOut: true, + proxied: true, + minedTx: 'success', + }, + }; + }, + }); + const transactionMeta = createTransactionMeta(); + + const result = getSmartTransactionMetricsProperties( + transactionMetricsRequest, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transactionMeta as any, + ); + + expect(result).toStrictEqual({ + is_smart_transaction: true, + smart_transaction_duplicated: true, + smart_transaction_proxied: true, + smart_transaction_timed_out: true, + }); + }); + + it('returns "is_smart_transaction: false" if it is not a smart transaction', () => { + const transactionMetricsRequest = createTransactionMetricsRequest({ + getIsSmartTransaction: () => false, + }); + const transactionMeta = createTransactionMeta(); + + const result = getSmartTransactionMetricsProperties( + transactionMetricsRequest, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transactionMeta as any, + ); + + expect(result).toStrictEqual({ + is_smart_transaction: false, + }); + }); + + it('returns "is_smart_transaction: true" only if it is a smart transaction, but does not have statusMetadata', () => { + const transactionMetricsRequest = createTransactionMetricsRequest({ + getIsSmartTransaction: () => true, + getSmartTransactionByMinedTxHash: () => { + return { + statusMetadata: null, + }; + }, + }); + const transactionMeta = createTransactionMeta(); + + const result = getSmartTransactionMetricsProperties( + transactionMetricsRequest, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transactionMeta as any, + ); + + expect(result).toStrictEqual({ + is_smart_transaction: true, + }); + }); +}); diff --git a/shared/modules/metametrics.ts b/shared/modules/metametrics.ts new file mode 100644 index 000000000000..af049231bdf5 --- /dev/null +++ b/shared/modules/metametrics.ts @@ -0,0 +1,36 @@ +import { TransactionMeta } from '@metamask/transaction-controller'; +import { TransactionMetricsRequest } from '../../app/scripts/lib/transaction/metrics'; + +type SmartTransactionMetricsProperties = { + is_smart_transaction: boolean; + smart_transaction_duplicated?: boolean; + smart_transaction_timed_out?: boolean; + smart_transaction_proxied?: boolean; +}; + +export const getSmartTransactionMetricsProperties = ( + transactionMetricsRequest: TransactionMetricsRequest, + transactionMeta: TransactionMeta, +) => { + const isSmartTransaction = transactionMetricsRequest.getIsSmartTransaction(); + const properties = { + is_smart_transaction: isSmartTransaction, + } as SmartTransactionMetricsProperties; + if (!isSmartTransaction) { + return properties; + } + const smartTransaction = + transactionMetricsRequest.getSmartTransactionByMinedTxHash( + transactionMeta.hash, + ); + const smartTransactionStatusMetadata = smartTransaction?.statusMetadata; + if (!smartTransactionStatusMetadata) { + return properties; + } + properties.smart_transaction_duplicated = + smartTransactionStatusMetadata.duplicated; + properties.smart_transaction_timed_out = + smartTransactionStatusMetadata.timedOut; + properties.smart_transaction_proxied = smartTransactionStatusMetadata.proxied; + return properties; +}; diff --git a/shared/modules/selectors/feature-flags.ts b/shared/modules/selectors/feature-flags.ts new file mode 100644 index 000000000000..930f478aa962 --- /dev/null +++ b/shared/modules/selectors/feature-flags.ts @@ -0,0 +1,35 @@ +import { getCurrentChainId } from '../../../ui/selectors/selectors'; // TODO: Migrate shared selectors to this file. +import { getNetworkNameByChainId } from '../feature-flags'; + +type FeatureFlagsMetaMaskState = { + metamask: { + swapsState: { + swapsFeatureFlags: { + [key: string]: { + extensionActive: boolean; + mobileActive: boolean; + smartTransactions: { + expectedDeadline?: number; + maxDeadline?: number; + returnTxHashAsap?: boolean; + }; + }; + }; + }; + }; +}; + +export function getFeatureFlagsByChainId(state: FeatureFlagsMetaMaskState) { + const chainId = getCurrentChainId(state); + const networkName = getNetworkNameByChainId(chainId); + const featureFlags = state.metamask.swapsState?.swapsFeatureFlags; + if (!featureFlags?.[networkName]) { + return null; + } + return { + smartTransactions: { + ...featureFlags.smartTransactions, + ...featureFlags[networkName].smartTransactions, + }, + }; +} diff --git a/shared/modules/selectors/index.test.ts b/shared/modules/selectors/index.test.ts new file mode 100644 index 000000000000..b2174eadcc8a --- /dev/null +++ b/shared/modules/selectors/index.test.ts @@ -0,0 +1,244 @@ +import { createSwapsMockStore } from '../../../test/jest'; +import { CHAIN_IDS, CURRENCY_SYMBOLS } from '../../constants/network'; +import { + getSmartTransactionsOptInStatus, + getCurrentChainSupportsSmartTransactions, + getSmartTransactionsEnabled, + getIsSmartTransaction, +} from '.'; + +describe('Selectors', () => { + const createMockState = () => { + return { + metamask: { + preferences: { + smartTransactionsOptInStatus: true, + }, + internalAccounts: { + selectedAccount: 'account1', + accounts: { + account1: { + metadata: { + keyring: { + type: 'Hardware', + }, + }, + }, + }, + }, + providerConfig: { + chainId: CHAIN_IDS.MAINNET, + }, + swapsState: { + swapsFeatureFlags: { + ethereum: { + extensionActive: true, + mobileActive: false, + smartTransactions: { + expectedDeadline: 45, + maxDeadline: 150, + returnTxHashAsap: false, + }, + }, + smartTransactions: { + extensionActive: true, + mobileActive: false, + }, + }, + }, + smartTransactionsState: { + liveness: true, + }, + networkConfigurations: { + 'network-configuration-id-1': { + chainId: CHAIN_IDS.MAINNET, + ticker: CURRENCY_SYMBOLS.ETH, + rpcUrl: 'https://mainnet.infura.io/v3/', + }, + }, + }, + }; + }; + + describe('getSmartTransactionsOptInStatus', () => { + it('should return the smart transactions opt-in status', () => { + const state = createMockState(); + const result = getSmartTransactionsOptInStatus(state); + expect(result).toBe(true); + }); + }); + + describe('getCurrentChainSupportsSmartTransactions', () => { + it('should return true if the chain ID is allowed for smart transactions', () => { + const state = createMockState(); + const result = getCurrentChainSupportsSmartTransactions(state); + expect(result).toBe(true); + }); + + it('should return false if the chain ID is not allowed for smart transactions', () => { + const state = createMockState(); + const newState = { + ...state, + metamask: { + ...state.metamask, + providerConfig: { + ...state.metamask.providerConfig, + chainId: CHAIN_IDS.POLYGON, + }, + }, + }; + const result = getCurrentChainSupportsSmartTransactions(newState); + expect(result).toBe(false); + }); + }); + + describe('getSmartTransactionsEnabled', () => { + it('returns true if feature flag is enabled, not a HW and is Ethereum network', () => { + const state = createSwapsMockStore(); + expect(getSmartTransactionsEnabled(state)).toBe(true); + }); + + it('returns false if feature flag is disabled, not a HW and is Ethereum network', () => { + const state = createSwapsMockStore(); + state.metamask.swapsState.swapsFeatureFlags.smartTransactions.extensionActive = + false; + expect(getSmartTransactionsEnabled(state)).toBe(false); + }); + + it('returns false if feature flag is enabled, not a HW, STX liveness is false and is Ethereum network', () => { + const state = createSwapsMockStore(); + state.metamask.smartTransactionsState.liveness = false; + expect(getSmartTransactionsEnabled(state)).toBe(false); + }); + + it('returns false if feature flag is enabled, is a HW and is Ethereum network', () => { + const state = createSwapsMockStore(); + const newState = { + ...state, + metamask: { + ...state.metamask, + internalAccounts: { + ...state.metamask.internalAccounts, + selectedAccount: 'account2', + accounts: { + account2: { + metadata: { + keyring: { + type: 'Trezor Hardware', + }, + }, + }, + }, + }, + }, + }; + expect(getSmartTransactionsEnabled(newState)).toBe(false); + }); + + it('returns false if feature flag is enabled, not a HW and is Polygon network', () => { + const state = createSwapsMockStore(); + const newState = { + ...state, + metamask: { + ...state.metamask, + providerConfig: { + ...state.metamask.providerConfig, + chainId: CHAIN_IDS.POLYGON, + }, + }, + }; + expect(getSmartTransactionsEnabled(newState)).toBe(false); + }); + + it('returns false if feature flag is enabled, not a HW and is BSC network', () => { + const state = createSwapsMockStore(); + const newState = { + ...state, + metamask: { + ...state.metamask, + providerConfig: { + ...state.metamask.providerConfig, + chainId: CHAIN_IDS.BSC, + }, + }, + }; + expect(getSmartTransactionsEnabled(newState)).toBe(false); + }); + + it('returns false if feature flag is enabled, not a HW and is Linea network', () => { + const state = createSwapsMockStore(); + const newState = { + ...state, + metamask: { + ...state.metamask, + providerConfig: { + ...state.metamask.providerConfig, + chainId: CHAIN_IDS.LINEA_MAINNET, + }, + }, + }; + expect(getSmartTransactionsEnabled(newState)).toBe(false); + }); + + it('returns false if a snap account is used', () => { + const state = createSwapsMockStore(); + state.metamask.internalAccounts.selectedAccount = + '36eb02e0-7925-47f0-859f-076608f09b69'; + expect(getSmartTransactionsEnabled(state)).toBe(false); + }); + }); + + describe('getIsSmartTransaction', () => { + it('should return true if smart transactions are opt-in and enabled', () => { + const state = createMockState(); + const result = getIsSmartTransaction(state); + expect(result).toBe(true); + }); + + it('should return false if smart transactions are not opt-in', () => { + const state = createMockState(); + const newState = { + ...state, + metamask: { + ...state.metamask, + preferences: { + ...state.metamask.preferences, + smartTransactionsOptInStatus: false, + }, + }, + }; + const result = getIsSmartTransaction(newState); + expect(result).toBe(false); + }); + + it('should return false if smart transactions are not enabled', () => { + const state = createMockState(); + const newState = { + ...state, + metamask: { + ...state.metamask, + swapsState: { + ...state.metamask.swapsState, + swapsFeatureFlags: { + ethereum: { + extensionActive: true, + mobileActive: false, + smartTransactions: { + expectedDeadline: 45, + maxDeadline: 150, + returnTxHashAsap: false, + }, + }, + smartTransactions: { + extensionActive: false, + mobileActive: false, + }, + }, + }, + }, + }; + const result = getIsSmartTransaction(newState); + expect(result).toBe(false); + }); + }); +}); diff --git a/shared/modules/selectors/index.ts b/shared/modules/selectors/index.ts new file mode 100644 index 000000000000..79362360877f --- /dev/null +++ b/shared/modules/selectors/index.ts @@ -0,0 +1,2 @@ +export * from './smart-transactions'; +export * from './feature-flags'; diff --git a/shared/modules/selectors/smart-transactions.ts b/shared/modules/selectors/smart-transactions.ts new file mode 100644 index 000000000000..a55c09e43859 --- /dev/null +++ b/shared/modules/selectors/smart-transactions.ts @@ -0,0 +1,130 @@ +import type { Hex } from '@metamask/utils'; +import { + ALLOWED_SMART_TRANSACTIONS_CHAIN_IDS, + SKIP_STX_RPC_URL_CHECK_CHAIN_IDS, +} from '../../constants/smartTransactions'; +import { ENVIRONMENT } from '../../../development/build/constants'; +import { + getCurrentChainId, + getCurrentNetwork, + accountSupportsSmartTx, +} from '../../../ui/selectors/selectors'; // TODO: Migrate shared selectors to this file. + +type SmartTransactionsMetaMaskState = { + metamask: { + preferences: { + smartTransactionsOptInStatus: boolean | null; + }; + internalAccounts: { + selectedAccount: string; + accounts: { + [key: string]: { + metadata: { + keyring: { + type: string; + }; + }; + }; + }; + }; + providerConfig: { + chainId: Hex; + }; + swapsState: { + swapsFeatureFlags: { + ethereum: { + extensionActive: boolean; + mobileActive: boolean; + smartTransactions: { + expectedDeadline?: number; + maxDeadline?: number; + returnTxHashAsap?: boolean; + }; + }; + smartTransactions: { + extensionActive: boolean; + mobileActive: boolean; + }; + }; + }; + smartTransactionsState: { + liveness: boolean; + }; + networkConfigurations: { + [key: string]: { + chainId: Hex; + rpcUrl: string; + }; + }; + }; +}; + +export const getSmartTransactionsOptInStatus = ( + state: SmartTransactionsMetaMaskState, +): boolean | null => { + return state.metamask.preferences?.smartTransactionsOptInStatus; +}; + +export const getCurrentChainSupportsSmartTransactions = ( + state: SmartTransactionsMetaMaskState, +): boolean => { + const chainId = getCurrentChainId(state); + return ALLOWED_SMART_TRANSACTIONS_CHAIN_IDS.includes(chainId); +}; + +const getIsAllowedRpcUrlForSmartTransactions = ( + state: SmartTransactionsMetaMaskState, +) => { + const chainId = getCurrentChainId(state); + const isDevelopment = + process.env.METAMASK_ENVIRONMENT === ENVIRONMENT.DEVELOPMENT || + process.env.METAMASK_ENVIRONMENT === ENVIRONMENT.TESTING; + if (isDevelopment || SKIP_STX_RPC_URL_CHECK_CHAIN_IDS.includes(chainId)) { + // Allow any STX RPC URL in development and testing environments or for specific chain IDs. + return true; + } + const currentNetwork = getCurrentNetwork(state); + if (!currentNetwork?.rpcUrl) { + return false; + } + const rpcUrl = new URL(currentNetwork.rpcUrl); + // Only allow STX in prod if an Infura RPC URL is being used. + return rpcUrl?.hostname?.endsWith('.infura.io'); +}; + +export const getIsSmartTransactionsOptInModalAvailable = ( + state: SmartTransactionsMetaMaskState, +) => { + return ( + getCurrentChainSupportsSmartTransactions(state) && + getIsAllowedRpcUrlForSmartTransactions(state) && + getSmartTransactionsOptInStatus(state) === null + ); +}; + +export const getSmartTransactionsEnabled = ( + state: SmartTransactionsMetaMaskState, +): boolean => { + const supportedAccount = accountSupportsSmartTx(state); + // TODO: Create a new proxy service only for MM feature flags. + const smartTransactionsFeatureFlagEnabled = + state.metamask.swapsState?.swapsFeatureFlags?.smartTransactions + ?.extensionActive; + const smartTransactionsLiveness = + state.metamask.smartTransactionsState?.liveness; + return Boolean( + getCurrentChainSupportsSmartTransactions(state) && + getIsAllowedRpcUrlForSmartTransactions(state) && + supportedAccount && + smartTransactionsFeatureFlagEnabled && + smartTransactionsLiveness, + ); +}; + +export const getIsSmartTransaction = ( + state: SmartTransactionsMetaMaskState, +): boolean => { + const smartTransactionsOptInStatus = getSmartTransactionsOptInStatus(state); + const smartTransactionsEnabled = getSmartTransactionsEnabled(state); + return Boolean(smartTransactionsOptInStatus && smartTransactionsEnabled); +}; diff --git a/shared/notifications/index.ts b/shared/notifications/index.ts index 5c0792661a34..dbe45e61f4d5 100644 --- a/shared/notifications/index.ts +++ b/shared/notifications/index.ts @@ -5,8 +5,6 @@ * into numbers in only one place. This should make merge conflicts easier. */ export const NOTIFICATION_DROP_LEDGER_FIREFOX = 25; -export const NOTIFICATION_OPEN_BETA_SNAPS = 26; -export const NOTIFICATION_BUY_SELL_BUTTON = 27; export const NOTIFICATION_U2F_LEDGER_LIVE = 28; export const NOTIFICATION_BLOCKAID_DEFAULT = 29; export const NOTIFICATION_STAKING_PORTFOLIO = 30; @@ -48,22 +46,6 @@ export const UI_NOTIFICATIONS: UINotifications = { id: Number(NOTIFICATION_DROP_LEDGER_FIREFOX), date: null, }, - [NOTIFICATION_OPEN_BETA_SNAPS]: { - id: Number(NOTIFICATION_OPEN_BETA_SNAPS), - date: null, - image: { - src: 'images/introducing-snaps.svg', - width: '100%', - }, - }, - [NOTIFICATION_BUY_SELL_BUTTON]: { - id: Number(NOTIFICATION_BUY_SELL_BUTTON), - date: null, - image: { - src: 'images/sell_button_whatsnew.png', - width: '100%', - }, - }, [NOTIFICATION_U2F_LEDGER_LIVE]: { id: Number(NOTIFICATION_U2F_LEDGER_LIVE), date: null, @@ -100,7 +82,7 @@ export const UI_NOTIFICATIONS: UINotifications = { }, [NOTIFICATION_SIMULATIONS]: { id: Number(NOTIFICATION_SIMULATIONS), - date: null, + date: '2024-04-09', }, }; @@ -189,34 +171,6 @@ export const getTranslatedUINotifications = ( ) : '', }, - [NOTIFICATION_OPEN_BETA_SNAPS]: { - ...UI_NOTIFICATIONS[NOTIFICATION_OPEN_BETA_SNAPS], - title: t('notificationsOpenBetaSnapsTitle'), - description: [ - t('notificationsOpenBetaSnapsDescriptionOne'), - t('notificationsOpenBetaSnapsDescriptionTwo'), - t('notificationsOpenBetaSnapsDescriptionThree'), - ], - actionText: t('notificationsOpenBetaSnapsActionText'), - date: UI_NOTIFICATIONS[NOTIFICATION_OPEN_BETA_SNAPS].date - ? formatDate( - UI_NOTIFICATIONS[NOTIFICATION_OPEN_BETA_SNAPS].date, - formattedLocale, - ) - : '', - }, - [NOTIFICATION_BUY_SELL_BUTTON]: { - ...UI_NOTIFICATIONS[NOTIFICATION_BUY_SELL_BUTTON], - title: t('notificationsBuySellTitle'), - description: t('notificationsBuySellDescription'), - actionText: t('notificationsBuySellActionText'), - date: UI_NOTIFICATIONS[NOTIFICATION_BUY_SELL_BUTTON].date - ? formatDate( - UI_NOTIFICATIONS[NOTIFICATION_BUY_SELL_BUTTON].date, - formattedLocale, - ) - : '', - }, [NOTIFICATION_U2F_LEDGER_LIVE]: { ...UI_NOTIFICATIONS[NOTIFICATION_U2F_LEDGER_LIVE], title: t('notificationsU2FLedgerLiveTitle'), diff --git a/test/data/mock-state.json b/test/data/mock-state.json index ee8531ee7f8a..d4d6555504f8 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -365,6 +365,7 @@ "showExtensionInFullSizeView": false, "showFiatInTestnets": false, "showTestNetworks": true, + "smartTransactionsOptInStatus": false, "useNativeCurrencyAsPrimaryCurrency": true, "petnamesEnabled": false }, diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 6445bba5b085..67889e1f8cab 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -176,6 +176,7 @@ function defaultFixture(inputChainId = CHAIN_IDS.LOCALHOST) { showExtensionInFullSizeView: false, showFiatInTestnets: false, showTestNetworks: false, + smartTransactionsOptInStatus: false, useNativeCurrencyAsPrimaryCurrency: true, petnamesEnabled: true, }, @@ -302,6 +303,7 @@ function onboardingFixture() { showExtensionInFullSizeView: false, showFiatInTestnets: false, showTestNetworks: false, + smartTransactionsOptInStatus: false, useNativeCurrencyAsPrimaryCurrency: true, petnamesEnabled: true, }, diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index e2331a9e0c90..23aa243abc2e 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -57,7 +57,7 @@ async function withFixtures(options, testSuite) { const bundlerServer = new Bundler(); const https = await mockttp.generateCACertificate(); const mockServer = mockttp.getLocal({ https, cors: true }); - let secondaryGanacheServer; + const secondaryGanacheServer = []; let numberOfDapps = dapp ? 1 : 0; const dappServer = []; const phishingPageServer = new PhishingWarningPageServer(); @@ -76,14 +76,17 @@ async function withFixtures(options, testSuite) { } if (ganacheOptions?.concurrent) { - const { port, chainId, ganacheOptions2 } = ganacheOptions.concurrent; - secondaryGanacheServer = new Ganache(); - await secondaryGanacheServer.start({ - blockTime: 2, - chain: { chainId }, - port, - vmErrorsOnRPCResponse: false, - ...ganacheOptions2, + ganacheOptions.concurrent.forEach(async (ganacheSettings) => { + const { port, chainId, ganacheOptions2 } = ganacheSettings; + const server = new Ganache(); + secondaryGanacheServer.push(server); + await server.start({ + blockTime: 2, + chain: { chainId }, + port, + vmErrorsOnRPCResponse: false, + ...ganacheOptions2, + }); }); } @@ -255,7 +258,9 @@ async function withFixtures(options, testSuite) { await ganacheServer.quit(); if (ganacheOptions?.concurrent) { - await secondaryGanacheServer.quit(); + secondaryGanacheServer.forEach(async (server) => { + await server.quit(); + }); } if (useBundler) { diff --git a/test/e2e/json-rpc/switchEthereumChain.spec.js b/test/e2e/json-rpc/switchEthereumChain.spec.js index 36856c89ddba..78b02c8634e5 100644 --- a/test/e2e/json-rpc/switchEthereumChain.spec.js +++ b/test/e2e/json-rpc/switchEthereumChain.spec.js @@ -22,7 +22,7 @@ describe('Switch Ethereum Chain for two dapps', function () { dappOptions: { numberOfDapps: 2 }, ganacheOptions: { ...defaultGanacheOptions, - concurrent: { port: 8546, chainId: 1338 }, + concurrent: [{ port: 8546, chainId: 1338 }], }, title: this.test.fullTitle(), }, @@ -92,7 +92,7 @@ describe('Switch Ethereum Chain for two dapps', function () { dappOptions: { numberOfDapps: 2 }, ganacheOptions: { ...defaultGanacheOptions, - concurrent: { port: 8546, chainId: 1338 }, + concurrent: [{ port: 8546, chainId: 1338 }], }, title: this.test.fullTitle(), }, @@ -164,7 +164,7 @@ describe('Switch Ethereum Chain for two dapps', function () { dappOptions: { numberOfDapps: 2 }, ganacheOptions: { ...defaultGanacheOptions, - concurrent: { port: 8546, chainId: 1338 }, + concurrent: [{ port: 8546, chainId: 1338 }], }, title: this.test.fullTitle(), }, @@ -237,7 +237,7 @@ describe('Switch Ethereum Chain for two dapps', function () { dappOptions: { numberOfDapps: 2 }, ganacheOptions: { ...defaultGanacheOptions, - concurrent: { port: 8546, chainId: 1338 }, + concurrent: [{ port: 8546, chainId: 1338 }], }, title: this.test.fullTitle(), }, diff --git a/test/e2e/mmi/specs/navigation.spec.ts b/test/e2e/mmi/specs/navigation.spec.ts index 0b3f26d89c96..e6703460f08f 100644 --- a/test/e2e/mmi/specs/navigation.spec.ts +++ b/test/e2e/mmi/specs/navigation.spec.ts @@ -18,12 +18,10 @@ const supportContactUs = 'https://mmi-support.metamask.io/hc/en-us/requests/new'; const mmiHomePage = 'https://metamask.io/institutions/'; const privacyAndPolicy = 'https://consensys.io/privacy-policy'; -const hwWalletPrivacyAndSecurity = - 'https://support.metamask.io/hc/en-us/articles/4408552261275'; const openSeaTermsOfUse = 'https://opensea.io/securityproviderterms'; const metamaskAttributions = 'https://metamask.io/attributions/'; const termsOfUse = 'https://consensys.io/terms-of-use'; -const learnMoreBlockaid = 'https://support.metamask.io/hc/en-us/articles/19878220833947-How-to-turn-on-Blockaid-security-alerts'; +const learnMoreArticles = 'https://support.metamask.io/hc/en-us/articles'; test.describe('MMI Navigation', () => { test('MMI full navigation links', async ({ context }) => { @@ -124,7 +122,7 @@ test.describe('MMI Navigation', () => { context, mainMenuPage.page, 'learn more', - hwWalletPrivacyAndSecurity, + learnMoreArticles, ); await mainMenuPage.selectSettings('Security & privacy'); @@ -138,7 +136,7 @@ test.describe('MMI Navigation', () => { context, mainMenuPage.page, 'requests. Learn more', - learnMoreBlockaid, + learnMoreArticles, ); await mainMenuPage.selectSettings('Experimental'); diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index ccd3cc9fded9..f3919e04e040 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -305,6 +305,38 @@ async function setupMocking(server, testSpecificMock, { chainId }) { ], occurrences: 9, }, + { + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + symbol: 'DAI', + decimals: 18, + name: 'Dai Stablecoin', + iconUrl: + 'https://raw.githubusercontent.com/MetaMask/contract-metadata/master/images/dai.svg', + type: 'erc20', + aggregators: [ + 'metamask', + 'aave', + 'bancor', + 'cmc', + 'cryptocom', + 'coinGecko', + 'oneInch', + 'pmm', + 'sushiswap', + 'zerion', + 'lifi', + 'socket', + 'squid', + 'openswap', + 'sonarwatch', + 'uniswapLabs', + 'coinmarketcap', + ], + occurrences: 17, + erc20Permit: true, + fees: { '0xb0da5965d43369968574d399dbe6374683773a65': 0 }, + storage: { balance: 2 }, + }, ], }; }); diff --git a/test/e2e/restore/MetaMaskUserData.json b/test/e2e/restore/MetaMaskUserData.json index 4ff35c60dcf6..d13a4b60c3e0 100644 --- a/test/e2e/restore/MetaMaskUserData.json +++ b/test/e2e/restore/MetaMaskUserData.json @@ -36,6 +36,7 @@ "showExtensionInFullSizeView": false, "showFiatInTestnets": false, "showTestNetworks": false, + "smartTransactionsOptInStatus": false, "useNativeCurrencyAsPrimaryCurrency": true }, "theme": "light", diff --git a/test/e2e/tests/custom-rpc-history.spec.js b/test/e2e/tests/custom-rpc-history.spec.js index ef6df367cdb6..e16c90a764e6 100644 --- a/test/e2e/tests/custom-rpc-history.spec.js +++ b/test/e2e/tests/custom-rpc-history.spec.js @@ -17,7 +17,7 @@ describe('Custom RPC history', function () { { fixtures: new FixtureBuilder().build(), ganacheOptions: generateGanacheOptions({ - concurrent: { port, chainId }, + concurrent: [{ port, chainId }], }), title: this.test.fullTitle(), }, diff --git a/test/e2e/tests/dapp-interactions/dapp-interactions.spec.js b/test/e2e/tests/dapp-interactions/dapp-interactions.spec.js index 1181e5856ffd..ff1c8762d408 100644 --- a/test/e2e/tests/dapp-interactions/dapp-interactions.spec.js +++ b/test/e2e/tests/dapp-interactions/dapp-interactions.spec.js @@ -18,7 +18,7 @@ describe('Dapp interactions', function () { dapp: true, fixtures: new FixtureBuilder().build(), ganacheOptions: generateGanacheOptions({ - concurrent: { port: 8546, chainId: 1338 }, + concurrent: [{ port: 8546, chainId: 1338 }], }), title: this.test.fullTitle(), }, diff --git a/test/e2e/tests/metrics/swaps.spec.js b/test/e2e/tests/metrics/swaps.spec.js index 65e9655abb53..4e5d3db29903 100644 --- a/test/e2e/tests/metrics/swaps.spec.js +++ b/test/e2e/tests/metrics/swaps.spec.js @@ -197,7 +197,7 @@ async function assertNavSwapButtonClickedEvent(reqs) { async function assertPrepareSwapPageLoadedEvents(reqs) { const assertionsReq1 = [ (req) => req.event === MetaMetricsEventName.PrepareSwapPageLoaded, - (req) => Object.keys(req.properties).length === 7, + (req) => Object.keys(req.properties).length === 8, (req) => req.properties?.category === MetaMetricsEventCategory.Swaps, (req) => req.properties?.chain_id === toHex(1337), @@ -228,7 +228,7 @@ async function assertPrepareSwapPageLoadedEvents(reqs) { async function assertQuotesRequestedEvents(reqs) { const assertionsReq3 = [ (req) => req.event === MetaMetricsEventName.QuotesRequested, - (req) => Object.keys(req.properties).length === 14, + (req) => Object.keys(req.properties).length === 15, (req) => req.properties?.category === MetaMetricsEventCategory.Swaps, (req) => req.properties?.chain_id === toHex(1337), @@ -266,7 +266,7 @@ async function assertQuotesRequestedEvents(reqs) { async function assertQuotesReceivedAndBestQuoteReviewedEvents(reqs) { const assertionsReq5 = [ (req) => req.event === MetaMetricsEventName.QuotesReceived, - (req) => Object.keys(req.properties).length === 18, + (req) => Object.keys(req.properties).length === 19, (req) => req.properties?.category === MetaMetricsEventCategory.Swaps, (req) => req.properties?.chain_id === toHex(1337), @@ -301,7 +301,7 @@ async function assertQuotesReceivedAndBestQuoteReviewedEvents(reqs) { const assertionsReq7 = [ (req) => req.event === MetaMetricsEventName.BestQuoteReviewed, - (req) => Object.keys(req.properties).length === 17, + (req) => Object.keys(req.properties).length === 18, (req) => req.properties?.category === MetaMetricsEventCategory.Swaps, (req) => req.properties?.chain_id === toHex(1337), @@ -348,7 +348,7 @@ async function assertQuotesReceivedAndBestQuoteReviewedEvents(reqs) { async function assertAllAvailableQuotesOpenedEvents(reqs) { const assertionsReq9 = [ (req) => req.event === MetaMetricsEventName.AllAvailableQuotesOpened, - (req) => Object.keys(req.properties).length === 18, + (req) => Object.keys(req.properties).length === 19, (req) => req.properties?.category === MetaMetricsEventCategory.Swaps, (req) => req.properties?.chain_id === toHex(1337), @@ -391,7 +391,7 @@ async function assertAllAvailableQuotesOpenedEvents(reqs) { async function assertSwapStartedEvents(reqs) { const assertionsReq11 = [ (req) => req.event === MetaMetricsEventName.SwapStarted, - (req) => Object.keys(req.properties).length === 24, + (req) => Object.keys(req.properties).length === 25, (req) => req.properties?.category === MetaMetricsEventCategory.Swaps, (req) => req.properties?.chain_id === toHex(1337), @@ -438,7 +438,7 @@ async function assertSwapStartedEvents(reqs) { async function assertSwapCompletedEvents(reqs) { const assertionsReq13 = [ (req) => req.event === MetaMetricsEventName.SwapCompleted, - (req) => Object.keys(req.properties).length === 30, + (req) => Object.keys(req.properties).length === 31, (req) => req.properties?.category === MetaMetricsEventCategory.Swaps, (req) => req.properties?.chain_id === toHex(1337), (req) => req.properties?.environment_type === 'background', @@ -490,7 +490,7 @@ async function assertSwapCompletedEvents(reqs) { async function assertExitedSwapsEvents(reqs) { const assertionsReq15 = [ (req) => req.event === MetaMetricsEventName.ExitedSwaps, - (req) => Object.keys(req.properties).length === 12, + (req) => Object.keys(req.properties).length === 13, (req) => req.properties?.category === MetaMetricsEventCategory.Swaps, (req) => req.properties?.chain_id === toHex(1337), @@ -524,7 +524,7 @@ async function assertExitedSwapsEvents(reqs) { const assertionsReq17 = [ (req) => req.event === MetaMetricsEventName.ExitedSwaps, - (req) => Object.keys(req.properties).length === 9, + (req) => Object.keys(req.properties).length === 10, (req) => req.properties?.category === MetaMetricsEventCategory.Swaps, (req) => req.properties?.chain_id === toHex(1337), diff --git a/test/e2e/tests/network/chain-interactions.spec.js b/test/e2e/tests/network/chain-interactions.spec.js index b8d05a0bd86a..c03387c93155 100644 --- a/test/e2e/tests/network/chain-interactions.spec.js +++ b/test/e2e/tests/network/chain-interactions.spec.js @@ -12,7 +12,7 @@ describe('Chain Interactions', function () { const port = 8546; const chainId = 1338; const ganacheOptions = generateGanacheOptions({ - concurrent: { port, chainId }, + concurrent: [{ port, chainId }], }); it('should add the Ganache test chain and not switch the network', async function () { await withFixtures( diff --git a/test/e2e/tests/network/deprecated-networks.spec.js b/test/e2e/tests/network/deprecated-networks.spec.js index b1e6d2e2df5a..c40061eb6d0a 100644 --- a/test/e2e/tests/network/deprecated-networks.spec.js +++ b/test/e2e/tests/network/deprecated-networks.spec.js @@ -201,4 +201,89 @@ describe('Deprecated networks', function () { }, ); }); + + it('Should show deprecation warning when switching to Polygon mumbai', async function () { + const TEST_CHAIN_ID = CHAIN_IDS.POLYGON_TESTNET; + async function mockRPCURLAndChainId(mockServer) { + return [ + await mockServer + .forPost('https://responsive-rpc.url/') + .thenCallback(() => ({ + statusCode: 200, + json: { + id: '1694444405781', + jsonrpc: '2.0', + result: TEST_CHAIN_ID, + }, + })), + ]; + } + + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ useSafeChainsListValidation: false }) + .build(), + title: this.test.fullTitle(), + testSpecificMock: mockRPCURLAndChainId, + }, + async ({ driver }) => { + await unlockWallet(driver); + + await openDapp(driver); + await driver.executeScript(` + var params = [{ + chainId: "${TEST_CHAIN_ID}", + chainName: "Polygon Mumbai", + nativeCurrency: { + name: "", + symbol: "MATIC", + decimals: 18 + }, + rpcUrls: ["https://responsive-rpc.url/"], + blockExplorerUrls: [ "http://localhost:8080/api/customRPC" ] + }] + window.ethereum.request({ + method: 'wallet_addEthereumChain', + params + }) + `); + await driver.waitUntilXWindowHandles(3); + const windowHandles = await driver.getAllWindowHandles(); + const [extension] = windowHandles; + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.Dialog, + windowHandles, + ); + + await driver.clickElement({ + tag: 'button', + text: 'Approve', + }); + + const switchNetworkBtn = await driver.findElement({ + tag: 'button', + text: 'Switch network', + }); + + await switchNetworkBtn.click(); + + await driver.waitUntilXWindowHandles(2); + await driver.switchToWindow(extension); + const deprecationWarningText = 'This network is deprecated'; + const isDeprecationWarningDisplayed = await driver.isElementPresent({ + text: deprecationWarningText, + }); + + assert.equal( + isDeprecationWarningDisplayed, + true, + 'Goerli deprecation warning is not displayed', + ); + }, + ); + }); }); diff --git a/test/e2e/tests/network/switch-custom-network.spec.js b/test/e2e/tests/network/switch-custom-network.spec.js index 68c474d81f9e..694a8f309f01 100644 --- a/test/e2e/tests/network/switch-custom-network.spec.js +++ b/test/e2e/tests/network/switch-custom-network.spec.js @@ -17,11 +17,13 @@ describe('Switch ethereum chain', function () { .withPermissionControllerConnectedToTestDapp() .build(), ganacheOptions: generateGanacheOptions({ - concurrent: { - port: 8546, - chainId: 1338, - ganacheOptions2: {}, - }, + concurrent: [ + { + port: 8546, + chainId: 1338, + ganacheOptions2: {}, + }, + ], }), title: this.test.fullTitle(), }, diff --git a/test/e2e/tests/onboarding/onboarding.spec.js b/test/e2e/tests/onboarding/onboarding.spec.js index 3e6bad3eda65..7a7739f2d954 100644 --- a/test/e2e/tests/onboarding/onboarding.spec.js +++ b/test/e2e/tests/onboarding/onboarding.spec.js @@ -258,7 +258,7 @@ describe('MetaMask onboarding @no-mmi', function () { fixtures: new FixtureBuilder({ onboarding: true }).build(), ganacheOptions: { ...defaultGanacheOptions, - concurrent: { port, chainId, ganacheOptions2 }, + concurrent: [{ port, chainId, ganacheOptions2 }], }, title: this.test.fullTitle(), }, @@ -303,7 +303,7 @@ describe('MetaMask onboarding @no-mmi', function () { text: networkName, }); - await locateAccountBalanceDOM(driver, secondaryGanacheServer); + await locateAccountBalanceDOM(driver, secondaryGanacheServer[0]); }, ); }); diff --git a/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js b/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js index 20cd7597224b..f5896e6a3432 100644 --- a/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js +++ b/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js @@ -28,11 +28,13 @@ describe('Request Queuing for Multiple Dapps and Txs on different networks.', fu dappOptions: { numberOfDapps: 2 }, ganacheOptions: { ...defaultGanacheOptions, - concurrent: { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, title: this.test.fullTitle(), }, @@ -45,7 +47,7 @@ describe('Request Queuing for Multiple Dapps and Txs on different networks.', fu // Open Dapp One await openDapp(driver, undefined, DAPP_URL); - // Connect to dapp + // Connect to dapp 1 await driver.findClickableElement({ text: 'Connect', tag: 'button' }); await driver.clickElement('#connectButton'); @@ -85,7 +87,7 @@ describe('Request Queuing for Multiple Dapps and Txs on different networks.', fu // Open Dapp Two await openDapp(driver, undefined, DAPP_ONE_URL); - // Connect to dapp + // Connect to dapp 2 await driver.findClickableElement({ text: 'Connect', tag: 'button' }); await driver.clickElement('#connectButton'); diff --git a/test/e2e/tests/request-queuing/switch-network.spec.js b/test/e2e/tests/request-queuing/switch-network.spec.js index 6bb0093d4665..b8eacb6b560c 100644 --- a/test/e2e/tests/request-queuing/switch-network.spec.js +++ b/test/e2e/tests/request-queuing/switch-network.spec.js @@ -24,11 +24,13 @@ describe('Request Queuing Switch Network on Dapp Send Tx while on different netw .build(), ganacheOptions: { ...defaultGanacheOptions, - concurrent: { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], }, title: this.test.fullTitle(), }, diff --git a/test/e2e/tests/simulation-details/mock-request-buy-erc721.ts b/test/e2e/tests/simulation-details/mock-request-buy-erc721.ts index fde638f94ae7..42f4e8dbfade 100644 --- a/test/e2e/tests/simulation-details/mock-request-buy-erc721.ts +++ b/test/e2e/tests/simulation-details/mock-request-buy-erc721.ts @@ -226,7 +226,7 @@ export const BUY_ERC721_REQUEST_2_MOCK: MockRequestResponse = { { "from": SENDER_ADDRESS_MOCK, "to": "0xef9c21e3ba31a74910fc7e7cb3fc814ad842ad6e", - "data": `0x70a08231000000000000000000000000${SENDER_ADDRESS_NO_0x_MOCK}` + "data": `0x6352211e00000000000000000000000000000000000000000000000000000000000002cf` }, { "chainId": "0x1", @@ -238,7 +238,7 @@ export const BUY_ERC721_REQUEST_2_MOCK: MockRequestResponse = { { "from": SENDER_ADDRESS_MOCK, "to": "0xef9c21e3ba31a74910fc7e7cb3fc814ad842ad6e", - "data": `0x70a08231000000000000000000000000${SENDER_ADDRESS_NO_0x_MOCK}` + "data": `0x6352211e00000000000000000000000000000000000000000000000000000000000002cf` } ] } @@ -355,7 +355,7 @@ export const BUY_ERC721_REQUEST_2_MOCK: MockRequestResponse = { "baseFeePerGas": 42103363836 }, { - "return": "0x0000000000000000000000000000000000000000000000000000000000000001", + "return": `0x00000000000000000000000000000000${SENDER_ADDRESS_NO_0x_MOCK}`, "status": "0x1", "gasUsed": "0x5f66", "gasLimit": "0x60b9", diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index 921e1bf4fbe4..ea5855549836 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -47,7 +47,8 @@ "surveyLinkLastClickedOrClosed": "object", "signatureSecurityAlertResponses": "object", "switchedNetworkDetails": "object", - "switchedNetworkNeverShowMessage": "boolean" + "switchedNetworkNeverShowMessage": "boolean", + "currentExtensionPopupId": "number" }, "ApprovalController": { "pendingApprovals": "object", @@ -169,6 +170,7 @@ "showExtensionInFullSizeView": false, "showFiatInTestnets": false, "showTestNetworks": false, + "smartTransactionsOptInStatus": false, "useNativeCurrencyAsPrimaryCurrency": true, "petnamesEnabled": true }, diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 0550781932be..06928aa7da9d 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -31,6 +31,7 @@ "showExtensionInFullSizeView": false, "showFiatInTestnets": false, "showTestNetworks": false, + "smartTransactionsOptInStatus": false, "useNativeCurrencyAsPrimaryCurrency": true, "petnamesEnabled": true }, @@ -84,6 +85,7 @@ "signatureSecurityAlertResponses": "object", "switchedNetworkDetails": "object", "switchedNetworkNeverShowMessage": "boolean", + "currentExtensionPopupId": "number", "currentAppVersion": "string", "previousAppVersion": "", "previousMigrationVersion": 0, diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json index b6489b41f841..1b53cca6e808 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json @@ -99,6 +99,7 @@ "showExtensionInFullSizeView": false, "showFiatInTestnets": false, "showTestNetworks": false, + "smartTransactionsOptInStatus": false, "useNativeCurrencyAsPrimaryCurrency": true, "petnamesEnabled": true }, diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json index 6c3c9bec6eb6..36212f192e6b 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -99,6 +99,7 @@ "showExtensionInFullSizeView": false, "showFiatInTestnets": false, "showTestNetworks": false, + "smartTransactionsOptInStatus": false, "useNativeCurrencyAsPrimaryCurrency": true, "petnamesEnabled": true }, diff --git a/test/e2e/tests/tokens/increase-token-allowance.spec.js b/test/e2e/tests/tokens/increase-token-allowance.spec.js index b80d7249d960..1a1e861d6bad 100644 --- a/test/e2e/tests/tokens/increase-token-allowance.spec.js +++ b/test/e2e/tests/tokens/increase-token-allowance.spec.js @@ -1,3 +1,4 @@ +const { strict: assert } = require('assert'); const FixtureBuilder = require('../../fixture-builder'); const { defaultGanacheOptions, @@ -11,6 +12,8 @@ const { } = require('../../helpers'); const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); +const DEFAULT_TEST_DAPP_INCREASE_ALLOWANCE_SPENDING_CAP = '1'; + describe('Increase Token Allowance', function () { const smartContract = SMART_CONTRACTS.HST; @@ -233,10 +236,37 @@ describe('Increase Token Allowance', function () { await driver.delay(2000); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - const setSpendingCap = await driver.findElement( + let spendingCapElement = await driver.findElement( + '[data-testid="custom-spending-cap-input"]', + ); + + let spendingCapValue = await spendingCapElement.getProperty('value'); + assert.equal( + spendingCapValue, + DEFAULT_TEST_DAPP_INCREASE_ALLOWANCE_SPENDING_CAP, + 'Default Test Dapp Increase Allowance Spending Cap is unexpected', + ); + + spendingCapElement = await driver.findElement( '[data-testid="custom-spending-cap-input"]', ); - await setSpendingCap.fill(finalSpendingCap); + await spendingCapElement.clear(); + + await spendingCapElement.fill('0'); + + await driver.clickElement({ + text: 'Use site suggestion', + tag: 'button', + }); + + spendingCapValue = await spendingCapElement.getProperty('value'); + assert.equal( + spendingCapValue, + DEFAULT_TEST_DAPP_INCREASE_ALLOWANCE_SPENDING_CAP, + 'Test Dapp Suggestion Increase Allowance Spending Cap is unexpected', + ); + + await spendingCapElement.fill(finalSpendingCap); await driver.clickElement({ tag: 'button', diff --git a/test/e2e/tests/tokens/nft/remove-nft.spec.js b/test/e2e/tests/tokens/nft/remove-nft.spec.js index 16dad20de072..7d54df845ba8 100644 --- a/test/e2e/tests/tokens/nft/remove-nft.spec.js +++ b/test/e2e/tests/tokens/nft/remove-nft.spec.js @@ -3,25 +3,57 @@ const { defaultGanacheOptions, withFixtures, unlockWallet, + getEventPayloads, } = require('../../../helpers'); const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); const FixtureBuilder = require('../../../fixture-builder'); +const { + MetaMetricsEventName, +} = require('../../../../../shared/constants/metametrics'); +const { CHAIN_IDS } = require('../../../../../shared/constants/network'); + +async function mockedNftRemoved(mockServer) { + return await mockServer + .forPost('https://api.segment.io/v1/batch') + .withJsonBodyIncluding({ + batch: [{ type: 'track', event: MetaMetricsEventName.NFTRemoved }], + }) + .thenCallback(() => { + return { + statusCode: 200, + }; + }); +} describe('Remove NFT', function () { const smartContract = SMART_CONTRACTS.NFTS; - it('user should be able to remove ERC721 NFT on details page', async function () { + it('user should be able to remove ERC721 NFT on details page and removeNft event should be emitted', async function () { + async function mockSegment(mockServer) { + return [await mockedNftRemoved(mockServer)]; + } await withFixtures( { dapp: true, - fixtures: new FixtureBuilder().withNftControllerERC721().build(), + fixtures: new FixtureBuilder() + .withNftControllerERC721() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), ganacheOptions: defaultGanacheOptions, smartContract, title: this.test.fullTitle(), + testSpecificMock: mockSegment, }, - async ({ driver }) => { + async ({ driver, mockedEndpoint: mockedEndpoints, contractRegistry }) => { await unlockWallet(driver); + const contractAddress = await contractRegistry.getContractAddress( + smartContract, + ); + // Open the details and click remove nft button await driver.clickElement('[data-testid="home__nfts-tab"]'); await driver.clickElement('.nft-item__container'); @@ -41,6 +73,20 @@ describe('Remove NFT', function () { text: 'No NFTs yet', }); assert.equal(await noNftInfo.isDisplayed(), true); + + // Check if event was emitted + const events = await getEventPayloads(driver, mockedEndpoints); + const nftRemovedProperties = events[0].properties; + assert.equal( + nftRemovedProperties.token_contract_address, + contractAddress, + ); + assert.equal(nftRemovedProperties.tokenId, '1'); + assert.equal(nftRemovedProperties.asset_type, 'NFT'); + assert.equal(nftRemovedProperties.token_standard, 'ERC721'); + assert.equal(nftRemovedProperties.token_standard, 'ERC721'); + assert.equal(nftRemovedProperties.isSuccessful, true); + assert.equal(nftRemovedProperties.chain_id, CHAIN_IDS.LOCALHOST); }, ); }); diff --git a/test/jest/index.js b/test/jest/index.js index 06ee400b24fe..e82392f8c0b4 100644 --- a/test/jest/index.js +++ b/test/jest/index.js @@ -1,5 +1,8 @@ export { screen, fireEvent, waitFor } from '@testing-library/react'; -export { createSwapsMockStore } from './mock-store'; +export { + createSwapsMockStore, + createGetSmartTransactionFeesApiResponse, +} from './mock-store'; export { renderWithProvider } from './rendering'; export * as MOCKS from './mocks'; export * as CONSTANTS from './constants'; diff --git a/test/jest/mock-store.js b/test/jest/mock-store.js index af851bba6105..0719c6f1eea8 100644 --- a/test/jest/mock-store.js +++ b/test/jest/mock-store.js @@ -1,10 +1,10 @@ import { NetworkType } from '@metamask/controller-utils'; import { NetworkStatus } from '@metamask/network-controller'; import { EthAccountType, EthMethod } from '@metamask/keyring-api'; -import { CHAIN_IDS } from '../../shared/constants/network'; +import { CHAIN_IDS, CURRENCY_SYMBOLS } from '../../shared/constants/network'; import { KeyringType } from '../../shared/constants/keyring'; -const createGetSmartTransactionFeesApiResponse = () => { +export const createGetSmartTransactionFeesApiResponse = () => { return { tradeTxFees: { // Approval tx. @@ -150,6 +150,7 @@ export const createSwapsMockStore = () => { }, preferences: { showFiatInTestnets: true, + smartTransactionsOptInStatus: true, }, transactions: [ { @@ -361,7 +362,13 @@ export const createSwapsMockStore = () => { accounts: ['0xd85a4b6a394794842887b8284293d69163007bbb'], }, ], - networkConfigurations: {}, + networkConfigurations: { + 'network-configuration-id-1': { + chainId: CHAIN_IDS.MAINNET, + ticker: CURRENCY_SYMBOLS.ETH, + rpcUrl: 'https://mainnet.infura.io/v3/', + }, + }, tokens: [ { erc20: true, @@ -378,6 +385,15 @@ export const createSwapsMockStore = () => { ], swapsState: { swapsFeatureFlags: { + ethereum: { + extensionActive: true, + mobileActive: false, + smartTransactions: { + expectedDeadline: 45, + maxDeadline: 150, + returnTxHashAsap: false, + }, + }, smartTransactions: { mobileActive: true, extensionActive: true, diff --git a/ui/components/app/add-network/add-network.test.js b/ui/components/app/add-network/add-network.test.js index 1bfd6dc26fc5..3a15f4d33c3e 100644 --- a/ui/components/app/add-network/add-network.test.js +++ b/ui/components/app/add-network/add-network.test.js @@ -27,6 +27,13 @@ jest.mock('../../../selectors', () => ({ getTheme: () => 'light', })); +jest.mock( + '../../../pages/confirmations/components/simulation-details/useBalanceChanges', + () => ({ + useBalanceChanges: jest.fn(), + }), +); + const render = () => { const store = configureStore({ metamask: { diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 4fd5b9ae16c4..eb4f8e148b31 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -43,6 +43,7 @@ @import 'recovery-phrase-reminder/index'; @import 'step-progress-bar/index.scss'; @import 'selected-account/index'; +@import 'smart-transactions/index'; @import 'srp-input/srp-input'; @import 'snaps/snap-privacy-warning/index'; @import 'tab-bar/index'; diff --git a/ui/components/app/connected-accounts-permissions/connected-accounts-permissions.js b/ui/components/app/connected-accounts-permissions/connected-accounts-permissions.js index e7928b187e22..9976961c5b53 100644 --- a/ui/components/app/connected-accounts-permissions/connected-accounts-permissions.js +++ b/ui/components/app/connected-accounts-permissions/connected-accounts-permissions.js @@ -2,6 +2,7 @@ import classnames from 'classnames'; import PropTypes from 'prop-types'; import React, { useState } from 'react'; import { flatten } from 'lodash'; +import { useSelector } from 'react-redux'; import { Box, ButtonIcon, @@ -20,10 +21,13 @@ import { JustifyContent, TextVariant, } from '../../../helpers/constants/design-system'; +import { getSnapName } from '../../../helpers/utils/util'; +import { getSnapsMetadata } from '../../../selectors'; const ConnectedAccountsPermissions = ({ permissions }) => { const t = useI18nContext(); const [expanded, setExpanded] = useState(false); + const snapsMetadata = useSelector(getSnapsMetadata); const toggleExpanded = () => { setExpanded((_expanded) => !_expanded); @@ -39,6 +43,7 @@ const ConnectedAccountsPermissions = ({ permissions }) => { t, permissionName: key, permissionValue: value, + getSubjectName: getSnapName(snapsMetadata), }), ), ); diff --git a/ui/components/app/metamask-template-renderer/safe-component-list.js b/ui/components/app/metamask-template-renderer/safe-component-list.js index f61dc60c871a..2705c7405d36 100644 --- a/ui/components/app/metamask-template-renderer/safe-component-list.js +++ b/ui/components/app/metamask-template-renderer/safe-component-list.js @@ -21,6 +21,7 @@ import { SnapDelineator } from '../snaps/snap-delineator'; import { Copyable } from '../snaps/copyable'; import Spinner from '../../ui/spinner'; import { SnapUIMarkdown } from '../snaps/snap-ui-markdown'; +import { SmartTransactionStatusPage } from '../../../pages/smart-transactions/smart-transaction-status-page'; import { SnapUIImage } from '../snaps/snap-ui-image'; import { SnapUIInput } from '../snaps/snap-ui-input'; import { SnapUIForm } from '../snaps/snap-ui-form'; @@ -62,6 +63,7 @@ export const safeComponentList = { Tooltip, TruncatedDefinitionList, Typography, + SmartTransactionStatusPage, UrlIcon, ///: BEGIN:ONLY_INCLUDE_IF(snaps) Copyable, diff --git a/ui/components/app/name/name.test.tsx b/ui/components/app/name/name.test.tsx index 3f845b41a6aa..c1746395fdc6 100644 --- a/ui/components/app/name/name.test.tsx +++ b/ui/components/app/name/name.test.tsx @@ -7,8 +7,11 @@ import { MetaMetricsEventCategory, MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; +import { useDisplayName } from '../../../hooks/useDisplayName'; import Name from './name'; +jest.mock('../../../hooks/useDisplayName'); + jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), useDispatch: () => jest.fn(), @@ -17,50 +20,30 @@ jest.mock('react-redux', () => ({ const ADDRESS_NO_SAVED_NAME_MOCK = '0xc0ffee254729296a45a3885639ac7e10f9d54977'; const ADDRESS_SAVED_NAME_MOCK = '0xc0ffee254729296a45a3885639ac7e10f9d54979'; const CHAIN_ID_MOCK = '0x1'; -const PROPOSED_NAME_MOCK = 'TestProposedName'; -const PROPOSED_NAME_2_MOCK = 'TestProposedName2'; const SAVED_NAME_MOCK = 'TestName'; -const SOURCE_ID_MOCK = 'TestSourceId'; -const SOURCE_ID_2_MOCK = 'TestSourceId2'; -const SOURCE_ID_EMPTY_MOCK = 'TestSourceIdEmpty'; -const SOURCE_ID_UNDEFINED_MOCK = 'TestSourceIdUndefined'; const STATE_MOCK = { metamask: { providerConfig: { chainId: CHAIN_ID_MOCK, }, - names: { - [NameType.ETHEREUM_ADDRESS]: { - [ADDRESS_NO_SAVED_NAME_MOCK]: { - [CHAIN_ID_MOCK]: { - proposedNames: { - [SOURCE_ID_MOCK]: [PROPOSED_NAME_MOCK], - [SOURCE_ID_2_MOCK]: [PROPOSED_NAME_2_MOCK], - [SOURCE_ID_EMPTY_MOCK]: [], - [SOURCE_ID_UNDEFINED_MOCK]: undefined, - }, - }, - }, - [ADDRESS_SAVED_NAME_MOCK]: { - [CHAIN_ID_MOCK]: { - proposedNames: { [SOURCE_ID_MOCK]: [PROPOSED_NAME_MOCK] }, - name: SAVED_NAME_MOCK, - }, - }, - }, - }, }, }; describe('Name', () => { const store = configureStore()(STATE_MOCK); + const useDisplayNameMock = jest.mocked(useDisplayName); beforeEach(() => { jest.resetAllMocks(); }); it('renders address with no saved name', () => { + useDisplayNameMock.mockReturnValue({ + name: null, + hasPetname: false, + }); + const { container } = renderWithProvider( { }); it('renders address with saved name', () => { + useDisplayNameMock.mockReturnValue({ + name: SAVED_NAME_MOCK, + hasPetname: true, + }); + const { container } = renderWithProvider( , store, @@ -88,6 +76,11 @@ describe('Name', () => { ])('sends displayed event with %s name', async (_, value, hasPetname) => { const trackEventMock = jest.fn(); + useDisplayNameMock.mockReturnValue({ + name: hasPetname ? SAVED_NAME_MOCK : null, + hasPetname, + }); + renderWithProvider( diff --git a/ui/components/app/network-display/index.scss b/ui/components/app/network-display/index.scss index 16755aa5f86e..9d6fa922819e 100644 --- a/ui/components/app/network-display/index.scss +++ b/ui/components/app/network-display/index.scss @@ -17,13 +17,14 @@ } & .chip__label { + padding-left: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } & .chip__left-icon { - margin-left: 4px; + padding-left: 8px; } & .chip__right-icon { diff --git a/ui/components/app/nft-details/nft-details.js b/ui/components/app/nft-details/nft-details.js index 7a9ebeea6ca9..16e544940b0b 100644 --- a/ui/components/app/nft-details/nft-details.js +++ b/ui/components/app/nft-details/nft-details.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useContext } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; @@ -54,6 +54,8 @@ import { ButtonIcon, IconName, Text } from '../../component-library'; import Tooltip from '../../ui/tooltip'; import { decWEIToDecETH } from '../../../../shared/modules/conversion.utils'; import { NftItem } from '../../multichain/nft-item'; +import { MetaMetricsEventName } from '../../../../shared/constants/metametrics'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; export default function NftDetails({ nft }) { const { @@ -74,6 +76,7 @@ export default function NftDetails({ nft }) { const nftContracts = useSelector(getNftContracts); const currentNetwork = useSelector(getCurrentChainId); const currentChain = useSelector(getCurrentNetwork); + const trackEvent = useContext(MetaMetricsContext); const [addressCopied, handleAddressCopy] = useCopyToClipboard(); @@ -94,11 +97,32 @@ export default function NftDetails({ nft }) { 'M/d/y', ); - const onRemove = () => { - dispatch(removeAndIgnoreNft(address, tokenId)); - dispatch(setNewNftAddedMessage('')); - dispatch(setRemoveNftMessage('success')); - history.push(DEFAULT_ROUTE); + const onRemove = async () => { + let isSuccessfulEvent = false; + try { + await dispatch(removeAndIgnoreNft(address, tokenId)); + dispatch(setNewNftAddedMessage('')); + dispatch(setRemoveNftMessage('success')); + isSuccessfulEvent = true; + } catch (err) { + dispatch(setNewNftAddedMessage('')); + dispatch(setRemoveNftMessage('error')); + } finally { + // track event + trackEvent({ + event: MetaMetricsEventName.NFTRemoved, + category: 'Wallet', + sensitiveProperties: { + token_contract_address: address, + tokenId: tokenId.toString(), + asset_type: AssetType.NFT, + token_standard: standard, + chain_id: currentNetwork, + isSuccessful: isSuccessfulEvent, + }, + }); + history.push(DEFAULT_ROUTE); + } }; const prevNft = usePrevious(nft); diff --git a/ui/components/app/nft-details/nft-details.test.js b/ui/components/app/nft-details/nft-details.test.js index 4aae1d61f995..91eea81ff634 100644 --- a/ui/components/app/nft-details/nft-details.test.js +++ b/ui/components/app/nft-details/nft-details.test.js @@ -85,7 +85,7 @@ describe('NFT Details', () => { expect(mockHistoryPush).toHaveBeenCalledWith(DEFAULT_ROUTE); }); - it(`should call removeAndIgnoreNFT with proper nft details and route to '/' when removing nft`, () => { + it(`should call removeAndIgnoreNFT with proper nft details and route to '/' when removing nft`, async () => { const { queryByTestId } = renderWithProvider( , mockStore, @@ -97,7 +97,7 @@ describe('NFT Details', () => { const removeNftButton = queryByTestId('nft-item-remove'); fireEvent.click(removeNftButton); - expect(removeAndIgnoreNft).toHaveBeenCalledWith( + await expect(removeAndIgnoreNft).toHaveBeenCalledWith( nfts[5].address, nfts[5].tokenId, ); @@ -105,6 +105,29 @@ describe('NFT Details', () => { expect(mockHistoryPush).toHaveBeenCalledWith(DEFAULT_ROUTE); }); + it(`should call setRemoveNftMessage with error when removeAndIgnoreNft fails and route to '/'`, async () => { + const { queryByTestId } = renderWithProvider( + , + mockStore, + ); + removeAndIgnoreNft.mockImplementation(() => { + throw new Error('Error'); + }); + + const openOptionMenuButton = queryByTestId('nft-options__button'); + fireEvent.click(openOptionMenuButton); + + const removeNftButton = queryByTestId('nft-item-remove'); + fireEvent.click(removeNftButton); + + await expect(removeAndIgnoreNft).toHaveBeenCalledWith( + nfts[5].address, + nfts[5].tokenId, + ); + expect(setRemoveNftMessage).toHaveBeenCalledWith('error'); + expect(mockHistoryPush).toHaveBeenCalledWith(DEFAULT_ROUTE); + }); + it('should copy nft address', async () => { const { queryByTestId } = renderWithProvider( , diff --git a/ui/components/app/nfts-tab/nfts-tab.js b/ui/components/app/nfts-tab/nfts-tab.js index 0a66e3526468..b267f7cf168d 100644 --- a/ui/components/app/nfts-tab/nfts-tab.js +++ b/ui/components/app/nfts-tab/nfts-tab.js @@ -64,8 +64,9 @@ export default function NftsTab() { const hasAnyNfts = Object.keys(collections).length > 0; const showNftBanner = hasAnyNfts === false; - const currentNetwork = useSelector(getCurrentNetwork); + const { chainId, nickname } = useSelector(getCurrentNetwork); const currentLocale = useSelector(getCurrentLocale); + useEffect(() => { if (!showNftBanner) { return; @@ -74,13 +75,13 @@ export default function NftsTab() { event: MetaMetricsEventName.EmptyNftsBannerDisplayed, category: MetaMetricsEventCategory.Navigation, properties: { - chain_id: currentNetwork.chainId, + chain_id: chainId, locale: currentLocale, - network: currentNetwork.nickname, + network: nickname, referrer: ORIGIN_METAMASK, }, }); - }, [showNftBanner, trackEvent, currentNetwork, currentLocale]); + }, [showNftBanner, trackEvent, chainId, nickname, currentLocale]); if (nftsLoading) { return
{t('loadingNFTs')}
; @@ -116,9 +117,9 @@ export default function NftsTab() { trackEvent({ event: MetaMetricsEventName.EmptyNftsBannerClicked, properties: { - chain_id: currentNetwork.chainId, + chain_id: chainId, locale: currentLocale, - network: currentNetwork.nickname, + network: nickname, referrer: ORIGIN_METAMASK, }, }); diff --git a/ui/components/app/smart-transactions/index.scss b/ui/components/app/smart-transactions/index.scss new file mode 100644 index 000000000000..bc893f06da8d --- /dev/null +++ b/ui/components/app/smart-transactions/index.scss @@ -0,0 +1,5 @@ +.mm-smart-transactions-opt-in-modal { + &__benefit { + flex: 1; + } +} diff --git a/ui/components/app/smart-transactions/smart-transactions-opt-in-modal.tsx b/ui/components/app/smart-transactions/smart-transactions-opt-in-modal.tsx new file mode 100644 index 000000000000..c53c06a9d0e3 --- /dev/null +++ b/ui/components/app/smart-transactions/smart-transactions-opt-in-modal.tsx @@ -0,0 +1,222 @@ +import React, { useCallback, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; + +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { + TextColor, + Display, + FlexDirection, + BlockSize, + AlignItems, + TextAlign, + JustifyContent, + TextVariant, + IconColor, +} from '../../../helpers/constants/design-system'; +import { + Modal, + ModalOverlay, + Text, + Box, + Button, + ButtonVariant, + ModalHeader, + ModalContent, + ButtonLink, + ButtonLinkSize, + AvatarIcon, + IconName, + AvatarIconSize, +} from '../../component-library'; +import { setSmartTransactionsOptInStatus } from '../../../store/actions'; +import { SMART_TRANSACTIONS_LEARN_MORE_URL } from '../../../../shared/constants/smartTransactions'; + +export type SmartTransactionsOptInModalProps = { + isOpen: boolean; + hideWhatsNewPopup: () => void; +}; + +const LearnMoreLink = () => { + const t = useI18nContext(); + return ( + + {t('learnMoreUpperCaseWithDot')} + + ); +}; + +const EnableSmartTransactionsButton = ({ + handleEnableButtonClick, +}: { + handleEnableButtonClick: () => void; +}) => { + const t = useI18nContext(); + return ( + + ); +}; + +const NotRightNowLink = ({ + handleNotRightNowLinkClick, +}: { + handleNotRightNowLinkClick: () => void; +}) => { + const t = useI18nContext(); + return ( + + ); +}; + +const Description = () => { + const t = useI18nContext(); + return ( + + + {t('smartTransactionsDescription')} + + + {t('smartTransactionsDescription2')} + + + {t('smartTransactionsDescription3', [])} + + + ); +}; + +const Benefit = ({ text, iconName }: { text: string; iconName: IconName }) => { + return ( + + + + {text} + + + ); +}; + +const Benefits = () => { + const t = useI18nContext(); + return ( + + + + + + ); +}; + +export default function SmartTransactionsOptInModal({ + isOpen, + hideWhatsNewPopup, +}: SmartTransactionsOptInModalProps) { + const t = useI18nContext(); + const dispatch = useDispatch(); + + const handleEnableButtonClick = useCallback(() => { + dispatch(setSmartTransactionsOptInStatus(true)); + }, [dispatch]); + + const handleNotRightNowLinkClick = useCallback(() => { + dispatch(setSmartTransactionsOptInStatus(false)); + }, [dispatch]); + + useEffect(() => { + if (!isOpen) { + return; + } + // If the Smart Transactions Opt-In modal is open, hide the What's New popup, + // because we don't want to show 2 modals at the same time. + hideWhatsNewPopup(); + }, [isOpen, hideWhatsNewPopup]); + + return ( + + + + + {t('introducingSmartTransactions')} + + + + + + + + + + ); +} diff --git a/ui/components/app/snaps/snap-ui-markdown/index.scss b/ui/components/app/snaps/snap-ui-markdown/index.scss index 17cc5a212854..1e206c28151f 100644 --- a/ui/components/app/snaps/snap-ui-markdown/index.scss +++ b/ui/components/app/snaps/snap-ui-markdown/index.scss @@ -4,15 +4,4 @@ font-style: revert; } } - - &__link { - & > span:last-child { - margin-inline-start: 2px; - } - - & .mm-icon--size-inherit { - // Fixes the icon misalignment in ButtonLink when using ButtonLinkSize.Inherit - top: 0.1em; - } - } } diff --git a/ui/components/app/snaps/snap-ui-markdown/snap-ui-markdown.js b/ui/components/app/snaps/snap-ui-markdown/snap-ui-markdown.js index 35bbb8b1f2f0..41c3a61425b0 100644 --- a/ui/components/app/snaps/snap-ui-markdown/snap-ui-markdown.js +++ b/ui/components/app/snaps/snap-ui-markdown/snap-ui-markdown.js @@ -5,11 +5,14 @@ import { TextVariant, OverflowWrap, TextColor, + Display, } from '../../../../helpers/constants/design-system'; import { ButtonLink, ButtonLinkSize, + Icon, IconName, + IconSize, Text, } from '../../../component-library'; import SnapLinkWarning from '../snap-link-warning'; @@ -27,13 +30,15 @@ const Paragraph = (props) => ( const Link = ({ onClick, children, ...rest }) => ( {children} + ); diff --git a/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js index e2fe8769b969..751b0f53e73a 100644 --- a/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js +++ b/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js @@ -12,8 +12,9 @@ import Button from '../../ui/button'; import Tooltip from '../../ui/tooltip'; import CancelButton from '../cancel-button'; import Popover from '../../ui/popover'; +import { Box } from '../../component-library/box'; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) -import { Box, Icon, IconName, Text } from '../../component-library'; +import { Icon, IconName, Text } from '../../component-library'; import { IconColor } from '../../../helpers/constants/design-system'; ///: END:ONLY_INCLUDE_IF import { SECOND } from '../../../../shared/constants/time'; @@ -380,19 +381,21 @@ export default class TransactionListItemDetails extends PureComponent { } {transactionGroup.initialTransaction.type !== TransactionType.incoming && ( - - - + + + + + )} diff --git a/ui/components/app/transaction-list-item/smart-transaction-list-item.component.js b/ui/components/app/transaction-list-item/smart-transaction-list-item.component.js index 206fa0ec30a1..d339d01f6fa1 100644 --- a/ui/components/app/transaction-list-item/smart-transaction-list-item.component.js +++ b/ui/components/app/transaction-list-item/smart-transaction-list-item.component.js @@ -3,11 +3,9 @@ import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import TransactionStatusLabel from '../transaction-status-label/transaction-status-label'; import TransactionIcon from '../transaction-icon'; -import { useI18nContext } from '../../../hooks/useI18nContext'; import { useTransactionDisplayData } from '../../../hooks/useTransactionDisplayData'; import { formatDateWithYearContext } from '../../../helpers/utils/util'; import { - TransactionGroupCategory, TransactionGroupStatus, SmartTransactionStatus, } from '../../../../shared/constants/transaction'; @@ -35,20 +33,19 @@ export default function SmartTransactionListItem({ isEarliestNonce = false, }) { const dispatch = useDispatch(); - const t = useI18nContext(); const [cancelSwapLinkClicked, setCancelSwapLinkClicked] = useState(false); const [showDetails, setShowDetails] = useState(false); - const { primaryCurrency, recipientAddress, isPending, senderAddress } = - useTransactionDisplayData(transactionGroup); + const { + title, + category, + primaryCurrency, + recipientAddress, + isPending, + senderAddress, + } = useTransactionDisplayData(transactionGroup); const currentChain = useSelector(getCurrentNetwork); - const { sourceTokenSymbol, destinationTokenSymbol, time, status } = - smartTransaction; - const category = TransactionGroupCategory.swap; - const title = t('swapTokenToToken', [ - sourceTokenSymbol, - destinationTokenSymbol, - ]); + const { time, status } = smartTransaction; const date = formatDateWithYearContext(time, 'MMM d, y', 'MMM d'); let displayedStatusKey; if (status === SmartTransactionStatus.pending) { diff --git a/ui/components/app/transaction-list-item/transaction-list-item.stories.js b/ui/components/app/transaction-list-item/transaction-list-item.stories.js index f3f3ec157369..ef4b0567fc27 100644 --- a/ui/components/app/transaction-list-item/transaction-list-item.stories.js +++ b/ui/components/app/transaction-list-item/transaction-list-item.stories.js @@ -156,13 +156,6 @@ SimpleSend.args = { }, }; -Smart.storyName = 'smart'; -Smart.args = { - 'transactionGroup.primaryTransaction': { - ...MOCK_TRANSACTION_BY_TYPE[TransactionType.smart], - }, -}; - Swap.storyName = 'swap'; Swap.args = { 'transactionGroup.primaryTransaction': { diff --git a/ui/components/app/transaction-list/transaction-list.component.js b/ui/components/app/transaction-list/transaction-list.component.js index e8719969ea44..9ec9cd3e395f 100644 --- a/ui/components/app/transaction-list/transaction-list.component.js +++ b/ui/components/app/transaction-list/transaction-list.component.js @@ -212,10 +212,7 @@ export default function TransactionList({ {pendingTransactions.map((dateGroup) => { return dateGroup.transactionGroups.map( (transactionGroup, index) => { - if ( - transactionGroup.initialTransaction.transactionType === - TransactionType.smart - ) { + if (transactionGroup.initialTransaction?.isSmartTransaction) { return ( {renderDateStamp(index, dateGroup)} @@ -261,7 +258,7 @@ export default function TransactionList({ > {renderDateStamp(index, dateGroup)} {transactionGroup.initialTransaction - ?.transactionType === TransactionType.smart ? ( + ?.isSmartTransaction ? ( { updateViewedNotifications({ [NOTIFICATION_DROP_LEDGER_FIREFOX]: true }); }, - [NOTIFICATION_OPEN_BETA_SNAPS]: () => { - updateViewedNotifications({ [NOTIFICATION_OPEN_BETA_SNAPS]: true }); - global.platform.openTab({ - url: 'https://metamask.io/snaps/', - }); - }, - [NOTIFICATION_BUY_SELL_BUTTON]: () => { - updateViewedNotifications({ [NOTIFICATION_BUY_SELL_BUTTON]: true }); - global.platform.openTab({ - url: 'https://portfolio.metamask.io/sell/build-quote', - }); - }, [NOTIFICATION_U2F_LEDGER_LIVE]: () => { updateViewedNotifications({ [NOTIFICATION_U2F_LEDGER_LIVE]: true }); }, @@ -308,8 +294,6 @@ export default function WhatsNewPopup({ onClose }) { 24: renderFirstNotification, // This syntax is unusual, but very helpful here. It's equivalent to `notificationRenderers[NOTIFICATION_DROP_LEDGER_FIREFOX] =` [NOTIFICATION_DROP_LEDGER_FIREFOX]: renderFirstNotification, - [NOTIFICATION_OPEN_BETA_SNAPS]: renderFirstNotification, - [NOTIFICATION_BUY_SELL_BUTTON]: renderFirstNotification, [NOTIFICATION_U2F_LEDGER_LIVE]: renderFirstNotification, [NOTIFICATION_STAKING_PORTFOLIO]: renderFirstNotification, ///: BEGIN:ONLY_INCLUDE_IF(blockaid) diff --git a/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap b/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap index bc77e6a4276f..ff700fe86852 100644 --- a/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap +++ b/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap @@ -3,7 +3,7 @@ exports[`AccountListItem renders AccountListItem component and shows account name, address, and balance 1`] = `
+

+ + + + Powered by + + Blockaid + + + + +

`; @@ -165,77 +217,8 @@ exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResp If you approve this request, a third party known for scams might take all your assets.

-
- -

- See details -

- -
-
-

- - - Something doesn't look right? - - Report an issue - - - - -

-
-
-
- - -`; - -exports[`Blockaid Banner Alert should render details section even when features is not provided 1`] = ` -
-
- -
-

- This is a deceptive request -

-

- If you approve this request, a third party known for scams might take all your assets. -

+

+ + + + Powered by + + Blockaid + + + + +

`; -exports[`Blockaid Banner Alert should render details when provided 1`] = ` +exports[`Blockaid Banner Alert should render details section even when features is not provided 1`] = `
-
- -

+

+ - See details -

- + See details +

+ +
+
+

+ + + Something doesn't look right? + + Report an issue + + + + +

+
+
-
+
+
+

+ + + + Powered by + -

+
+ +`; + +exports[`Blockaid Banner Alert should render details when provided 1`] = ` + @@ -395,50 +525,76 @@ exports[`Blockaid Banner Alert should render link to report url 1`] = ` If you approve this request, a third party known for scams might take all your assets.

-
- -

+

+ - See details -

- -
-
-

+ See details +

+ +
+
- - - Something doesn't look right? - - Report an issue - - - - -

-
-
+

+ + + Something doesn't look right? + + Report an issue + + + + +

+
+ +

+ + + + Powered by + + Blockaid + + + + +

diff --git a/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js b/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js index d60c90e0138e..6a195c257a3d 100644 --- a/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js +++ b/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js @@ -70,7 +70,12 @@ function BlockaidBannerAlert({ txData, ...props }) { ) { return null; } else if (securityAlertResponse.reason === 'loading') { - return ; + return ( + + ); } const { diff --git a/ui/pages/confirmations/components/security-provider-banner-alert/security-provider-banner-alert.js b/ui/pages/confirmations/components/security-provider-banner-alert/security-provider-banner-alert.js index 6c7931599b84..5b454bee1225 100644 --- a/ui/pages/confirmations/components/security-provider-banner-alert/security-provider-banner-alert.js +++ b/ui/pages/confirmations/components/security-provider-banner-alert/security-provider-banner-alert.js @@ -2,7 +2,11 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; import { BannerAlert, + Box, ButtonLink, + Icon, + IconName, + IconSize, Text, } from '../../../../components/component-library'; import Disclosure from '../../../../components/ui/disclosure'; @@ -10,12 +14,19 @@ import { DisclosureVariant } from '../../../../components/ui/disclosure/disclosu import { I18nContext } from '../../../../contexts/i18n'; import { + AlignItems, + Color, Display, + IconColor, Severity, Size, + TextVariant, } from '../../../../helpers/constants/design-system'; -import { SecurityProvider } from '../../../../../shared/constants/security-provider'; +import { + SecurityProvider, + SECURITY_PROVIDER_CONFIG, +} from '../../../../../shared/constants/security-provider'; import ZENDESK_URLS from '../../../../helpers/constants/zendesk-url'; function SecurityProviderBannerAlert({ @@ -39,22 +50,52 @@ function SecurityProviderBannerAlert({ > {description} - - {details} - - {t('somethingDoesntLookRight', [ + + + {details} + + {t('somethingDoesntLookRight', [ + + {t('reportIssue')} + , + ])} + + + + + {provider && ( + + + {t('securityProviderPoweredBy', [ - {t('reportIssue')} + {t(SECURITY_PROVIDER_CONFIG[provider].tKeyName)} , ])} - + )} ); } diff --git a/ui/pages/confirmations/components/signature-request-original/signature-request-original.component.js b/ui/pages/confirmations/components/signature-request-original/signature-request-original.component.js index fcf24c05237f..0a0516e73dce 100644 --- a/ui/pages/confirmations/components/signature-request-original/signature-request-original.component.js +++ b/ui/pages/confirmations/components/signature-request-original/signature-request-original.component.js @@ -176,7 +176,12 @@ export default class SignatureRequestOriginal extends Component {
{ ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - + ///: END:ONLY_INCLUDE_IF } {isSuspiciousResponse(txData?.securityProviderResponse) && ( diff --git a/ui/pages/confirmations/components/signature-request-siwe/signature-request-siwe.js b/ui/pages/confirmations/components/signature-request-siwe/signature-request-siwe.js index f3b72b0634df..0baa9dd7e801 100644 --- a/ui/pages/confirmations/components/signature-request-siwe/signature-request-siwe.js +++ b/ui/pages/confirmations/components/signature-request-siwe/signature-request-siwe.js @@ -153,7 +153,12 @@ export default function SignatureRequestSIWE({ { ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - + ///: END:ONLY_INCLUDE_IF } {showSecurityProviderBanner && ( diff --git a/ui/pages/confirmations/components/signature-request/signature-request.js b/ui/pages/confirmations/components/signature-request/signature-request.js index 2581125fac8c..80d978bc7b3a 100644 --- a/ui/pages/confirmations/components/signature-request/signature-request.js +++ b/ui/pages/confirmations/components/signature-request/signature-request.js @@ -246,7 +246,12 @@ const SignatureRequest = ({
{ ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - + ///: END:ONLY_INCLUDE_IF } {showOpenSeaToBlockaidBannerAlert ? ( diff --git a/ui/pages/confirmations/components/simulation-details/simulation-details.tsx b/ui/pages/confirmations/components/simulation-details/simulation-details.tsx index 664e76e4df69..15a3b95461f7 100644 --- a/ui/pages/confirmations/components/simulation-details/simulation-details.tsx +++ b/ui/pages/confirmations/components/simulation-details/simulation-details.tsx @@ -132,6 +132,7 @@ const SimulationDetailsLayout: React.FC<{ }> = ({ inHeader, children }) => ( { const { estimateUsed, hasSimulationError, supportsEIP1559, isNetworkBusy } = useGasFeeContext(); + + ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) const pendingTransactions = useSelector(submittedPendingTransactionsSelector); + ///: END:ONLY_INCLUDE_IF + const t = useI18nContext(); const nativeCurrency = useSelector(getNativeCurrency); const transactionData = txData.txParams.data; @@ -63,7 +71,7 @@ const TransactionAlerts = ({
{ ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - + ///: END:ONLY_INCLUDE_IF } {isSuspiciousResponse(txData?.securityProviderResponse) && ( @@ -78,30 +86,36 @@ const TransactionAlerts = ({ setUserAcknowledgedGasMissing={setUserAcknowledgedGasMissing} /> )} - {supportsEIP1559 && pendingTransactions?.length > 0 && ( - - - - {pendingTransactions?.length === 1 - ? t('pendingTransactionSingle', [pendingTransactions?.length]) - : t('pendingTransactionMultiple', [ - pendingTransactions?.length, - ])} - {' '} - {t('pendingTransactionInfo')} - {t('learnCancelSpeeedup', [ - - {t('cancelSpeedUp')} - , - ])} - - - )} + + { + ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) + supportsEIP1559 && pendingTransactions?.length > 0 && ( + + + + {pendingTransactions?.length === 1 + ? t('pendingTransactionSingle', [pendingTransactions?.length]) + : t('pendingTransactionMultiple', [ + pendingTransactions?.length, + ])} + {' '} + {t('pendingTransactionInfo')} + {t('learnCancelSpeeedup', [ + + {t('cancelSpeedUp')} + , + ])} + + + ) + ///: END:ONLY_INCLUDE_IF + } + {estimateUsed === PriorityLevels.low && ( { ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - + ///: END:ONLY_INCLUDE_IF } {isSuspiciousResponse(txData?.securityProviderResponse) && ( diff --git a/ui/pages/confirmations/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap b/ui/pages/confirmations/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap index 584b992aacac..f597edaa5192 100644 --- a/ui/pages/confirmations/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap +++ b/ui/pages/confirmations/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap @@ -335,7 +335,7 @@ exports[`ConfirmSendEther should render correct information for for confirm send
-
-
-
-
-
- Total -
-
-
-
-
- - 0.00021 - -
-
-
-
-
- - 0.00021 - - - ETH - -
-
-
-
-
-
- Amount + gas fee -
-
-
- - Max amount: - - -
- - 0.00021 - - - ETH - -
-
-
-
-
-
-
+

+ No changes predicted for your wallet +

-
-
-
-
-
- Total -
-
-
-
- - 0.000121 - -
-
-
-
-
-
- Amount + gas fee -
-
-
- - Max amount: - - -
- - 0.000121 - -
-
-
-
-
-
-
diff --git a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.component.js b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.component.js index 7dc6b5ef39b9..b595532fde71 100644 --- a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.component.js @@ -68,6 +68,8 @@ import { SimulationDetails } from '../components/simulation-details'; import { BannerAlert } from '../../../components/component-library'; import { Severity } from '../../../helpers/constants/design-system'; +import { fetchSwapsFeatureFlags } from '../../swaps/swaps.util'; + export default class ConfirmTransactionBase extends Component { static contextTypes = { t: PropTypes.func, @@ -164,6 +166,8 @@ export default class ConfirmTransactionBase extends Component { tokenSymbol: PropTypes.string, updateTransaction: PropTypes.func, updateTransactionValue: PropTypes.func, + setSwapsFeatureFlags: PropTypes.func, + fetchSmartTransactionsLiveness: PropTypes.func, isUsingPaymaster: PropTypes.bool, isSigningOrSubmitting: PropTypes.bool, isUserOpContractDeployError: PropTypes.bool, @@ -174,6 +178,9 @@ export default class ConfirmTransactionBase extends Component { hasDismissedOpenSeaToBlockaidBanner: PropTypes.bool, dismissOpenSeaToBlockaidBanner: PropTypes.func, isNetworkSupportedByBlockaid: PropTypes.bool, + isSmartTransaction: PropTypes.bool, + smartTransactionsOptInStatus: PropTypes.bool, + currentChainSupportsSmartTransactions: PropTypes.bool, }; state = { @@ -519,9 +526,11 @@ export default class ConfirmTransactionBase extends Component { ); + const { simulationData } = txData; + const simulationDetails = ( ); @@ -534,6 +543,8 @@ export default class ConfirmTransactionBase extends Component { dismissOpenSeaToBlockaidBanner(); }; + const showTotals = Boolean(simulationData?.error); + return (
{showOpenSeaToBlockaidBannerAlert ? ( @@ -589,33 +600,36 @@ export default class ConfirmTransactionBase extends Component { ), ]} /> - - - - {t('editGasSubTextAmountLabel')} - {' '} - {renderTotalMaxAmount(true)} -
- } - />, - ]} - /> + {showTotals && ( + + + + {t('editGasSubTextAmountLabel')} + {' '} + {renderTotalMaxAmount(true)} + + } + />, + ]} + /> + )} {nonceField} {showLedgerSteps ? ( { this._removeBeforeUnload(); - sendTransaction( - txData, - false, // hideLoadingIndicator - loadingIndicatorMessage, // loadingIndicatorMessage - ) + sendTransaction(txData, hideLoadingIndicator, loadingIndicatorMessage) .then(() => { if (!this._isMounted) { return; @@ -990,13 +1003,17 @@ export default class ConfirmTransactionBase extends Component { window.removeEventListener('beforeunload', this._beforeUnloadForGasPolling); }; - componentDidMount() { + async componentDidMount() { this._isMounted = true; const { toAddress, txData: { origin } = {}, getNextNonce, tryReverseResolveAddress, + smartTransactionsOptInStatus, + currentChainSupportsSmartTransactions, + setSwapsFeatureFlags, + fetchSmartTransactionsLiveness, } = this.props; const { trackEvent } = this.context; trackEvent({ @@ -1032,6 +1049,16 @@ export default class ConfirmTransactionBase extends Component { }); window.addEventListener('beforeunload', this._beforeUnloadForGasPolling); + + if (smartTransactionsOptInStatus && currentChainSupportsSmartTransactions) { + // TODO: Fetching swaps feature flags, which include feature flags for smart transactions, is only a short-term solution. + // Long-term, we want to have a new proxy service specifically for feature flags. + const [swapsFeatureFlags] = await Promise.all([ + fetchSwapsFeatureFlags(), + fetchSmartTransactionsLiveness(), + ]); + await setSwapsFeatureFlags(swapsFeatureFlags); + } } componentWillUnmount() { diff --git a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js index f4b74be4e688..d0c4cf629073 100644 --- a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js @@ -26,6 +26,8 @@ import { updateTransaction, updateEditableParams, dismissOpenSeaToBlockaidBanner, + setSwapsFeatureFlags, + fetchSmartTransactionsLiveness, } from '../../../store/actions'; import { isBalanceSufficient } from '../send/send.utils'; import { shortenAddress, valuesFor } from '../../../helpers/utils/util'; @@ -57,6 +59,10 @@ import { getHasMigratedFromOpenSeaToBlockaid, getIsNetworkSupportedByBlockaid, } from '../../../selectors'; +import { + getCurrentChainSupportsSmartTransactions, + getSmartTransactionsOptInStatus, +} from '../../../../shared/modules/selectors'; import { getMostRecentOverviewPage } from '../../../ducks/history/history'; import { isAddressLedger, @@ -159,6 +165,9 @@ const mapStateToProps = (state, ownProps) => { data, } = (transaction && transaction.txParams) || txParams; const accounts = getMetaMaskAccounts(state); + const smartTransactionsOptInStatus = getSmartTransactionsOptInStatus(state); + const currentChainSupportsSmartTransactions = + getCurrentChainSupportsSmartTransactions(state); const transactionData = parseStandardTokenTransactionData(data); const tokenToAddress = getTokenAddressParam(transactionData); @@ -338,6 +347,8 @@ const mapStateToProps = (state, ownProps) => { isUserOpContractDeployError, useMaxValue, maxValue, + smartTransactionsOptInStatus, + currentChainSupportsSmartTransactions, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) accountType, isNoteToTraderSupported, @@ -395,6 +406,12 @@ export const mapDispatchToProps = (dispatch) => { updateTransactionValue: (id, value) => { dispatch(updateEditableParams(id, { value })); }, + setSwapsFeatureFlags: (swapsFeatureFlags) => { + dispatch(setSwapsFeatureFlags(swapsFeatureFlags)); + }, + fetchSmartTransactionsLiveness: () => { + dispatch(fetchSmartTransactionsLiveness()); + }, getNextNonce: () => dispatch(getNextNonce()), setDefaultHomeActiveTabName: (tabName) => dispatch(setDefaultHomeActiveTabName(tabName)), diff --git a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.test.js b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.test.js index 47bab3aac1d7..d399bcad2073 100644 --- a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.test.js +++ b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.test.js @@ -92,6 +92,7 @@ const baseStore = { chainId: '0x5', txParams: { ...mockTxParams }, status: 'unapproved', + simulationData: {}, }, ], gasEstimateType: GasEstimateTypes.legacy, @@ -289,6 +290,15 @@ describe('Confirm Transaction Base', () => { ...baseStore.send, hasSimulationError: true, }, + metamask: { + ...baseStore.metamask, + transactions: [ + { + ...baseStore.metamask.transactions[0], + simulationData: { error: {} }, + }, + ], + }, }; const { queryByText } = await render({ state }); diff --git a/ui/pages/confirmations/confirm/confirm.tsx b/ui/pages/confirmations/confirm/confirm.tsx index 6fe0deefaa2d..d08d5084a4ec 100644 --- a/ui/pages/confirmations/confirm/confirm.tsx +++ b/ui/pages/confirmations/confirm/confirm.tsx @@ -32,7 +32,7 @@ const Confirm = () => { { // todo: section below is to be removed once new alerts implementation is there ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - + ///: END:ONLY_INCLUDE_IF } diff --git a/ui/pages/confirmations/confirmation/templates/index.js b/ui/pages/confirmations/confirmation/templates/index.js index 4c8a2ed103de..42d48cc7c1b4 100644 --- a/ui/pages/confirmations/confirmation/templates/index.js +++ b/ui/pages/confirmations/confirmation/templates/index.js @@ -7,8 +7,14 @@ import { setNewNetworkAdded, upsertNetworkConfiguration, } from '../../../../store/actions'; +import { + ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) + SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES, + ///: END:ONLY_INCLUDE_IF + SMART_TRANSACTION_CONFIRMATION_TYPES, +} from '../../../../../shared/constants/app'; +import smartTransactionStatusPage from './smart-transaction-status-page'; ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) -import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../../shared/constants/app'; import createSnapAccount from './create-snap-account'; import removeSnapAccount from './remove-snap-account'; import snapAccountRedirect from './snap-account-redirect'; @@ -29,6 +35,8 @@ const APPROVAL_TEMPLATES = { // Use ApprovalType from utils controller [ApprovalType.ResultSuccess]: success, [ApprovalType.ResultError]: error, + [SMART_TRANSACTION_CONFIRMATION_TYPES.showSmartTransactionStatusPage]: + smartTransactionStatusPage, ///: BEGIN:ONLY_INCLUDE_IF(snaps) [ApprovalType.SnapDialogAlert]: snapAlert, [ApprovalType.SnapDialogConfirmation]: snapConfirmation, diff --git a/ui/pages/confirmations/confirmation/templates/smart-transaction-status-page.js b/ui/pages/confirmations/confirmation/templates/smart-transaction-status-page.js new file mode 100644 index 000000000000..0143e26310ae --- /dev/null +++ b/ui/pages/confirmations/confirmation/templates/smart-transaction-status-page.js @@ -0,0 +1,28 @@ +// eslint-disable-next-line no-unused-vars +function getValues(pendingApproval, t, actions, _history) { + const { id, requestState } = pendingApproval; + return { + content: [ + { + element: 'SmartTransactionStatusPage', + key: 'smart-transaction-status-page', + props: { + requestState, + onCloseExtension: () => { + actions.resolvePendingApproval(id, true); + }, + onViewActivity: () => { + actions.resolvePendingApproval(id, true); + }, + }, + }, + ], + hideSubmitButton: true, + }; +} + +const smartTransactionStatusPage = { + getValues, +}; + +export default smartTransactionStatusPage; diff --git a/ui/pages/confirmations/hooks/useTransactionEventFragment.js b/ui/pages/confirmations/hooks/useTransactionEventFragment.js index 3931045a10ac..b28938b4a613 100644 --- a/ui/pages/confirmations/hooks/useTransactionEventFragment.js +++ b/ui/pages/confirmations/hooks/useTransactionEventFragment.js @@ -17,19 +17,22 @@ export const useTransactionEventFragment = () => { }), ); + const fragmentExists = Boolean(fragment); + const gasTransactionId = transaction?.id; + const updateTransactionEventFragment = useCallback( async (params, _transactionId) => { - const transactionId = _transactionId || transaction?.id; + const transactionId = _transactionId || gasTransactionId; if (!transactionId) { return; } - if (!fragment) { + if (!fragmentExists) { await createTransactionEventFragment(transactionId); } updateEventFragment(`transaction-added-${transactionId}`, params); }, - [fragment, transaction], + [fragmentExists, gasTransactionId], ); return { diff --git a/ui/pages/confirmations/token-allowance/token-allowance.js b/ui/pages/confirmations/token-allowance/token-allowance.js index eed5a269a1f8..fff3e82e34df 100644 --- a/ui/pages/confirmations/token-allowance/token-allowance.js +++ b/ui/pages/confirmations/token-allowance/token-allowance.js @@ -413,7 +413,12 @@ export default function TokenAllowance({ /> { ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - <BlockaidBannerAlert txData={txData} margin={[4, 4, 0, 4]} /> + <BlockaidBannerAlert + txData={txData} + marginTop={4} + marginLeft={4} + marginRight={4} + /> ///: END:ONLY_INCLUDE_IF } {showOpenSeaToBlockaidBannerAlert ? ( diff --git a/ui/pages/home/home.component.js b/ui/pages/home/home.component.js index 094802c7d223..856242d0bded 100644 --- a/ui/pages/home/home.component.js +++ b/ui/pages/home/home.component.js @@ -15,6 +15,7 @@ import TermsOfUsePopup from '../../components/app/terms-of-use-popup'; import RecoveryPhraseReminder from '../../components/app/recovery-phrase-reminder'; import WhatsNewPopup from '../../components/app/whats-new-popup'; import { FirstTimeFlowType } from '../../../shared/constants/onboarding'; +import SmartTransactionsOptInModal from '../../components/app/smart-transactions/smart-transactions-opt-in-modal'; ///: END:ONLY_INCLUDE_IF import HomeNotification from '../../components/app/home-notification'; import MultipleNotifications from '../../components/app/multiple-notifications'; @@ -152,6 +153,7 @@ export default class Home extends PureComponent { hideWhatsNewPopup: PropTypes.func.isRequired, announcementsToShow: PropTypes.bool.isRequired, onboardedInThisUISession: PropTypes.bool, + isSmartTransactionsOptInModalAvailable: PropTypes.bool.isRequired, ///: END:ONLY_INCLUDE_IF newNetworkAddedConfigurationId: PropTypes.string, isNotification: PropTypes.bool.isRequired, @@ -507,7 +509,7 @@ export default class Home extends PureComponent { ) : null} {removeNftMessage === 'success' ? ( <ActionableMessage - type="danger" + type="success" className="home__new-network-notification" autoHideTime={autoHideDelay} onAutoHide={onAutoHide} @@ -527,6 +529,28 @@ export default class Home extends PureComponent { } /> ) : null} + {removeNftMessage === 'error' ? ( + <ActionableMessage + type="danger" + className="home__new-network-notification" + autoHideTime={autoHideDelay} + onAutoHide={onAutoHide} + message={ + <Box display={Display.InlineFlex}> + <i className="fa fa-check-circle home__new-nft-notification-icon" /> + <Text variant={TextVariant.bodySm} as="h6"> + {t('removeNftErrorMessage')} + </Text> + <ButtonIcon + iconName={IconName.Close} + size={ButtonIconSize.Sm} + ariaLabel={t('close')} + onClick={onAutoHide} + /> + </Box> + } + /> + ) : null} {newNetworkAddedName ? ( <ActionableMessage type="success" @@ -800,6 +824,7 @@ export default class Home extends PureComponent { announcementsToShow, firstTimeFlowType, newNetworkAddedConfigurationId, + isSmartTransactionsOptInModalAvailable, ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) mmiPortfolioEnabled, @@ -814,15 +839,22 @@ export default class Home extends PureComponent { const tabPadding = process.env.MULTICHAIN ? 4 : 0; // TODO: Remove tabPadding and add paddingTop={4} to parent container Box of Tabs ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) - const showWhatsNew = + const canSeeModals = completedOnboarding && (!onboardedInThisUISession || firstTimeFlowType === FirstTimeFlowType.import) && - announcementsToShow && - showWhatsNewPopup && !process.env.IN_TEST && !newNetworkAddedConfigurationId; + const showSmartTransactionsOptInModal = + canSeeModals && isSmartTransactionsOptInModalAvailable; + + const showWhatsNew = + canSeeModals && + announcementsToShow && + showWhatsNewPopup && + !showSmartTransactionsOptInModal; + const showTermsOfUse = completedOnboarding && !onboardedInThisUISession && showTermsOfUsePopup; ///: END:ONLY_INCLUDE_IF @@ -856,6 +888,10 @@ export default class Home extends PureComponent { { ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) } + <SmartTransactionsOptInModal + isOpen={showSmartTransactionsOptInModal} + hideWhatsNewPopup={hideWhatsNewPopup} + /> {showWhatsNew ? <WhatsNewPopup onClose={hideWhatsNewPopup} /> : null} {!showWhatsNew && showRecoveryPhraseReminder ? ( <RecoveryPhraseReminder diff --git a/ui/pages/home/home.container.js b/ui/pages/home/home.container.js index 3149cb5b6ada..f34b93b5f16e 100644 --- a/ui/pages/home/home.container.js +++ b/ui/pages/home/home.container.js @@ -49,6 +49,7 @@ import { getAccountType, ///: END:ONLY_INCLUDE_IF } from '../../selectors'; +import { getIsSmartTransactionsOptInModalAvailable } from '../../../shared/modules/selectors'; import { closeNotificationPopup, @@ -193,6 +194,8 @@ const mapStateToProps = (state) => { custodianDeepLink: getCustodianDeepLink(state), accountType: getAccountType(state), ///: END:ONLY_INCLUDE_IF + isSmartTransactionsOptInModalAvailable: + getIsSmartTransactionsOptInModalAvailable(state), }; }; diff --git a/ui/pages/onboarding-flow/onboarding-flow.test.js b/ui/pages/onboarding-flow/onboarding-flow.test.js index bd745a4006c6..e1f34cc557b6 100644 --- a/ui/pages/onboarding-flow/onboarding-flow.test.js +++ b/ui/pages/onboarding-flow/onboarding-flow.test.js @@ -47,6 +47,9 @@ describe('Onboarding Flow', () => { incomingTransactionsPreferences: { [CHAIN_IDS.MAINNET]: true, }, + preferences: { + petnamesEnabled: true, + }, }, localeMessages: { currentLocale: 'en', diff --git a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js index 08c1b5c31cb2..0de51a5d14eb 100644 --- a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js +++ b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js @@ -10,6 +10,7 @@ import { COINGECKO_LINK, CRYPTOCOMPARE_LINK, PRIVACY_POLICY_LINK, + TRANSACTION_SIMULATIONS_LEARN_MORE_LINK, } from '../../../../shared/lib/ui-utils'; import { Box, @@ -28,7 +29,11 @@ import { } from '../../../helpers/constants/design-system'; import { ONBOARDING_PIN_EXTENSION_ROUTE } from '../../../helpers/constants/routes'; import { useI18nContext } from '../../../hooks/useI18nContext'; -import { getAllNetworks, getCurrentNetwork } from '../../../selectors'; +import { + getAllNetworks, + getCurrentNetwork, + getPetnamesEnabled, +} from '../../../selectors'; import { setCompletedOnboarding, setIpfsGateway, @@ -42,6 +47,7 @@ import { toggleNetworkMenu, setIncomingTransactionsPreferences, setUseTransactionSimulations, + setPetnamesEnabled, } from '../../../store/actions'; import IncomingTransactionToggle from '../../../components/app/incoming-trasaction-toggle/incoming-transaction-toggle'; import { Setting } from './setting'; @@ -63,6 +69,7 @@ export default function PrivacySettings() { useAddressBarEnsResolution, useTransactionSimulations, } = defaultState; + const petnamesEnabled = useSelector(getPetnamesEnabled); const [usePhishingDetection, setUsePhishingDetection] = useState(usePhishDetect); @@ -84,6 +91,7 @@ export default function PrivacySettings() { const [addressBarResolution, setAddressBarResolution] = useState( useAddressBarEnsResolution, ); + const [turnOnPetnames, setTurnOnPetnames] = useState(petnamesEnabled); const trackEvent = useContext(MetaMetricsContext); const currentNetwork = useSelector(getCurrentNetwork); @@ -100,6 +108,7 @@ export default function PrivacySettings() { dispatch(setCompletedOnboarding()); dispatch(setUseAddressBarEnsResolution(addressBarResolution)); setUseTransactionSimulations(isTransactionSimulationsEnabled); + dispatch(setPetnamesEnabled(turnOnPetnames)); if (ipfsURL && !ipfsError) { const { host } = new URL(addUrlProtocolPrefix(ipfsURL)); @@ -272,7 +281,16 @@ export default function PrivacySettings() { value={isTransactionSimulationsEnabled} setValue={setTransactionSimulationsEnabled} title={t('simulationsSettingSubHeader')} - description={t('simulationsSettingDescription')} + description={t('simulationsSettingDescription', [ + <a + key="learn_more_link" + href={TRANSACTION_SIMULATIONS_LEARN_MORE_LINK} + rel="noreferrer" + target="_blank" + > + {t('learnMoreUpperCase')} + </a>, + ])} /> <Setting value={addressBarResolution} @@ -334,6 +352,12 @@ export default function PrivacySettings() { </a>, ])} /> + <Setting + value={turnOnPetnames} + setValue={setTurnOnPetnames} + title={t('petnamesEnabledToggle')} + description={t('petnamesEnabledToggleDescription')} + /> <ButtonPrimary size={ButtonPrimarySize.Lg} onClick={handleSubmit} diff --git a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.test.js b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.test.js index 7f6bf91fcedb..e7e391214179 100644 --- a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.test.js +++ b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.test.js @@ -11,6 +11,9 @@ describe('Privacy Settings Onboarding View', () => { const mockStore = { metamask: { networkConfigurations: {}, + preferences: { + petnamesEnabled: true, + }, providerConfig: { type: 'test', }, @@ -46,6 +49,7 @@ describe('Privacy Settings Onboarding View', () => { const setUseAddressBarEnsResolutionStub = jest.fn(); const setIncomingTransactionsPreferencesStub = jest.fn(); const setUseTransactionSimulationsStub = jest.fn(); + const setPreferenceStub = jest.fn(); setBackgroundConnection({ setFeatureFlag: setFeatureFlagStub, @@ -59,6 +63,7 @@ describe('Privacy Settings Onboarding View', () => { setUseAddressBarEnsResolution: setUseAddressBarEnsResolutionStub, setIncomingTransactionsPreferences: setIncomingTransactionsPreferencesStub, setUseTransactionSimulations: setUseTransactionSimulationsStub, + setPreference: setPreferenceStub, }); it('should update preferences', () => { @@ -75,6 +80,7 @@ describe('Privacy Settings Onboarding View', () => { expect(setUseAddressBarEnsResolutionStub).toHaveBeenCalledTimes(0); expect(setIncomingTransactionsPreferencesStub).toHaveBeenCalledTimes(0); expect(setUseTransactionSimulationsStub).toHaveBeenCalledTimes(0); + expect(setPreferenceStub).toHaveBeenCalledTimes(0); const toggles = container.querySelectorAll('input[type=checkbox]'); const submitButton = getByText('Done'); @@ -87,6 +93,7 @@ describe('Privacy Settings Onboarding View', () => { fireEvent.click(toggles[8]); fireEvent.click(toggles[9]); fireEvent.click(toggles[10]); + fireEvent.click(toggles[12]); fireEvent.click(submitButton); @@ -98,6 +105,7 @@ describe('Privacy Settings Onboarding View', () => { expect(setUseCurrencyRateCheckStub).toHaveBeenCalledTimes(1); expect(setUseAddressBarEnsResolutionStub).toHaveBeenCalledTimes(1); expect(setUseTransactionSimulationsStub).toHaveBeenCalledTimes(1); + expect(setPreferenceStub).toHaveBeenCalledTimes(1); expect(setIncomingTransactionsPreferencesStub).toHaveBeenCalledWith( CHAIN_IDS.MAINNET, @@ -115,6 +123,13 @@ describe('Privacy Settings Onboarding View', () => { expect(setUseAddressBarEnsResolutionStub.mock.calls[0][0]).toStrictEqual( false, ); + expect(setUseTransactionSimulationsStub.mock.calls[0][0]).toStrictEqual( + false, + ); + expect(setPreferenceStub.mock.calls[0][0]).toStrictEqual( + 'petnamesEnabled', + false, + ); }); describe('IPFS', () => { diff --git a/ui/pages/pages.scss b/ui/pages/pages.scss index 2cbefdd14071..1a61352bd89a 100644 --- a/ui/pages/pages.scss +++ b/ui/pages/pages.scss @@ -25,6 +25,7 @@ @import 'snaps/snaps-list/index'; @import 'snaps/snap-view/index'; @import 'create-snap-account/index'; +@import 'smart-transactions/smart-transaction-status-page/index'; @import 'remove-snap-account/index'; @import 'swaps/index'; @import 'token-details/index'; diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index 5f2398116557..c84827eb7300 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -190,13 +190,15 @@ export default class Routes extends Component { isDeprecatedNetworkModalOpen: PropTypes.bool.isRequired, hideDeprecatedNetworkModal: PropTypes.func.isRequired, addPermittedAccount: PropTypes.func.isRequired, - switchedNetworkDetails: PropTypes.oneOfType([PropTypes.object, null]), + switchedNetworkDetails: PropTypes.object, clearSwitchedNetworkDetails: PropTypes.func.isRequired, setSwitchedNetworkNeverShowMessage: PropTypes.func.isRequired, networkToAutomaticallySwitchTo: PropTypes.object, neverShowSwitchedNetworkMessage: PropTypes.bool.isRequired, automaticallySwitchNetwork: PropTypes.func.isRequired, unapprovedTransactions: PropTypes.number.isRequired, + currentExtensionPopupId: PropTypes.number, + useRequestQueue: PropTypes.bool, ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) isShowKeyringSnapRemovalResultModal: PropTypes.bool.isRequired, hideShowKeyringSnapRemovalResultModal: PropTypes.func.isRequired, @@ -253,6 +255,8 @@ export default class Routes extends Component { activeTabOrigin, unapprovedTransactions, isUnlocked, + useRequestQueue, + currentExtensionPopupId, } = this.props; if (theme !== prevProps.theme) { this.setTheme(); @@ -277,6 +281,18 @@ export default class Routes extends Component { activeTabOrigin, ); } + + // Terminate the popup when another popup is opened + // if the user is using RPC queueing + if ( + useRequestQueue && + process.env.MULTICHAIN && + currentExtensionPopupId !== undefined && + global.metamask.id !== undefined && + currentExtensionPopupId !== global.metamask.id + ) { + window.close(); + } } UNSAFE_componentWillMount() { @@ -497,6 +513,11 @@ export default class Routes extends Component { ); } + onHomeScreen() { + const { location } = this.props; + return location.pathname === DEFAULT_ROUTE; + } + hideAppHeader() { const { location } = this.props; @@ -758,7 +779,9 @@ export default class Routes extends Component { </Box> {isUnlocked ? <Alerts history={this.props.history} /> : null} <ToastContainer> - {showConnectAccountToast && !this.state.hideConnectAccountToast ? ( + {showConnectAccountToast && + this.onHomeScreen() && + !this.state.hideConnectAccountToast ? ( <Toast key="connect-account-toast" startAdornment={ diff --git a/ui/pages/routes/routes.container.js b/ui/pages/routes/routes.container.js index 44b37644daf6..25c18a211243 100644 --- a/ui/pages/routes/routes.container.js +++ b/ui/pages/routes/routes.container.js @@ -22,7 +22,9 @@ import { getNeverShowSwitchedNetworkMessage, getNetworkToAutomaticallySwitchTo, getNumberOfAllUnapprovedTransactionsAndMessages, + getUseRequestQueue, } from '../../selectors'; +import { getSmartTransactionsOptInStatus } from '../../../shared/modules/selectors'; import { lockMetamask, hideImportNftsModal, @@ -98,6 +100,7 @@ function mapStateToProps(state) { allAccountsOnNetworkAreEmpty: getAllAccountsOnNetworkAreEmpty(state), isTestNet: getIsTestnet(state), showExtensionInFullSizeView: getShowExtensionInFullSizeView(state), + smartTransactionsOptInStatus: getSmartTransactionsOptInStatus(state), currentChainId: getCurrentChainId(state), shouldShowSeedPhraseReminder: getShouldShowSeedPhraseReminder(state), forgottenPassword: state.metamask.forgottenPassword, @@ -115,6 +118,8 @@ function mapStateToProps(state) { unapprovedTransactions: getNumberOfAllUnapprovedTransactionsAndMessages(state), neverShowSwitchedNetworkMessage: getNeverShowSwitchedNetworkMessage(state), + currentExtensionPopupId: state.metamask.currentExtensionPopupId, + useRequestQueue: getUseRequestQueue(state), ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) isShowKeyringSnapRemovalResultModal: state.appState.showKeyringRemovalSnapModal, diff --git a/ui/pages/settings/advanced-tab/__snapshots__/advanced-tab.component.test.js.snap b/ui/pages/settings/advanced-tab/__snapshots__/advanced-tab.component.test.js.snap index c5792a4df6a1..0fe85ecb0da1 100644 --- a/ui/pages/settings/advanced-tab/__snapshots__/advanced-tab.component.test.js.snap +++ b/ui/pages/settings/advanced-tab/__snapshots__/advanced-tab.component.test.js.snap @@ -65,6 +65,86 @@ exports[`AdvancedTab Component should match snapshot 1`] = ` </div> </div> </div> + <div + class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between" + data-testid="advanced-setting-enable-smart-transactions" + > + <div + class="settings-page__content-item" + > + <span> + Smart transactions + </span> + <div + class="settings-page__content-description" + > + <span> + + Turn on smart transactions for more reliable and secure transactions, and adjustable fees on ETH Mainnet. + <a + class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-inherit mm-text--body-md-medium mm-box--padding-0 mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent" + href="https://support.metamask.io/hc/en-us/articles/9184393821211" + rel="noopener noreferrer" + target="_blank" + > + Learn more + </a> + + + </span> + </div> + </div> + <div + class="settings-page__content-item-col" + > + <label + class="toggle-button toggle-button--off" + tabindex="0" + > + <div + style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;" + > + <div + style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > + <div + style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 0; width: 26px; height: 20px; left: 4px;" + /> + <div + style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgba(255, 255, 255, 0.6); bottom: 0px; margin-top: auto; margin-bottom: auto; padding-right: 5px; line-height: 0; width: 26px; height: 20px; opacity: 1;" + /> + </div> + <div + style="position: absolute; height: 100%; top: 0px; left: 0px; display: flex; flex: 1; align-self: stretch; align-items: center; justify-content: flex-start;" + > + <div + style="width: 18px; height: 18px; display: flex; align-self: center; box-shadow: none; border-radius: 50%; box-sizing: border-box; position: relative; background-color: rgb(106, 115, 125); left: 3px;" + /> + </div> + <input + data-testid="settings-page-stx-opt-in-toggle" + style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px;" + type="checkbox" + value="false" + /> + </div> + <div + class="toggle-button__status" + > + <span + class="toggle-button__label-off" + > + Off + </span> + <span + class="toggle-button__label-on" + > + On + </span> + </div> + </label> + </div> + </div> <div class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between" data-testid="advanced-setting-hex-data" diff --git a/ui/pages/settings/advanced-tab/advanced-tab.component.js b/ui/pages/settings/advanced-tab/advanced-tab.component.js index 027189aff37e..4fb87f52bae9 100644 --- a/ui/pages/settings/advanced-tab/advanced-tab.component.js +++ b/ui/pages/settings/advanced-tab/advanced-tab.component.js @@ -5,10 +5,13 @@ import { MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; import { DEFAULT_AUTO_LOCK_TIME_LIMIT } from '../../../../shared/constants/preferences'; +import { SMART_TRANSACTIONS_LEARN_MORE_URL } from '../../../../shared/constants/smartTransactions'; import { BannerAlert, BannerAlertSeverity, Box, + ButtonLink, + ButtonLinkSize, } from '../../../components/component-library'; import Button from '../../../components/ui/button'; import TextField from '../../../components/ui/text-field'; @@ -19,6 +22,7 @@ import { JustifyContent, Severity, TextVariant, + AlignItems, } from '../../../helpers/constants/design-system'; import { ExportableContentType, @@ -48,12 +52,12 @@ export default class AdvancedTab extends PureComponent { sendHexData: PropTypes.bool, showFiatInTestnets: PropTypes.bool, showTestNetworks: PropTypes.bool, - showExtensionInFullSizeView: PropTypes.bool, + smartTransactionsOptInStatus: PropTypes.bool, autoLockTimeLimit: PropTypes.number, setAutoLockTimeLimit: PropTypes.func.isRequired, setShowFiatConversionOnTestnetsPreference: PropTypes.func.isRequired, setShowTestNetworks: PropTypes.func.isRequired, - setShowExtensionInFullSizeView: PropTypes.func.isRequired, + setSmartTransactionsOptInStatus: PropTypes.func.isRequired, setDismissSeedBackUpReminder: PropTypes.func.isRequired, dismissSeedBackUpReminder: PropTypes.bool.isRequired, backupUserData: PropTypes.func.isRequired, @@ -62,6 +66,8 @@ export default class AdvancedTab extends PureComponent { disabledRpcMethodPreferences: PropTypes.shape({ eth_sign: PropTypes.bool.isRequired, }), + showExtensionInFullSizeView: PropTypes.bool, + setShowExtensionInFullSizeView: PropTypes.func.isRequired, }; state = { @@ -238,13 +244,74 @@ export default class AdvancedTab extends PureComponent { ); } + renderToggleStxOptIn() { + const { t } = this.context; + const { smartTransactionsOptInStatus, setSmartTransactionsOptInStatus } = + this.props; + + const learMoreLink = ( + <ButtonLink + size={ButtonLinkSize.Inherit} + textProps={{ + variant: TextVariant.bodyMd, + alignItems: AlignItems.flexStart, + }} + as="a" + href={SMART_TRANSACTIONS_LEARN_MORE_URL} + target="_blank" + rel="noopener noreferrer" + > + {t('learnMoreUpperCase')} + </ButtonLink> + ); + + return ( + <Box + ref={this.settingsRefs[2]} + className="settings-page__content-row" + data-testid="advanced-setting-enable-smart-transactions" + display={Display.Flex} + flexDirection={FlexDirection.Row} + justifyContent={JustifyContent.spaceBetween} + gap={4} + > + <div className="settings-page__content-item"> + <span>{t('smartTransactions')}</span> + <div className="settings-page__content-description"> + {t('stxOptInDescription', [learMoreLink])} + </div> + </div> + + <div className="settings-page__content-item-col"> + <ToggleButton + value={smartTransactionsOptInStatus} + onToggle={(oldValue) => { + const newValue = !oldValue; + this.context.trackEvent({ + category: MetaMetricsEventCategory.Settings, + event: MetaMetricsEventName.SettingsUpdated, + properties: { + stx_opt_in: newValue, + }, + }); + setSmartTransactionsOptInStatus(newValue); + }} + offLabel={t('off')} + onLabel={t('on')} + dataTestId="settings-page-stx-opt-in-toggle" + /> + </div> + </Box> + ); + } + renderHexDataOptIn() { const { t } = this.context; const { sendHexData, setHexDataFeatureFlag } = this.props; return ( <Box - ref={this.settingsRefs[2]} + ref={this.settingsRefs[3]} className="settings-page__content-row" display={Display.Flex} flexDirection={FlexDirection.Row} @@ -277,7 +344,7 @@ export default class AdvancedTab extends PureComponent { return ( <Box - ref={this.settingsRefs[3]} + ref={this.settingsRefs[4]} className="settings-page__content-row" display={Display.Flex} flexDirection={FlexDirection.Row} @@ -312,7 +379,7 @@ export default class AdvancedTab extends PureComponent { return ( <Box - ref={this.settingsRefs[4]} + ref={this.settingsRefs[5]} className="settings-page__content-row" data-testid="advanced-setting-show-testnet-conversion" display={Display.Flex} @@ -346,7 +413,7 @@ export default class AdvancedTab extends PureComponent { return ( <Box - ref={this.settingsRefs[7]} + ref={this.settingsRefs[8]} className="settings-page__content-row" data-testid="advanced-setting-show-extension-in-full-size-view" display={Display.Flex} @@ -379,7 +446,7 @@ export default class AdvancedTab extends PureComponent { return ( <Box - ref={this.settingsRefs[5]} + ref={this.settingsRefs[6]} className="settings-page__content-row" data-testid="advanced-setting-custom-nonce" display={Display.Flex} @@ -413,7 +480,7 @@ export default class AdvancedTab extends PureComponent { return ( <Box - ref={this.settingsRefs[6]} + ref={this.settingsRefs[7]} className="settings-page__content-row" data-testid="advanced-setting-auto-lock" display={Display.Flex} @@ -462,7 +529,7 @@ export default class AdvancedTab extends PureComponent { return ( <Box - ref={this.settingsRefs[8]} + ref={this.settingsRefs[9]} className="settings-page__content-row" data-testid="advanced-setting-dismiss-reminder" display={Display.Flex} @@ -509,7 +576,7 @@ export default class AdvancedTab extends PureComponent { }; return ( <Box - ref={this.settingsRefs[9]} + ref={this.settingsRefs[10]} className="settings-page__content-row" data-testid="advanced-setting-toggle-ethsign" display={Display.Flex} @@ -589,7 +656,7 @@ export default class AdvancedTab extends PureComponent { const { t } = this.context; return ( <Box - ref={this.settingsRefs[10]} + ref={this.settingsRefs[11]} className="settings-page__content-row" data-testid="advanced-setting-data-backup" display={Display.Flex} @@ -631,7 +698,7 @@ export default class AdvancedTab extends PureComponent { return ( <Box - ref={this.settingsRefs[11]} + ref={this.settingsRefs[12]} className="settings-page__content-row" data-testid="advanced-setting-data-restore" display={Display.Flex} @@ -694,6 +761,7 @@ export default class AdvancedTab extends PureComponent { {warning ? <div className="settings-tab__error">{warning}</div> : null} {this.renderStateLogs()} {this.renderResetAccount()} + {this.renderToggleStxOptIn()} {this.renderHexDataOptIn()} {this.renderShowConversionInTestnets()} {this.renderToggleTestNetworks()} diff --git a/ui/pages/settings/advanced-tab/advanced-tab.component.test.js b/ui/pages/settings/advanced-tab/advanced-tab.component.test.js index ff59b7f83cf9..c50a32a7bd26 100644 --- a/ui/pages/settings/advanced-tab/advanced-tab.component.test.js +++ b/ui/pages/settings/advanced-tab/advanced-tab.component.test.js @@ -8,11 +8,13 @@ import AdvancedTab from '.'; const mockSetAutoLockTimeLimit = jest.fn(); const mockSetShowTestNetworks = jest.fn(); +const mockSetStxOptIn = jest.fn(); jest.mock('../../../store/actions.ts', () => { return { setAutoLockTimeLimit: () => mockSetAutoLockTimeLimit, setShowTestNetworks: () => mockSetShowTestNetworks, + setSmartTransactionsOptInStatus: () => mockSetStxOptIn, }; }); @@ -61,7 +63,7 @@ describe('AdvancedTab Component', () => { it('should toggle show test networks', () => { const { queryAllByRole } = renderWithProvider(<AdvancedTab />, mockStore); - const testNetworkToggle = queryAllByRole('checkbox')[2]; + const testNetworkToggle = queryAllByRole('checkbox')[3]; fireEvent.click(testNetworkToggle); @@ -84,4 +86,19 @@ describe('AdvancedTab Component', () => { expect(queryByTestId('ledger-live-control')).not.toBeInTheDocument(); }); + + describe('renderToggleStxOptIn', () => { + it('should render the toggle button for Smart Transactions', () => { + const { queryByTestId } = renderWithProvider(<AdvancedTab />, mockStore); + const toggleButton = queryByTestId('settings-page-stx-opt-in-toggle'); + expect(toggleButton).toBeInTheDocument(); + }); + + it('should call setSmartTransactionsOptInStatus when the toggle button is clicked', () => { + const { queryByTestId } = renderWithProvider(<AdvancedTab />, mockStore); + const toggleButton = queryByTestId('settings-page-stx-opt-in-toggle'); + fireEvent.click(toggleButton); + expect(mockSetStxOptIn).toHaveBeenCalled(); + }); + }); }); diff --git a/ui/pages/settings/advanced-tab/advanced-tab.container.js b/ui/pages/settings/advanced-tab/advanced-tab.container.js index e89b71557856..a0dcd4a7bcc3 100644 --- a/ui/pages/settings/advanced-tab/advanced-tab.container.js +++ b/ui/pages/settings/advanced-tab/advanced-tab.container.js @@ -14,6 +14,7 @@ import { setShowExtensionInFullSizeView, setShowFiatConversionOnTestnetsPreference, setShowTestNetworks, + setSmartTransactionsOptInStatus, setUseNonceField, showModal, } from '../../../store/actions'; @@ -34,6 +35,7 @@ export const mapStateToProps = (state) => { showFiatInTestnets, showTestNetworks, showExtensionInFullSizeView, + smartTransactionsOptInStatus, autoLockTimeLimit = DEFAULT_AUTO_LOCK_TIME_LIMIT, } = getPreferences(state); @@ -43,6 +45,7 @@ export const mapStateToProps = (state) => { showFiatInTestnets, showTestNetworks, showExtensionInFullSizeView, + smartTransactionsOptInStatus, autoLockTimeLimit, useNonceField, dismissSeedBackUpReminder, @@ -70,6 +73,9 @@ export const mapDispatchToProps = (dispatch) => { setShowExtensionInFullSizeView: (value) => { return dispatch(setShowExtensionInFullSizeView(value)); }, + setSmartTransactionsOptInStatus: (value) => { + return dispatch(setSmartTransactionsOptInStatus(value)); + }, setAutoLockTimeLimit: (value) => { return dispatch(setAutoLockTimeLimit(value)); }, 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 e88415966bb3..867b83543192 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 @@ -918,7 +918,19 @@ exports[`Security Tab should match snapshot 1`] = ` <div class="settings-page__content-description" > - Turn this on to estimate balance changes of transactions before you confirm them. This doesn't guarantee the final outcome of your transactions. + <span> + + Turn this on to estimate balance changes of transactions before you confirm them. This doesn't guarantee the final outcome of your transactions. + <a + href="https://support.metamask.io/transactions-and-gas/transactions/simulations/" + rel="noreferrer" + target="_blank" + > + Learn more + </a> + + + </span> </div> </div> <div diff --git a/ui/pages/settings/security-tab/security-tab.component.js b/ui/pages/settings/security-tab/security-tab.component.js index 45c3a9b6d67a..b2348831e757 100644 --- a/ui/pages/settings/security-tab/security-tab.component.js +++ b/ui/pages/settings/security-tab/security-tab.component.js @@ -19,6 +19,7 @@ import { CRYPTOCOMPARE_LINK, PRIVACY_POLICY_LINK, SECURITY_ALERTS_LEARN_MORE_LINK, + TRANSACTION_SIMULATIONS_LEARN_MORE_LINK, } from '../../../../shared/lib/ui-utils'; import SRPQuiz from '../../../components/app/srp-quiz-modal/SRPQuiz'; import { @@ -929,7 +930,16 @@ export default class SecurityTab extends PureComponent { <div className="settings-page__content-item"> <span>{t('simulationsSettingSubHeader')}</span> <div className="settings-page__content-description"> - {t('simulationsSettingDescription')} + {t('simulationsSettingDescription', [ + <a + key="learn_more_link" + href={TRANSACTION_SIMULATIONS_LEARN_MORE_LINK} + rel="noreferrer" + target="_blank" + > + {t('learnMoreUpperCase')} + </a>, + ])} </div> </div> diff --git a/ui/pages/smart-transactions/smart-transaction-status-page/__snapshots__/smart-transactions-status-page.test.js.snap b/ui/pages/smart-transactions/smart-transaction-status-page/__snapshots__/smart-transactions-status-page.test.js.snap new file mode 100644 index 000000000000..bc3eeec5262f --- /dev/null +++ b/ui/pages/smart-transactions/smart-transaction-status-page/__snapshots__/smart-transactions-status-page.test.js.snap @@ -0,0 +1,443 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SmartTransactionStatusPage renders the "Sorry for the wait" pending status 1`] = ` +<div> + <div + class="mm-box smart-transaction-status-page mm-box--margin-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center mm-box--width-full mm-box--height-full mm-box--border-style-none" + > + <div + class="mm-box mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-flex mm-box--flex-direction-column mm-box--justify-content-center mm-box--align-items-center mm-box--width-full" + style="flex-grow: 1;" + > + <div + class="mm-box mm-box--padding-right-6 mm-box--padding-left-6 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center" + > + <div + class="mm-box mm-box--display-flex" + style="font-size: 48px;" + > + <span + class="mm-box mm-icon mm-icon--size-inherit mm-box--margin-bottom-4 mm-box--display-inline-block mm-box--color-primary-default" + style="mask-image: url('./images/icons/clock.svg');" + /> + </div> + <h4 + class="mm-box mm-text mm-text--heading-md mm-text--font-weight-bold mm-box--color-text-default" + > + Sorry for the wait + </h4> + <div + class="mm-box mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center mm-box--width-full" + > + <div + class="smart-transaction-status-page__loading-bar-container" + > + <div + class="smart-transaction-status-page__loading-bar" + style="width: 100%;" + /> + </div> + </div> + <div + class="mm-box mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center" + > + <p + class="mm-box mm-text mm-text--body-sm mm-box--margin-top-2 mm-box--color-text-alternative" + > + <span> + + If your transaction is not finalized within + <p + class="mm-box mm-text smart-transaction-status-page__countdown mm-text--body-sm mm-text--text-align-center mm-box--display-inline-block mm-box--color-text-alternative" + > + 0:00 + </p> + , it will be canceled and you will not be charged for gas. + + </span> + </p> + </div> + </div> + </div> + <div + class="mm-box smart-transaction-status-page__footer mm-box--padding-4 mm-box--padding-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--width-full" + > + <button + class="mm-box mm-text mm-button-base mm-button-base--size-md mm-button-secondary mm-text--body-md-medium mm-box--margin-top-3 mm-box--padding-0 mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--width-full mm-box--color-primary-default mm-box--background-color-transparent mm-box--rounded-pill mm-box--border-color-primary-default box--border-style-solid box--border-width-1" + data-testid="smart-transaction-status-page-footer-close-button" + > + View activity + </button> + </div> + </div> +</div> +`; + +exports[`SmartTransactionStatusPage renders the "cancelled" STX status 1`] = ` +<div> + <div + class="mm-box smart-transaction-status-page mm-box--margin-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center mm-box--width-full mm-box--height-full mm-box--border-style-none" + > + <div + class="mm-box mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-flex mm-box--flex-direction-column mm-box--justify-content-center mm-box--align-items-center mm-box--width-full" + style="flex-grow: 1;" + > + <div + class="mm-box mm-box--padding-right-6 mm-box--padding-left-6 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center" + > + <div + class="mm-box mm-box--display-flex" + style="font-size: 48px;" + > + <span + class="mm-box mm-icon mm-icon--size-inherit mm-box--margin-bottom-4 mm-box--display-inline-block mm-box--color-error-default" + style="mask-image: url('./images/icons/danger.svg');" + /> + </div> + <h4 + class="mm-box mm-text mm-text--heading-md mm-text--font-weight-bold mm-box--color-text-default" + > + Your transaction was canceled + </h4> + <div + class="mm-box mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center" + > + <p + class="mm-box mm-text mm-text--body-sm mm-box--margin-top-2 mm-box--color-text-alternative" + > + Your transaction couldn't be completed, so it was canceled to save you from paying unnecessary gas fees. + </p> + </div> + <div + class="mm-box mm-box--margin-top-2 mm-box--display-flex mm-box--flex-direction-column" + > + <button + class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md-medium mm-box--padding-0 mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent" + type="link" + > + View transaction + </button> + </div> + </div> + </div> + <div + class="mm-box smart-transaction-status-page__footer mm-box--padding-4 mm-box--padding-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--width-full" + > + <button + class="mm-box mm-text mm-button-base mm-button-base--size-md mm-button-secondary mm-text--body-md-medium mm-box--margin-top-3 mm-box--padding-0 mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--width-full mm-box--color-primary-default mm-box--background-color-transparent mm-box--rounded-pill mm-box--border-color-primary-default box--border-style-solid box--border-width-1" + data-testid="smart-transaction-status-page-footer-close-button" + > + View activity + </button> + </div> + </div> +</div> +`; + +exports[`SmartTransactionStatusPage renders the "deadline_missed" STX status 1`] = ` +<div> + <div + class="mm-box smart-transaction-status-page mm-box--margin-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center mm-box--width-full mm-box--height-full mm-box--border-style-none" + > + <div + class="mm-box mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-flex mm-box--flex-direction-column mm-box--justify-content-center mm-box--align-items-center mm-box--width-full" + style="flex-grow: 1;" + > + <div + class="mm-box mm-box--padding-right-6 mm-box--padding-left-6 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center" + > + <div + class="mm-box mm-box--display-flex" + style="font-size: 48px;" + > + <span + class="mm-box mm-icon mm-icon--size-inherit mm-box--margin-bottom-4 mm-box--display-inline-block mm-box--color-error-default" + style="mask-image: url('./images/icons/danger.svg');" + /> + </div> + <h4 + class="mm-box mm-text mm-text--heading-md mm-text--font-weight-bold mm-box--color-text-default" + > + Your transaction was canceled + </h4> + <div + class="mm-box mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center" + > + <p + class="mm-box mm-text mm-text--body-sm mm-box--margin-top-2 mm-box--color-text-alternative" + > + Your transaction couldn't be completed, so it was canceled to save you from paying unnecessary gas fees. + </p> + </div> + <div + class="mm-box mm-box--margin-top-2 mm-box--display-flex mm-box--flex-direction-column" + > + <button + class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md-medium mm-box--padding-0 mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent" + type="link" + > + View transaction + </button> + </div> + </div> + </div> + <div + class="mm-box smart-transaction-status-page__footer mm-box--padding-4 mm-box--padding-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--width-full" + > + <button + class="mm-box mm-text mm-button-base mm-button-base--size-md mm-button-secondary mm-text--body-md-medium mm-box--margin-top-3 mm-box--padding-0 mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--width-full mm-box--color-primary-default mm-box--background-color-transparent mm-box--rounded-pill mm-box--border-color-primary-default box--border-style-solid box--border-width-1" + data-testid="smart-transaction-status-page-footer-close-button" + > + View activity + </button> + </div> + </div> +</div> +`; + +exports[`SmartTransactionStatusPage renders the "reverted" STX status 1`] = ` +<div> + <div + class="mm-box smart-transaction-status-page mm-box--margin-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center mm-box--width-full mm-box--height-full mm-box--border-style-none" + > + <div + class="mm-box mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-flex mm-box--flex-direction-column mm-box--justify-content-center mm-box--align-items-center mm-box--width-full" + style="flex-grow: 1;" + > + <div + class="mm-box mm-box--padding-right-6 mm-box--padding-left-6 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center" + > + <div + class="mm-box mm-box--display-flex" + style="font-size: 48px;" + > + <span + class="mm-box mm-icon mm-icon--size-inherit mm-box--margin-bottom-4 mm-box--display-inline-block mm-box--color-error-default" + style="mask-image: url('./images/icons/danger.svg');" + /> + </div> + <h4 + class="mm-box mm-text mm-text--heading-md mm-text--font-weight-bold mm-box--color-text-default" + > + Your transaction failed + </h4> + <div + class="mm-box mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center" + > + <p + class="mm-box mm-text mm-text--body-sm mm-box--margin-top-2 mm-box--color-text-alternative" + > + Sudden market changes can cause failures. If the problem continues, reach out to MetaMask customer support. + </p> + </div> + <div + class="mm-box mm-box--margin-top-2 mm-box--display-flex mm-box--flex-direction-column" + > + <button + class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md-medium mm-box--padding-0 mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent" + type="link" + > + View transaction + </button> + </div> + </div> + </div> + <div + class="mm-box smart-transaction-status-page__footer mm-box--padding-4 mm-box--padding-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--width-full" + > + <button + class="mm-box mm-text mm-button-base mm-button-base--size-md mm-button-secondary mm-text--body-md-medium mm-box--margin-top-3 mm-box--padding-0 mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--width-full mm-box--color-primary-default mm-box--background-color-transparent mm-box--rounded-pill mm-box--border-color-primary-default box--border-style-solid box--border-width-1" + data-testid="smart-transaction-status-page-footer-close-button" + > + View activity + </button> + </div> + </div> +</div> +`; + +exports[`SmartTransactionStatusPage renders the "success" STX status 1`] = ` +<div> + <div + class="mm-box smart-transaction-status-page mm-box--margin-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center mm-box--width-full mm-box--height-full mm-box--border-style-none" + > + <div + class="mm-box mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-flex mm-box--flex-direction-column mm-box--justify-content-center mm-box--align-items-center mm-box--width-full" + style="flex-grow: 1;" + > + <div + class="mm-box mm-box--padding-right-6 mm-box--padding-left-6 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center" + > + <div + class="mm-box mm-box--display-flex" + style="font-size: 48px;" + > + <span + class="mm-box mm-icon mm-icon--size-inherit mm-box--margin-bottom-4 mm-box--display-inline-block mm-box--color-success-default" + style="mask-image: url('./images/icons/confirmation.svg');" + /> + </div> + <h4 + class="mm-box mm-text mm-text--heading-md mm-text--font-weight-bold mm-box--color-text-default" + > + Your transaction is complete + </h4> + <div + class="mm-box mm-box--margin-top-2 mm-box--display-flex mm-box--flex-direction-column" + > + <button + class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md-medium mm-box--padding-0 mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent" + type="link" + > + View transaction + </button> + </div> + </div> + </div> + <div + class="mm-box smart-transaction-status-page__footer mm-box--padding-4 mm-box--padding-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--width-full" + > + <button + class="mm-box mm-text mm-button-base mm-button-base--size-md mm-button-secondary mm-text--body-md-medium mm-box--margin-top-3 mm-box--padding-0 mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--width-full mm-box--color-primary-default mm-box--background-color-transparent mm-box--rounded-pill mm-box--border-color-primary-default box--border-style-solid box--border-width-1" + data-testid="smart-transaction-status-page-footer-close-button" + > + View activity + </button> + </div> + </div> +</div> +`; + +exports[`SmartTransactionStatusPage renders the "unknown" STX status 1`] = ` +<div> + <div + class="mm-box smart-transaction-status-page mm-box--margin-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center mm-box--width-full mm-box--height-full mm-box--border-style-none" + > + <div + class="mm-box mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-flex mm-box--flex-direction-column mm-box--justify-content-center mm-box--align-items-center mm-box--width-full" + style="flex-grow: 1;" + > + <div + class="mm-box mm-box--padding-right-6 mm-box--padding-left-6 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center" + > + <div + class="mm-box mm-box--display-flex" + style="font-size: 48px;" + > + <span + class="mm-box mm-icon mm-icon--size-inherit mm-box--margin-bottom-4 mm-box--display-inline-block mm-box--color-error-default" + style="mask-image: url('./images/icons/danger.svg');" + /> + </div> + <h4 + class="mm-box mm-text mm-text--heading-md mm-text--font-weight-bold mm-box--color-text-default" + > + Your transaction failed + </h4> + <div + class="mm-box mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center" + > + <p + class="mm-box mm-text mm-text--body-sm mm-box--margin-top-2 mm-box--color-text-alternative" + > + Sudden market changes can cause failures. If the problem continues, reach out to MetaMask customer support. + </p> + </div> + <div + class="mm-box mm-box--margin-top-2 mm-box--display-flex mm-box--flex-direction-column" + > + <button + class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md-medium mm-box--padding-0 mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent" + type="link" + > + View transaction + </button> + </div> + </div> + </div> + <div + class="mm-box smart-transaction-status-page__footer mm-box--padding-4 mm-box--padding-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--width-full" + > + <button + class="mm-box mm-text mm-button-base mm-button-base--size-md mm-button-secondary mm-text--body-md-medium mm-box--margin-top-3 mm-box--padding-0 mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--width-full mm-box--color-primary-default mm-box--background-color-transparent mm-box--rounded-pill mm-box--border-color-primary-default box--border-style-solid box--border-width-1" + data-testid="smart-transaction-status-page-footer-close-button" + > + View activity + </button> + </div> + </div> +</div> +`; + +exports[`SmartTransactionStatusPage renders the component with initial props 1`] = ` +<div> + <div + class="mm-box smart-transaction-status-page mm-box--margin-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center mm-box--width-full mm-box--height-full mm-box--border-style-none" + > + <div + class="mm-box mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-flex mm-box--flex-direction-column mm-box--justify-content-center mm-box--align-items-center mm-box--width-full" + style="flex-grow: 1;" + > + <div + class="mm-box mm-box--padding-right-6 mm-box--padding-left-6 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center" + > + <div + class="mm-box mm-box--display-flex" + style="font-size: 48px;" + > + <span + class="mm-box mm-icon mm-icon--size-inherit mm-box--margin-bottom-4 mm-box--display-inline-block mm-box--color-primary-default" + style="mask-image: url('./images/icons/clock.svg');" + /> + </div> + <h4 + class="mm-box mm-text mm-text--heading-md mm-text--font-weight-bold mm-box--color-text-default" + > + Submitting your transaction + </h4> + <div + class="mm-box mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center mm-box--width-full" + > + <div + class="smart-transaction-status-page__loading-bar-container" + > + <div + class="smart-transaction-status-page__loading-bar" + style="width: 0%;" + /> + </div> + </div> + <div + class="mm-box mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center" + > + <p + class="mm-box mm-text mm-text--body-sm mm-box--margin-top-2 mm-box--color-text-alternative" + > + <span> + + Estimated completion in < + <p + class="mm-box mm-text smart-transaction-status-page__countdown mm-text--body-sm mm-text--text-align-center mm-box--display-inline-block mm-box--color-text-alternative" + > + 0:45 + </p> + + + </span> + </p> + </div> + </div> + </div> + <div + class="mm-box smart-transaction-status-page__footer mm-box--padding-4 mm-box--padding-bottom-0 mm-box--display-flex mm-box--flex-direction-column mm-box--width-full" + > + <button + class="mm-box mm-text mm-button-base mm-button-base--size-md mm-button-secondary mm-text--body-md-medium mm-box--margin-top-3 mm-box--padding-0 mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--width-full mm-box--color-primary-default mm-box--background-color-transparent mm-box--rounded-pill mm-box--border-color-primary-default box--border-style-solid box--border-width-1" + data-testid="smart-transaction-status-page-footer-close-button" + > + View activity + </button> + </div> + </div> +</div> +`; diff --git a/ui/pages/smart-transactions/smart-transaction-status-page/index.scss b/ui/pages/smart-transactions/smart-transaction-status-page/index.scss new file mode 100644 index 000000000000..cbc707bd19b7 --- /dev/null +++ b/ui/pages/smart-transactions/smart-transaction-status-page/index.scss @@ -0,0 +1,36 @@ + +.smart-transaction-status-page { + text-align: center; + + &__loading-bar-container { + width: 100%; + height: 3px; + background: var(--color-background-alternative); + display: flex; + margin-top: 16px; + } + + &__loading-bar { + height: 3px; + background: var(--color-primary-default); + transition: width 0.5s linear; + } + + &__footer { + grid-area: footer; + } + + &__countdown { + width: 25px; + } + + // Slightly overwrite the default SimulationDetails layout to look better on the Smart Transaction status page. + .simulation-details { + &__layout { + margin-left: 0; + margin-right: 0; + width: 100%; + text-align: left; + } + } +} diff --git a/ui/pages/smart-transactions/smart-transaction-status-page/index.ts b/ui/pages/smart-transactions/smart-transaction-status-page/index.ts new file mode 100644 index 000000000000..9fc47833935e --- /dev/null +++ b/ui/pages/smart-transactions/smart-transaction-status-page/index.ts @@ -0,0 +1 @@ +export { default as SmartTransactionStatusPage } from './smart-transaction-status-page'; diff --git a/ui/pages/smart-transactions/smart-transaction-status-page/smart-transaction-status-page.tsx b/ui/pages/smart-transactions/smart-transaction-status-page/smart-transaction-status-page.tsx new file mode 100644 index 000000000000..704bf7b09506 --- /dev/null +++ b/ui/pages/smart-transactions/smart-transaction-status-page/smart-transaction-status-page.tsx @@ -0,0 +1,530 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { + SmartTransactionStatuses, + SmartTransaction, +} from '@metamask/smart-transactions-controller/dist/types'; + +import { + Box, + Text, + Icon, + IconName, + IconSize, + Button, + ButtonVariant, + ButtonSecondary, +} from '../../../components/component-library'; +import { + AlignItems, + BlockSize, + BorderStyle, + Display, + FlexDirection, + JustifyContent, + TextVariant, + TextColor, + FontWeight, + IconColor, + TextAlign, +} from '../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { getCurrentChainId, getFullTxData } from '../../../selectors'; +import { getFeatureFlagsByChainId } from '../../../../shared/modules/selectors'; +import { BaseUrl } from '../../../../shared/constants/urls'; +import { + FALLBACK_SMART_TRANSACTIONS_EXPECTED_DEADLINE, + FALLBACK_SMART_TRANSACTIONS_MAX_DEADLINE, +} from '../../../../shared/constants/smartTransactions'; +import { hideLoadingIndication } from '../../../store/actions'; +import { hexToDecimal } from '../../../../shared/modules/conversion.utils'; +import { SimulationDetails } from '../../confirmations/components/simulation-details'; + +type RequestState = { + smartTransaction?: SmartTransaction; + isDapp: boolean; + txId?: string; +}; + +export type SmartTransactionStatusPageProps = { + requestState: RequestState; + onCloseExtension: () => void; + onViewActivity: () => void; +}; + +export const showRemainingTimeInMinAndSec = ( + remainingTimeInSec: number, +): string => { + if (!Number.isInteger(remainingTimeInSec)) { + return '0:00'; + } + const minutes = Math.floor(remainingTimeInSec / 60); + const seconds = remainingTimeInSec % 60; + return `${minutes}:${seconds.toString().padStart(2, '0')}`; +}; + +const getDisplayValues = ({ + t, + countdown, + isSmartTransactionPending, + isSmartTransactionTakingTooLong, + isSmartTransactionSuccess, + isSmartTransactionCancelled, +}: { + t: ReturnType<typeof useI18nContext>; + countdown: JSX.Element | undefined; + isSmartTransactionPending: boolean; + isSmartTransactionTakingTooLong: boolean; + isSmartTransactionSuccess: boolean; + isSmartTransactionCancelled: boolean; +}) => { + if (isSmartTransactionPending && isSmartTransactionTakingTooLong) { + return { + title: t('smartTransactionTakingTooLong'), + description: t('smartTransactionTakingTooLongDescription', [countdown]), + iconName: IconName.Clock, + iconColor: IconColor.primaryDefault, + }; + } else if (isSmartTransactionPending) { + return { + title: t('smartTransactionPending'), + description: t('stxEstimatedCompletion', [countdown]), + iconName: IconName.Clock, + iconColor: IconColor.primaryDefault, + }; + } else if (isSmartTransactionSuccess) { + return { + title: t('smartTransactionSuccess'), + iconName: IconName.Confirmation, + iconColor: IconColor.successDefault, + }; + } else if (isSmartTransactionCancelled) { + return { + title: t('smartTransactionCancelled'), + description: t('smartTransactionCancelledDescription', [countdown]), + iconName: IconName.Danger, + iconColor: IconColor.errorDefault, + }; + } + // E.g. reverted or unknown statuses. + return { + title: t('smartTransactionError'), + description: t('smartTransactionErrorDescription'), + iconName: IconName.Danger, + iconColor: IconColor.errorDefault, + }; +}; + +const useRemainingTime = ({ + isSmartTransactionPending, + smartTransaction, + stxMaxDeadline, + stxEstimatedDeadline, +}: { + isSmartTransactionPending: boolean; + smartTransaction?: SmartTransaction; + stxMaxDeadline: number; + stxEstimatedDeadline: number; +}) => { + const [timeLeftForPendingStxInSec, setTimeLeftForPendingStxInSec] = + useState(0); + const [isSmartTransactionTakingTooLong, setIsSmartTransactionTakingTooLong] = + useState(false); + const stxDeadline = isSmartTransactionTakingTooLong + ? stxMaxDeadline + : stxEstimatedDeadline; + + useEffect(() => { + if (!isSmartTransactionPending) { + return; + } + + const calculateRemainingTime = () => { + const secondsAfterStxSubmission = smartTransaction?.creationTime + ? Math.round((Date.now() - smartTransaction.creationTime) / 1000) + : 0; + + if (secondsAfterStxSubmission > stxDeadline) { + setTimeLeftForPendingStxInSec(0); + if (!isSmartTransactionTakingTooLong) { + setIsSmartTransactionTakingTooLong(true); + } + return; + } + + setTimeLeftForPendingStxInSec(stxDeadline - secondsAfterStxSubmission); + }; + + const intervalId = setInterval(calculateRemainingTime, 1000); + calculateRemainingTime(); + + // eslint-disable-next-line consistent-return + return () => clearInterval(intervalId); + }, [ + isSmartTransactionPending, + isSmartTransactionTakingTooLong, + smartTransaction?.creationTime, + stxDeadline, + ]); + + return { + timeLeftForPendingStxInSec, + isSmartTransactionTakingTooLong, + stxDeadline, + }; +}; + +const Deadline = ({ + isSmartTransactionPending, + stxDeadline, + timeLeftForPendingStxInSec, +}: { + isSmartTransactionPending: boolean; + stxDeadline: number; + timeLeftForPendingStxInSec: number; +}) => { + if (!isSmartTransactionPending) { + return null; + } + return ( + <Box + display={Display.Flex} + flexDirection={FlexDirection.Column} + alignItems={AlignItems.center} + width={BlockSize.Full} + > + <div className="smart-transaction-status-page__loading-bar-container"> + <div + className="smart-transaction-status-page__loading-bar" + style={{ + width: `${ + (100 / stxDeadline) * (stxDeadline - timeLeftForPendingStxInSec) + }%`, + }} + /> + </div> + </Box> + ); +}; + +const Description = ({ description }: { description: string | undefined }) => { + if (!description) { + return null; + } + + return ( + <Box + display={Display.Flex} + flexDirection={FlexDirection.Column} + alignItems={AlignItems.center} + > + <Text + marginTop={2} + color={TextColor.textAlternative} + variant={TextVariant.bodySm} + > + {description} + </Text> + </Box> + ); +}; + +const PortfolioSmartTransactionStatusUrl = ({ + portfolioSmartTransactionStatusUrl, + isSmartTransactionPending, + onCloseExtension, +}: { + portfolioSmartTransactionStatusUrl?: string; + isSmartTransactionPending: boolean; + onCloseExtension: () => void; +}) => { + if (!portfolioSmartTransactionStatusUrl) { + return null; + } + + const handleViewTransactionLinkClick = useCallback(() => { + if (!isSmartTransactionPending) { + onCloseExtension(); + } + global.platform.openTab({ + url: portfolioSmartTransactionStatusUrl, + }); + }, [ + isSmartTransactionPending, + onCloseExtension, + portfolioSmartTransactionStatusUrl, + ]); + const t = useI18nContext(); + + return ( + <Box + display={Display.Flex} + flexDirection={FlexDirection.Column} + marginTop={2} + > + <Button + type="link" + variant={ButtonVariant.Link} + onClick={handleViewTransactionLinkClick} + > + {t('viewTransaction')} + </Button> + </Box> + ); +}; + +const CloseExtensionButton = ({ + isDapp, + isSmartTransactionPending, + onCloseExtension, +}: { + isDapp: boolean; + isSmartTransactionPending: boolean; + onCloseExtension: () => void; +}) => { + if (!isDapp || isSmartTransactionPending) { + return null; + } + const t = useI18nContext(); + + return ( + <ButtonSecondary + data-testid="smart-transaction-status-page-footer-close-button" + onClick={onCloseExtension} + width={BlockSize.Full} + marginTop={3} + > + {t('closeExtension')} + </ButtonSecondary> + ); +}; + +const ViewActivityButton = ({ + isDapp, + onViewActivity, +}: { + isDapp: boolean; + onViewActivity: () => void; +}) => { + if (isDapp) { + return null; + } + const t = useI18nContext(); + + return ( + <ButtonSecondary + data-testid="smart-transaction-status-page-footer-close-button" + onClick={onViewActivity} + width={BlockSize.Full} + marginTop={3} + > + {t('viewActivity')} + </ButtonSecondary> + ); +}; + +const SmartTransactionsStatusPageFooter = ({ + isDapp, + isSmartTransactionPending, + onCloseExtension, + onViewActivity, +}: { + isDapp: boolean; + isSmartTransactionPending: boolean; + onCloseExtension: () => void; + onViewActivity: () => void; +}) => { + return ( + <Box + className="smart-transaction-status-page__footer" + display={Display.Flex} + flexDirection={FlexDirection.Column} + width={BlockSize.Full} + padding={4} + paddingBottom={0} + > + <CloseExtensionButton + isDapp={isDapp} + isSmartTransactionPending={isSmartTransactionPending} + onCloseExtension={onCloseExtension} + /> + <ViewActivityButton isDapp={isDapp} onViewActivity={onViewActivity} /> + </Box> + ); +}; + +const Title = ({ title }: { title: string }) => { + return ( + <Text + color={TextColor.textDefault} + variant={TextVariant.headingMd} + as="h4" + fontWeight={FontWeight.Bold} + > + {title} + </Text> + ); +}; + +const SmartTransactionsStatusIcon = ({ + iconName, + iconColor, +}: { + iconName: IconName; + iconColor: IconColor; +}) => { + return ( + <Box display={Display.Flex} style={{ fontSize: '48px' }}> + <Icon + name={iconName} + color={iconColor} + size={IconSize.Inherit} + marginBottom={4} + /> + </Box> + ); +}; + +export const SmartTransactionStatusPage = ({ + requestState, + onCloseExtension, + onViewActivity, +}: SmartTransactionStatusPageProps) => { + const t = useI18nContext(); + const dispatch = useDispatch(); + const { smartTransaction, isDapp, txId } = requestState; + const isSmartTransactionPending = + !smartTransaction || + smartTransaction.status === SmartTransactionStatuses.PENDING; + const isSmartTransactionSuccess = + smartTransaction?.status === SmartTransactionStatuses.SUCCESS; + const isSmartTransactionCancelled = Boolean( + smartTransaction?.status?.startsWith(SmartTransactionStatuses.CANCELLED), + ); + const featureFlags: { + smartTransactions?: { + expectedDeadline?: number; + maxDeadline?: number; + }; + } | null = useSelector(getFeatureFlagsByChainId); + const stxEstimatedDeadline = + featureFlags?.smartTransactions?.expectedDeadline || + FALLBACK_SMART_TRANSACTIONS_EXPECTED_DEADLINE; + const stxMaxDeadline = + featureFlags?.smartTransactions?.maxDeadline || + FALLBACK_SMART_TRANSACTIONS_MAX_DEADLINE; + const { + timeLeftForPendingStxInSec, + isSmartTransactionTakingTooLong, + stxDeadline, + } = useRemainingTime({ + isSmartTransactionPending, + smartTransaction, + stxMaxDeadline, + stxEstimatedDeadline, + }); + const chainId: string = useSelector(getCurrentChainId); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: This same selector is used in the awaiting-swap component. + const fullTxData = useSelector((state) => getFullTxData(state, txId)) || {}; + + const countdown = isSmartTransactionPending ? ( + <Text + display={Display.InlineBlock} + textAlign={TextAlign.Center} + color={TextColor.textAlternative} + variant={TextVariant.bodySm} + className="smart-transaction-status-page__countdown" + > + {showRemainingTimeInMinAndSec(timeLeftForPendingStxInSec)} + </Text> + ) : undefined; + + const { title, description, iconName, iconColor } = getDisplayValues({ + t, + countdown, + isSmartTransactionPending, + isSmartTransactionTakingTooLong, + isSmartTransactionSuccess, + isSmartTransactionCancelled, + }); + + useEffect(() => { + dispatch(hideLoadingIndication()); + }, []); + + const canShowSimulationDetails = + fullTxData.simulationData?.tokenBalanceChanges?.length > 0; + const uuid = smartTransaction?.uuid; + const portfolioSmartTransactionStatusUrl = + uuid && chainId + ? `${BaseUrl.Portfolio}/networks/${Number( + hexToDecimal(chainId), + )}/smart-transactions/${uuid}` + : undefined; + + return ( + <Box + className="smart-transaction-status-page" + height={BlockSize.Full} + width={BlockSize.Full} + display={Display.Flex} + borderStyle={BorderStyle.none} + flexDirection={FlexDirection.Column} + alignItems={AlignItems.center} + marginBottom={0} + > + <Box + display={Display.Flex} + flexDirection={FlexDirection.Column} + alignItems={AlignItems.center} + justifyContent={JustifyContent.center} + paddingLeft={4} + paddingRight={4} + width={BlockSize.Full} + style={{ flexGrow: 1 }} + > + <Box + display={Display.Flex} + flexDirection={FlexDirection.Column} + alignItems={AlignItems.center} + paddingLeft={6} + paddingRight={6} + > + <SmartTransactionsStatusIcon + iconName={iconName} + iconColor={iconColor} + /> + <Title title={title} /> + <Deadline + isSmartTransactionPending={isSmartTransactionPending} + stxDeadline={stxDeadline} + timeLeftForPendingStxInSec={timeLeftForPendingStxInSec} + /> + <Description description={description} /> + <PortfolioSmartTransactionStatusUrl + portfolioSmartTransactionStatusUrl={ + portfolioSmartTransactionStatusUrl + } + isSmartTransactionPending={isSmartTransactionPending} + onCloseExtension={onCloseExtension} + /> + </Box> + {canShowSimulationDetails && ( + <SimulationDetails + simulationData={fullTxData.simulationData} + transactionId={fullTxData.id} + /> + )} + </Box> + <SmartTransactionsStatusPageFooter + isDapp={isDapp} + isSmartTransactionPending={isSmartTransactionPending} + onCloseExtension={onCloseExtension} + onViewActivity={onViewActivity} + /> + </Box> + ); +}; + +export default SmartTransactionStatusPage; diff --git a/ui/pages/smart-transactions/smart-transaction-status-page/smart-transactions-status-page.test.js b/ui/pages/smart-transactions/smart-transaction-status-page/smart-transactions-status-page.test.js new file mode 100644 index 000000000000..852960825a23 --- /dev/null +++ b/ui/pages/smart-transactions/smart-transaction-status-page/smart-transactions-status-page.test.js @@ -0,0 +1,145 @@ +import React from 'react'; +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { SmartTransactionStatuses } from '@metamask/smart-transactions-controller/dist/types'; + +import { + renderWithProvider, + createSwapsMockStore, +} from '../../../../test/jest'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; +import { SmartTransactionStatusPage } from '.'; + +const middleware = [thunk]; + +describe('SmartTransactionStatusPage', () => { + const requestState = { + smartTransaction: { + status: SmartTransactionStatuses.PENDING, + creationTime: Date.now(), + }, + }; + + it('renders the component with initial props', () => { + const store = configureMockStore(middleware)(createSwapsMockStore()); + const { getByText, container } = renderWithProvider( + <SmartTransactionStatusPage requestState={requestState} />, + store, + ); + expect(getByText('Submitting your transaction')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + + it('renders the "Sorry for the wait" pending status', () => { + const store = configureMockStore(middleware)(createSwapsMockStore()); + const newRequestState = { + ...requestState, + smartTransaction: { + ...requestState.smartTransaction, + creationTime: 1519211809934, + }, + }; + const { getByText, container } = renderWithProvider( + <SmartTransactionStatusPage requestState={newRequestState} />, + store, + ); + expect(getByText('Sorry for the wait')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + + it('renders the "success" STX status', () => { + const mockStore = createSwapsMockStore(); + const latestSmartTransaction = + mockStore.metamask.smartTransactionsState.smartTransactions[ + CHAIN_IDS.MAINNET + ][1]; + latestSmartTransaction.status = SmartTransactionStatuses.SUCCESS; + requestState.smartTransaction = latestSmartTransaction; + const store = configureMockStore(middleware)(mockStore); + const { getByText, container } = renderWithProvider( + <SmartTransactionStatusPage requestState={requestState} />, + store, + ); + expect(getByText('Your transaction is complete')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + + it('renders the "reverted" STX status', () => { + const mockStore = createSwapsMockStore(); + const latestSmartTransaction = + mockStore.metamask.smartTransactionsState.smartTransactions[ + CHAIN_IDS.MAINNET + ][1]; + latestSmartTransaction.status = SmartTransactionStatuses.REVERTED; + requestState.smartTransaction = latestSmartTransaction; + const store = configureMockStore(middleware)(mockStore); + const { getByText, container } = renderWithProvider( + <SmartTransactionStatusPage requestState={requestState} />, + store, + ); + expect(getByText('Your transaction failed')).toBeInTheDocument(); + expect( + getByText( + 'Sudden market changes can cause failures. If the problem continues, reach out to MetaMask customer support.', + ), + ).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + + it('renders the "cancelled" STX status', () => { + const mockStore = createSwapsMockStore(); + const latestSmartTransaction = + mockStore.metamask.smartTransactionsState.smartTransactions[ + CHAIN_IDS.MAINNET + ][1]; + requestState.smartTransaction = latestSmartTransaction; + latestSmartTransaction.status = SmartTransactionStatuses.CANCELLED; + const store = configureMockStore(middleware)(mockStore); + const { getByText, container } = renderWithProvider( + <SmartTransactionStatusPage requestState={requestState} />, + store, + ); + expect(getByText('Your transaction was canceled')).toBeInTheDocument(); + expect( + getByText( + `Your transaction couldn't be completed, so it was canceled to save you from paying unnecessary gas fees.`, + ), + ).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + + it('renders the "deadline_missed" STX status', () => { + const mockStore = createSwapsMockStore(); + const latestSmartTransaction = + mockStore.metamask.smartTransactionsState.smartTransactions[ + CHAIN_IDS.MAINNET + ][1]; + latestSmartTransaction.status = + SmartTransactionStatuses.CANCELLED_DEADLINE_MISSED; + requestState.smartTransaction = latestSmartTransaction; + const store = configureMockStore(middleware)(mockStore); + const { getByText, container } = renderWithProvider( + <SmartTransactionStatusPage requestState={requestState} />, + store, + ); + expect(getByText('Your transaction was canceled')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + + it('renders the "unknown" STX status', () => { + const mockStore = createSwapsMockStore(); + const latestSmartTransaction = + mockStore.metamask.smartTransactionsState.smartTransactions[ + CHAIN_IDS.MAINNET + ][1]; + latestSmartTransaction.status = SmartTransactionStatuses.UNKNOWN; + requestState.smartTransaction = latestSmartTransaction; + const store = configureMockStore(middleware)(mockStore); + const { getByText, container } = renderWithProvider( + <SmartTransactionStatusPage requestState={requestState} />, + store, + ); + expect(getByText('Your transaction failed')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/ui/pages/swaps/awaiting-signatures/awaiting-signatures.js b/ui/pages/swaps/awaiting-signatures/awaiting-signatures.js index dacb937294d9..777e253a70fe 100644 --- a/ui/pages/swaps/awaiting-signatures/awaiting-signatures.js +++ b/ui/pages/swaps/awaiting-signatures/awaiting-signatures.js @@ -8,14 +8,16 @@ import { getFetchParams, getApproveTxParams, prepareToLeaveSwaps, - getSmartTransactionsOptInStatus, - getSmartTransactionsEnabled, getCurrentSmartTransactionsEnabled, } from '../../../ducks/swaps/swaps'; import { isHardwareWallet, getHardwareWalletType, } from '../../../selectors/selectors'; +import { + getSmartTransactionsOptInStatus, + getSmartTransactionsEnabled, +} from '../../../../shared/modules/selectors'; import { DEFAULT_ROUTE, BUILD_QUOTE_ROUTE, diff --git a/ui/pages/swaps/awaiting-swap/awaiting-swap.js b/ui/pages/swaps/awaiting-swap/awaiting-swap.js index 27d72f9edd73..e7f47bc3f006 100644 --- a/ui/pages/swaps/awaiting-swap/awaiting-swap.js +++ b/ui/pages/swaps/awaiting-swap/awaiting-swap.js @@ -22,6 +22,10 @@ import { getHardwareWalletType, getFullTxData, } from '../../../selectors'; +import { + getSmartTransactionsOptInStatus, + getSmartTransactionsEnabled, +} from '../../../../shared/modules/selectors'; import { getUsedQuote, @@ -32,8 +36,6 @@ import { navigateBackToBuildQuote, prepareForRetryGetQuotes, prepareToLeaveSwaps, - getSmartTransactionsOptInStatus, - getSmartTransactionsEnabled, getCurrentSmartTransactionsEnabled, getFromTokenInputValue, getMaxSlippage, diff --git a/ui/pages/swaps/build-quote/build-quote.js b/ui/pages/swaps/build-quote/build-quote.js index 36cdb780d9a8..23ca35b3f2e9 100644 --- a/ui/pages/swaps/build-quote/build-quote.js +++ b/ui/pages/swaps/build-quote/build-quote.js @@ -39,14 +39,11 @@ import { setFromTokenError, setMaxSlippage, setReviewSwapClickedTimestamp, - getSmartTransactionsOptInStatus, - getSmartTransactionsEnabled, getCurrentSmartTransactionsEnabled, getFromTokenInputValue, getFromTokenError, getMaxSlippage, getIsFeatureFlagLoaded, - getCurrentSmartTransactionsError, getSmartTransactionFees, getLatestAddedTokenTo, } from '../../../ducks/swaps/swaps'; @@ -61,6 +58,10 @@ import { getHardwareWalletType, getUseCurrencyRateCheck, } from '../../../selectors'; +import { + getSmartTransactionsOptInStatus, + getSmartTransactionsEnabled, +} from '../../../../shared/modules/selectors'; import { getURLHostName } from '../../../helpers/utils/util'; import { usePrevious } from '../../../hooks/usePrevious'; @@ -90,7 +91,6 @@ import { setBackgroundSwapRouteState, clearSwapsQuotes, stopPollingForQuotes, - setSmartTransactionsOptInStatus, clearSmartTransactionFees, } from '../../../store/actions'; import { countDecimals, fetchTokenPrice } from '../swaps.util'; @@ -103,7 +103,6 @@ import { getValueFromWeiHex, hexToDecimal, } from '../../../../shared/modules/conversion.utils'; -import SmartTransactionsPopover from '../prepare-swap-page/smart-transactions-popover'; const fuseSearchKeys = [ { name: 'name', weight: 0.499 }, @@ -160,24 +159,8 @@ export default function BuildQuote({ getCurrentSmartTransactionsEnabled, ); const smartTransactionFees = useSelector(getSmartTransactionFees); - const smartTransactionsOptInPopoverDisplayed = - smartTransactionsOptInStatus !== undefined; - const currentSmartTransactionsError = useSelector( - getCurrentSmartTransactionsError, - ); const currentCurrency = useSelector(getCurrentCurrency); - const showSmartTransactionsOptInPopover = - smartTransactionsEnabled && !smartTransactionsOptInPopoverDisplayed; - - const onManageStxInSettings = (e) => { - e?.preventDefault(); - setSmartTransactionsOptInStatus(false, smartTransactionsOptInStatus); - }; - - const onStartSwapping = () => - setSmartTransactionsOptInStatus(true, smartTransactionsOptInStatus); - const fetchParamsFromToken = isSwapsDefaultTokenSymbol( sourceTokenInfo?.symbol, chainId, @@ -574,12 +557,6 @@ export default function BuildQuote({ return ( <div className="build-quote"> <div className="build-quote__content"> - <SmartTransactionsPopover - onStartSwapping={onStartSwapping} - onManageStxInSettings={onManageStxInSettings} - isOpen={showSmartTransactionsOptInPopover} - /> - <div className="build-quote__dropdown-input-pair-header"> <div className="build-quote__input-label">{t('swapSwapFrom')}</div> {!isSwapsDefaultTokenSymbol(fromTokenSymbol, chainId) && ( @@ -766,8 +743,7 @@ export default function BuildQuote({ )} </div> ))} - {(smartTransactionsEnabled || - (!smartTransactionsEnabled && !isDirectWrappingEnabled)) && ( + {!isDirectWrappingEnabled && ( <div className="build-quote__slippage-buttons-container"> <SlippageButtons onSelect={(newSlippage) => { @@ -775,10 +751,6 @@ export default function BuildQuote({ }} maxAllowedSlippage={MAX_ALLOWED_SLIPPAGE} currentSlippage={maxSlippage} - smartTransactionsEnabled={smartTransactionsEnabled} - smartTransactionsOptInStatus={smartTransactionsOptInStatus} - setSmartTransactionsOptInStatus={setSmartTransactionsOptInStatus} - currentSmartTransactionsError={currentSmartTransactionsError} isDirectWrappingEnabled={isDirectWrappingEnabled} /> </div> diff --git a/ui/pages/swaps/dropdown-search-list/dropdown-search-list.js b/ui/pages/swaps/dropdown-search-list/dropdown-search-list.js index 28c122a30026..1182ad12d72a 100644 --- a/ui/pages/swaps/dropdown-search-list/dropdown-search-list.js +++ b/ui/pages/swaps/dropdown-search-list/dropdown-search-list.js @@ -26,13 +26,13 @@ import { getCurrentChainId, getRpcPrefsForCurrentProvider, } from '../../../selectors/selectors'; -import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../shared/constants/swaps'; -import { getURLHostName } from '../../../helpers/utils/util'; import { getSmartTransactionsOptInStatus, getSmartTransactionsEnabled, - getCurrentSmartTransactionsEnabled, -} from '../../../ducks/swaps/swaps'; +} from '../../../../shared/modules/selectors'; +import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../shared/constants/swaps'; +import { getURLHostName } from '../../../helpers/utils/util'; +import { getCurrentSmartTransactionsEnabled } from '../../../ducks/swaps/swaps'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics'; diff --git a/ui/pages/swaps/index.js b/ui/pages/swaps/index.js index 4c71811a6872..968bde404ea0 100644 --- a/ui/pages/swaps/index.js +++ b/ui/pages/swaps/index.js @@ -44,8 +44,6 @@ import { fetchSwapsLivenessAndFeatureFlags, getReviewSwapClickedTimestamp, getPendingSmartTransactions, - getSmartTransactionsOptInStatus, - getSmartTransactionsEnabled, getCurrentSmartTransactionsEnabled, getCurrentSmartTransactionsError, navigateBackToBuildQuote, @@ -57,6 +55,10 @@ import { checkNetworkAndAccountSupports1559, getCurrentNetworkTransactions, } from '../../selectors'; +import { + getSmartTransactionsOptInStatus, + getSmartTransactionsEnabled, +} from '../../../shared/modules/selectors'; import { AWAITING_SIGNATURES_ROUTE, AWAITING_SWAP_ROUTE, diff --git a/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js b/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js index ee964e8aaba6..e2691f9c7a2d 100644 --- a/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js +++ b/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js @@ -9,14 +9,16 @@ import { navigateBackToBuildQuote, getFetchParams, getQuotesFetchStartTime, - getSmartTransactionsOptInStatus, - getSmartTransactionsEnabled, getCurrentSmartTransactionsEnabled, } from '../../../ducks/swaps/swaps'; import { isHardwareWallet, getHardwareWalletType, } from '../../../selectors/selectors'; +import { + getSmartTransactionsOptInStatus, + getSmartTransactionsEnabled, +} from '../../../../shared/modules/selectors'; import { I18nContext } from '../../../contexts/i18n'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import Mascot from '../../../components/ui/mascot'; diff --git a/ui/pages/swaps/prepare-swap-page/prepare-swap-page.js b/ui/pages/swaps/prepare-swap-page/prepare-swap-page.js index 6f6eb0895b34..206936aed148 100644 --- a/ui/pages/swaps/prepare-swap-page/prepare-swap-page.js +++ b/ui/pages/swaps/prepare-swap-page/prepare-swap-page.js @@ -41,14 +41,11 @@ import { setFromTokenError, setMaxSlippage, setReviewSwapClickedTimestamp, - getSmartTransactionsOptInStatus, - getSmartTransactionsEnabled, getCurrentSmartTransactionsEnabled, getFromTokenInputValue, getFromTokenError, getMaxSlippage, getIsFeatureFlagLoaded, - getCurrentSmartTransactionsError, getFetchingQuotes, getSwapsErrorKey, getAggregatorMetadata, @@ -68,6 +65,10 @@ import { getIsBridgeChain, getMetaMetricsId, } from '../../../selectors'; +import { + getSmartTransactionsOptInStatus, + getSmartTransactionsEnabled, +} from '../../../../shared/modules/selectors'; import { getValueFromWeiHex, hexToDecimal, @@ -100,7 +101,6 @@ import { ignoreTokens, clearSwapsQuotes, stopPollingForQuotes, - setSmartTransactionsOptInStatus, clearSmartTransactionFees, setSwapsErrorKey, setBackgroundSwapRouteState, @@ -138,7 +138,6 @@ import SwapsBannerAlert from '../swaps-banner-alert/swaps-banner-alert'; import SwapsFooter from '../swaps-footer'; import SelectedToken from '../selected-token/selected-token'; import ListWithSearch from '../list-with-search/list-with-search'; -import SmartTransactionsPopover from './smart-transactions-popover'; import QuotesLoadingAnimation from './quotes-loading-animation'; import ReviewQuote from './review-quote'; @@ -217,28 +216,10 @@ export default function PrepareSwapPage({ ); const isSmartTransaction = currentSmartTransactionsEnabled && smartTransactionsOptInStatus; - const smartTransactionsOptInPopoverDisplayed = - smartTransactionsOptInStatus !== undefined; - const currentSmartTransactionsError = useSelector( - getCurrentSmartTransactionsError, - ); const currentCurrency = useSelector(getCurrentCurrency); const fetchingQuotes = useSelector(getFetchingQuotes); const loadingComplete = !fetchingQuotes && areQuotesPresent; - const showSmartTransactionsOptInPopover = - smartTransactionsEnabled && !smartTransactionsOptInPopoverDisplayed; - - const onManageStxInSettings = (e) => { - e?.preventDefault(); - setSmartTransactionsOptInStatus(true, smartTransactionsOptInStatus); - dispatch(setTransactionSettingsOpened(true)); - }; - - const onStartSwapping = () => { - setSmartTransactionsOptInStatus(true, smartTransactionsOptInStatus); - }; - const fetchParamsFromToken = isSwapsDefaultTokenSymbol( sourceTokenInfo?.symbol, chainId, @@ -877,12 +858,6 @@ export default function PrepareSwapPage({ </ModalContent> </Modal> - <SmartTransactionsPopover - onStartSwapping={onStartSwapping} - onManageStxInSettings={onManageStxInSettings} - isOpen={showSmartTransactionsOptInPopover} - /> - <div className="prepare-swap-page__swap-from-content"> <Box display={DISPLAY.FLEX} @@ -1130,25 +1105,19 @@ export default function PrepareSwapPage({ /> </Box> )} - {transactionSettingsOpened && - (smartTransactionsEnabled || - (!smartTransactionsEnabled && !isDirectWrappingEnabled)) && ( - <TransactionSettings - onSelect={(newSlippage) => { - dispatch(setMaxSlippage(newSlippage)); - }} - maxAllowedSlippage={MAX_ALLOWED_SLIPPAGE} - currentSlippage={maxSlippage} - smartTransactionsEnabled={smartTransactionsEnabled} - smartTransactionsOptInStatus={smartTransactionsOptInStatus} - setSmartTransactionsOptInStatus={setSmartTransactionsOptInStatus} - currentSmartTransactionsError={currentSmartTransactionsError} - isDirectWrappingEnabled={isDirectWrappingEnabled} - onModalClose={() => { - dispatch(setTransactionSettingsOpened(false)); - }} - /> - )} + {transactionSettingsOpened && !isDirectWrappingEnabled && ( + <TransactionSettings + onSelect={(newSlippage) => { + dispatch(setMaxSlippage(newSlippage)); + }} + maxAllowedSlippage={MAX_ALLOWED_SLIPPAGE} + currentSlippage={maxSlippage} + isDirectWrappingEnabled={isDirectWrappingEnabled} + onModalClose={() => { + dispatch(setTransactionSettingsOpened(false)); + }} + /> + )} {showQuotesLoadingAnimation && ( <QuotesLoadingAnimation quoteCount={quoteCount} diff --git a/ui/pages/swaps/prepare-swap-page/review-quote.js b/ui/pages/swaps/prepare-swap-page/review-quote.js index 3b4d351d07da..dd6ed21467a2 100644 --- a/ui/pages/swaps/prepare-swap-page/review-quote.js +++ b/ui/pages/swaps/prepare-swap-page/review-quote.js @@ -41,10 +41,8 @@ import { getBackgroundSwapRouteState, swapsQuoteSelected, getReviewSwapClickedTimestamp, - getSmartTransactionsOptInStatus, signAndSendSwapsSmartTransaction, getSwapsNetworkConfig, - getSmartTransactionsEnabled, getSmartTransactionsError, getCurrentSmartTransactionsError, getSwapsSTXLoading, @@ -65,6 +63,10 @@ import { getUSDConversionRate, getIsMultiLayerFeeNetwork, } from '../../../selectors'; +import { + getSmartTransactionsOptInStatus, + getSmartTransactionsEnabled, +} from '../../../../shared/modules/selectors'; import { getNativeCurrency, getTokens } from '../../../ducks/metamask/metamask'; import { setCustomApproveTxData, diff --git a/ui/pages/swaps/prepare-swap-page/smart-transactions-popover.stories.tsx b/ui/pages/swaps/prepare-swap-page/smart-transactions-popover.stories.tsx deleted file mode 100644 index ea53d12badec..000000000000 --- a/ui/pages/swaps/prepare-swap-page/smart-transactions-popover.stories.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import { StoryFn, Meta } from '@storybook/react'; -import { useArgs } from '@storybook/client-api'; -import { ButtonVariant, Button } from '../../../components/component-library'; -import SmartTransactionPopover from './smart-transactions-popover'; - -export default { - title: 'Pages/Swaps/SmartTransactionsPopover', - component: SmartTransactionPopover, - argTypes: { - isShowingModal: { - control: 'boolean', - }, - }, -} as Meta<typeof SmartTransactionPopover>; - -export const DefaultStory: StoryFn<typeof SmartTransactionPopover> = () => { - const [{ isShowingModal }, updateArgs] = useArgs(); - const toggleModal = () => updateArgs({ isShowingModal: !isShowingModal }); - - return ( - <> - <Button variant={ButtonVariant.Primary} onClick={toggleModal}> - Open modal - </Button> - {isShowingModal && ( - <SmartTransactionPopover - isOpen={isShowingModal} - onStartSwapping={() => { - console.log('onStartSwapping'); - }} - onManageStxInSettings={toggleModal} - /> - )} - </> - ); -}; - -DefaultStory.storyName = 'Default'; diff --git a/ui/pages/swaps/prepare-swap-page/smart-transactions-popover.tsx b/ui/pages/swaps/prepare-swap-page/smart-transactions-popover.tsx deleted file mode 100644 index ac3bcd53c804..000000000000 --- a/ui/pages/swaps/prepare-swap-page/smart-transactions-popover.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import React from 'react'; - -import { useI18nContext } from '../../../hooks/useI18nContext'; -import { - TextColor, - Display, - FlexDirection, - FontWeight, - BlockSize, - AlignItems, - JustifyContent, -} from '../../../helpers/constants/design-system'; -import { - Modal, - ModalOverlay, - Text, - Box, - Button, - ButtonVariant, - ButtonLink, - ButtonLinkSize, -} from '../../../components/component-library'; -import { ModalContent } from '../../../components/component-library/modal-content/deprecated'; -import { ModalHeader } from '../../../components/component-library/modal-header/deprecated'; -import { SMART_SWAPS_FAQ_AND_RISK_DISCLOSURES_URL } from '../../../../shared/constants/swaps'; - -type Props = { - onStartSwapping: () => void; - onManageStxInSettings: () => void; - isOpen: boolean; -}; - -export default function SmartTransactionsPopover({ - onStartSwapping, - onManageStxInSettings, - isOpen, -}: Props) { - const t = useI18nContext(); - return ( - <Modal - isOpen={isOpen} - onClose={onStartSwapping} - isClosedOnOutsideClick={false} - isClosedOnEscapeKey={false} - className="mm-modal__custom-scrollbar" - > - <ModalOverlay /> - <ModalContent> - <ModalHeader - alignItems={AlignItems.center} - justifyContent={JustifyContent.center} - > - {t('smartSwapsAreHere')} - </ModalHeader> - <Box - display={Display.Flex} - flexDirection={FlexDirection.Column} - gap={4} - marginTop={4} - > - <Box display={Display.Flex} flexDirection={FlexDirection.Column}> - <img - src="./images/logo/metamask-smart-transactions.png" - alt={t('swapSwapSwitch')} - /> - </Box> - <Text>{t('smartSwapsDescription')}</Text> - <Text - as="ul" - marginTop={3} - marginBottom={3} - style={{ listStyle: 'inside' }} - > - <li key="stxBenefit1">{t('stxBenefit1')}</li> - <li key="stxBenefit2">{t('stxBenefit2')}</li> - <li key="stxBenefit3">{t('stxBenefit3')}</li> - <li key="stxBenefit4"> - {t('stxBenefit4')} - <Text as="span" fontWeight={FontWeight.Normal}> - {' *'} - </Text> - </li> - </Text> - <Text color={TextColor.textAlternative}> - {t('smartSwapsDescription2', [ - <ButtonLink - size={ButtonLinkSize.Inherit} - href={SMART_SWAPS_FAQ_AND_RISK_DISCLOSURES_URL} - externalLink - display={Display.Inline} - key="smartSwapsDescription2" - > - {t('faqAndRiskDisclosures')} - </ButtonLink>, - ])} - </Text> - <Button - variant={ButtonVariant.Primary} - onClick={onStartSwapping} - width={BlockSize.Full} - > - {t('enableSmartSwaps')} - </Button> - <Button - type="link" - variant={ButtonVariant.Link} - onClick={onManageStxInSettings} - width={BlockSize.Full} - > - {t('manageInSettings')} - </Button> - </Box> - </ModalContent> - </Modal> - ); -} diff --git a/ui/pages/swaps/slippage-buttons/__snapshots__/slippage-buttons.test.js.snap b/ui/pages/swaps/slippage-buttons/__snapshots__/slippage-buttons.test.js.snap index 198faf5e624f..9bdc3284c232 100644 --- a/ui/pages/swaps/slippage-buttons/__snapshots__/slippage-buttons.test.js.snap +++ b/ui/pages/swaps/slippage-buttons/__snapshots__/slippage-buttons.test.js.snap @@ -46,50 +46,3 @@ exports[`SlippageButtons renders the component with initial props 2`] = ` </button> </div> `; - -exports[`SlippageButtons renders the component with the smart transaction opt-in button available, opt into STX 1`] = ` -<button - class="slippage-buttons__header slippage-buttons__header--open" -> - <div - class="slippage-buttons__header-text" - > - Advanced options - </div> - <i - class="fa fa-angle-up" - /> -</button> -`; - -exports[`SlippageButtons renders the component with the smart transaction opt-in button available, opt into STX 2`] = ` -<div - class="button-group slippage-buttons__button-group radio-button-group" - role="radiogroup" -> - <button - aria-checked="false" - class="button-group__button radio-button" - data-testid="button-group__button0" - role="radio" - > - 2% - </button> - <button - aria-checked="true" - class="button-group__button radio-button button-group__button--active radio-button--active" - data-testid="button-group__button1" - role="radio" - > - 3% - </button> - <button - aria-checked="false" - class="button-group__button slippage-buttons__button-group-custom-button radio-button" - data-testid="button-group__button2" - role="radio" - > - custom - </button> -</div> -`; diff --git a/ui/pages/swaps/slippage-buttons/slippage-buttons.js b/ui/pages/swaps/slippage-buttons/slippage-buttons.js index 9b29065c6d42..647705182656 100644 --- a/ui/pages/swaps/slippage-buttons/slippage-buttons.js +++ b/ui/pages/swaps/slippage-buttons/slippage-buttons.js @@ -5,33 +5,12 @@ import { I18nContext } from '../../../contexts/i18n'; import ButtonGroup from '../../../components/ui/button-group'; import Button from '../../../components/ui/button'; import InfoTooltip from '../../../components/ui/info-tooltip'; -import ToggleButton from '../../../components/ui/toggle-button'; -import Box from '../../../components/ui/box'; -import { - TextVariant, - FontWeight, - AlignItems, - Display, -} from '../../../helpers/constants/design-system'; -import { getTranslatedStxErrorMessage } from '../swaps.util'; -import { - Slippage, - SMART_SWAPS_FAQ_AND_RISK_DISCLOSURES_URL, -} from '../../../../shared/constants/swaps'; -import { - Text, - ButtonLink, - ButtonLinkSize, -} from '../../../components/component-library'; +import { Slippage } from '../../../../shared/constants/swaps'; export default function SlippageButtons({ onSelect, maxAllowedSlippage, currentSlippage, - smartTransactionsEnabled, - smartTransactionsOptInStatus, - setSmartTransactionsOptInStatus, - currentSmartTransactionsError, isDirectWrappingEnabled, }) { const t = useContext(I18nContext); @@ -207,57 +186,6 @@ export default function SlippageButtons({ </ButtonGroup> </div> )} - {smartTransactionsEnabled && ( - <Box marginTop={2} display={Display.Flex}> - <Box - display={Display.Flex} - alignItems={AlignItems.center} - paddingRight={3} - > - <Text - variant={TextVariant.bodySm} - as="h6" - paddingRight={2} - fontWeight={FontWeight.Bold} - > - {t('smartSwaps')} - </Text> - {currentSmartTransactionsError ? ( - <InfoTooltip - position="top" - contentText={getTranslatedStxErrorMessage( - currentSmartTransactionsError, - t, - )} - /> - ) : ( - <InfoTooltip - position="top" - contentText={t('smartSwapsTooltip', [ - <ButtonLink - key="smart-swaps-faq-and-risk-disclosures" - size={ButtonLinkSize.Inherit} - href={SMART_SWAPS_FAQ_AND_RISK_DISCLOSURES_URL} - externalLink - display={Display.Inline} - > - {t('faqAndRiskDisclosures')} - </ButtonLink>, - ])} - /> - )} - </Box> - <ToggleButton - value={smartTransactionsOptInStatus} - onToggle={(value) => { - setSmartTransactionsOptInStatus(!value, value); - }} - offLabel={t('off')} - onLabel={t('on')} - disabled={Boolean(currentSmartTransactionsError)} - /> - </Box> - )} </> )} {errorText && ( @@ -272,9 +200,5 @@ SlippageButtons.propTypes = { onSelect: PropTypes.func.isRequired, maxAllowedSlippage: PropTypes.number.isRequired, currentSlippage: PropTypes.number, - smartTransactionsEnabled: PropTypes.bool.isRequired, - smartTransactionsOptInStatus: PropTypes.bool, - setSmartTransactionsOptInStatus: PropTypes.func, - currentSmartTransactionsError: PropTypes.string, isDirectWrappingEnabled: PropTypes.bool, }; diff --git a/ui/pages/swaps/slippage-buttons/slippage-buttons.test.js b/ui/pages/swaps/slippage-buttons/slippage-buttons.test.js index c0e8a18cbd96..7834eefd59ea 100644 --- a/ui/pages/swaps/slippage-buttons/slippage-buttons.test.js +++ b/ui/pages/swaps/slippage-buttons/slippage-buttons.test.js @@ -36,32 +36,6 @@ describe('SlippageButtons', () => { ); }); - it('renders the component with the smart transaction opt-in button available, opt into STX', () => { - const setSmartTransactionsOptInStatus = jest.fn(); - const { getByText } = renderWithProvider( - <SlippageButtons - {...createProps({ - smartTransactionsEnabled: true, - setSmartTransactionsOptInStatus, - })} - />, - ); - expect(getByText('2%')).toBeInTheDocument(); - expect(getByText('3%')).toBeInTheDocument(); - expect(getByText('custom')).toBeInTheDocument(); - expect(getByText('Advanced options')).toBeInTheDocument(); - expect( - document.querySelector('.slippage-buttons__header'), - ).toMatchSnapshot(); - expect( - document.querySelector('.slippage-buttons__button-group'), - ).toMatchSnapshot(); - expect(getByText('Smart Swaps')).toBeInTheDocument(); - expect(document.querySelector('.toggle-button--off')).toBeInTheDocument(); - fireEvent.click(document.querySelector('.toggle-button')); - expect(setSmartTransactionsOptInStatus).toHaveBeenCalledWith(true, false); - }); - it('renders slippage with a custom value', () => { const { getByText } = renderWithProvider( <SlippageButtons {...createProps({ currentSlippage: 2.5 })} />, diff --git a/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js b/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js index 0a3a66f927d9..ad60188f4ce1 100644 --- a/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js +++ b/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js @@ -10,8 +10,6 @@ import { getCurrentSmartTransactions, getSelectedQuote, getTopQuote, - getSmartTransactionsOptInStatus, - getSmartTransactionsEnabled, getCurrentSmartTransactionsEnabled, getSwapsNetworkConfig, cancelSwapsSmartTransaction, @@ -22,6 +20,10 @@ import { getCurrentChainId, getRpcPrefsForCurrentProvider, } from '../../../selectors'; +import { + getSmartTransactionsOptInStatus, + getSmartTransactionsEnabled, +} from '../../../../shared/modules/selectors'; import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../shared/constants/swaps'; import { DEFAULT_ROUTE, diff --git a/ui/pages/swaps/transaction-settings/__snapshots__/transaction-settings.test.js.snap b/ui/pages/swaps/transaction-settings/__snapshots__/transaction-settings.test.js.snap index 3acd3682e1d4..b9e19665bf99 100644 --- a/ui/pages/swaps/transaction-settings/__snapshots__/transaction-settings.test.js.snap +++ b/ui/pages/swaps/transaction-settings/__snapshots__/transaction-settings.test.js.snap @@ -31,37 +31,3 @@ exports[`TransactionSettings renders the component with initial props 1`] = ` </button> </div> `; - -exports[`TransactionSettings renders the component with the smart transaction opt-in button available, opt into STX 1`] = `null`; - -exports[`TransactionSettings renders the component with the smart transaction opt-in button available, opt into STX 2`] = ` -<div - class="button-group transaction-settings__button-group radio-button-group" - role="radiogroup" -> - <button - aria-checked="false" - class="button-group__button radio-button" - data-testid="button-group__button0" - role="radio" - > - 2% - </button> - <button - aria-checked="true" - class="button-group__button radio-button button-group__button--active radio-button--active" - data-testid="button-group__button1" - role="radio" - > - 3% - </button> - <button - aria-checked="false" - class="button-group__button transaction-settings__button-group-custom-button radio-button" - data-testid="button-group__button2" - role="radio" - > - custom - </button> -</div> -`; diff --git a/ui/pages/swaps/transaction-settings/transaction-settings.js b/ui/pages/swaps/transaction-settings/transaction-settings.js index 18611b67ffdd..51b35718d8f3 100644 --- a/ui/pages/swaps/transaction-settings/transaction-settings.js +++ b/ui/pages/swaps/transaction-settings/transaction-settings.js @@ -7,7 +7,6 @@ import { I18nContext } from '../../../contexts/i18n'; import ButtonGroup from '../../../components/ui/button-group'; import Button from '../../../components/ui/button'; import InfoTooltip from '../../../components/ui/info-tooltip'; -import ToggleButton from '../../../components/ui/toggle-button'; import Box from '../../../components/ui/box'; import Typography from '../../../components/ui/typography'; import { @@ -17,22 +16,17 @@ import { DISPLAY, SEVERITIES, FlexDirection, - Display, } from '../../../helpers/constants/design-system'; -import { getTranslatedStxErrorMessage } from '../swaps.util'; import { Slippage, SLIPPAGE_VERY_HIGH_ERROR, SLIPPAGE_NEGATIVE_ERROR, - SMART_SWAPS_FAQ_AND_RISK_DISCLOSURES_URL, } from '../../../../shared/constants/swaps'; import { BannerAlert, Modal, ModalOverlay, ButtonPrimary, - ButtonLink, - ButtonLinkSize, } from '../../../components/component-library'; import { ModalContent } from '../../../components/component-library/modal-content/deprecated'; import { ModalHeader } from '../../../components/component-library/modal-header/deprecated'; @@ -44,10 +38,6 @@ export default function TransactionSettings({ onModalClose, maxAllowedSlippage, currentSlippage, - smartTransactionsEnabled, - smartTransactionsOptInStatus, - setSmartTransactionsOptInStatus, - currentSmartTransactionsError, isDirectWrappingEnabled, }) { const t = useContext(I18nContext); @@ -75,20 +65,13 @@ export default function TransactionSettings({ }); const [inputRef, setInputRef] = useState(null); const [newSlippage, setNewSlippage] = useState(currentSlippage); - const [newSmartTransactionsOptInStatus, setNewSmartTransactionsOptInStatus] = - useState(smartTransactionsOptInStatus); - const didFormChange = - newSlippage !== currentSlippage || - newSmartTransactionsOptInStatus !== smartTransactionsOptInStatus; + const didFormChange = newSlippage !== currentSlippage; const updateTransactionSettings = () => { if (newSlippage !== currentSlippage) { onSelect(newSlippage); } - if (newSmartTransactionsOptInStatus !== smartTransactionsOptInStatus) { - setSmartTransactionsOptInStatus(newSmartTransactionsOptInStatus); - } }; let notificationText = ''; @@ -148,12 +131,6 @@ export default function TransactionSettings({ } }, [dispatch, activeButtonIndex]); - useEffect(() => { - if (newSmartTransactionsOptInStatus === undefined) { - setNewSmartTransactionsOptInStatus(smartTransactionsOptInStatus); - } - }, [smartTransactionsOptInStatus, newSmartTransactionsOptInStatus]); - return ( <Modal onClose={onModalClose} @@ -176,63 +153,6 @@ export default function TransactionSettings({ > <Box marginTop={7} marginBottom={5}> <> - {smartTransactionsEnabled && ( - <Box - marginTop={2} - marginBottom={6} - display={DISPLAY.FLEX} - justifyContent={JustifyContent.spaceBetween} - > - <Box - display={DISPLAY.FLEX} - alignItems={AlignItems.center} - paddingRight={3} - > - <Typography - variant={TypographyVariant.H6} - boxProps={{ paddingRight: 2 }} - > - {t('smartSwaps')} - </Typography> - {currentSmartTransactionsError ? ( - <InfoTooltip - position="top" - iconFillColor="var(--color-icon-muted)" - contentText={getTranslatedStxErrorMessage( - currentSmartTransactionsError, - t, - )} - /> - ) : ( - <InfoTooltip - position="top" - contentText={t('smartSwapsTooltip', [ - <ButtonLink - key="smart-swaps-faq-and-risk-disclosures" - size={ButtonLinkSize.Inherit} - href={SMART_SWAPS_FAQ_AND_RISK_DISCLOSURES_URL} - externalLink - display={Display.Inline} - > - {t('faqAndRiskDisclosures')} - </ButtonLink>, - ])} - iconFillColor="var(--color-icon-muted)" - /> - )} - </Box> - <ToggleButton - value={newSmartTransactionsOptInStatus} - onToggle={(value) => { - setNewSmartTransactionsOptInStatus(!value, value); - }} - offLabel={t('off')} - onLabel={t('on')} - disabled={Boolean(currentSmartTransactionsError)} - dataTestId="transaction-settings-smart-swaps-toggle" - /> - </Box> - )} {!isDirectWrappingEnabled && ( <> <Box display={DISPLAY.FLEX} alignItems={AlignItems.center}> @@ -242,22 +162,11 @@ export default function TransactionSettings({ > {t('swapsMaxSlippage')} </Typography> - {currentSmartTransactionsError ? ( - <InfoTooltip - position="top" - iconFillColor="var(--color-icon-muted)" - contentText={getTranslatedStxErrorMessage( - currentSmartTransactionsError, - t, - )} - /> - ) : ( - <InfoTooltip - position="top" - iconFillColor="var(--color-icon-muted)" - contentText={t('swapSlippageTooltip')} - /> - )} + <InfoTooltip + position="top" + iconFillColor="var(--color-icon-muted)" + contentText={t('swapSlippageTooltip')} + /> </Box> <Box display={DISPLAY.FLEX}> <ButtonGroup @@ -394,9 +303,5 @@ TransactionSettings.propTypes = { onModalClose: PropTypes.func.isRequired, maxAllowedSlippage: PropTypes.number.isRequired, currentSlippage: PropTypes.number, - smartTransactionsEnabled: PropTypes.bool.isRequired, - smartTransactionsOptInStatus: PropTypes.bool, - setSmartTransactionsOptInStatus: PropTypes.func, - currentSmartTransactionsError: PropTypes.string, isDirectWrappingEnabled: PropTypes.bool, }; diff --git a/ui/pages/swaps/transaction-settings/transaction-settings.test.js b/ui/pages/swaps/transaction-settings/transaction-settings.test.js index c487b2f35a29..c7a29652751b 100644 --- a/ui/pages/swaps/transaction-settings/transaction-settings.test.js +++ b/ui/pages/swaps/transaction-settings/transaction-settings.test.js @@ -58,33 +58,6 @@ describe('TransactionSettings', () => { ); }); - it('renders the component with the smart transaction opt-in button available, opt into STX', async () => { - const setSmartTransactionsOptInStatus = jest.fn(); - const { getByText, getByTestId } = renderWithProvider( - <TransactionSettings - {...createProps({ - smartTransactionsEnabled: true, - setSmartTransactionsOptInStatus, - })} - />, - store, - ); - expect(getByText('2%')).toBeInTheDocument(); - expect(getByText('3%')).toBeInTheDocument(); - expect(getByText('custom')).toBeInTheDocument(); - expect( - document.querySelector('.transaction-settings__header'), - ).toMatchSnapshot(); - expect( - document.querySelector('.transaction-settings__button-group'), - ).toMatchSnapshot(); - expect(getByText('Smart Swaps')).toBeInTheDocument(); - expect(document.querySelector('.toggle-button--off')).toBeInTheDocument(); - await fireEvent.click(document.querySelector('.toggle-button')); - await fireEvent.click(getByTestId('update-transaction-settings-button')); - expect(setSmartTransactionsOptInStatus).toHaveBeenCalledWith(true); - }); - it('renders slippage with a custom value', () => { const { getByText } = renderWithProvider( <TransactionSettings {...createProps({ currentSlippage: 2.5 })} />, diff --git a/ui/pages/swaps/view-quote/view-quote.js b/ui/pages/swaps/view-quote/view-quote.js index 0b139113f68e..33cc464bfd6d 100644 --- a/ui/pages/swaps/view-quote/view-quote.js +++ b/ui/pages/swaps/view-quote/view-quote.js @@ -42,10 +42,8 @@ import { swapsQuoteSelected, getSwapsQuoteRefreshTime, getReviewSwapClickedTimestamp, - getSmartTransactionsOptInStatus, signAndSendSwapsSmartTransaction, getSwapsNetworkConfig, - getSmartTransactionsEnabled, getSmartTransactionsError, getCurrentSmartTransactionsError, getSwapsSTXLoading, @@ -66,6 +64,10 @@ import { getUSDConversionRate, getIsMultiLayerFeeNetwork, } from '../../../selectors'; +import { + getSmartTransactionsOptInStatus, + getSmartTransactionsEnabled, +} from '../../../../shared/modules/selectors'; import { getNativeCurrency, getTokens } from '../../../ducks/metamask/metamask'; import { safeRefetchQuotes, diff --git a/ui/selectors/permissions.js b/ui/selectors/permissions.js index 4211b6cbffb7..a0b7da870a1f 100644 --- a/ui/selectors/permissions.js +++ b/ui/selectors/permissions.js @@ -73,6 +73,10 @@ export function getPermittedAccountsForCurrentTab(state) { return getPermittedAccounts(state, getOriginOfCurrentTab(state)); } +export function getPermittedAccountsForSelectedTab(state, activeTab) { + return getPermittedAccounts(state, activeTab); +} + /** * Returns a map of permitted accounts by origin for all origins. * @@ -137,7 +141,10 @@ export function getConnectedSubjectsForAllAddresses(state) { if (!accountsToConnections[address]) { accountsToConnections[address] = []; } - accountsToConnections[address].push(subjectMetadata[subjectKey] || {}); + const metadata = subjectMetadata[subjectKey]; + if (metadata) { + accountsToConnections[address].push(metadata); + } }); }); @@ -313,6 +320,44 @@ export function getOrderedConnectedAccountsForActiveTab(state) { ); } +export function getOrderedConnectedAccountsForConnectedDapp(state, activeTab) { + const { + metamask: { permissionHistory }, + } = state; + + const permissionHistoryByAccount = + // eslint-disable-next-line camelcase + permissionHistory[activeTab.origin]?.eth_accounts?.accounts; + const orderedAccounts = getMetaMaskAccountsOrdered(state); + const connectedAccounts = getPermittedAccountsForSelectedTab( + state, + activeTab, + ); + + return orderedAccounts + .filter((account) => connectedAccounts.includes(account.address)) + .map((account) => ({ + ...account, + metadata: { + ...account.metadata, + lastActive: permissionHistoryByAccount?.[account.address], + }, + })) + .sort( + ({ lastSelected: lastSelectedA }, { lastSelected: lastSelectedB }) => { + if (lastSelectedA === lastSelectedB) { + return 0; + } else if (lastSelectedA === undefined) { + return 1; + } else if (lastSelectedB === undefined) { + return -1; + } + + return lastSelectedB - lastSelectedA; + }, + ); +} + export function getPermissionsForActiveTab(state) { const { activeTab, metamask } = state; const { subjects = {} } = metamask; diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 29fd4a4211c8..ff0b686dec0d 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -49,6 +49,7 @@ import { HardwareTransportStates, } from '../../shared/constants/hardware-wallets'; import { KeyringType } from '../../shared/constants/keyring'; +import { getIsSmartTransaction } from '../../shared/modules/selectors'; import { TRUNCATED_NAME_CHAR_LIMIT } from '../../shared/constants/labels'; @@ -93,9 +94,7 @@ import { ///: BEGIN:ONLY_INCLUDE_IF(blockaid) NOTIFICATION_BLOCKAID_DEFAULT, ///: END:ONLY_INCLUDE_IF - NOTIFICATION_BUY_SELL_BUTTON, NOTIFICATION_DROP_LEDGER_FIREFOX, - NOTIFICATION_OPEN_BETA_SNAPS, NOTIFICATION_PETNAMES, NOTIFICATION_U2F_LEDGER_LIVE, NOTIFICATION_STAKING_PORTFOLIO, @@ -637,6 +636,112 @@ export function getNeverShowSwitchedNetworkMessage(state) { return state.metamask.switchedNetworkNeverShowMessage; } +export const getNonTestNetworks = createDeepEqualSelector( + getNetworkConfigurations, + (networkConfigurations = {}) => { + return [ + // Mainnet always first + { + chainId: CHAIN_IDS.MAINNET, + nickname: MAINNET_DISPLAY_NAME, + rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.MAINNET], + rpcPrefs: { + imageUrl: ETH_TOKEN_IMAGE_URL, + }, + providerType: NETWORK_TYPES.MAINNET, + ticker: CURRENCY_SYMBOLS.ETH, + id: NETWORK_TYPES.MAINNET, + removable: false, + }, + { + chainId: CHAIN_IDS.LINEA_MAINNET, + nickname: LINEA_MAINNET_DISPLAY_NAME, + rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.LINEA_MAINNET], + rpcPrefs: { + imageUrl: LINEA_MAINNET_TOKEN_IMAGE_URL, + }, + providerType: NETWORK_TYPES.LINEA_MAINNET, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_MAINNET], + id: NETWORK_TYPES.LINEA_MAINNET, + removable: false, + }, + // Custom networks added by the user + ...Object.values(networkConfigurations) + .filter(({ chainId }) => ![CHAIN_IDS.LOCALHOST].includes(chainId)) + .map((network) => ({ + ...network, + rpcPrefs: { + ...network.rpcPrefs, + // Provide an image based on chainID if a network + // has been added without an image + imageUrl: + network?.rpcPrefs?.imageUrl ?? + CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[network.chainId], + }, + removable: true, + })), + ]; + }, +); + +export const getTestNetworks = createDeepEqualSelector( + getNetworkConfigurations, + (networkConfigurations = {}) => { + return [ + { + chainId: CHAIN_IDS.SEPOLIA, + nickname: SEPOLIA_DISPLAY_NAME, + rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.SEPOLIA], + providerType: NETWORK_TYPES.SEPOLIA, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.SEPOLIA], + id: NETWORK_TYPES.SEPOLIA, + removable: false, + }, + { + chainId: CHAIN_IDS.LINEA_GOERLI, + nickname: LINEA_GOERLI_DISPLAY_NAME, + rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.LINEA_GOERLI], + rpcPrefs: { + imageUrl: LINEA_GOERLI_TOKEN_IMAGE_URL, + }, + providerType: NETWORK_TYPES.LINEA_GOERLI, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_GOERLI], + id: NETWORK_TYPES.LINEA_GOERLI, + removable: false, + }, + { + chainId: CHAIN_IDS.LINEA_SEPOLIA, + nickname: LINEA_SEPOLIA_DISPLAY_NAME, + rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.LINEA_SEPOLIA], + rpcPrefs: { + imageUrl: LINEA_SEPOLIA_TOKEN_IMAGE_URL, + }, + providerType: NETWORK_TYPES.LINEA_SEPOLIA, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_SEPOLIA], + id: NETWORK_TYPES.LINEA_SEPOLIA, + removable: false, + }, + // Localhosts + ...Object.values(networkConfigurations) + .filter(({ chainId }) => chainId === CHAIN_IDS.LOCALHOST) + .map((network) => ({ ...network, removable: true })), + ]; + }, +); + +export const getAllNetworks = createDeepEqualSelector( + getNonTestNetworks, + getTestNetworks, + (nonTestNetworks, testNetworks) => { + return [ + // Mainnet and custom networks + ...nonTestNetworks, + // Test networks + ...testNetworks, + ]; + }, +); + /** * Provides information about the last network change if present * @@ -830,7 +935,8 @@ export function getAdvancedInlineGasShown(state) { } export function getUseNonceField(state) { - return Boolean(state.metamask.useNonceField); + const isSmartTransaction = getIsSmartTransaction(state); + return Boolean(!isSmartTransaction && state.metamask.useNonceField); } export function getCustomNonceValue(state) { @@ -1215,8 +1321,17 @@ export const getMemoizedAddressBook = createDeepEqualSelector( (addressBook) => addressBook, ); +/** + * Returns a memoized selector that gets contract info. + * + * @param state - The Redux store state. + * @param addresses - An array of contract addresses. + * @param forceRemoteTokenList - Whether to force the use of the remote token list. + * @returns {Array} A map of contract info, keyed by address. + */ export const getMemoizedMetadataContracts = createDeepEqualSelector( - getTokenList, + (state, _addresses, forceRemoteTokenList) => + getTokenList(state, forceRemoteTokenList), (_tokenList, addresses) => addresses, (tokenList, addresses) => { return addresses.map((address) => @@ -1600,8 +1715,6 @@ function getAllowedAnnouncementIds(state) { 24: state.metamask.hadAdvancedGasFeesSetPriorToMigration92_3 === true, // This syntax is unusual, but very helpful here. It's equivalent to `unnamedObject[NOTIFICATION_DROP_LEDGER_FIREFOX] =` [NOTIFICATION_DROP_LEDGER_FIREFOX]: currentKeyringIsLedger && isFirefox, - [NOTIFICATION_OPEN_BETA_SNAPS]: true, - [NOTIFICATION_BUY_SELL_BUTTON]: true, [NOTIFICATION_U2F_LEDGER_LIVE]: currentKeyringIsLedger && !isFirefox, [NOTIFICATION_STAKING_PORTFOLIO]: true, ///: BEGIN:ONLY_INCLUDE_IF(blockaid) @@ -1688,6 +1801,18 @@ export function getNumberOfAllUnapprovedTransactionsAndMessages(state) { return numUnapprovedMessages; } +export const getCurrentNetwork = createDeepEqualSelector( + getAllNetworks, + getProviderConfig, + (allNetworks, providerConfig) => { + const filter = + providerConfig.type === 'rpc' + ? (network) => network.id === providerConfig.id + : (network) => network.id === providerConfig.type; + return allNetworks.find(filter); + }, +); + /** * Returns the network client ID of the network that should be auto-switched to * based on the current tab origin and its last network connected to @@ -1832,15 +1957,18 @@ export function getTheme(state) { * from the tokens controller if token detection is enabled, or the static list if not. * * @param {*} state + * @param {boolean} forceRemote - Whether to force the use of the remote token list regardless of the user preference. Defaults to false. * @returns {object} */ -export function getTokenList(state) { +export function getTokenList(state, forceRemote = false) { const isTokenDetectionInactiveOnMainnet = getIsTokenDetectionInactiveOnMainnet(state); - const caseInSensitiveTokenList = isTokenDetectionInactiveOnMainnet - ? STATIC_MAINNET_TOKEN_LIST - : state.metamask.tokenList; - return caseInSensitiveTokenList; + + if (isTokenDetectionInactiveOnMainnet && !forceRemote) { + return STATIC_MAINNET_TOKEN_LIST; + } + + return state.metamask.tokenList; } export function doesAddressRequireLedgerHidConnection(state, address) { @@ -1886,17 +2014,6 @@ export function getNetworkConfigurations(state) { return state.metamask.networkConfigurations; } -export function getCurrentNetwork(state) { - const allNetworks = getAllNetworks(state); - const providerConfig = getProviderConfig(state); - - const filter = - providerConfig.type === 'rpc' - ? (network) => network.id === providerConfig.id - : (network) => network.id === providerConfig.type; - return allNetworks.find(filter); -} - export function getIsNetworkSupportedByBlockaid(state) { const currentChainId = getCurrentChainId(state); const isSupported = SUPPORTED_CHAIN_IDS.includes(currentChainId); @@ -1904,115 +2021,14 @@ export function getIsNetworkSupportedByBlockaid(state) { return isSupported; } -export function getAllEnabledNetworks(state) { - const nonTestNetworks = getNonTestNetworks(state); - const allNetworks = getAllNetworks(state); - const showTestnetNetworks = getShowTestNetworks(state); - - return showTestnetNetworks ? allNetworks : nonTestNetworks; -} - -export function getTestNetworks(state) { - const networkConfigurations = getNetworkConfigurations(state) || {}; - - return [ - { - chainId: CHAIN_IDS.SEPOLIA, - nickname: SEPOLIA_DISPLAY_NAME, - rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.SEPOLIA], - providerType: NETWORK_TYPES.SEPOLIA, - ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.SEPOLIA], - id: NETWORK_TYPES.SEPOLIA, - removable: false, - }, - { - chainId: CHAIN_IDS.LINEA_GOERLI, - nickname: LINEA_GOERLI_DISPLAY_NAME, - rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.LINEA_GOERLI], - rpcPrefs: { - imageUrl: LINEA_GOERLI_TOKEN_IMAGE_URL, - }, - providerType: NETWORK_TYPES.LINEA_GOERLI, - ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_GOERLI], - id: NETWORK_TYPES.LINEA_GOERLI, - removable: false, - }, - { - chainId: CHAIN_IDS.LINEA_SEPOLIA, - nickname: LINEA_SEPOLIA_DISPLAY_NAME, - rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.LINEA_SEPOLIA], - rpcPrefs: { - imageUrl: LINEA_SEPOLIA_TOKEN_IMAGE_URL, - }, - providerType: NETWORK_TYPES.LINEA_SEPOLIA, - ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_SEPOLIA], - id: NETWORK_TYPES.LINEA_SEPOLIA, - removable: false, - }, - // Localhosts - ...Object.values(networkConfigurations) - .filter(({ chainId }) => chainId === CHAIN_IDS.LOCALHOST) - .map((network) => ({ ...network, removable: true })), - ]; -} - -export function getNonTestNetworks(state) { - const networkConfigurations = getNetworkConfigurations(state) || {}; - - return [ - // Mainnet always first - { - chainId: CHAIN_IDS.MAINNET, - nickname: MAINNET_DISPLAY_NAME, - rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.MAINNET], - rpcPrefs: { - imageUrl: ETH_TOKEN_IMAGE_URL, - }, - providerType: NETWORK_TYPES.MAINNET, - ticker: CURRENCY_SYMBOLS.ETH, - id: NETWORK_TYPES.MAINNET, - removable: false, - }, - { - chainId: CHAIN_IDS.LINEA_MAINNET, - nickname: LINEA_MAINNET_DISPLAY_NAME, - rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.LINEA_MAINNET], - rpcPrefs: { - imageUrl: LINEA_MAINNET_TOKEN_IMAGE_URL, - }, - providerType: NETWORK_TYPES.LINEA_MAINNET, - ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_MAINNET], - id: NETWORK_TYPES.LINEA_MAINNET, - removable: false, - }, - // Custom networks added by the user - ...Object.values(networkConfigurations) - .filter(({ chainId }) => ![CHAIN_IDS.LOCALHOST].includes(chainId)) - .map((network) => ({ - ...network, - rpcPrefs: { - ...network.rpcPrefs, - // Provide an image based on chainID if a network - // has been added without an image - imageUrl: - network?.rpcPrefs?.imageUrl ?? - CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[network.chainId], - }, - removable: true, - })), - ]; -} - -export function getAllNetworks(state) { - const networks = [ - // Mainnet and custom networks - ...getNonTestNetworks(state), - // Test networks - ...getTestNetworks(state), - ]; - - return networks; -} +export const getAllEnabledNetworks = createDeepEqualSelector( + getNonTestNetworks, + getAllNetworks, + getShowTestNetworks, + (nonTestNetworks, allNetworks, showTestnetNetworks) => { + return showTestnetNetworks ? allNetworks : nonTestNetworks; + }, +); export function getIsOptimism(state) { return ( diff --git a/ui/selectors/transactions.js b/ui/selectors/transactions.js index 5c116b2cd866..e8daa65b9887 100644 --- a/ui/selectors/transactions.js +++ b/ui/selectors/transactions.js @@ -97,18 +97,23 @@ export const unapprovedEncryptionPublicKeyMsgsSelector = (state) => export const unapprovedTypedMessagesSelector = (state) => state.metamask.unapprovedTypedMessages; -export const smartTransactionsListSelector = (state) => - state.metamask.smartTransactionsState?.smartTransactions?.[ +export const smartTransactionsListSelector = (state) => { + const { address: selectedAddress } = getSelectedInternalAccount(state); + return state.metamask.smartTransactionsState?.smartTransactions?.[ getCurrentChainId(state) ] - ?.filter((stx) => !stx.confirmed) + ?.filter((stx) => { + const { txParams } = stx; + return txParams?.from === selectedAddress && !stx.confirmed; + }) .map((stx) => ({ ...stx, - transactionType: TransactionType.smart, + isSmartTransaction: true, status: stx.status?.startsWith('cancelled') ? SmartTransactionStatus.cancelled : stx.status, })); +}; export const selectedAddressTxListSelector = createSelector( getSelectedInternalAccount, diff --git a/ui/store/actions.test.js b/ui/store/actions.test.js index eb6935d83ef0..9f7a71cf4a4a 100644 --- a/ui/store/actions.test.js +++ b/ui/store/actions.test.js @@ -2191,4 +2191,38 @@ describe('Actions', () => { expect(expectedAction.payload.originalTransactionId).toBe(txId); }); }); + + describe('#removeAndIgnoreNft', () => { + afterEach(() => { + sinon.restore(); + }); + + it('should throw when no address found', async () => { + const store = mockStore(); + + await expect( + store.dispatch(actions.removeAndIgnoreNft(undefined, '55')), + ).rejects.toThrow('MetaMask - Cannot ignore NFT without address'); + }); + + it('should throw when no tokenId found', async () => { + const store = mockStore(); + + await expect( + store.dispatch(actions.removeAndIgnoreNft('Oxtest', undefined)), + ).rejects.toThrow('MetaMask - Cannot ignore NFT without tokenID'); + }); + + it('should throw when removeAndIgnoreNft throws an error', async () => { + const store = mockStore(); + const error = new Error('remove nft fake error'); + background.removeAndIgnoreNft = sinon.stub().throws(error); + + setBackgroundConnection(background); + + await expect( + store.dispatch(actions.removeAndIgnoreNft('Oxtest', '6')), + ).rejects.toThrow(error); + }); + }); }); diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 4986eea1176d..cec6b79449e5 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -84,7 +84,6 @@ import { LEDGER_USB_VENDOR_ID, } from '../../shared/constants/hardware-wallets'; import { - MetaMetricsEventCategory, MetaMetricsEventFragment, MetaMetricsEventOptions, MetaMetricsEventPayload, @@ -2083,7 +2082,7 @@ export function addNftVerifyOwnership( export function removeAndIgnoreNft( address: string, tokenID: string, - dontShowLoadingIndicator: boolean, + shouldShowLoadingIndicator?: boolean, ): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> { return async (dispatch: MetaMaskReduxDispatch) => { if (!address) { @@ -2092,7 +2091,7 @@ export function removeAndIgnoreNft( if (!tokenID) { throw new Error('MetaMask - Cannot ignore NFT without tokenID'); } - if (!dontShowLoadingIndicator) { + if (!shouldShowLoadingIndicator) { dispatch(showLoadingIndication()); } try { @@ -2100,6 +2099,7 @@ export function removeAndIgnoreNft( } catch (error) { logErrorWithMessage(error); dispatch(displayWarning(error)); + throw error; } finally { await forceUpdateMetamaskState(dispatch); dispatch(hideLoadingIndication()); @@ -2244,6 +2244,21 @@ export function clearSwitchedNetworkDetails(): ThunkAction< }; } +/** + * Update the currentPopupid generated when the user opened the popup + * + * @param id - The Snap interface ID. + * @returns Promise Resolved on successfully submitted background request. + */ +export function setCurrentExtensionPopupId( + id: number, +): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> { + return async (dispatch: MetaMaskReduxDispatch) => { + await submitRequestToBackground<void>('setCurrentExtensionPopupId', [id]); + await forceUpdateMetamaskState(dispatch); + }; +} + export function abortTransactionSigning( transactionId: string, // TODO: Replace `any` with type @@ -3066,6 +3081,10 @@ export function setShowExtensionInFullSizeView(value: boolean) { return setPreference('showExtensionInFullSizeView', value); } +export function setSmartTransactionsOptInStatus(value: boolean) { + return setPreference('smartTransactionsOptInStatus', value); +} + export function setAutoLockTimeLimit(value: boolean) { return setPreference('autoLockTimeLimit', value); } @@ -4531,26 +4550,6 @@ export async function setWeb3ShimUsageAlertDismissed(origin: string) { } // Smart Transactions Controller -export async function setSmartTransactionsOptInStatus( - optInState: boolean, - prevOptInState: boolean, -) { - trackMetaMetricsEvent({ - actionId: generateActionId(), - event: 'STX OptIn', - category: MetaMetricsEventCategory.Swaps, - sensitiveProperties: { - stx_enabled: true, - current_stx_enabled: true, - stx_user_opt_in: optInState, - stx_prev_user_opt_in: prevOptInState, - }, - }); - await submitRequestToBackground('setSmartTransactionsOptInStatus', [ - optInState, - ]); -} - export function clearSmartTransactionFees() { submitRequestToBackground('clearSmartTransactionFees'); } @@ -4775,6 +4774,22 @@ export function neverShowSwitchedNetworkMessage() { ]); } +/** + * Sends the background state the networkClientId and domain upon network switch + * + * @param selectedTabOrigin - The origin to set the new networkClientId for + * @param networkClientId - The new networkClientId + */ +export function setNetworkClientIdForDomain( + selectedTabOrigin: string, + networkClientId: string, +): Promise<void> { + return submitRequestToBackground('setNetworkClientIdForDomain', [ + selectedTabOrigin, + networkClientId, + ]); +} + ///: BEGIN:ONLY_INCLUDE_IF(blockaid) export function setSecurityAlertsEnabled(val: boolean): void { try { diff --git a/yarn.lock b/yarn.lock index 4e14a5d6a164..0b86acf8f199 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1618,7 +1618,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.24.0, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.23.4": +"@babel/runtime@npm:7.24.0": version: 7.24.0 resolution: "@babel/runtime@npm:7.24.0" dependencies: @@ -1627,6 +1627,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.23.4, @babel/runtime@npm:^7.24.1": + version: 7.24.1 + resolution: "@babel/runtime@npm:7.24.1" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 3a8d61400c636d1ce3a42895a106cd4dfb4e9b88832a8a754a724c68652f821d7a46dce394305d7623f9f0d3597bf0a98aeb5f9c150ef60e14bbbf66caab4654 + languageName: node + linkType: hard + "@babel/runtime@patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch": version: 7.24.0 resolution: "@babel/runtime@patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch::version=7.24.0&hash=cce522" @@ -2345,12 +2354,12 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/common@npm:^4.1.0, @ethereumjs/common@npm:^4.2.0": - version: 4.2.0 - resolution: "@ethereumjs/common@npm:4.2.0" +"@ethereumjs/common@npm:^4.1.0, @ethereumjs/common@npm:^4.3.0": + version: 4.3.0 + resolution: "@ethereumjs/common@npm:4.3.0" dependencies: - "@ethereumjs/util": "npm:^9.0.2" - checksum: dbf208d118ac30ff9a14d72a0fd16d3a1766c7627f29ca5a34134684e9200bc083f4dbeccab084018711a2f974a34f2f9deecbb495ec07d6f94cf9bd81bfa2ef + "@ethereumjs/util": "npm:^9.0.3" + checksum: 90f7fe1ba6827b65cd25e9bb4adf07a117ea554a950bb364d5fd9873cb770d383addb0ad34839a91fbec22ebc25516c6fb7e70ae0198c78f933920bf39797a94 languageName: node linkType: hard @@ -2403,20 +2412,15 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/tx@npm:^5.1.0": - version: 5.2.1 - resolution: "@ethereumjs/tx@npm:5.2.1" +"@ethereumjs/tx@npm:^5.1.0, @ethereumjs/tx@npm:^5.2.1": + version: 5.3.0 + resolution: "@ethereumjs/tx@npm:5.3.0" dependencies: - "@ethereumjs/common": "npm:^4.2.0" + "@ethereumjs/common": "npm:^4.3.0" "@ethereumjs/rlp": "npm:^5.0.2" - "@ethereumjs/util": "npm:^9.0.2" + "@ethereumjs/util": "npm:^9.0.3" ethereum-cryptography: "npm:^2.1.3" - peerDependencies: - c-kzg: ^2.1.2 - peerDependenciesMeta: - c-kzg: - optional: true - checksum: 87b473a99105c82f5a17f1f99442e421a0f515770ac3a9b58d9462a6d10ae1144f92a76a15abcf1b27496dfdcbb6e826eeb7b17636c8bde0613205623b0a1ddf + checksum: 4eb48e763d81ea0978648367d61c568c8d10f769c1ea7d32307ebe02299d4fa9fe5d7bf794ec1ee22e92edef6bfe1f459d5816e1c62d3f93602d931807ca488b languageName: node linkType: hard @@ -2431,18 +2435,13 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/util@npm:^9.0.2": - version: 9.0.2 - resolution: "@ethereumjs/util@npm:9.0.2" +"@ethereumjs/util@npm:^9.0.2, @ethereumjs/util@npm:^9.0.3": + version: 9.0.3 + resolution: "@ethereumjs/util@npm:9.0.3" dependencies: "@ethereumjs/rlp": "npm:^5.0.2" ethereum-cryptography: "npm:^2.1.3" - peerDependencies: - c-kzg: ^2.1.2 - peerDependenciesMeta: - c-kzg: - optional: true - checksum: 1b8aeb733f4c576bf9f0ade5a3d19da25d602cb068ed735a1fc344cc5f534a7dffe1f5c13139a3d7c805853bd9ec8ba194243fe6dee57d273f1201209846df83 + checksum: d9c313a0672e0b4842ba80e2f413ccd5302c3942dc4ed718d6e3faab89c4eeeaaeb6661e9275679c22b83b1cb6502ea772e1c837d105b6a9faf5957a59bc3e4c languageName: node linkType: hard @@ -3797,60 +3796,60 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/custody-controller@npm:^0.2.22": - version: 0.2.22 - resolution: "@metamask-institutional/custody-controller@npm:0.2.22" +"@metamask-institutional/custody-controller@npm:^0.2.24": + version: 0.2.24 + resolution: "@metamask-institutional/custody-controller@npm:0.2.24" dependencies: "@ethereumjs/util": "npm:^8.0.5" - "@metamask-institutional/custody-keyring": "npm:^1.0.10" - "@metamask-institutional/sdk": "npm:^0.1.24" + "@metamask-institutional/custody-keyring": "npm:^1.0.12" + "@metamask-institutional/sdk": "npm:^0.1.25" "@metamask-institutional/types": "npm:^1.0.4" "@metamask/obs-store": "npm:^8.0.0" - checksum: e5ee0ce9dfca87ddaff977a48e4914263de9f930797ad9adc0568381ec2a4373f8e307bafc1162c0215eccfa93b8b42ce684bac4b294758c53164958e44a7d69 + checksum: 6815d46eeb97d0d9188eba2af5c948f281ed554f3d920905431d3093bb864a9a43aebea6a773f59d5e34e8d4c2f7cbca0d2c4eff6bb8c4e05595505af5bbfccc languageName: node linkType: hard -"@metamask-institutional/custody-keyring@npm:^1.0.10, @metamask-institutional/custody-keyring@npm:^1.0.8": - version: 1.0.10 - resolution: "@metamask-institutional/custody-keyring@npm:1.0.10" +"@metamask-institutional/custody-keyring@npm:^1.0.12": + version: 1.0.12 + resolution: "@metamask-institutional/custody-keyring@npm:1.0.12" dependencies: "@ethereumjs/tx": "npm:^4.1.1" "@ethereumjs/util": "npm:^8.0.5" "@metamask-institutional/configuration-client": "npm:^2.0.1" - "@metamask-institutional/sdk": "npm:^0.1.24" + "@metamask-institutional/sdk": "npm:^0.1.25" "@metamask-institutional/types": "npm:^1.0.4" "@metamask/obs-store": "npm:^8.0.0" crypto: "npm:^1.0.1" lodash.clonedeep: "npm:^4.5.0" - checksum: 5009f4d0bd6779898b632a0f00ad3f572678ec3cf36dafac2365199645f0702dba76e62ad1b08dc405b502d501070f3efadac1409c3c1eeade5d6b66aefd8eb3 + checksum: adc993dcfd86aaf263567f26aea88fd76b69aae76380d266d68f5aaf28b9b37c8d44e8849e1479ab8febed65bbe9f84b203e702ba11e4ba6e350c8ba09b2bc43 languageName: node linkType: hard -"@metamask-institutional/extension@npm:^0.3.18": - version: 0.3.18 - resolution: "@metamask-institutional/extension@npm:0.3.18" +"@metamask-institutional/extension@npm:^0.3.20": + version: 0.3.20 + resolution: "@metamask-institutional/extension@npm:0.3.20" dependencies: "@ethereumjs/util": "npm:^8.0.5" - "@metamask-institutional/custody-controller": "npm:^0.2.22" - "@metamask-institutional/custody-keyring": "npm:^1.0.10" + "@metamask-institutional/custody-controller": "npm:^0.2.24" + "@metamask-institutional/custody-keyring": "npm:^1.0.12" "@metamask-institutional/portfolio-dashboard": "npm:^1.4.0" - "@metamask-institutional/sdk": "npm:^0.1.24" - "@metamask-institutional/transaction-update": "npm:^0.1.36" + "@metamask-institutional/sdk": "npm:^0.1.25" + "@metamask-institutional/transaction-update": "npm:^0.1.38" "@metamask-institutional/types": "npm:^1.0.4" jest-create-mock-instance: "npm:^2.0.0" jest-fetch-mock: "npm:3.0.3" lodash.clonedeep: "npm:^4.5.0" - checksum: 0adcdb89869870b00ec9227d211befcea851ff096c9e029938a81329e8dc0c1c3a1a7b208b9120fe647124223710cfb73a281e150553cf96c7f98e2cf091e7bd + checksum: bacb836f2dd20d7800ec6062185359d5c32720be1db06700d6b96b902968f7860195564bbf532a8524d2a03b15d0c031520d96dafb7aa78f5a0bf5a3ec512d0b languageName: node linkType: hard -"@metamask-institutional/institutional-features@npm:^1.2.11": - version: 1.2.11 - resolution: "@metamask-institutional/institutional-features@npm:1.2.11" +"@metamask-institutional/institutional-features@npm:^1.2.15": + version: 1.2.15 + resolution: "@metamask-institutional/institutional-features@npm:1.2.15" dependencies: - "@metamask-institutional/custody-keyring": "npm:^1.0.8" + "@metamask-institutional/custody-keyring": "npm:^1.0.12" "@metamask/obs-store": "npm:^8.0.0" - checksum: b9425bb97e310228fe9aa52376375e0205acf1500dd75360e8bb6c7949abbe7068837d5b97c5e15c814d81d115af9dc809c1e2f7aa8979437030a376b8a09164 + checksum: c67421567e85e34b9c8de8460ff8836cf0acf04d06927273c3120eb2dc8748294f6aa5690dbf587d886155842b66b6464957b9ff30d1d78a8e1931e2f119e77d languageName: node linkType: hard @@ -3861,24 +3860,24 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/rpc-allowlist@npm:^1.0.1": - version: 1.0.1 - resolution: "@metamask-institutional/rpc-allowlist@npm:1.0.1" - checksum: 6ffccae64a42b0c63696929fcfb752b75528394ce04b4e6c2145aebc6118e94ba81f204c91ad1394a0073f8cd85923a13415cbda1c4996a83efba1babeae545d +"@metamask-institutional/rpc-allowlist@npm:^1.0.2": + version: 1.0.2 + resolution: "@metamask-institutional/rpc-allowlist@npm:1.0.2" + checksum: 34939415457c5856c49c01330de976e276d5525b9ca4bdf5eca55570ccb47be77a809bf3a6eb05f817bd91c981e8614b573385e0b9ed7b218233d4b559e58b75 languageName: node linkType: hard -"@metamask-institutional/sdk@npm:^0.1.23, @metamask-institutional/sdk@npm:^0.1.24": - version: 0.1.24 - resolution: "@metamask-institutional/sdk@npm:0.1.24" +"@metamask-institutional/sdk@npm:^0.1.25": + version: 0.1.25 + resolution: "@metamask-institutional/sdk@npm:0.1.25" dependencies: "@metamask-institutional/simplecache": "npm:^1.1.0" "@metamask-institutional/types": "npm:^1.0.4" "@types/jsonwebtoken": "npm:^9.0.1" - "@types/node": "npm:^20.4.2" + "@types/node": "npm:^20.11.17" bignumber.js: "npm:^9.1.1" jsonwebtoken: "npm:^9.0.0" - checksum: 4dd9e065880179fbf770d536deb2309cd7640bc844e7bac13023cd5ffe68636ab97849c0ee827a3026115eaa5d40cf5507644ca6f34d89e7beca2e32419915ae + checksum: ac9fc0d50c7963745ae96e7ba7a845217ff3681571f2ce8729f42e5dc903d4fb1df13fcc1465b2246c551b331e30a685deccbc56e89dd7070d83bdc5c3fe912b languageName: node linkType: hard @@ -3889,17 +3888,17 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/transaction-update@npm:^0.1.32, @metamask-institutional/transaction-update@npm:^0.1.36": - version: 0.1.36 - resolution: "@metamask-institutional/transaction-update@npm:0.1.36" +"@metamask-institutional/transaction-update@npm:^0.1.38": + version: 0.1.38 + resolution: "@metamask-institutional/transaction-update@npm:0.1.38" dependencies: - "@metamask-institutional/custody-keyring": "npm:^1.0.10" - "@metamask-institutional/sdk": "npm:^0.1.24" + "@metamask-institutional/custody-keyring": "npm:^1.0.12" + "@metamask-institutional/sdk": "npm:^0.1.25" "@metamask-institutional/types": "npm:^1.0.4" - "@metamask-institutional/websocket-client": "npm:^0.1.38" + "@metamask-institutional/websocket-client": "npm:^0.1.40" "@metamask/obs-store": "npm:^8.0.0" ethereumjs-util: "npm:^7.1.5" - checksum: e35fad2e51a541679a36d3f8db11a16114a2516fed7c740ff77560a35cacf67880c8d90e57ba13fd9f179136a9fbdd108b3b58f0dc38c64a85797e8738749ad4 + checksum: 4dac477b175c870545f48e57ed6d3bb503f69bed0fb50e909ca630080c9427b44a2a44a82f16abfc289cd302ffdb82306b725faf20a4a6d2395938e657821650 languageName: node linkType: hard @@ -3910,15 +3909,15 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/websocket-client@npm:^0.1.38": - version: 0.1.38 - resolution: "@metamask-institutional/websocket-client@npm:0.1.38" +"@metamask-institutional/websocket-client@npm:^0.1.40": + version: 0.1.40 + resolution: "@metamask-institutional/websocket-client@npm:0.1.40" dependencies: - "@metamask-institutional/custody-keyring": "npm:^1.0.10" - "@metamask-institutional/sdk": "npm:^0.1.24" + "@metamask-institutional/custody-keyring": "npm:^1.0.12" + "@metamask-institutional/sdk": "npm:^0.1.25" "@metamask-institutional/types": "npm:^1.0.4" mock-socket: "npm:^9.2.1" - checksum: 6b1cb6798f58f83b128e55348fbb738d3cfb54c76d557731a8fd1bd3c1dde5604d958699d8c030ede225017fdc8977112d2397d161a5f9da6d9fced8452494e8 + checksum: c1cef7416fb40778403e4e34225a948fa6849266c12fa42ff645d208429a5f55441c8cd0be121d0b525098e3d96e3f529f059bc76480497da97e8ab7b7f6924d languageName: node linkType: hard @@ -3998,7 +3997,7 @@ __metadata: languageName: node linkType: hard -"@metamask/assets-controllers@npm:^26.0.0": +"@metamask/assets-controllers@npm:26.0.0": version: 26.0.0 resolution: "@metamask/assets-controllers@npm:26.0.0" dependencies: @@ -4040,6 +4039,48 @@ __metadata: languageName: node linkType: hard +"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A26.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-26.0.0-17c0e9432c.patch": + version: 26.0.0 + resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A26.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-26.0.0-17c0e9432c.patch::version=26.0.0&hash=cf1d54" + dependencies: + "@ethereumjs/util": "npm:^8.1.0" + "@ethersproject/address": "npm:^5.7.0" + "@ethersproject/bignumber": "npm:^5.7.0" + "@ethersproject/contracts": "npm:^5.7.0" + "@ethersproject/providers": "npm:^5.7.0" + "@metamask/abi-utils": "npm:^2.0.2" + "@metamask/accounts-controller": "npm:^11.0.0" + "@metamask/approval-controller": "npm:^5.1.3" + "@metamask/base-controller": "npm:^4.1.1" + "@metamask/contract-metadata": "npm:^2.4.0" + "@metamask/controller-utils": "npm:^8.0.4" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/keyring-controller": "npm:^13.0.0" + "@metamask/metamask-eth-abis": "npm:3.0.0" + "@metamask/network-controller": "npm:^17.2.1" + "@metamask/polling-controller": "npm:^5.0.1" + "@metamask/preferences-controller": "npm:^8.0.0" + "@metamask/rpc-errors": "npm:^6.2.1" + "@metamask/utils": "npm:^8.3.0" + "@types/bn.js": "npm:^5.1.5" + "@types/uuid": "npm:^8.3.0" + async-mutex: "npm:^0.2.6" + bn.js: "npm:^5.2.1" + cockatiel: "npm:^3.1.2" + lodash: "npm:^4.17.21" + multiformats: "npm:^9.5.2" + single-call-balance-checker-abi: "npm:^1.0.0" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/accounts-controller": ^11.0.0 + "@metamask/approval-controller": ^5.1.2 + "@metamask/keyring-controller": ^13.0.0 + "@metamask/network-controller": ^17.2.0 + "@metamask/preferences-controller": ^8.0.0 + checksum: 44e0ce87c9a2e4e161771c212460947629502ad6e72542e87a29a3de6e9c6665190396a2ae7d8206e57ad091cb80ad6634dfbfd254632a5ee6833c496f1ad0ad + languageName: node + linkType: hard + "@metamask/auto-changelog@npm:^2.1.0": version: 2.6.1 resolution: "@metamask/auto-changelog@npm:2.6.1" @@ -4054,7 +4095,7 @@ __metadata: languageName: node linkType: hard -"@metamask/base-controller@npm:^3.0.0, @metamask/base-controller@npm:^3.2.1": +"@metamask/base-controller@npm:^3.0.0": version: 3.2.3 resolution: "@metamask/base-controller@npm:3.2.3" dependencies: @@ -4103,25 +4144,10 @@ __metadata: languageName: node linkType: hard -"@metamask/contract-metadata@npm:^2.3.1, @metamask/contract-metadata@npm:^2.4.0": - version: 2.4.0 - resolution: "@metamask/contract-metadata@npm:2.4.0" - checksum: 09829751ec3239cb06b74ee338207a802e4c0e7b0184e2b2a3e43c1c043bb6cc432081b6375707d3a5cbe0870502729c2d4c6aa3dd040a2e66432275b47a32ec - languageName: node - linkType: hard - -"@metamask/controller-utils@npm:^5.0.0": - version: 5.0.2 - resolution: "@metamask/controller-utils@npm:5.0.2" - dependencies: - "@metamask/eth-query": "npm:^3.0.1" - "@metamask/utils": "npm:^8.1.0" - "@spruceid/siwe-parser": "npm:1.1.3" - eth-ens-namehash: "npm:^2.0.8" - ethereumjs-util: "npm:^7.0.10" - ethjs-unit: "npm:^0.1.6" - fast-deep-equal: "npm:^3.1.3" - checksum: fde3d4893ddd408e1b6138180d53568e27626b5884a404473a3e75d4615ba4525aa33deb21a24a438ff0928431d278efd9c084e9aca7eeaaf2a1c300691318d5 +"@metamask/contract-metadata@npm:^2.4.0, @metamask/contract-metadata@npm:^2.5.0": + version: 2.5.0 + resolution: "@metamask/contract-metadata@npm:2.5.0" + checksum: 462b3ec7b32311ff5e6eec001abf25143e67ee667c70e0b9a3dee7545e7fc245b8098856ae79fef9758ef1db4930fa1d1d7681410995520935f2885bdb9641d2 languageName: node linkType: hard @@ -4759,6 +4785,27 @@ __metadata: languageName: node linkType: hard +"@metamask/keyring-controller@npm:13.0.0": + version: 13.0.0 + resolution: "@metamask/keyring-controller@npm:13.0.0" + dependencies: + "@ethereumjs/util": "npm:^8.1.0" + "@keystonehq/metamask-airgapped-keyring": "npm:^0.13.1" + "@metamask/base-controller": "npm:^4.1.1" + "@metamask/browser-passworder": "npm:^4.3.0" + "@metamask/eth-hd-keyring": "npm:^7.0.1" + "@metamask/eth-sig-util": "npm:^7.0.1" + "@metamask/eth-simple-keyring": "npm:^6.0.1" + "@metamask/keyring-api": "npm:^3.0.0" + "@metamask/message-manager": "npm:^7.3.9" + "@metamask/utils": "npm:^8.3.0" + async-mutex: "npm:^0.2.6" + ethereumjs-wallet: "npm:^1.0.1" + immer: "npm:^9.0.6" + checksum: 477b6d7b9104370f562a862a11406013a744109f8d49e34eacedef8c9a1101e9f02f101aca3ea3af2b32b77b17535443c02d771c384ef436718b5d2fc28cbbb7 + languageName: node + linkType: hard + "@metamask/keyring-controller@npm:^12.2.0": version: 12.2.0 resolution: "@metamask/keyring-controller@npm:12.2.0" @@ -4777,45 +4824,45 @@ __metadata: languageName: node linkType: hard -"@metamask/keyring-controller@npm:^13.0.0": - version: 13.0.0 - resolution: "@metamask/keyring-controller@npm:13.0.0" +"@metamask/keyring-controller@npm:^14.0.1": + version: 14.0.1 + resolution: "@metamask/keyring-controller@npm:14.0.1" dependencies: "@ethereumjs/util": "npm:^8.1.0" "@keystonehq/metamask-airgapped-keyring": "npm:^0.13.1" - "@metamask/base-controller": "npm:^4.1.1" + "@metamask/base-controller": "npm:^5.0.1" "@metamask/browser-passworder": "npm:^4.3.0" "@metamask/eth-hd-keyring": "npm:^7.0.1" "@metamask/eth-sig-util": "npm:^7.0.1" "@metamask/eth-simple-keyring": "npm:^6.0.1" "@metamask/keyring-api": "npm:^3.0.0" - "@metamask/message-manager": "npm:^7.3.9" + "@metamask/message-manager": "npm:^8.0.1" "@metamask/utils": "npm:^8.3.0" async-mutex: "npm:^0.2.6" ethereumjs-wallet: "npm:^1.0.1" immer: "npm:^9.0.6" - checksum: 477b6d7b9104370f562a862a11406013a744109f8d49e34eacedef8c9a1101e9f02f101aca3ea3af2b32b77b17535443c02d771c384ef436718b5d2fc28cbbb7 + checksum: 060411df9e93f6a71deea47b2beb3c058530f1f99f525d7a5773a24cd76e22f21c1f8fc30810ff72f4092ec31410a337915a3dadd77d513caa5ad194a034b11f languageName: node linkType: hard -"@metamask/keyring-controller@npm:^14.0.1": - version: 14.0.1 - resolution: "@metamask/keyring-controller@npm:14.0.1" +"@metamask/keyring-controller@patch:@metamask/keyring-controller@npm%3A13.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-13.0.0-d94816a680.patch": + version: 13.0.0 + resolution: "@metamask/keyring-controller@patch:@metamask/keyring-controller@npm%3A13.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-13.0.0-d94816a680.patch::version=13.0.0&hash=be29a2" dependencies: "@ethereumjs/util": "npm:^8.1.0" "@keystonehq/metamask-airgapped-keyring": "npm:^0.13.1" - "@metamask/base-controller": "npm:^5.0.1" + "@metamask/base-controller": "npm:^4.1.1" "@metamask/browser-passworder": "npm:^4.3.0" "@metamask/eth-hd-keyring": "npm:^7.0.1" "@metamask/eth-sig-util": "npm:^7.0.1" "@metamask/eth-simple-keyring": "npm:^6.0.1" "@metamask/keyring-api": "npm:^3.0.0" - "@metamask/message-manager": "npm:^8.0.1" + "@metamask/message-manager": "npm:^7.3.9" "@metamask/utils": "npm:^8.3.0" async-mutex: "npm:^0.2.6" ethereumjs-wallet: "npm:^1.0.1" immer: "npm:^9.0.6" - checksum: 060411df9e93f6a71deea47b2beb3c058530f1f99f525d7a5773a24cd76e22f21c1f8fc30810ff72f4092ec31410a337915a3dadd77d513caa5ad194a034b11f + checksum: 4995160ba60b792b8df4694cfde6762423e72d5ea9a637e39e40b602becd42c08b67dfa8b35b26690be3af5c93d6a4ef453e1ccdc7aff5100c0af5b71c226787 languageName: node linkType: hard @@ -4870,9 +4917,9 @@ __metadata: languageName: node linkType: hard -"@metamask/message-signing-snap@npm:^0.3.1": - version: 0.3.1 - resolution: "@metamask/message-signing-snap@npm:0.3.1" +"@metamask/message-signing-snap@npm:^0.3.3": + version: 0.3.3 + resolution: "@metamask/message-signing-snap@npm:0.3.3" dependencies: "@metamask/rpc-errors": "npm:^6.2.1" "@metamask/snaps-sdk": "npm:^3.1.1" @@ -4881,7 +4928,7 @@ __metadata: "@noble/curves": "npm:^1.4.0" "@noble/hashes": "npm:^1.4.0" zod: "npm:^3.22.4" - checksum: e8971fa1facbc4a63adf37dc4dfd95ab99b69c0c70aba76d4ef3c7de272a05c5618848ceb1ddc5357daf1361d88391d280fff27497640b341d8f2de7851dc5bb + checksum: 8290f9779e826965082ef1c18189e96502a51b9ed3ade486dab91a1bcf4af150ffb04207f620ba2b98b7b268efe107d4953ab64fed0932b66b87c72f98cc944e languageName: node linkType: hard @@ -5159,7 +5206,7 @@ __metadata: languageName: node linkType: hard -"@metamask/polling-controller@npm:^5.0.1": +"@metamask/polling-controller@npm:^5.0.0, @metamask/polling-controller@npm:^5.0.1": version: 5.0.1 resolution: "@metamask/polling-controller@npm:5.0.1" dependencies: @@ -5390,20 +5437,25 @@ __metadata: languageName: node linkType: hard -"@metamask/smart-transactions-controller@npm:^6.2.2": - version: 6.2.2 - resolution: "@metamask/smart-transactions-controller@npm:6.2.2" +"@metamask/smart-transactions-controller@npm:^8.1.0": + version: 8.1.0 + resolution: "@metamask/smart-transactions-controller@npm:8.1.0" dependencies: - "@ethersproject/bignumber": "npm:^5.7.0" + "@babel/runtime": "npm:^7.24.1" + "@ethereumjs/tx": "npm:^5.2.1" + "@ethereumjs/util": "npm:^9.0.2" "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/providers": "npm:^5.7.0" - "@metamask/base-controller": "npm:^3.2.1" - "@metamask/controller-utils": "npm:^5.0.0" - "@metamask/network-controller": "npm:^15.0.0" + "@metamask/base-controller": "npm:^4.0.0" + "@metamask/controller-utils": "npm:^8.0.2" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/network-controller": "npm:^17.2.0" + "@metamask/polling-controller": "npm:^5.0.0" + "@metamask/transaction-controller": "npm:^25.1.0" bignumber.js: "npm:^9.0.1" + events: "npm:^3.3.0" fast-json-patch: "npm:^3.1.0" lodash: "npm:^4.17.21" - checksum: 48886eee59ed2db8eb9a10c23b302a00024eabc3080c58f63f498caa497a6b8d32caf4c85cf994b4fff977f9f0cc8b808efda91a08091b7fcf903eb5acc7ae0e + checksum: e125be94851c44ddca11719c25228bf0b079666d987b3fb21de233514e7add58633d70167441f9722083afc3c091b9d3966ff03fe8844f30623b403a812729c4 languageName: node linkType: hard @@ -5738,9 +5790,9 @@ __metadata: languageName: node linkType: hard -"@metamask/transaction-controller@npm:^26.0.0": - version: 26.0.0 - resolution: "@metamask/transaction-controller@npm:26.0.0" +"@metamask/transaction-controller@npm:^27.0.1": + version: 27.0.1 + resolution: "@metamask/transaction-controller@npm:27.0.1" dependencies: "@ethereumjs/common": "npm:^3.2.0" "@ethereumjs/tx": "npm:^4.2.0" @@ -5769,7 +5821,7 @@ __metadata: "@metamask/approval-controller": ^6.0.0 "@metamask/gas-fee-controller": ^15.0.0 "@metamask/network-controller": ^18.0.0 - checksum: 13af8e619d09ce1da9c2a05885fbd5aba78c5b60bce9a9d7d56ee5026421986a909a2d65b81a5b90b1d7568f6e077d0bc49fbbe55870a40b34fc0dec65eb23dd + checksum: aeb682e943120de802f1125a55b4fbd50924cf21d939d264b29562ad302aa59c242eb2ace4a473f0383a3f88bb9b26b3460075797300bbb824ceed8057c170f8 languageName: node linkType: hard @@ -9894,12 +9946,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:>=13.7.0, @types/node@npm:^20, @types/node@npm:^20.4.2": - version: 20.11.13 - resolution: "@types/node@npm:20.11.13" +"@types/node@npm:*, @types/node@npm:>=13.7.0, @types/node@npm:^20, @types/node@npm:^20.11.17": + version: 20.12.3 + resolution: "@types/node@npm:20.12.3" dependencies: undici-types: "npm:~5.26.4" - checksum: ffe143dd5e52d0d29f5bcea165b752c5355eaff8b6b933441108b540419e14c0c7e0e7912783e914125ab957e30db93f9c827e8dd437ec7e5f03def995e9a5e6 + checksum: 3f3c5c6ba118a18aa997c51cdc3c66259d69021f87475a99ed6c913a6956e22de49748e09843bf6447a7a63ae474e61945a6dbcca93e23b2359fc0b6f9914f7a languageName: node linkType: hard @@ -12646,10 +12698,10 @@ __metadata: languageName: node linkType: hard -"blo@npm:1.1.1": - version: 1.1.1 - resolution: "blo@npm:1.1.1" - checksum: 47fac139d8aaa0dc85510fb1ada9a0c54beb20a2c485bf25eb64b51176397e58aa95da5c414139c95171d080a5bf3385afffea2b176b449d24c69605833d7868 +"blo@npm:1.2.0": + version: 1.2.0 + resolution: "blo@npm:1.2.0" + checksum: 17ec61e41b201bbba8ab3c874cad94696604b6ac4029de5050c92ea5817dff6814d8642b9b2948e0d6804194437b751fa2755652201f26fa5d606913c489b757 languageName: node linkType: hard @@ -17675,16 +17727,6 @@ __metadata: languageName: node linkType: hard -"ethjs-unit@npm:^0.1.6": - version: 0.1.6 - resolution: "ethjs-unit@npm:0.1.6" - dependencies: - bn.js: "npm:4.11.6" - number-to-bn: "npm:1.7.0" - checksum: 35086cb671806992ec36d5dd43ab67e68ad7a9237e42c0e963f9081c88e40147cda86c1a258b0a3180bf2b7bc1960e607c5bcaefdb2196e0f3564acf73276189 - languageName: node - linkType: hard - "event-emitter@npm:^0.3.5, event-emitter@npm:~0.3.5": version: 0.3.5 resolution: "event-emitter@npm:0.3.5" @@ -24802,25 +24844,25 @@ __metadata: "@lavamoat/snow": "npm:^2.0.1" "@lgbot/madge": "npm:^6.2.0" "@material-ui/core": "npm:^4.11.0" - "@metamask-institutional/custody-controller": "npm:^0.2.22" - "@metamask-institutional/custody-keyring": "npm:^1.0.10" - "@metamask-institutional/extension": "npm:^0.3.18" - "@metamask-institutional/institutional-features": "npm:^1.2.11" + "@metamask-institutional/custody-controller": "npm:^0.2.24" + "@metamask-institutional/custody-keyring": "npm:^1.0.12" + "@metamask-institutional/extension": "npm:^0.3.20" + "@metamask-institutional/institutional-features": "npm:^1.2.15" "@metamask-institutional/portfolio-dashboard": "npm:^1.4.0" - "@metamask-institutional/rpc-allowlist": "npm:^1.0.1" - "@metamask-institutional/sdk": "npm:^0.1.23" - "@metamask-institutional/transaction-update": "npm:^0.1.32" + "@metamask-institutional/rpc-allowlist": "npm:^1.0.2" + "@metamask-institutional/sdk": "npm:^0.1.25" + "@metamask-institutional/transaction-update": "npm:^0.1.38" "@metamask/abi-utils": "npm:^2.0.2" "@metamask/accounts-controller": "npm:^11.0.0" "@metamask/address-book-controller": "npm:^3.1.7" "@metamask/announcement-controller": "npm:^5.0.1" "@metamask/approval-controller": "npm:^6.0.0" - "@metamask/assets-controllers": "npm:^26.0.0" + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A26.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-26.0.0-17c0e9432c.patch" "@metamask/auto-changelog": "npm:^2.1.0" "@metamask/base-controller": "npm:^4.1.0" "@metamask/browser-passworder": "npm:^4.3.0" "@metamask/build-utils": "npm:^1.0.0" - "@metamask/contract-metadata": "npm:^2.3.1" + "@metamask/contract-metadata": "npm:^2.5.0" "@metamask/controller-utils": "npm:^9.0.2" "@metamask/design-tokens": "npm:^2.1.1" "@metamask/desktop": "npm:^0.3.0" @@ -24847,11 +24889,11 @@ __metadata: "@metamask/gas-fee-controller": "npm:^14.0.0" "@metamask/jazzicon": "npm:^2.0.0" "@metamask/keyring-api": "npm:^3.0.0" - "@metamask/keyring-controller": "npm:^13.0.0" + "@metamask/keyring-controller": "patch:@metamask/keyring-controller@npm%3A13.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-13.0.0-d94816a680.patch" "@metamask/logging-controller": "npm:^2.0.2" "@metamask/logo": "npm:^3.1.2" "@metamask/message-manager": "npm:^7.3.0" - "@metamask/message-signing-snap": "npm:^0.3.1" + "@metamask/message-signing-snap": "npm:^0.3.3" "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/name-controller": "npm:^4.2.0" "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A18.0.1#~/.yarn/patches/@metamask-network-controller-npm-18.0.1-c4d0cfaecd.patch" @@ -24870,7 +24912,7 @@ __metadata: "@metamask/scure-bip39": "npm:^2.0.3" "@metamask/selected-network-controller": "npm:^9.0.0" "@metamask/signature-controller": "npm:^12.0.0" - "@metamask/smart-transactions-controller": "npm:^6.2.2" + "@metamask/smart-transactions-controller": "npm:^8.1.0" "@metamask/snaps-controllers": "npm:^6.0.4" "@metamask/snaps-execution-environments": "npm:^5.0.4" "@metamask/snaps-rpc-methods": "npm:^7.0.2" @@ -24878,7 +24920,7 @@ __metadata: "@metamask/snaps-utils": "npm:^7.0.4" "@metamask/test-bundler": "npm:^1.0.0" "@metamask/test-dapp": "npm:^8.4.0" - "@metamask/transaction-controller": "npm:^26.0.0" + "@metamask/transaction-controller": "npm:^27.0.1" "@metamask/user-operation-controller": "npm:^6.0.0" "@metamask/utils": "npm:^8.2.1" "@ngraveio/bc-ur": "npm:^1.1.12" @@ -24958,7 +25000,7 @@ __metadata: base64-js: "npm:^1.5.1" bify-module-groups: "npm:^2.0.0" bignumber.js: "npm:^4.1.0" - blo: "npm:1.1.1" + blo: "npm:1.2.0" bn.js: "npm:^5.2.1" bowser: "npm:^2.11.0" brfs: "npm:^2.0.2" @@ -33043,8 +33085,8 @@ __metadata: linkType: hard "tar@npm:^6.1.11, tar@npm:^6.1.13, tar@npm:^6.1.2": - version: 6.1.14 - resolution: "tar@npm:6.1.14" + version: 6.2.1 + resolution: "tar@npm:6.2.1" dependencies: chownr: "npm:^2.0.0" fs-minipass: "npm:^2.0.0" @@ -33052,7 +33094,7 @@ __metadata: minizlib: "npm:^2.1.1" mkdirp: "npm:^1.0.3" yallist: "npm:^4.0.0" - checksum: 5bf69e135e82b6135767654940b8b4cdb984d1e01b3a2e1cb28d27ef4a9e2db8e4b305dac8fa0c26d18d5cea00d13bf85349a19998d0ead91393d8b9939910ac + checksum: bfbfbb2861888077fc1130b84029cdc2721efb93d1d1fb80f22a7ac3a98ec6f8972f29e564103bbebf5e97be67ebc356d37fa48dbc4960600a1eb7230fbd1ea0 languageName: node linkType: hard