diff --git a/.circleci/config.yml b/.circleci/config.yml
index 1b4242570daf..3e3ccba9005e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -103,9 +103,11 @@ workflows:
test_and_release:
when:
not:
- matches:
- pattern: /^l10n_crowdin_action$/
- value: << pipeline.git.branch >>
+ or:
+ - matches:
+ pattern: /^l10n_crowdin_action$/
+ value: << pipeline.git.branch >>
+ - equal: [rerun-from-failed, << pipeline.schedule.name >>]
jobs:
- create_release_pull_request:
<<: *rc_branch_only
@@ -176,6 +178,7 @@ workflows:
- prep-build-test-mmi:
requires:
- prep-deps
+ - check-mmi-trigger
- prep-build-test-mmi-playwright:
requires:
- prep-deps
@@ -358,8 +361,7 @@ workflows:
rerun-from-failed:
when:
- condition:
- equal: ["<< pipeline.schedule.name >>", "rerun-from-failed"]
+ equal: [rerun-from-failed, << pipeline.schedule.name >>]
jobs:
- prep-deps
- rerun-workflows-from-failed:
@@ -802,6 +804,7 @@ jobs:
- run: corepack enable
- attach_workspace:
at: .
+ - run: *check-mmi-trigger
- run:
name: Build extension for testing
command: yarn build:test:mmi
@@ -1196,6 +1199,7 @@ jobs:
- run: sudo corepack enable
- attach_workspace:
at: .
+ - run: *check-mmi-trigger
- run:
name: Move test build to dist
command: mv ./dist-test-mmi ./dist
@@ -1285,6 +1289,7 @@ jobs:
- run: sudo corepack enable
- attach_workspace:
at: .
+ - run: *check-mmi-trigger
- run:
name: Move test build to dist
command: mv ./dist-test-mmi ./dist
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index 9ca9f02e2ae5..8b12876de1cd 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -8,7 +8,7 @@ name: SonarCloud
on:
workflow_run:
workflows:
- - Run tests
+ - Main
types:
- completed
diff --git a/.yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch b/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch
similarity index 51%
rename from .yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch
rename to .yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch
index 77a2e7f21cfb..5dec24d6e625 100644
--- a/.yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch
+++ b/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch
@@ -1,39 +1,3 @@
-diff --git a/dist/TokenDetectionController.cjs b/dist/TokenDetectionController.cjs
-index 8fd5efde7a3c24080f8a43f79d10300e8c271245..66f656d9a55f1154024a8c18a9fe27b4ed39a21d 100644
---- a/dist/TokenDetectionController.cjs
-+++ b/dist/TokenDetectionController.cjs
-@@ -250,17 +250,20 @@ _TokenDetectionController_intervalId = new WeakMap(), _TokenDetectionController_
- }
- });
- this.messagingSystem.subscribe('AccountsController:selectedEvmAccountChange',
-- // TODO: Either fix this lint violation or explain why it's necessary to ignore.
-- // eslint-disable-next-line @typescript-eslint/no-misused-promises
-- async (selectedAccount) => {
-- const isSelectedAccountIdChanged = __classPrivateFieldGet(this, _TokenDetectionController_selectedAccountId, "f") !== selectedAccount.id;
-- if (isSelectedAccountIdChanged) {
-- __classPrivateFieldSet(this, _TokenDetectionController_selectedAccountId, selectedAccount.id, "f");
-- await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, {
-- selectedAddress: selectedAccount.address,
-- });
-- }
-- });
-+ // TODO: Either fix this lint violation or explain why it's necessary to ignore.
-+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
-+ async (selectedAccount) => {
-+ const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
-+ const chainIds = Object.keys(networkConfigurationsByChainId);
-+ const isSelectedAccountIdChanged = __classPrivateFieldGet(this, _TokenDetectionController_selectedAccountId, "f") !== selectedAccount.id;
-+ if (isSelectedAccountIdChanged) {
-+ __classPrivateFieldSet(this, _TokenDetectionController_selectedAccountId, selectedAccount.id, "f");
-+ await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, {
-+ selectedAddress: selectedAccount.address,
-+ chainIds,
-+ });
-+ }
-+ });
- }, _TokenDetectionController_stopPolling = function _TokenDetectionController_stopPolling() {
- if (__classPrivateFieldGet(this, _TokenDetectionController_intervalId, "f")) {
- clearInterval(__classPrivateFieldGet(this, _TokenDetectionController_intervalId, "f"));
diff --git a/dist/assetsUtil.cjs b/dist/assetsUtil.cjs
index 48571b8c1b78e94d88e1837e986b5f8735ac651b..61246f51500c8cab48f18296a73629fb73454caa 100644
--- a/dist/assetsUtil.cjs
@@ -56,7 +20,7 @@ index 48571b8c1b78e94d88e1837e986b5f8735ac651b..61246f51500c8cab48f18296a73629fb
// because most cid v0s appear to be incompatible with IPFS subdomains
return {
diff --git a/dist/token-prices-service/codefi-v2.mjs b/dist/token-prices-service/codefi-v2.mjs
-index e7eaad2cfa8b233c4fd42a51f745233a1cc5c387..bf8ec7819f678c2f185d6a85d7e3ea81f055a309 100644
+index a13403446a2376d4d905a9ef733941798da89c88..3c8229f9ea40f4c1ee760a22884e1066dac82ec7 100644
--- a/dist/token-prices-service/codefi-v2.mjs
+++ b/dist/token-prices-service/codefi-v2.mjs
@@ -12,8 +12,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
@@ -65,7 +29,31 @@ index e7eaad2cfa8b233c4fd42a51f745233a1cc5c387..bf8ec7819f678c2f185d6a85d7e3ea81
import { hexToNumber } from "@metamask/utils";
-import $cockatiel from "cockatiel";
-const { circuitBreaker, ConsecutiveBreaker, ExponentialBackoff, handleAll, retry, wrap, CircuitState } = $cockatiel;
-+import { circuitBreaker, ConsecutiveBreaker, ExponentialBackoff, handleAll, retry, wrap, CircuitState } from "cockatiel"
++import { circuitBreaker, ConsecutiveBreaker, ExponentialBackoff, handleAll, retry, wrap, CircuitState } from "cockatiel";
/**
* The list of currencies that can be supplied as the `vsCurrency` parameter to
* the `/spot-prices` endpoint, in lowercase form.
+diff --git a/dist/TokensController.cjs b/dist/TokensController.cjs
+index 343b343b8300136756d96acac77aab8140efc95a..69d8e2ea84d6303a3af02bd95458ef3060c76f2b 100644
+--- a/dist/TokensController.cjs
++++ b/dist/TokensController.cjs
+@@ -270,13 +270,16 @@ class TokensController extends base_controller_1.BaseController {
+ * @param networkClientId - Optional network client ID used to determine interacting chain ID.
+ */
+ ignoreTokens(tokenAddressesToIgnore, networkClientId) {
+- const { ignoredTokens, detectedTokens, tokens } = this.state;
+- const ignoredTokensMap = {};
+- let newIgnoredTokens = [...ignoredTokens];
+ let interactingChainId;
+ if (networkClientId) {
+ interactingChainId = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId).configuration.chainId;
+ }
++ const { allTokens, allDetectedTokens, allIgnoredTokens } = this.state;
++ const ignoredTokensMap = {};
++ const ignoredTokens = allIgnoredTokens[interactingChainId ?? __classPrivateFieldGet(this, _TokensController_chainId, "f")]?.[__classPrivateFieldGet(this, _TokensController_instances, "m", _TokensController_getSelectedAddress).call(this)] || [];
++ let newIgnoredTokens = [...ignoredTokens];
++ const tokens = allTokens[interactingChainId ?? __classPrivateFieldGet(this, _TokensController_chainId, "f")]?.[__classPrivateFieldGet(this, _TokensController_instances, "m", _TokensController_getSelectedAddress).call(this)] || [];
++ const detectedTokens = allDetectedTokens[interactingChainId ?? __classPrivateFieldGet(this, _TokensController_chainId, "f")]?.[__classPrivateFieldGet(this, _TokensController_instances, "m", _TokensController_getSelectedAddress).call(this)] || [];
+ const checksummedTokenAddresses = tokenAddressesToIgnore.map((address) => {
+ const checksumAddress = (0, controller_utils_1.toChecksumHexAddress)(address);
+ ignoredTokensMap[address.toLowerCase()] = true;
diff --git a/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch b/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch
deleted file mode 100644
index 1b9e5a4ba848..000000000000
--- a/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch
+++ /dev/null
@@ -1,62 +0,0 @@
-diff --git a/dist/TokenDetectionController.cjs b/dist/TokenDetectionController.cjs
-index ab23c95d667357db365f925c4c4acce4736797f8..8fd5efde7a3c24080f8a43f79d10300e8c271245 100644
---- a/dist/TokenDetectionController.cjs
-+++ b/dist/TokenDetectionController.cjs
-@@ -204,13 +204,10 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
- // Try detecting tokens via Account API first if conditions allow
- if (supportedNetworks && chainsToDetectUsingAccountAPI.length > 0) {
- const apiResult = await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_attemptAccountAPIDetection).call(this, chainsToDetectUsingAccountAPI, addressToDetect, supportedNetworks);
-- // If API succeeds and no chains are left for RPC detection, we can return early
-- if (apiResult?.result === 'success' &&
-- chainsToDetectUsingRpc.length === 0) {
-- return;
-+ // If the account API call failed, have those chains fall back to RPC detection
-+ if (apiResult?.result === 'failed') {
-+ __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks);
- }
-- // If API fails or chainsToDetectUsingRpc still has items, add chains to RPC detection
-- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks);
- }
- // Proceed with RPC detection if there are chains remaining in chainsToDetectUsingRpc
- if (chainsToDetectUsingRpc.length > 0) {
-@@ -446,8 +443,7 @@ async function _TokenDetectionController_addDetectedTokensViaAPI({ selectedAddre
- const tokenBalancesByChain = await __classPrivateFieldGet(this, _TokenDetectionController_accountsAPI, "f")
- .getMultiNetworksBalances(selectedAddress, chainIds, supportedNetworks)
- .catch(() => null);
-- if (!tokenBalancesByChain ||
-- Object.keys(tokenBalancesByChain).length === 0) {
-+ if (tokenBalancesByChain === null) {
- return { result: 'failed' };
- }
- // Process each chain ID individually
-diff --git a/dist/TokenDetectionController.mjs b/dist/TokenDetectionController.mjs
-index f75eb5c2c74f2a9d15a79760985111171dc938e1..ebc30bb915cc39dabf49f9e0da84a7948ae1ed48 100644
---- a/dist/TokenDetectionController.mjs
-+++ b/dist/TokenDetectionController.mjs
-@@ -205,13 +205,10 @@ export class TokenDetectionController extends StaticIntervalPollingController()
- // Try detecting tokens via Account API first if conditions allow
- if (supportedNetworks && chainsToDetectUsingAccountAPI.length > 0) {
- const apiResult = await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_attemptAccountAPIDetection).call(this, chainsToDetectUsingAccountAPI, addressToDetect, supportedNetworks);
-- // If API succeeds and no chains are left for RPC detection, we can return early
-- if (apiResult?.result === 'success' &&
-- chainsToDetectUsingRpc.length === 0) {
-- return;
-+ // If the account API call failed, have those chains fall back to RPC detection
-+ if (apiResult?.result === 'failed') {
-+ __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks);
- }
-- // If API fails or chainsToDetectUsingRpc still has items, add chains to RPC detection
-- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks);
- }
- // Proceed with RPC detection if there are chains remaining in chainsToDetectUsingRpc
- if (chainsToDetectUsingRpc.length > 0) {
-@@ -446,8 +443,7 @@ async function _TokenDetectionController_addDetectedTokensViaAPI({ selectedAddre
- const tokenBalancesByChain = await __classPrivateFieldGet(this, _TokenDetectionController_accountsAPI, "f")
- .getMultiNetworksBalances(selectedAddress, chainIds, supportedNetworks)
- .catch(() => null);
-- if (!tokenBalancesByChain ||
-- Object.keys(tokenBalancesByChain).length === 0) {
-+ if (tokenBalancesByChain === null) {
- return { result: 'failed' };
- }
- // Process each chain ID individually
diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json
index 172fd05a5a73..0c8ba19030f2 100644
--- a/app/_locales/de/messages.json
+++ b/app/_locales/de/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "Wir konnten das Gas nicht schätzen. Es könnte einen Fehler im Contract geben und diese Transaktion könnte fehlschlagen."
},
- "simulationsSettingDescription": {
- "message": "Schalten Sie diese Option ein, um die Saldoänderungen von Transaktionen zu schätzen, bevor Sie diese bestätigen. Dies ist keine Garantie für das endgültige Ergebnis Ihrer Transaktionen. $1"
- },
"simulationsSettingSubHeader": {
"message": "Geschätzte Saldoänderungen"
},
diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json
index 132265ee4167..31cd6809080b 100644
--- a/app/_locales/el/messages.json
+++ b/app/_locales/el/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "Δεν ήμασταν σε θέση να εκτιμήσουμε το τέλος συναλλαγής. Μπορεί να υπάρχει σφάλμα στο συμβόλαιο και η συναλλαγή αυτή να αποτύχει."
},
- "simulationsSettingDescription": {
- "message": "Ενεργοποιήστε το για να εκτιμήσετε τις αλλαγές στο υπόλοιπο των συναλλαγών πριν τις επιβεβαιώσετε. Αυτό δεν εγγυάται το τελικό αποτέλεσμα στις συναλλαγές σας. $1"
- },
"simulationsSettingSubHeader": {
"message": "Εκτίμηση μεταβολών υπολοίπου"
},
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index f51c9708fc20..e9e9cc807ccd 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -419,6 +419,9 @@
"alertMessageChangeInSimulationResults": {
"message": "Estimated changes for this transaction have been updated. Review them closely before proceeding."
},
+ "alertMessageFirstTimeInteraction": {
+ "message": "You're interacting with this address for the first time. Make sure that it's correct before you continue."
+ },
"alertMessageGasEstimateFailed": {
"message": "We’re unable to provide an accurate fee and this estimate might be high. We suggest you to input a custom gas limit, but there’s a risk the transaction will still fail."
},
@@ -461,6 +464,9 @@
"alertReasonChangeInSimulationResults": {
"message": "Results have changed"
},
+ "alertReasonFirstTimeInteraction": {
+ "message": "1st interaction"
+ },
"alertReasonGasEstimateFailed": {
"message": "Inaccurate fee"
},
@@ -488,6 +494,9 @@
"alertReasonWrongAccount": {
"message": "Wrong account"
},
+ "alertSelectedAccountWarning": {
+ "message": "This request is for a different account than the one selected in your wallet. To use another account, connect it to the site."
+ },
"alerts": {
"message": "Alerts"
},
@@ -863,8 +872,8 @@
"bridgeFrom": {
"message": "Bridge from"
},
- "bridgeOverallCost": {
- "message": "Overall cost"
+ "bridgeNetCost": {
+ "message": "Net cost"
},
"bridgeSelectNetwork": {
"message": "Select network"
@@ -873,7 +882,7 @@
"message": "Select token and amount"
},
"bridgeTimingMinutes": {
- "message": "$1 minutes",
+ "message": "$1 min",
"description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase"
},
"bridgeTimingTooltipText": {
@@ -3196,6 +3205,9 @@
"networkNamePolygon": {
"message": "Polygon"
},
+ "networkNameSolana": {
+ "message": "Solana"
+ },
"networkNameTestnet": {
"message": "Testnet"
},
@@ -4373,6 +4385,13 @@
"quoteRate": {
"message": "Quote rate"
},
+ "quotedNetworkFee": { "message": "$1 network fee" },
+ "quotedReceiveAmount": {
+ "message": "$1 receive amount"
+ },
+ "quotedReceivingAmount": {
+ "message": "$1 receiving"
+ },
"rank": {
"message": "Rank"
},
@@ -4734,7 +4753,7 @@
"message": "Security alerts"
},
"securityAlertsDescription": {
- "message": "This feature alerts you to malicious activity by actively reviewing transaction and signature requests. $1",
+ "message": "This feature alerts you to malicious or unusual activity by actively reviewing transaction and signature requests. $1",
"description": "Link to learn more about security alerts"
},
"securityAndPrivacy": {
@@ -4856,6 +4875,9 @@
"selectType": {
"message": "Select Type"
},
+ "selectedAccountMismatch": {
+ "message": "Different account selected"
+ },
"selectingAllWillAllow": {
"message": "Selecting all will allow this site to view all of your current accounts. Make sure you trust this site."
},
@@ -5007,6 +5029,12 @@
"signatureRequestGuidance": {
"message": "Only sign this message if you fully understand the content and trust the requesting site."
},
+ "signature_decoding_bid_nft_tooltip": {
+ "message": "The NFT will be reflected in your wallet, when the bid is accepted."
+ },
+ "signature_decoding_list_nft_tooltip": {
+ "message": "Expect changes only if someone buys your NFTs."
+ },
"signed": {
"message": "Signed"
},
@@ -5066,7 +5094,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. $1"
+ "message": "Turn this on to estimate balance changes of transactions and signatures before you confirm them. This doesn't guarantee their final outcome. $1"
},
"simulationsSettingSubHeader": {
"message": "Estimate balance changes"
diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json
index 746c099ae75e..f2afe012534a 100644
--- a/app/_locales/es/messages.json
+++ b/app/_locales/es/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "No pudimos estimar el gas. Podría haber un error en el contrato y esta transacción podría fallar."
},
- "simulationsSettingDescription": {
- "message": "Active esta opción para estimar los cambios de saldo de las transacciones antes de confirmarlas. Esto no garantiza el resultado final de sus transacciones. $1"
- },
"simulationsSettingSubHeader": {
"message": "Estimar cambios de saldo"
},
diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json
index bcfa71f5661b..99c3a6da2333 100644
--- a/app/_locales/fr/messages.json
+++ b/app/_locales/fr/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "Nous n’avons pas pu estimer le prix de carburant. Par conséquent, il se peut qu’il y ait une erreur dans le contrat et que cette transaction échoue."
},
- "simulationsSettingDescription": {
- "message": "Activez cette option pour estimer les changements de solde des transactions avant de les confirmer. Cela ne garantit pas le résultat final de vos transactions. $1"
- },
"simulationsSettingSubHeader": {
"message": "Estimer les changements de solde"
},
diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json
index f5484e624348..92d8d0bc7fa8 100644
--- a/app/_locales/hi/messages.json
+++ b/app/_locales/hi/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "हम गैस का अनुमान नहीं लगा पाए। कॉन्ट्रैक्ट में कोई गड़बड़ी हो सकती है और यह ट्रांसेक्शन विफल हो सकता है।"
},
- "simulationsSettingDescription": {
- "message": "ट्रांसेक्शन को कन्फर्म करने से पहले बैलेंस अमाउंट में बदलाव का अनुमान लगाने के लिए इसे चालू करें। यह आपके ट्रांसेक्शन के फाइनल आउटकम की गारंटी नहीं देता है।$1"
- },
"simulationsSettingSubHeader": {
"message": "बैलेंस अमाउंट में बदलावों का अनुमान लगाएं"
},
diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json
index e7c719318f69..a474fc1afa1a 100644
--- a/app/_locales/id/messages.json
+++ b/app/_locales/id/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "Kami tidak dapat memperkirakan gas. Tampaknya ada kesalahan dalam kontrak dan transaksi ini berpotensi gagal."
},
- "simulationsSettingDescription": {
- "message": "Aktifkan untuk mengestimasikan perubahan saldo transaksi sebelum Anda mengonfirmasikannya. Ini tidak menjamin hasil akhir transaksi Anda. $1"
- },
"simulationsSettingSubHeader": {
"message": "Estimasikan perubahan saldo"
},
diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json
index a3e917d46600..80982f5b4144 100644
--- a/app/_locales/ja/messages.json
+++ b/app/_locales/ja/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "ガス代を見積もることができませんでした。コントラクトにエラーがある可能性があり、このトランザクションは失敗するかもしれません。"
},
- "simulationsSettingDescription": {
- "message": "トランザクションを確定する前に残高の増減を予測するには、この機能をオンにします。これはトランザクションの最終的な結果を保証するものではありません。$1"
- },
"simulationsSettingSubHeader": {
"message": "予測される残高の増減"
},
diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json
index e1eeda4487a6..5fc7deb6a9ef 100644
--- a/app/_locales/ko/messages.json
+++ b/app/_locales/ko/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "가스를 추정할 수 없었습니다. 계약에 오류가 있을 수 있으며 이 트랜잭션이 실패할 수 있습니다."
},
- "simulationsSettingDescription": {
- "message": "이 기능을 켜면 트랜잭션을 확정하기 전에 트랜잭션의 잔액 변동을 추정할 수 있습니다. 이 기능이 트랜잭션의 최종 결과를 보장하지는 않습니다. $1"
- },
"simulationsSettingSubHeader": {
"message": "예상 잔액 변동"
},
diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json
index 1e39077afa51..18e1c97fb129 100644
--- a/app/_locales/pt/messages.json
+++ b/app/_locales/pt/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "Não conseguimos estimar o preço do gás. Pode haver um erro no contrato, e essa transação poderá falhar."
},
- "simulationsSettingDescription": {
- "message": "Ative para estimar as alterações de saldo das transações antes de confirmá-las. Isso não garante o resultado das suas transações. $1"
- },
"simulationsSettingSubHeader": {
"message": "Estimar alterações de saldo"
},
diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json
index f68ee11792a1..43e617a0aecd 100644
--- a/app/_locales/ru/messages.json
+++ b/app/_locales/ru/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "Мы не смогли оценить размер платы за газ. В контракте может быть ошибка, и эта транзакция может завершиться неудачно."
},
- "simulationsSettingDescription": {
- "message": "Включите эту опцию, чтобы спрогнозировать изменения баланса транзакций перед их подтверждением. Это не гарантирует окончательный результат ваших транзакций. $1"
- },
"simulationsSettingSubHeader": {
"message": "Спрогнозировать изменения баланса"
},
diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json
index 4f6e3098171d..5bc72667356e 100644
--- a/app/_locales/tl/messages.json
+++ b/app/_locales/tl/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "Hindi namin nagawang tantyahin ang gas. Maaaring may error sa kontrata at maaaring mabigo ang transaksyong ito."
},
- "simulationsSettingDescription": {
- "message": "I-on ito para gumawa ng pagtataya sa mga pagbabago ng balanse ng mga transaksyon bago mo kumpirmahin ang mga iyon. Hindi nito iginagarantiya ang panghuling resulta ng iyong mga transaksyon. $1"
- },
"simulationsSettingSubHeader": {
"message": "Tinatayang mga pagbabago sa balanse"
},
diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json
index 771089050317..a71a64576142 100644
--- a/app/_locales/tr/messages.json
+++ b/app/_locales/tr/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "Gaz tahmini yapamadık. Sözleşmede bir hata olabilir ve bu işlem başarısız olabilir."
},
- "simulationsSettingDescription": {
- "message": "Onaylamadan önce işlemlerdeki bakiye değişikliklerini tahmin etmek için bunu açın. İşlemlerinizin nihai sonucunu garanti etmez. $1"
- },
"simulationsSettingSubHeader": {
"message": "Bakiye değişikliklerini tahmin edin"
},
diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json
index 1983185816c0..5fb6ee43bfb9 100644
--- a/app/_locales/vi/messages.json
+++ b/app/_locales/vi/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "Chúng tôi không thể ước tính gas. Có thể đã xảy ra lỗi trong hợp đồng và giao dịch này có thể thất bại."
},
- "simulationsSettingDescription": {
- "message": "Bật tính năng này để ước tính thay đổi số dư của các giao dịch trước khi bạn xác nhận. Điều này không đảm bảo cho kết quả cuối cùng của giao dịch. $1"
- },
"simulationsSettingSubHeader": {
"message": "Ước tính thay đổi số dư"
},
diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json
index b6c050f6b264..ca1d0e3f7b48 100644
--- a/app/_locales/zh_CN/messages.json
+++ b/app/_locales/zh_CN/messages.json
@@ -4920,9 +4920,6 @@
"simulationErrorMessageV2": {
"message": "我们无法估算燃料。合约中可能存在错误,这笔交易可能会失败。"
},
- "simulationsSettingDescription": {
- "message": "开启此选项,以便在确认交易之前估计交易余额的变化。这并不能保证交易的最终结果。$1"
- },
"simulationsSettingSubHeader": {
"message": "预计余额变化"
},
diff --git a/app/scripts/constants/sentry-state.ts b/app/scripts/constants/sentry-state.ts
index d0fbe7bcb085..289bc0a0d29c 100644
--- a/app/scripts/constants/sentry-state.ts
+++ b/app/scripts/constants/sentry-state.ts
@@ -122,6 +122,11 @@ export const SENTRY_BACKGROUND_STATE = {
quotesRefreshCount: true,
},
},
+ BridgeStatusController: {
+ bridgeStatusState: {
+ txHistory: false,
+ },
+ },
CronjobController: {
jobs: false,
},
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index 7c427a1821c5..a38ac9fe5833 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -1,8 +1,10 @@
import { getIsBrowserPrerenderBroken } from '../../shared/modules/browser-runtime.utils';
import shouldInjectProvider from '../../shared/modules/provider-injection';
import {
+ destroyStreams,
initStreams,
onDisconnectDestroyStreams,
+ setupExtensionStreams,
} from './streams/provider-stream';
import {
isDetectedPhishingSite,
@@ -33,6 +35,20 @@ const start = () => {
);
});
}
+
+ window.addEventListener('pageshow', (event) => {
+ if (event.persisted) {
+ console.warn('BFCached page has become active. Restoring the streams.');
+ setupExtensionStreams();
+ }
+ });
+
+ window.addEventListener('pagehide', (event) => {
+ if (event.persisted) {
+ console.warn('Page may become BFCached. Destroying the streams.');
+ destroyStreams();
+ }
+ });
}
};
diff --git a/app/scripts/controllers/bridge-status/__snapshots__/bridge-status-controller.test.ts.snap b/app/scripts/controllers/bridge-status/__snapshots__/bridge-status-controller.test.ts.snap
new file mode 100644
index 000000000000..ebd3a938822e
--- /dev/null
+++ b/app/scripts/controllers/bridge-status/__snapshots__/bridge-status-controller.test.ts.snap
@@ -0,0 +1,213 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`BridgeStatusController constructor rehydrates the tx history state 1`] = `
+{
+ "0xsrcTxHash1": {
+ "account": "0xaccount1",
+ "estimatedProcessingTimeInSeconds": 15,
+ "initialDestAssetBalance": undefined,
+ "pricingData": undefined,
+ "quote": {
+ "bridgeId": "lifi",
+ "bridges": [
+ "across",
+ ],
+ "destAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 10,
+ "coinKey": "ETH",
+ "decimals": 18,
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "name": "ETH",
+ "priceUSD": "2478.63",
+ "symbol": "ETH",
+ },
+ "destChainId": 10,
+ "destTokenAmount": "990654755978612",
+ "feeData": {
+ "metabridge": {
+ "amount": "8750000000000",
+ "asset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "coinKey": "ETH",
+ "decimals": 18,
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "name": "ETH",
+ "priceUSD": "2478.7",
+ "symbol": "ETH",
+ },
+ },
+ },
+ "requestId": "197c402f-cb96-4096-9f8c-54aed84ca776",
+ "srcAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "coinKey": "ETH",
+ "decimals": 18,
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "name": "ETH",
+ "priceUSD": "2478.7",
+ "symbol": "ETH",
+ },
+ "srcChainId": 42161,
+ "srcTokenAmount": "991250000000000",
+ "steps": [
+ {
+ "action": "bridge",
+ "destAmount": "990654755978612",
+ "destAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 10,
+ "coinKey": "ETH",
+ "decimals": 18,
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "name": "ETH",
+ "priceUSD": "2478.63",
+ "symbol": "ETH",
+ },
+ "destChainId": 10,
+ "protocol": {
+ "displayName": "Across",
+ "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png",
+ "name": "across",
+ },
+ "srcAmount": "991250000000000",
+ "srcAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "coinKey": "ETH",
+ "decimals": 18,
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "name": "ETH",
+ "priceUSD": "2478.7",
+ "symbol": "ETH",
+ },
+ "srcChainId": 42161,
+ },
+ ],
+ },
+ "slippagePercentage": 0,
+ "startTime": 1729964825189,
+ "status": {
+ "srcChain": {
+ "chainId": 42161,
+ "txHash": "0xsrcTxHash1",
+ },
+ "status": "PENDING",
+ },
+ "targetContractAddress": "0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC",
+ },
+}
+`;
+
+exports[`BridgeStatusController startPollingForBridgeTxStatus sets the inital tx history state 1`] = `
+{
+ "0xsrcTxHash1": {
+ "account": "0xaccount1",
+ "estimatedProcessingTimeInSeconds": 15,
+ "initialDestAssetBalance": undefined,
+ "pricingData": undefined,
+ "quote": {
+ "bridgeId": "lifi",
+ "bridges": [
+ "across",
+ ],
+ "destAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 10,
+ "coinKey": "ETH",
+ "decimals": 18,
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "name": "ETH",
+ "priceUSD": "2478.63",
+ "symbol": "ETH",
+ },
+ "destChainId": 10,
+ "destTokenAmount": "990654755978612",
+ "feeData": {
+ "metabridge": {
+ "amount": "8750000000000",
+ "asset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "coinKey": "ETH",
+ "decimals": 18,
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "name": "ETH",
+ "priceUSD": "2478.7",
+ "symbol": "ETH",
+ },
+ },
+ },
+ "requestId": "197c402f-cb96-4096-9f8c-54aed84ca776",
+ "srcAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "coinKey": "ETH",
+ "decimals": 18,
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "name": "ETH",
+ "priceUSD": "2478.7",
+ "symbol": "ETH",
+ },
+ "srcChainId": 42161,
+ "srcTokenAmount": "991250000000000",
+ "steps": [
+ {
+ "action": "bridge",
+ "destAmount": "990654755978612",
+ "destAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 10,
+ "coinKey": "ETH",
+ "decimals": 18,
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "name": "ETH",
+ "priceUSD": "2478.63",
+ "symbol": "ETH",
+ },
+ "destChainId": 10,
+ "protocol": {
+ "displayName": "Across",
+ "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png",
+ "name": "across",
+ },
+ "srcAmount": "991250000000000",
+ "srcAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "coinKey": "ETH",
+ "decimals": 18,
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "name": "ETH",
+ "priceUSD": "2478.7",
+ "symbol": "ETH",
+ },
+ "srcChainId": 42161,
+ },
+ ],
+ },
+ "slippagePercentage": 0,
+ "startTime": 1729964825189,
+ "status": {
+ "srcChain": {
+ "chainId": 42161,
+ "txHash": "0xsrcTxHash1",
+ },
+ "status": "PENDING",
+ },
+ "targetContractAddress": "0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC",
+ },
+}
+`;
diff --git a/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts b/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts
new file mode 100644
index 000000000000..3890f27f7f65
--- /dev/null
+++ b/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts
@@ -0,0 +1,739 @@
+import { flushPromises } from '../../../../test/lib/timer-helpers';
+import { Numeric } from '../../../../shared/modules/Numeric';
+import {
+ StatusTypes,
+ ActionTypes,
+ BridgeId,
+} from '../../../../shared/types/bridge-status';
+import BridgeStatusController from './bridge-status-controller';
+import { BridgeStatusControllerMessenger } from './types';
+import { DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE } from './constants';
+import * as bridgeStatusUtils from './utils';
+
+const EMPTY_INIT_STATE = {
+ bridgeStatusState: DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE,
+};
+
+const getMockQuote = ({ srcChainId = 42161, destChainId = 10 } = {}) => ({
+ requestId: '197c402f-cb96-4096-9f8c-54aed84ca776',
+ srcChainId,
+ srcTokenAmount: '991250000000000',
+ srcAsset: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: srcChainId,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2478.7',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ destChainId,
+ destTokenAmount: '990654755978612',
+ destAsset: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: destChainId,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2478.63',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ feeData: {
+ metabridge: {
+ amount: '8750000000000',
+ asset: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: srcChainId,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2478.7',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ },
+ },
+ bridgeId: 'lifi',
+ bridges: ['across'],
+ steps: [
+ {
+ action: 'bridge' as ActionTypes,
+ srcChainId,
+ destChainId,
+ protocol: {
+ name: 'across',
+ displayName: 'Across',
+ icon: 'https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png',
+ },
+ srcAsset: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: srcChainId,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2478.7',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ destAsset: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: destChainId,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2478.63',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ srcAmount: '991250000000000',
+ destAmount: '990654755978612',
+ },
+ ],
+});
+
+const getMockStartPollingForBridgeTxStatusArgs = ({
+ srcTxHash = '0xsrcTxHash1',
+ account = '0xaccount1',
+ srcChainId = 42161,
+ destChainId = 10,
+} = {}) => ({
+ statusRequest: {
+ bridgeId: 'lifi',
+ srcTxHash,
+ bridge: 'across',
+ srcChainId,
+ destChainId,
+ quote: getMockQuote({ srcChainId, destChainId }),
+ refuel: false,
+ },
+ quoteResponse: {
+ quote: getMockQuote({ srcChainId, destChainId }),
+ trade: {
+ chainId: srcChainId,
+ to: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC',
+ from: account,
+ value: '0x038d7ea4c68000',
+ data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038589602234000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000007f544a44c0000000000000000000000000056ca675c3633cc16bd6849e2b431d4e8de5e23bf000000000000000000000000000000000000000000000000000000000000006c5a39b10a4f4f0747826140d2c5fe6ef47965741f6f7a4734bf784bf3ae3f24520000000a000222266cc2dca0671d2a17ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b7100000000000000000000000000000000000000009ce3c510b3f58edc8d53ae708056e30926f62d0b42d5c9b61c391bb4e8a2c1917f8ed995169ffad0d79af2590303e83c57e15a9e0b248679849556c2e03a1c811b',
+ gasLimit: 282915,
+ },
+ approval: null,
+ estimatedProcessingTimeInSeconds: 15,
+ },
+ startTime: 1729964825189,
+ slippagePercentage: 0,
+ pricingData: undefined,
+ initialDestAssetBalance: undefined,
+ targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC',
+});
+
+const MockStatusResponse = {
+ getPending: ({
+ srcTxHash = '0xsrcTxHash1',
+ srcChainId = 42161,
+ destChainId = 10,
+ } = {}) => ({
+ status: 'PENDING' as StatusTypes,
+ srcChain: {
+ chainId: srcChainId,
+ txHash: srcTxHash,
+ amount: '991250000000000',
+ token: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: srcChainId,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2518.47',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ },
+ destChain: {
+ chainId: destChainId,
+ token: {},
+ },
+ }),
+ getComplete: ({
+ srcTxHash = '0xsrcTxHash1',
+ destTxHash = '0xdestTxHash1',
+ srcChainId = 42161,
+ destChainId = 10,
+ } = {}) => ({
+ status: 'COMPLETE' as StatusTypes,
+ isExpectedToken: true,
+ bridge: 'across' as BridgeId,
+ srcChain: {
+ chainId: srcChainId,
+ txHash: srcTxHash,
+ amount: '991250000000000',
+ token: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: srcChainId,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2478.7',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ },
+ destChain: {
+ chainId: destChainId,
+ txHash: destTxHash,
+ amount: '990654755978611',
+ token: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: destChainId,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2478.63',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ },
+ }),
+};
+
+const MockTxHistory = {
+ getInit: ({
+ srcTxHash = '0xsrcTxHash1',
+ account = '0xaccount1',
+ srcChainId = 42161,
+ destChainId = 10,
+ } = {}) => ({
+ [srcTxHash]: {
+ quote: getMockQuote({ srcChainId, destChainId }),
+ startTime: 1729964825189,
+ estimatedProcessingTimeInSeconds: 15,
+ slippagePercentage: 0,
+ account,
+ targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC',
+ },
+ }),
+ getPending: ({
+ srcTxHash = '0xsrcTxHash1',
+ account = '0xaccount1',
+ srcChainId = 42161,
+ destChainId = 10,
+ } = {}) => ({
+ [srcTxHash]: {
+ quote: getMockQuote({ srcChainId, destChainId }),
+ startTime: 1729964825189,
+ estimatedProcessingTimeInSeconds: 15,
+ slippagePercentage: 0,
+ account,
+ status: MockStatusResponse.getPending({
+ srcTxHash,
+ srcChainId,
+ }),
+ targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC',
+ },
+ }),
+ getComplete: ({
+ srcTxHash = '0xsrcTxHash1',
+ account = '0xaccount1',
+ srcChainId = 42161,
+ destChainId = 10,
+ } = {}) => ({
+ [srcTxHash]: {
+ quote: getMockQuote({ srcChainId, destChainId }),
+ startTime: 1729964825189,
+ estimatedProcessingTimeInSeconds: 15,
+ slippagePercentage: 0,
+ account,
+ status: MockStatusResponse.getComplete({ srcTxHash }),
+ targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC',
+ },
+ }),
+};
+
+const getMessengerMock = ({
+ account = '0xaccount1',
+ srcChainId = 42161,
+} = {}) =>
+ ({
+ call: jest.fn((method: string) => {
+ if (method === 'AccountsController:getSelectedAccount') {
+ return { address: account };
+ } else if (method === 'NetworkController:findNetworkClientIdByChainId') {
+ return 'networkClientId';
+ } else if (method === 'NetworkController:getState') {
+ return { selectedNetworkClientId: 'networkClientId' };
+ } else if (method === 'NetworkController:getNetworkClientById') {
+ return {
+ configuration: {
+ chainId: new Numeric(srcChainId, 10).toPrefixedHexString(),
+ },
+ };
+ }
+ return null;
+ }),
+ publish: jest.fn(),
+ registerActionHandler: jest.fn(),
+ registerInitialEventPayload: jest.fn(),
+ } as unknown as jest.Mocked);
+
+const executePollingWithPendingStatus = async () => {
+ // Setup
+ jest.useFakeTimers();
+ const bridgeStatusController = new BridgeStatusController({
+ messenger: getMessengerMock(),
+ });
+ const startPollingByNetworkClientIdSpy = jest.spyOn(
+ bridgeStatusController,
+ 'startPollingByNetworkClientId',
+ );
+ const fetchBridgeTxStatusSpy = jest.spyOn(
+ bridgeStatusUtils,
+ 'fetchBridgeTxStatus',
+ );
+
+ // Execution
+ await bridgeStatusController.startPollingForBridgeTxStatus(
+ getMockStartPollingForBridgeTxStatusArgs(),
+ );
+ fetchBridgeTxStatusSpy.mockImplementationOnce(async () => {
+ return MockStatusResponse.getPending();
+ });
+ jest.advanceTimersByTime(10000);
+ await flushPromises();
+
+ return {
+ bridgeStatusController,
+ startPollingByNetworkClientIdSpy,
+ fetchBridgeTxStatusSpy,
+ };
+};
+
+describe('BridgeStatusController', () => {
+ describe('constructor', () => {
+ it('should setup correctly', () => {
+ const bridgeStatusController = new BridgeStatusController({
+ messenger: getMessengerMock(),
+ });
+ expect(bridgeStatusController.state).toEqual(EMPTY_INIT_STATE);
+ });
+ it('rehydrates the tx history state', async () => {
+ // Setup
+ const bridgeStatusController = new BridgeStatusController({
+ messenger: getMessengerMock(),
+ state: {
+ bridgeStatusState: {
+ txHistory: MockTxHistory.getPending(),
+ },
+ },
+ });
+
+ // Execution
+ await bridgeStatusController.startPollingForBridgeTxStatus(
+ getMockStartPollingForBridgeTxStatusArgs(),
+ );
+
+ // Assertion
+ expect(
+ bridgeStatusController.state.bridgeStatusState.txHistory,
+ ).toMatchSnapshot();
+ });
+ it('restarts polling for history items that are not complete', async () => {
+ // Setup
+ jest.useFakeTimers();
+ const fetchBridgeTxStatusSpy = jest.spyOn(
+ bridgeStatusUtils,
+ 'fetchBridgeTxStatus',
+ );
+
+ // Execution
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const bridgeStatusController = new BridgeStatusController({
+ messenger: getMessengerMock(),
+ state: {
+ bridgeStatusState: {
+ txHistory: MockTxHistory.getPending(),
+ },
+ },
+ });
+ jest.advanceTimersByTime(10000);
+ await flushPromises();
+
+ // Assertions
+ expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1);
+ });
+ });
+ describe('startPollingForBridgeTxStatus', () => {
+ it('sets the inital tx history state', async () => {
+ // Setup
+ const bridgeStatusController = new BridgeStatusController({
+ messenger: getMessengerMock(),
+ });
+
+ // Execution
+ await bridgeStatusController.startPollingForBridgeTxStatus(
+ getMockStartPollingForBridgeTxStatusArgs(),
+ );
+
+ // Assertion
+ expect(
+ bridgeStatusController.state.bridgeStatusState.txHistory,
+ ).toMatchSnapshot();
+ });
+ it('starts polling and updates the tx history when the status response is received', async () => {
+ const {
+ bridgeStatusController,
+ startPollingByNetworkClientIdSpy,
+ fetchBridgeTxStatusSpy,
+ } = await executePollingWithPendingStatus();
+
+ // Assertions
+ expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledTimes(1);
+ expect(fetchBridgeTxStatusSpy).toHaveBeenCalled();
+ expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual(
+ MockTxHistory.getPending(),
+ );
+ });
+ it('stops polling when the status response is complete', async () => {
+ // Setup
+ jest.useFakeTimers();
+ const bridgeStatusController = new BridgeStatusController({
+ messenger: getMessengerMock(),
+ });
+ const fetchBridgeTxStatusSpy = jest.spyOn(
+ bridgeStatusUtils,
+ 'fetchBridgeTxStatus',
+ );
+ const stopPollingByNetworkClientIdSpy = jest.spyOn(
+ bridgeStatusController,
+ 'stopPollingByPollingToken',
+ );
+
+ // Execution
+ await bridgeStatusController.startPollingForBridgeTxStatus(
+ getMockStartPollingForBridgeTxStatusArgs(),
+ );
+ fetchBridgeTxStatusSpy.mockImplementationOnce(async () => {
+ return MockStatusResponse.getComplete();
+ });
+ jest.advanceTimersByTime(10000);
+ await flushPromises();
+
+ // Assertions
+ expect(stopPollingByNetworkClientIdSpy).toHaveBeenCalledTimes(1);
+ expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual(
+ MockTxHistory.getComplete(),
+ );
+ });
+ });
+ describe('resetState', () => {
+ it('resets the state', async () => {
+ const { bridgeStatusController } =
+ await executePollingWithPendingStatus();
+
+ expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual(
+ MockTxHistory.getPending(),
+ );
+ bridgeStatusController.resetState();
+ expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual(
+ EMPTY_INIT_STATE.bridgeStatusState.txHistory,
+ );
+ });
+ });
+ describe('wipeBridgeStatus', () => {
+ it('wipes the bridge status for the given address', async () => {
+ // Setup
+ jest.useFakeTimers();
+
+ let getSelectedAccountCalledTimes = 0;
+ const messengerMock = {
+ call: jest.fn((method: string) => {
+ if (method === 'AccountsController:getSelectedAccount') {
+ let account;
+ if (getSelectedAccountCalledTimes === 0) {
+ account = '0xaccount1';
+ } else {
+ account = '0xaccount2';
+ }
+ getSelectedAccountCalledTimes += 1;
+ return { address: account };
+ } else if (
+ method === 'NetworkController:findNetworkClientIdByChainId'
+ ) {
+ return 'networkClientId';
+ } else if (method === 'NetworkController:getState') {
+ return { selectedNetworkClientId: 'networkClientId' };
+ } else if (method === 'NetworkController:getNetworkClientById') {
+ return {
+ configuration: {
+ chainId: new Numeric(42161, 10).toPrefixedHexString(),
+ },
+ };
+ }
+ return null;
+ }),
+ publish: jest.fn(),
+ registerActionHandler: jest.fn(),
+ registerInitialEventPayload: jest.fn(),
+ } as unknown as jest.Mocked;
+ const bridgeStatusController = new BridgeStatusController({
+ messenger: messengerMock,
+ });
+ const fetchBridgeTxStatusSpy = jest
+ .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus')
+ .mockImplementationOnce(async () => {
+ return MockStatusResponse.getComplete();
+ })
+ .mockImplementationOnce(async () => {
+ return MockStatusResponse.getComplete({
+ srcTxHash: '0xsrcTxHash2',
+ destTxHash: '0xdestTxHash2',
+ });
+ });
+
+ // Start polling for 0xaccount1
+ bridgeStatusController.startPollingForBridgeTxStatus(
+ getMockStartPollingForBridgeTxStatusArgs(),
+ );
+ jest.advanceTimersByTime(10_000);
+ expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1);
+
+ // Start polling for 0xaccount2
+ bridgeStatusController.startPollingForBridgeTxStatus(
+ getMockStartPollingForBridgeTxStatusArgs({
+ srcTxHash: '0xsrcTxHash2',
+ account: '0xaccount2',
+ }),
+ );
+ jest.advanceTimersByTime(10_000);
+ expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(2);
+
+ // Check that both accounts have a tx history entry
+ expect(
+ bridgeStatusController.state.bridgeStatusState.txHistory,
+ ).toHaveProperty('0xsrcTxHash1');
+ expect(
+ bridgeStatusController.state.bridgeStatusState.txHistory,
+ ).toHaveProperty('0xsrcTxHash2');
+
+ // Wipe the status for 1 account only
+ bridgeStatusController.wipeBridgeStatus({
+ address: '0xaccount1',
+ ignoreNetwork: false,
+ });
+
+ // Assertions
+ const txHistoryItems = Object.values(
+ bridgeStatusController.state.bridgeStatusState.txHistory,
+ );
+ expect(txHistoryItems).toHaveLength(1);
+ expect(txHistoryItems[0].account).toEqual('0xaccount2');
+ });
+ it('wipes the bridge status for all networks if ignoreNetwork is true', () => {
+ // Setup
+ jest.useFakeTimers();
+ const messengerMock = {
+ call: jest.fn((method: string) => {
+ if (method === 'AccountsController:getSelectedAccount') {
+ return { address: '0xaccount1' };
+ } else if (
+ method === 'NetworkController:findNetworkClientIdByChainId'
+ ) {
+ return 'networkClientId';
+ } else if (method === 'NetworkController:getState') {
+ return { selectedNetworkClientId: 'networkClientId' };
+ } else if (method === 'NetworkController:getNetworkClientById') {
+ return {
+ configuration: {
+ chainId: new Numeric(42161, 10).toPrefixedHexString(),
+ },
+ };
+ }
+ return null;
+ }),
+ publish: jest.fn(),
+ registerActionHandler: jest.fn(),
+ registerInitialEventPayload: jest.fn(),
+ } as unknown as jest.Mocked;
+ const bridgeStatusController = new BridgeStatusController({
+ messenger: messengerMock,
+ });
+ const fetchBridgeTxStatusSpy = jest
+ .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus')
+ .mockImplementationOnce(async () => {
+ return MockStatusResponse.getComplete();
+ })
+ .mockImplementationOnce(async () => {
+ return MockStatusResponse.getComplete({
+ srcTxHash: '0xsrcTxHash2',
+ });
+ });
+
+ // Start polling for chainId 42161 to chainId 1
+ bridgeStatusController.startPollingForBridgeTxStatus(
+ getMockStartPollingForBridgeTxStatusArgs({
+ account: '0xaccount1',
+ srcTxHash: '0xsrcTxHash1',
+ srcChainId: 42161,
+ destChainId: 1,
+ }),
+ );
+ jest.advanceTimersByTime(10_000);
+ expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1);
+
+ // Start polling for chainId 10 to chainId 123
+ bridgeStatusController.startPollingForBridgeTxStatus(
+ getMockStartPollingForBridgeTxStatusArgs({
+ account: '0xaccount1',
+ srcTxHash: '0xsrcTxHash2',
+ srcChainId: 10,
+ destChainId: 123,
+ }),
+ );
+ jest.advanceTimersByTime(10_000);
+ expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(2);
+
+ // Check we have a tx history entry for each chainId
+ expect(
+ bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash1']
+ .quote.srcChainId,
+ ).toEqual(42161);
+ expect(
+ bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash1']
+ .quote.destChainId,
+ ).toEqual(1);
+
+ expect(
+ bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash2']
+ .quote.srcChainId,
+ ).toEqual(10);
+ expect(
+ bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash2']
+ .quote.destChainId,
+ ).toEqual(123);
+
+ bridgeStatusController.wipeBridgeStatus({
+ address: '0xaccount1',
+ ignoreNetwork: true,
+ });
+
+ // Assertions
+ const txHistoryItems = Object.values(
+ bridgeStatusController.state.bridgeStatusState.txHistory,
+ );
+ expect(txHistoryItems).toHaveLength(0);
+ });
+ it('wipes the bridge status only for the current network if ignoreNetwork is false', () => {
+ // Setup
+ jest.useFakeTimers();
+ const messengerMock = {
+ call: jest.fn((method: string) => {
+ if (method === 'AccountsController:getSelectedAccount') {
+ return { address: '0xaccount1' };
+ } else if (
+ method === 'NetworkController:findNetworkClientIdByChainId'
+ ) {
+ return 'networkClientId';
+ } else if (method === 'NetworkController:getState') {
+ return { selectedNetworkClientId: 'networkClientId' };
+ } else if (method === 'NetworkController:getNetworkClientById') {
+ return {
+ configuration: {
+ // This is what controls the selectedNetwork and what gets wiped in this test
+ chainId: new Numeric(42161, 10).toPrefixedHexString(),
+ },
+ };
+ }
+ return null;
+ }),
+ publish: jest.fn(),
+ registerActionHandler: jest.fn(),
+ registerInitialEventPayload: jest.fn(),
+ } as unknown as jest.Mocked;
+ const bridgeStatusController = new BridgeStatusController({
+ messenger: messengerMock,
+ });
+ const fetchBridgeTxStatusSpy = jest
+ .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus')
+ .mockImplementationOnce(async () => {
+ return MockStatusResponse.getComplete();
+ })
+ .mockImplementationOnce(async () => {
+ return MockStatusResponse.getComplete({
+ srcTxHash: '0xsrcTxHash2',
+ });
+ });
+
+ // Start polling for chainId 42161 to chainId 1
+ bridgeStatusController.startPollingForBridgeTxStatus(
+ getMockStartPollingForBridgeTxStatusArgs({
+ account: '0xaccount1',
+ srcTxHash: '0xsrcTxHash1',
+ srcChainId: 42161,
+ destChainId: 1,
+ }),
+ );
+ jest.advanceTimersByTime(10_000);
+ expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1);
+
+ // Start polling for chainId 10 to chainId 123
+ bridgeStatusController.startPollingForBridgeTxStatus(
+ getMockStartPollingForBridgeTxStatusArgs({
+ account: '0xaccount1',
+ srcTxHash: '0xsrcTxHash2',
+ srcChainId: 10,
+ destChainId: 123,
+ }),
+ );
+ jest.advanceTimersByTime(10_000);
+ expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(2);
+
+ // Check we have a tx history entry for each chainId
+ expect(
+ bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash1']
+ .quote.srcChainId,
+ ).toEqual(42161);
+ expect(
+ bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash1']
+ .quote.destChainId,
+ ).toEqual(1);
+
+ expect(
+ bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash2']
+ .quote.srcChainId,
+ ).toEqual(10);
+ expect(
+ bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash2']
+ .quote.destChainId,
+ ).toEqual(123);
+
+ bridgeStatusController.wipeBridgeStatus({
+ address: '0xaccount1',
+ ignoreNetwork: false,
+ });
+
+ // Assertions
+ const txHistoryItems = Object.values(
+ bridgeStatusController.state.bridgeStatusState.txHistory,
+ );
+ expect(txHistoryItems).toHaveLength(1);
+ expect(txHistoryItems[0].quote.srcChainId).toEqual(10);
+ expect(txHistoryItems[0].quote.destChainId).toEqual(123);
+ });
+ });
+});
diff --git a/app/scripts/controllers/bridge-status/bridge-status-controller.ts b/app/scripts/controllers/bridge-status/bridge-status-controller.ts
new file mode 100644
index 000000000000..18010ae0de3d
--- /dev/null
+++ b/app/scripts/controllers/bridge-status/bridge-status-controller.ts
@@ -0,0 +1,310 @@
+import { StateMetadata } from '@metamask/base-controller';
+import { StaticIntervalPollingController } from '@metamask/polling-controller';
+import { Hex } from '@metamask/utils';
+// eslint-disable-next-line import/no-restricted-paths
+import {
+ StartPollingForBridgeTxStatusArgs,
+ StatusRequest,
+ StatusTypes,
+ BridgeStatusControllerState,
+} from '../../../../shared/types/bridge-status';
+import { decimalToPrefixedHex } from '../../../../shared/modules/conversion.utils';
+import {
+ BRIDGE_STATUS_CONTROLLER_NAME,
+ DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE,
+ REFRESH_INTERVAL_MS,
+} from './constants';
+import { BridgeStatusControllerMessenger } from './types';
+import { fetchBridgeTxStatus } from './utils';
+
+const metadata: StateMetadata<{
+ bridgeStatusState: BridgeStatusControllerState;
+}> = {
+ // We want to persist the bridge status state so that we can show the proper data for the Activity list
+ // basically match the behavior of TransactionController
+ bridgeStatusState: {
+ persist: true,
+ anonymous: false,
+ },
+};
+
+type SrcTxHash = string;
+export type FetchBridgeTxStatusArgs = {
+ statusRequest: StatusRequest;
+};
+export default class BridgeStatusController extends StaticIntervalPollingController<
+ typeof BRIDGE_STATUS_CONTROLLER_NAME,
+ { bridgeStatusState: BridgeStatusControllerState },
+ BridgeStatusControllerMessenger
+> {
+ #pollingTokensBySrcTxHash: Record = {};
+
+ constructor({
+ messenger,
+ state,
+ }: {
+ messenger: BridgeStatusControllerMessenger;
+ state?: Partial<{
+ bridgeStatusState: BridgeStatusControllerState;
+ }>;
+ }) {
+ super({
+ name: BRIDGE_STATUS_CONTROLLER_NAME,
+ metadata,
+ messenger,
+ // Restore the persisted state
+ state: {
+ ...state,
+ bridgeStatusState: {
+ ...DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE,
+ ...state?.bridgeStatusState,
+ },
+ },
+ });
+
+ // Register action handlers
+ this.messagingSystem.registerActionHandler(
+ `${BRIDGE_STATUS_CONTROLLER_NAME}:startPollingForBridgeTxStatus`,
+ this.startPollingForBridgeTxStatus.bind(this),
+ );
+ this.messagingSystem.registerActionHandler(
+ `${BRIDGE_STATUS_CONTROLLER_NAME}:wipeBridgeStatus`,
+ this.wipeBridgeStatus.bind(this),
+ );
+
+ // Set interval
+ this.setIntervalLength(REFRESH_INTERVAL_MS);
+
+ // If you close the extension, but keep the browser open, the polling continues
+ // If you close the browser, the polling stops
+ // Check for historyItems that do not have a status of complete and restart polling
+ this.#restartPollingForIncompleteHistoryItems();
+ }
+
+ resetState = () => {
+ this.update((_state) => {
+ _state.bridgeStatusState = {
+ ...DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE,
+ };
+ });
+ };
+
+ wipeBridgeStatus = ({
+ address,
+ ignoreNetwork,
+ }: {
+ address: string;
+ ignoreNetwork: boolean;
+ }) => {
+ // Wipe all networks for this address
+ if (ignoreNetwork) {
+ this.update((_state) => {
+ _state.bridgeStatusState = {
+ ...DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE,
+ };
+ });
+ } else {
+ const { selectedNetworkClientId } = this.messagingSystem.call(
+ 'NetworkController:getState',
+ );
+ const selectedNetworkClient = this.messagingSystem.call(
+ 'NetworkController:getNetworkClientById',
+ selectedNetworkClientId,
+ );
+ const selectedChainId = selectedNetworkClient.configuration.chainId;
+
+ this.#wipeBridgeStatusByChainId(address, selectedChainId);
+ }
+ };
+
+ #restartPollingForIncompleteHistoryItems = () => {
+ // Check for historyItems that do not have a status of complete and restart polling
+ const { bridgeStatusState } = this.state;
+ const historyItems = Object.values(bridgeStatusState.txHistory);
+ const incompleteHistoryItems = historyItems
+ .filter(
+ (historyItem) => historyItem.status.status !== StatusTypes.COMPLETE,
+ )
+ .filter((historyItem) => {
+ // Check if we are already polling this tx, if so, skip restarting polling for that
+ const srcTxHash = historyItem.status.srcChain.txHash;
+ const pollingToken = this.#pollingTokensBySrcTxHash[srcTxHash];
+ return !pollingToken;
+ });
+
+ incompleteHistoryItems.forEach((historyItem) => {
+ const statusRequest = {
+ bridgeId: historyItem.quote.bridgeId,
+ srcTxHash: historyItem.status.srcChain.txHash,
+ bridge: historyItem.quote.bridges[0],
+ srcChainId: historyItem.quote.srcChainId,
+ destChainId: historyItem.quote.destChainId,
+ quote: historyItem.quote,
+ refuel: Boolean(historyItem.quote.refuel),
+ };
+
+ const hexSourceChainId = decimalToPrefixedHex(statusRequest.srcChainId);
+ const networkClientId = this.messagingSystem.call(
+ 'NetworkController:findNetworkClientIdByChainId',
+ hexSourceChainId,
+ );
+
+ // We manually call startPollingByNetworkClientId() here rather than go through startPollingForBridgeTxStatus()
+ // because we don't want to overwrite the existing historyItem in state
+ const options: FetchBridgeTxStatusArgs = { statusRequest };
+ this.#pollingTokensBySrcTxHash[statusRequest.srcTxHash] =
+ this.startPollingByNetworkClientId(networkClientId, options);
+ });
+ };
+
+ startPollingForBridgeTxStatus = (
+ startPollingForBridgeTxStatusArgs: StartPollingForBridgeTxStatusArgs,
+ ) => {
+ const {
+ statusRequest,
+ quoteResponse,
+ startTime,
+ slippagePercentage,
+ pricingData,
+ initialDestAssetBalance,
+ targetContractAddress,
+ } = startPollingForBridgeTxStatusArgs;
+ const hexSourceChainId = decimalToPrefixedHex(statusRequest.srcChainId);
+
+ const { bridgeStatusState } = this.state;
+ const { address: account } = this.#getSelectedAccount();
+
+ // Write all non-status fields to state so we can reference the quote in Activity list without the Bridge API
+ // We know it's in progress but not the exact status yet
+ this.update((_state) => {
+ _state.bridgeStatusState = {
+ ...bridgeStatusState,
+ txHistory: {
+ ...bridgeStatusState.txHistory,
+ [statusRequest.srcTxHash]: {
+ quote: quoteResponse.quote,
+ startTime,
+ estimatedProcessingTimeInSeconds:
+ quoteResponse.estimatedProcessingTimeInSeconds,
+ slippagePercentage,
+ pricingData,
+ initialDestAssetBalance,
+ targetContractAddress,
+ account,
+ status: {
+ // We always have a PENDING status when we start polling for a tx, don't need the Bridge API for that
+ // Also we know the bare minimum fields for status at this point in time
+ status: StatusTypes.PENDING,
+ srcChain: {
+ chainId: statusRequest.srcChainId,
+ txHash: statusRequest.srcTxHash,
+ },
+ },
+ },
+ },
+ };
+ });
+
+ const networkClientId = this.messagingSystem.call(
+ 'NetworkController:findNetworkClientIdByChainId',
+ hexSourceChainId,
+ );
+ this.#pollingTokensBySrcTxHash[statusRequest.srcTxHash] =
+ this.startPollingByNetworkClientId(networkClientId, { statusRequest });
+ };
+
+ // This will be called after you call this.startPollingByNetworkClientId()
+ // The args passed in are the args you passed in to startPollingByNetworkClientId()
+ _executePoll = async (
+ _networkClientId: string,
+ fetchBridgeTxStatusArgs: FetchBridgeTxStatusArgs,
+ ) => {
+ await this.#fetchBridgeTxStatus(fetchBridgeTxStatusArgs);
+ };
+
+ #getSelectedAccount() {
+ return this.messagingSystem.call('AccountsController:getSelectedAccount');
+ }
+
+ #fetchBridgeTxStatus = async ({ statusRequest }: FetchBridgeTxStatusArgs) => {
+ const { bridgeStatusState } = this.state;
+
+ try {
+ // We try here because we receive 500 errors from Bridge API if we try to fetch immediately after submitting the source tx
+ // Oddly mostly happens on Optimism, never on Arbitrum. By the 2nd fetch, the Bridge API responds properly.
+ const status = await fetchBridgeTxStatus(statusRequest);
+
+ // No need to purge these on network change or account change, TransactionController does not purge either.
+ // TODO In theory we can skip checking status if it's not the current account/network
+ // we need to keep track of the account that this is associated with as well so that we don't show it in Activity list for other accounts
+ // First stab at this will not stop polling when you are on a different account
+ this.update((_state) => {
+ const bridgeHistoryItem =
+ _state.bridgeStatusState.txHistory[statusRequest.srcTxHash];
+
+ _state.bridgeStatusState = {
+ ...bridgeStatusState,
+ txHistory: {
+ ...bridgeStatusState.txHistory,
+ [statusRequest.srcTxHash]: {
+ ...bridgeHistoryItem,
+ status,
+ },
+ },
+ };
+ });
+
+ const pollingToken =
+ this.#pollingTokensBySrcTxHash[statusRequest.srcTxHash];
+ if (status.status === StatusTypes.COMPLETE && pollingToken) {
+ this.stopPollingByPollingToken(pollingToken);
+ }
+ } catch (e) {
+ console.log('Failed to fetch bridge tx status', e);
+ }
+ };
+
+ // Wipes the bridge status for the given address and chainId
+ // Will match either source or destination chainId to the selectedChainId
+ #wipeBridgeStatusByChainId = (address: string, selectedChainId: Hex) => {
+ const sourceTxHashesToDelete = Object.keys(
+ this.state.bridgeStatusState.txHistory,
+ ).filter((sourceTxHash) => {
+ const bridgeHistoryItem =
+ this.state.bridgeStatusState.txHistory[sourceTxHash];
+
+ const hexSourceChainId = decimalToPrefixedHex(
+ bridgeHistoryItem.quote.srcChainId,
+ );
+ const hexDestChainId = decimalToPrefixedHex(
+ bridgeHistoryItem.quote.destChainId,
+ );
+
+ return (
+ bridgeHistoryItem.account === address &&
+ (hexSourceChainId === selectedChainId ||
+ hexDestChainId === selectedChainId)
+ );
+ });
+
+ sourceTxHashesToDelete.forEach((sourceTxHash) => {
+ const pollingToken = this.#pollingTokensBySrcTxHash[sourceTxHash];
+
+ if (pollingToken) {
+ this.stopPollingByPollingToken(
+ this.#pollingTokensBySrcTxHash[sourceTxHash],
+ );
+ }
+ });
+
+ this.update((_state) => {
+ _state.bridgeStatusState.txHistory = sourceTxHashesToDelete.reduce(
+ (acc, sourceTxHash) => {
+ delete acc[sourceTxHash];
+ return acc;
+ },
+ _state.bridgeStatusState.txHistory,
+ );
+ });
+ };
+}
diff --git a/app/scripts/controllers/bridge-status/constants.ts b/app/scripts/controllers/bridge-status/constants.ts
new file mode 100644
index 000000000000..83208bdc73d8
--- /dev/null
+++ b/app/scripts/controllers/bridge-status/constants.ts
@@ -0,0 +1,10 @@
+import { BridgeStatusControllerState } from '../../../../shared/types/bridge-status';
+
+export const REFRESH_INTERVAL_MS = 10 * 1000;
+
+export const BRIDGE_STATUS_CONTROLLER_NAME = 'BridgeStatusController';
+
+export const DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE: BridgeStatusControllerState =
+ {
+ txHistory: {},
+ };
diff --git a/app/scripts/controllers/bridge-status/types.ts b/app/scripts/controllers/bridge-status/types.ts
new file mode 100644
index 000000000000..040cd1e0c9bd
--- /dev/null
+++ b/app/scripts/controllers/bridge-status/types.ts
@@ -0,0 +1,56 @@
+import {
+ ControllerGetStateAction,
+ ControllerStateChangeEvent,
+ RestrictedControllerMessenger,
+} from '@metamask/base-controller';
+import {
+ NetworkControllerFindNetworkClientIdByChainIdAction,
+ NetworkControllerGetNetworkClientByIdAction,
+ NetworkControllerGetStateAction,
+} from '@metamask/network-controller';
+import { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller';
+import {
+ BridgeStatusAction,
+ BridgeStatusControllerState,
+} from '../../../../shared/types/bridge-status';
+import { BRIDGE_STATUS_CONTROLLER_NAME } from './constants';
+import BridgeStatusController from './bridge-status-controller';
+
+type BridgeStatusControllerAction<
+ FunctionName extends keyof BridgeStatusController,
+> = {
+ type: `${typeof BRIDGE_STATUS_CONTROLLER_NAME}:${FunctionName}`;
+ handler: BridgeStatusController[FunctionName];
+};
+
+// Maps to BridgeController function names
+type BridgeStatusControllerActions =
+ | BridgeStatusControllerAction
+ | BridgeStatusControllerAction
+ | ControllerGetStateAction<
+ typeof BRIDGE_STATUS_CONTROLLER_NAME,
+ BridgeStatusControllerState
+ >;
+
+type BridgeStatusControllerEvents = ControllerStateChangeEvent<
+ typeof BRIDGE_STATUS_CONTROLLER_NAME,
+ BridgeStatusControllerState
+>;
+
+type AllowedActions =
+ | NetworkControllerFindNetworkClientIdByChainIdAction
+ | NetworkControllerGetStateAction
+ | NetworkControllerGetNetworkClientByIdAction
+ | AccountsControllerGetSelectedAccountAction;
+type AllowedEvents = never;
+
+/**
+ * The messenger for the BridgeStatusController.
+ */
+export type BridgeStatusControllerMessenger = RestrictedControllerMessenger<
+ typeof BRIDGE_STATUS_CONTROLLER_NAME,
+ BridgeStatusControllerActions | AllowedActions,
+ BridgeStatusControllerEvents | AllowedEvents,
+ AllowedActions['type'],
+ AllowedEvents['type']
+>;
diff --git a/app/scripts/controllers/bridge-status/utils.ts b/app/scripts/controllers/bridge-status/utils.ts
new file mode 100644
index 000000000000..323e7e2faeab
--- /dev/null
+++ b/app/scripts/controllers/bridge-status/utils.ts
@@ -0,0 +1,49 @@
+import {
+ BRIDGE_API_BASE_URL,
+ BRIDGE_CLIENT_ID,
+} from '../../../../shared/constants/bridge';
+import fetchWithCache from '../../../../shared/lib/fetch-with-cache';
+import {
+ StatusResponse,
+ StatusRequest,
+} from '../../../../shared/types/bridge-status';
+import { validateResponse, validators } from './validators';
+
+const CLIENT_ID_HEADER = { 'X-Client-Id': BRIDGE_CLIENT_ID };
+
+export const BRIDGE_STATUS_BASE_URL = `${BRIDGE_API_BASE_URL}/getTxStatus`;
+
+export const fetchBridgeTxStatus = async (statusRequest: StatusRequest) => {
+ // Assemble params
+ const { quote, ...statusRequestNoQuote } = statusRequest;
+ const statusRequestNoQuoteFormatted = Object.fromEntries(
+ Object.entries(statusRequestNoQuote).map(([key, value]) => [
+ key,
+ value.toString(),
+ ]),
+ );
+ const params = new URLSearchParams(statusRequestNoQuoteFormatted);
+
+ // Fetch
+ const url = `${BRIDGE_STATUS_BASE_URL}?${params.toString()}`;
+
+ const rawTxStatus = await fetchWithCache({
+ url,
+ fetchOptions: { method: 'GET', headers: CLIENT_ID_HEADER },
+ cacheOptions: { cacheRefreshTime: 0 },
+ functionName: 'fetchBridgeTxStatus',
+ });
+
+ // Validate
+ const isValid = validateResponse(
+ validators,
+ rawTxStatus,
+ BRIDGE_STATUS_BASE_URL,
+ );
+ if (!isValid) {
+ throw new Error('Invalid response from bridge');
+ }
+
+ // Return
+ return rawTxStatus;
+};
diff --git a/app/scripts/controllers/bridge-status/validators.test.ts b/app/scripts/controllers/bridge-status/validators.test.ts
new file mode 100644
index 000000000000..18ca81d7a5b2
--- /dev/null
+++ b/app/scripts/controllers/bridge-status/validators.test.ts
@@ -0,0 +1,238 @@
+import { StatusResponse } from '../../../../shared/types/bridge-status';
+import { validateResponse, validators } from './validators';
+
+const BridgeTxStatusResponses = {
+ STATUS_PENDING_VALID: {
+ status: 'PENDING',
+ bridge: 'across',
+ srcChain: {
+ chainId: 42161,
+ txHash:
+ '0x76a65e4cea35d8732f0e3250faed00ba764ad5a0e7c51cb1bafbc9d76ac0b325',
+ amount: '991250000000000',
+ token: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: 42161,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2550.12',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ },
+ destChain: {
+ chainId: '10',
+ token: {},
+ },
+ },
+ STATUS_PENDING_VALID_MISSING_FIELDS: {
+ status: 'PENDING',
+ srcChain: {
+ chainId: 42161,
+ txHash:
+ '0x5cbda572c686a5a57fe62735325e408f9164f77a4787df29ce13edef765adaa9',
+ },
+ },
+ STATUS_PENDING_VALID_MISSING_FIELDS_2: {
+ status: 'PENDING',
+ bridge: 'hop',
+ srcChain: {
+ chainId: 42161,
+ txHash:
+ '0x5cbda572c686a5a57fe62735325e408f9164f77a4787df29ce13edef765adaa9',
+ amount: '991250000000000',
+ token: {
+ chainId: 42161,
+ address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
+ symbol: 'ETH',
+ name: 'Ethereum',
+ decimals: 18,
+ icon: 'https://media.socket.tech/tokens/all/ETH',
+ logoURI: 'https://media.socket.tech/tokens/all/ETH',
+ chainAgnosticId: null,
+ },
+ },
+ },
+ STATUS_PENDING_INVALID_MISSING_FIELDS: {
+ status: 'PENDING',
+ bridge: 'across',
+ srcChain: {
+ chainId: 42161,
+ txHash:
+ '0x76a65e4cea35d8732f0e3250faed00ba764ad5a0e7c51cb1bafbc9d76ac0b325',
+ amount: '991250000000000',
+ token: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: 42161,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2550.12',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ },
+ destChain: {
+ token: {},
+ },
+ },
+ STATUS_COMPLETE_VALID: {
+ status: 'COMPLETE',
+ isExpectedToken: true,
+ bridge: 'across',
+ srcChain: {
+ chainId: 10,
+ txHash:
+ '0x9fdc426692aba1f81e145834602ed59ed331054e5b91a09a673cb12d4b4f6a33',
+ amount: '4956250000000000',
+ token: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: 10,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2649.21',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ },
+ destChain: {
+ chainId: '42161',
+ txHash:
+ '0x3a494e672717f9b1f2b64a48a19985842d82d0747400fccebebc7a4e99c8eaab',
+ amount: '4926701727965948',
+ token: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: 42161,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2648.72',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ },
+ },
+ STATUS_COMPLETE_VALID_MISSING_FIELDS: {
+ status: 'COMPLETE',
+ bridge: 'across',
+ srcChain: {
+ chainId: 10,
+ txHash:
+ '0x9fdc426692aba1f81e145834602ed59ed331054e5b91a09a673cb12d4b4f6a33',
+ amount: '4956250000000000',
+ token: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: 10,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2649.21',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ },
+ destChain: {
+ chainId: '42161',
+ txHash:
+ '0x3a494e672717f9b1f2b64a48a19985842d82d0747400fccebebc7a4e99c8eaab',
+ amount: '4926701727965948',
+ token: {
+ address: '0x0000000000000000000000000000000000000000',
+ chainId: 42161,
+ symbol: 'ETH',
+ decimals: 18,
+ name: 'ETH',
+ coinKey: 'ETH',
+ logoURI:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ priceUSD: '2648.72',
+ icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
+ },
+ },
+ },
+ STATUS_COMPLETE_INVALID_MISSING_FIELDS: {
+ status: 'COMPLETE',
+ isExpectedToken: true,
+ bridge: 'across',
+ },
+};
+
+describe('validators', () => {
+ describe('bridgeStatusValidator', () => {
+ // @ts-expect-error - it.each is a function
+ it.each([
+ {
+ input: BridgeTxStatusResponses.STATUS_PENDING_VALID,
+ expected: true,
+ description: 'valid pending bridge status',
+ },
+ {
+ input: BridgeTxStatusResponses.STATUS_PENDING_VALID_MISSING_FIELDS,
+ expected: true,
+ description: 'valid pending bridge status missing fields',
+ },
+ {
+ input: BridgeTxStatusResponses.STATUS_PENDING_VALID_MISSING_FIELDS_2,
+ expected: true,
+ description: 'valid pending bridge status missing fields 2',
+ },
+ {
+ input: BridgeTxStatusResponses.STATUS_PENDING_INVALID_MISSING_FIELDS,
+ expected: false,
+ description: 'pending bridge status with missing fields',
+ },
+ {
+ input: BridgeTxStatusResponses.STATUS_COMPLETE_VALID,
+ expected: true,
+ description: 'valid complete bridge status',
+ },
+ {
+ input: BridgeTxStatusResponses.STATUS_COMPLETE_INVALID_MISSING_FIELDS,
+ expected: false,
+ description: 'complete bridge status with missing fields',
+ },
+ {
+ input: BridgeTxStatusResponses.STATUS_COMPLETE_VALID_MISSING_FIELDS,
+ expected: true,
+ description: 'complete bridge status with missing fields',
+ },
+ {
+ input: undefined,
+ expected: false,
+ description: 'undefined',
+ },
+ {
+ input: null,
+ expected: false,
+ description: 'null',
+ },
+ {
+ input: {},
+ expected: false,
+ description: 'empty object',
+ },
+ ])(
+ 'should return $expected for $description',
+ ({ input, expected }: { input: unknown; expected: boolean }) => {
+ const res = validateResponse(
+ validators,
+ input,
+ 'dummyurl.com',
+ );
+ expect(res).toBe(expected);
+ },
+ );
+ });
+});
diff --git a/app/scripts/controllers/bridge-status/validators.ts b/app/scripts/controllers/bridge-status/validators.ts
new file mode 100644
index 000000000000..69e788025b01
--- /dev/null
+++ b/app/scripts/controllers/bridge-status/validators.ts
@@ -0,0 +1,179 @@
+import { validHex, validateData } from '../../../../shared/lib/swaps-utils';
+import { isValidHexAddress } from '../../../../shared/modules/hexstring-utils';
+import {
+ BridgeId,
+ DestChainStatus,
+ SrcChainStatus,
+ Asset,
+ StatusTypes,
+} from '../../../../shared/types/bridge-status';
+import { BRIDGE_STATUS_BASE_URL } from './utils';
+
+type Validator = {
+ property: keyof ExpectedResponse | string;
+ type: string;
+ validator: (value: DataToValidate) => boolean;
+};
+
+export const validateResponse = (
+ validators: Validator[],
+ data: unknown,
+ urlUsed: string,
+): data is ExpectedResponse => {
+ if (data === null || data === undefined) {
+ return false;
+ }
+ return validateData(validators, data, urlUsed);
+};
+
+const assetValidators = [
+ {
+ property: 'chainId',
+ type: 'number',
+ validator: (v: unknown): v is number => typeof v === 'number',
+ },
+ {
+ property: 'address',
+ type: 'string',
+ validator: (v: unknown): v is string => isValidHexAddress(v as string),
+ },
+ {
+ property: 'symbol',
+ type: 'string',
+ validator: (v: unknown): v is string => typeof v === 'string',
+ },
+ {
+ property: 'name',
+ type: 'string',
+ validator: (v: unknown): v is string => typeof v === 'string',
+ },
+ {
+ property: 'decimals',
+ type: 'number',
+ validator: (v: unknown): v is number => typeof v === 'number',
+ },
+ {
+ property: 'icon',
+ type: 'string|undefined',
+ validator: (v: unknown): v is string | undefined =>
+ v === undefined || typeof v === 'string',
+ },
+];
+
+const assetValidator = (v: unknown): v is Asset =>
+ validateResponse(assetValidators, v, BRIDGE_STATUS_BASE_URL);
+
+const srcChainStatusValidators = [
+ {
+ property: 'chainId',
+ // For some reason, API returns destChain.chainId as a string, it's a number everywhere else
+ type: 'number|string',
+ validator: (v: unknown): v is number | string =>
+ typeof v === 'number' || typeof v === 'string',
+ },
+ {
+ property: 'txHash',
+ type: 'string',
+ validator: validHex,
+ },
+ {
+ property: 'amount',
+ type: 'string|undefined',
+ validator: (v: unknown): v is string | undefined =>
+ v === undefined || typeof v === 'string',
+ },
+ {
+ property: 'token',
+ type: 'object|undefined',
+ validator: (v: unknown): v is object | undefined =>
+ v === undefined || assetValidator(v),
+ },
+];
+
+const srcChainStatusValidator = (v: unknown): v is SrcChainStatus =>
+ validateResponse(
+ srcChainStatusValidators,
+ v,
+ BRIDGE_STATUS_BASE_URL,
+ );
+
+const destChainStatusValidators = [
+ {
+ property: 'chainId',
+ // For some reason, API returns destChain.chainId as a string, it's a number everywhere else
+ type: 'number|string',
+ validator: (v: unknown): v is number | string =>
+ typeof v === 'number' || typeof v === 'string',
+ },
+ {
+ property: 'amount',
+ type: 'string|undefined',
+ validator: (v: unknown): v is string | undefined =>
+ v === undefined || typeof v === 'string',
+ },
+ {
+ property: 'txHash',
+ type: 'string|undefined',
+ validator: (v: unknown): v is string | undefined =>
+ v === undefined || typeof v === 'string',
+ },
+ {
+ property: 'token',
+ type: 'object|undefined',
+ validator: (v: unknown): v is Asset | undefined =>
+ v === undefined ||
+ (v && typeof v === 'object' && Object.keys(v).length === 0) ||
+ assetValidator(v),
+ },
+];
+
+const destChainStatusValidator = (v: unknown): v is DestChainStatus =>
+ validateResponse(
+ destChainStatusValidators,
+ v,
+ BRIDGE_STATUS_BASE_URL,
+ );
+
+export const validators = [
+ {
+ property: 'status',
+ type: 'string',
+ validator: (v: unknown): v is StatusTypes =>
+ Object.values(StatusTypes).includes(v as StatusTypes),
+ },
+ {
+ property: 'srcChain',
+ type: 'object',
+ validator: srcChainStatusValidator,
+ },
+ {
+ property: 'destChain',
+ type: 'object|undefined',
+ validator: (v: unknown): v is object | unknown =>
+ v === undefined || destChainStatusValidator(v),
+ },
+ {
+ property: 'bridge',
+ type: 'string|undefined',
+ validator: (v: unknown): v is BridgeId | undefined =>
+ v === undefined || Object.values(BridgeId).includes(v as BridgeId),
+ },
+ {
+ property: 'isExpectedToken',
+ type: 'boolean|undefined',
+ validator: (v: unknown): v is boolean | undefined =>
+ v === undefined || typeof v === 'boolean',
+ },
+ {
+ property: 'isUnrecognizedRouterAddress',
+ type: 'boolean|undefined',
+ validator: (v: unknown): v is boolean | undefined =>
+ v === undefined || typeof v === 'boolean',
+ },
+ // TODO: add refuel validator
+ // {
+ // property: 'refuel',
+ // type: 'object',
+ // validator: (v: unknown) => Object.values(RefuelStatusResponse).includes(v),
+ // },
+];
diff --git a/app/scripts/controllers/bridge/bridge-controller.test.ts b/app/scripts/controllers/bridge/bridge-controller.test.ts
index 8369d910f78b..5cadcb1bd375 100644
--- a/app/scripts/controllers/bridge/bridge-controller.test.ts
+++ b/app/scripts/controllers/bridge/bridge-controller.test.ts
@@ -1,4 +1,6 @@
import nock from 'nock';
+import { BigNumber } from 'bignumber.js';
+import { add0x } from '@metamask/utils';
import { BRIDGE_API_BASE_URL } from '../../../../shared/constants/bridge';
import { CHAIN_IDS } from '../../../../shared/constants/network';
import { SWAPS_API_V2_BASE_URL } from '../../../../shared/constants/swaps';
@@ -7,6 +9,13 @@ import { flushPromises } from '../../../../test/lib/timer-helpers';
// eslint-disable-next-line import/no-restricted-paths
import * as bridgeUtil from '../../../../ui/pages/bridge/bridge.util';
import * as balanceUtils from '../../../../shared/modules/bridge-utils/balance';
+import mockBridgeQuotesErc20Native from '../../../../test/data/bridge/mock-quotes-erc20-native.json';
+import mockBridgeQuotesNativeErc20 from '../../../../test/data/bridge/mock-quotes-native-erc20.json';
+import mockBridgeQuotesNativeErc20Eth from '../../../../test/data/bridge/mock-quotes-native-erc20-eth.json';
+// TODO: Remove restricted import
+// eslint-disable-next-line import/no-restricted-paths
+import { QuoteResponse } from '../../../../ui/pages/bridge/types';
+import { decimalToHex } from '../../../../shared/modules/conversion.utils';
import BridgeController from './bridge-controller';
import { BridgeControllerMessenger } from './types';
import { DEFAULT_BRIDGE_CONTROLLER_STATE } from './constants';
@@ -35,6 +44,7 @@ jest.mock('@ethersproject/providers', () => {
Web3Provider: jest.fn(),
};
});
+const getLayer1GasFeeMock = jest.fn();
describe('BridgeController', function () {
let bridgeController: BridgeController;
@@ -42,6 +52,7 @@ describe('BridgeController', function () {
beforeAll(function () {
bridgeController = new BridgeController({
messenger: messengerMock,
+ getLayer1GasFee: getLayer1GasFeeMock,
});
});
@@ -278,7 +289,7 @@ describe('BridgeController', function () {
.mockImplementationOnce(async () => {
return await new Promise((resolve) => {
return setTimeout(() => {
- resolve([1, 2, 3] as never);
+ resolve(mockBridgeQuotesNativeErc20Eth as never);
}, 5000);
});
});
@@ -286,7 +297,10 @@ describe('BridgeController', function () {
fetchBridgeQuotesSpy.mockImplementationOnce(async () => {
return await new Promise((resolve) => {
return setTimeout(() => {
- resolve([5, 6, 7] as never);
+ resolve([
+ ...mockBridgeQuotesNativeErc20Eth,
+ ...mockBridgeQuotesNativeErc20Eth,
+ ] as never);
}, 10000);
});
});
@@ -363,7 +377,7 @@ describe('BridgeController', function () {
expect(bridgeController.state.bridgeState).toEqual(
expect.objectContaining({
quoteRequest: { ...quoteRequest, insufficientBal: false },
- quotes: [1, 2, 3],
+ quotes: mockBridgeQuotesNativeErc20Eth,
quotesLoadingStatus: 1,
}),
);
@@ -377,7 +391,10 @@ describe('BridgeController', function () {
expect(bridgeController.state.bridgeState).toEqual(
expect.objectContaining({
quoteRequest: { ...quoteRequest, insufficientBal: false },
- quotes: [5, 6, 7],
+ quotes: [
+ ...mockBridgeQuotesNativeErc20Eth,
+ ...mockBridgeQuotesNativeErc20Eth,
+ ],
quotesLoadingStatus: 1,
quotesRefreshCount: 2,
}),
@@ -394,7 +411,10 @@ describe('BridgeController', function () {
expect(bridgeController.state.bridgeState).toEqual(
expect.objectContaining({
quoteRequest: { ...quoteRequest, insufficientBal: false },
- quotes: [5, 6, 7],
+ quotes: [
+ ...mockBridgeQuotesNativeErc20Eth,
+ ...mockBridgeQuotesNativeErc20Eth,
+ ],
quotesLoadingStatus: 2,
quotesRefreshCount: 3,
}),
@@ -404,6 +424,7 @@ describe('BridgeController', function () {
);
expect(hasSufficientBalanceSpy).toHaveBeenCalledTimes(1);
+ expect(getLayer1GasFeeMock).not.toHaveBeenCalled();
});
it('updateBridgeQuoteRequestParams should only poll once if insufficientBal=true', async function () {
@@ -426,7 +447,7 @@ describe('BridgeController', function () {
.mockImplementationOnce(async () => {
return await new Promise((resolve) => {
return setTimeout(() => {
- resolve([1, 2, 3] as never);
+ resolve(mockBridgeQuotesNativeErc20Eth as never);
}, 5000);
});
});
@@ -434,7 +455,10 @@ describe('BridgeController', function () {
fetchBridgeQuotesSpy.mockImplementation(async () => {
return await new Promise((resolve) => {
return setTimeout(() => {
- resolve([5, 6, 7] as never);
+ resolve([
+ ...mockBridgeQuotesNativeErc20Eth,
+ ...mockBridgeQuotesNativeErc20Eth,
+ ] as never);
}, 10000);
});
});
@@ -503,7 +527,7 @@ describe('BridgeController', function () {
expect(bridgeController.state.bridgeState).toEqual(
expect.objectContaining({
quoteRequest: { ...quoteRequest, insufficientBal: true },
- quotes: [1, 2, 3],
+ quotes: mockBridgeQuotesNativeErc20Eth,
quotesLoadingStatus: 1,
quotesRefreshCount: 1,
}),
@@ -519,7 +543,7 @@ describe('BridgeController', function () {
expect(bridgeController.state.bridgeState).toEqual(
expect.objectContaining({
quoteRequest: { ...quoteRequest, insufficientBal: true },
- quotes: [1, 2, 3],
+ quotes: mockBridgeQuotesNativeErc20Eth,
quotesLoadingStatus: 1,
quotesRefreshCount: 1,
}),
@@ -527,6 +551,7 @@ describe('BridgeController', function () {
const secondFetchTime =
bridgeController.state.bridgeState.quotesLastFetched;
expect(secondFetchTime).toStrictEqual(firstFetchTime);
+ expect(getLayer1GasFeeMock).not.toHaveBeenCalled();
});
it('updateBridgeQuoteRequestParams should not trigger quote polling if request is invalid', function () {
@@ -574,6 +599,7 @@ describe('BridgeController', function () {
address: '0x123',
provider: jest.fn(),
} as never);
+
const allowance = await bridgeController.getBridgeERC20Allowance(
'0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
'0xa',
@@ -581,4 +607,143 @@ describe('BridgeController', function () {
expect(allowance).toBe('100000000000000000000');
});
});
+
+ // @ts-expect-error This is missing from the Mocha type definitions
+ it.each([
+ [
+ 'should append l1GasFees if srcChain is 10 and srcToken is erc20',
+ mockBridgeQuotesErc20Native,
+ add0x(decimalToHex(new BigNumber('2608710388388').mul(2).toFixed())),
+ 12,
+ ],
+ [
+ 'should append l1GasFees if srcChain is 10 and srcToken is native',
+ mockBridgeQuotesNativeErc20,
+ add0x(decimalToHex(new BigNumber('2608710388388').toFixed())),
+ 2,
+ ],
+ [
+ 'should not append l1GasFees if srcChain is not 10',
+ mockBridgeQuotesNativeErc20Eth,
+ undefined,
+ 0,
+ ],
+ ])(
+ 'updateBridgeQuoteRequestParams: %s',
+ async (
+ _: string,
+ quoteResponse: QuoteResponse[],
+ l1GasFeesInHexWei: string,
+ getLayer1GasFeeMockCallCount: number,
+ ) => {
+ jest.useFakeTimers();
+ const stopAllPollingSpy = jest.spyOn(bridgeController, 'stopAllPolling');
+ const startPollingByNetworkClientIdSpy = jest.spyOn(
+ bridgeController,
+ 'startPollingByNetworkClientId',
+ );
+ const hasSufficientBalanceSpy = jest
+ .spyOn(balanceUtils, 'hasSufficientBalance')
+ .mockResolvedValue(false);
+ messengerMock.call.mockReturnValue({
+ address: '0x123',
+ provider: jest.fn(),
+ } as never);
+ getLayer1GasFeeMock.mockResolvedValue('0x25F63418AA4');
+
+ const fetchBridgeQuotesSpy = jest
+ .spyOn(bridgeUtil, 'fetchBridgeQuotes')
+ .mockImplementationOnce(async () => {
+ return await new Promise((resolve) => {
+ return setTimeout(() => {
+ resolve(quoteResponse as never);
+ }, 1000);
+ });
+ });
+
+ const quoteParams = {
+ srcChainId: 10,
+ destChainId: 1,
+ srcTokenAddress: '0x4200000000000000000000000000000000000006',
+ destTokenAddress: '0x0000000000000000000000000000000000000000',
+ srcTokenAmount: '991250000000000000',
+ };
+ const quoteRequest = {
+ ...quoteParams,
+ slippage: 0.5,
+ walletAddress: '0x123',
+ };
+ await bridgeController.updateBridgeQuoteRequestParams(quoteParams);
+
+ expect(stopAllPollingSpy).toHaveBeenCalledTimes(1);
+ expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledTimes(1);
+ expect(hasSufficientBalanceSpy).toHaveBeenCalledTimes(1);
+ expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledWith(
+ expect.anything(),
+ {
+ ...quoteRequest,
+ insufficientBal: true,
+ },
+ );
+
+ expect(bridgeController.state.bridgeState).toStrictEqual(
+ expect.objectContaining({
+ quoteRequest: { ...quoteRequest, walletAddress: undefined },
+ quotes: DEFAULT_BRIDGE_CONTROLLER_STATE.quotes,
+ quotesLastFetched: DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLastFetched,
+ quotesLoadingStatus:
+ DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLoadingStatus,
+ }),
+ );
+
+ // // Loading state
+ jest.advanceTimersByTime(500);
+ await flushPromises();
+ expect(fetchBridgeQuotesSpy).toHaveBeenCalledTimes(1);
+ expect(fetchBridgeQuotesSpy).toHaveBeenCalledWith(
+ {
+ ...quoteRequest,
+ insufficientBal: true,
+ },
+ expect.any(AbortSignal),
+ );
+ expect(
+ bridgeController.state.bridgeState.quotesLastFetched,
+ ).toStrictEqual(undefined);
+
+ expect(bridgeController.state.bridgeState).toEqual(
+ expect.objectContaining({
+ quoteRequest: { ...quoteRequest, insufficientBal: true },
+ quotes: [],
+ quotesLoadingStatus: 0,
+ }),
+ );
+
+ // After first fetch
+ jest.advanceTimersByTime(1500);
+ await flushPromises();
+ const { quotes } = bridgeController.state.bridgeState;
+ expect(bridgeController.state.bridgeState).toEqual(
+ expect.objectContaining({
+ quoteRequest: { ...quoteRequest, insufficientBal: true },
+ quotesLoadingStatus: 1,
+ quotesRefreshCount: 1,
+ }),
+ );
+ quotes.forEach((quote) => {
+ const expectedQuote = l1GasFeesInHexWei
+ ? { ...quote, l1GasFeesInHexWei }
+ : quote;
+ expect(quote).toStrictEqual(expectedQuote);
+ });
+
+ const firstFetchTime =
+ bridgeController.state.bridgeState.quotesLastFetched ?? 0;
+ expect(firstFetchTime).toBeGreaterThan(0);
+
+ expect(getLayer1GasFeeMock).toHaveBeenCalledTimes(
+ getLayer1GasFeeMockCallCount,
+ );
+ },
+ );
});
diff --git a/app/scripts/controllers/bridge/bridge-controller.ts b/app/scripts/controllers/bridge/bridge-controller.ts
index 2518e9caa9bd..bbe016ac7aea 100644
--- a/app/scripts/controllers/bridge/bridge-controller.ts
+++ b/app/scripts/controllers/bridge/bridge-controller.ts
@@ -6,6 +6,8 @@ import { Contract } from '@ethersproject/contracts';
import { abiERC20 } from '@metamask/metamask-eth-abis';
import { Web3Provider } from '@ethersproject/providers';
import { BigNumber } from '@ethersproject/bignumber';
+import { TransactionParams } from '@metamask/transaction-controller';
+import type { ChainId } from '@metamask/controller-utils';
import {
fetchBridgeFeatureFlags,
fetchBridgeQuotes,
@@ -16,14 +18,23 @@ import {
// TODO: Remove restricted import
// eslint-disable-next-line import/no-restricted-paths
import { fetchTopAssetsList } from '../../../../ui/pages/swaps/swaps.util';
-import { decimalToHex } from '../../../../shared/modules/conversion.utils';
-// TODO: Remove restricted import
-// eslint-disable-next-line import/no-restricted-paths
-import { QuoteRequest } from '../../../../ui/pages/bridge/types';
+import {
+ decimalToHex,
+ sumHexes,
+} from '../../../../shared/modules/conversion.utils';
+import {
+ L1GasFees,
+ QuoteRequest,
+ QuoteResponse,
+ TxData,
+ // TODO: Remove restricted import
+ // eslint-disable-next-line import/no-restricted-paths
+} from '../../../../ui/pages/bridge/types';
// TODO: Remove restricted import
// eslint-disable-next-line import/no-restricted-paths
import { isValidQuoteRequest } from '../../../../ui/pages/bridge/utils/quote';
import { hasSufficientBalance } from '../../../../shared/modules/bridge-utils/balance';
+import { CHAIN_IDS } from '../../../../shared/constants/network';
import {
BRIDGE_CONTROLLER_NAME,
DEFAULT_BRIDGE_CONTROLLER_STATE,
@@ -53,7 +64,21 @@ export default class BridgeController extends StaticIntervalPollingController<
> {
#abortController: AbortController | undefined;
- constructor({ messenger }: { messenger: BridgeControllerMessenger }) {
+ #getLayer1GasFee: (params: {
+ transactionParams: TransactionParams;
+ chainId: ChainId;
+ }) => Promise;
+
+ constructor({
+ messenger,
+ getLayer1GasFee,
+ }: {
+ messenger: BridgeControllerMessenger;
+ getLayer1GasFee: (params: {
+ transactionParams: TransactionParams;
+ chainId: ChainId;
+ }) => Promise;
+ }) {
super({
name: BRIDGE_CONTROLLER_NAME,
metadata,
@@ -91,6 +116,8 @@ export default class BridgeController extends StaticIntervalPollingController<
`${BRIDGE_CONTROLLER_NAME}:getBridgeERC20Allowance`,
this.getBridgeERC20Allowance.bind(this),
);
+
+ this.#getLayer1GasFee = getLayer1GasFee;
}
_executePoll = async (
@@ -226,10 +253,12 @@ export default class BridgeController extends StaticIntervalPollingController<
this.stopAllPolling();
}
+ const quotesWithL1GasFees = await this.#appendL1GasFees(quotes);
+
this.update((_state) => {
_state.bridgeState = {
..._state.bridgeState,
- quotes,
+ quotes: quotesWithL1GasFees,
quotesLastFetched: Date.now(),
quotesLoadingStatus: RequestStatus.FETCHED,
quotesRefreshCount: newQuotesRefreshCount,
@@ -253,6 +282,45 @@ export default class BridgeController extends StaticIntervalPollingController<
}
};
+ #appendL1GasFees = async (
+ quotes: QuoteResponse[],
+ ): Promise<(QuoteResponse & L1GasFees)[]> => {
+ return await Promise.all(
+ quotes.map(async (quoteResponse) => {
+ const { quote, trade, approval } = quoteResponse;
+ const chainId = add0x(decimalToHex(quote.srcChainId)) as ChainId;
+ if (
+ [CHAIN_IDS.OPTIMISM.toString(), CHAIN_IDS.BASE.toString()].includes(
+ chainId,
+ )
+ ) {
+ const getTxParams = (txData: TxData) => ({
+ from: txData.from,
+ to: txData.to,
+ value: txData.value,
+ data: txData.data,
+ gasLimit: txData.gasLimit?.toString(),
+ });
+ const approvalL1GasFees = approval
+ ? await this.#getLayer1GasFee({
+ transactionParams: getTxParams(approval),
+ chainId,
+ })
+ : '0';
+ const tradeL1GasFees = await this.#getLayer1GasFee({
+ transactionParams: getTxParams(trade),
+ chainId,
+ });
+ return {
+ ...quoteResponse,
+ l1GasFeesInHexWei: sumHexes(approvalL1GasFees, tradeL1GasFees),
+ };
+ }
+ return quoteResponse;
+ }),
+ );
+ };
+
#setTopAssets = async (
chainId: Hex,
stateKey: 'srcTopAssets' | 'destTopAssets',
diff --git a/app/scripts/controllers/bridge/types.ts b/app/scripts/controllers/bridge/types.ts
index 577a9fa99836..c9e221b82418 100644
--- a/app/scripts/controllers/bridge/types.ts
+++ b/app/scripts/controllers/bridge/types.ts
@@ -9,9 +9,13 @@ import {
NetworkControllerGetSelectedNetworkClientAction,
} from '@metamask/network-controller';
import { SwapsTokenObject } from '../../../../shared/constants/swaps';
-// TODO: Remove restricted import
-// eslint-disable-next-line import/no-restricted-paths
-import { QuoteRequest, QuoteResponse } from '../../../../ui/pages/bridge/types';
+import {
+ L1GasFees,
+ QuoteRequest,
+ QuoteResponse,
+ // TODO: Remove restricted import
+ // eslint-disable-next-line import/no-restricted-paths
+} from '../../../../ui/pages/bridge/types';
import BridgeController from './bridge-controller';
import { BRIDGE_CONTROLLER_NAME, RequestStatus } from './constants';
@@ -39,7 +43,7 @@ export type BridgeControllerState = {
destTokens: Record;
destTopAssets: { address: string }[];
quoteRequest: Partial;
- quotes: QuoteResponse[];
+ quotes: (QuoteResponse & L1GasFees)[];
quotesLastFetched?: number;
quotesLoadingStatus?: RequestStatus;
quotesRefreshCount: number;
diff --git a/app/scripts/controllers/permissions/specifications.js b/app/scripts/controllers/permissions/specifications.js
index fffc9ae44f49..b0b2051b10f5 100644
--- a/app/scripts/controllers/permissions/specifications.js
+++ b/app/scripts/controllers/permissions/specifications.js
@@ -412,6 +412,7 @@ export const unrestrictedMethods = Object.freeze([
'snap_createInterface',
'snap_updateInterface',
'snap_getInterfaceState',
+ 'snap_getInterfaceContext',
'snap_resolveInterface',
'snap_getCurrencyRate',
///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
diff --git a/app/scripts/controllers/swaps/index.ts b/app/scripts/controllers/swaps/index.ts
index 308021cd14cf..9160eb0b66e1 100644
--- a/app/scripts/controllers/swaps/index.ts
+++ b/app/scripts/controllers/swaps/index.ts
@@ -383,12 +383,11 @@ export default class SwapsController extends BaseController<
const [firstQuote] = Object.values(newQuotes);
// For a user to be able to swap a token, they need to have approved the MetaSwap contract to withdraw that token.
- // _getERC20Allowance() returns the amount of the token they have approved for withdrawal. If that amount is greater
- // than 0, it means that approval has already occurred and is not needed. Otherwise, for tokens to be swapped, a new
- // call of the ERC-20 approve method is required.
+ // _getERC20Allowance() returns the amount of the token they have approved for withdrawal. If that amount is either
+ // zero or less than the soucreAmount of the swap, a new call of the ERC-20 approve method is required.
approvalRequired =
firstQuote.approvalNeeded &&
- allowance.eq(0) &&
+ (allowance.eq(0) || allowance.lt(firstQuote.sourceAmount)) &&
firstQuote.aggregator !== 'wrappedNative';
if (!approvalRequired) {
newQuotes = mapValues(newQuotes, (quote) => ({
diff --git a/app/scripts/lib/accounts/BalancesController.test.ts b/app/scripts/lib/accounts/BalancesController.test.ts
index e8ddd89f021e..982df0289fea 100644
--- a/app/scripts/lib/accounts/BalancesController.test.ts
+++ b/app/scripts/lib/accounts/BalancesController.test.ts
@@ -6,6 +6,7 @@ import {
InternalAccount,
} from '@metamask/keyring-api';
import { createMockInternalAccount } from '../../../../test/jest/mocks';
+import { MultichainNetworks } from '../../../../shared/constants/multichain/networks';
import {
BalancesController,
AllowedActions,
@@ -25,6 +26,9 @@ const mockBtcAccount = createMockInternalAccount({
name: 'mock-btc-snap',
enabled: true,
},
+ options: {
+ scope: MultichainNetworks.BITCOIN_TESTNET,
+ },
});
const mockBalanceResult = {
diff --git a/app/scripts/lib/accounts/BalancesController.ts b/app/scripts/lib/accounts/BalancesController.ts
index e657fe47e64f..588053d6ea2a 100644
--- a/app/scripts/lib/accounts/BalancesController.ts
+++ b/app/scripts/lib/accounts/BalancesController.ts
@@ -13,6 +13,7 @@ import {
type CaipAssetType,
type InternalAccount,
isEvmAccountType,
+ SolAccountType,
} from '@metamask/keyring-api';
import type { HandleSnapRequest } from '@metamask/snaps-controllers';
import type { SnapId } from '@metamask/snaps-sdk';
@@ -23,6 +24,8 @@ import type {
AccountsControllerAccountRemovedEvent,
AccountsControllerListMultichainAccountsAction,
} from '@metamask/accounts-controller';
+import { MultichainNetworks } from '../../../../shared/constants/multichain/networks';
+import { MULTICHAIN_NETWORK_TO_ASSET_TYPES } from '../../../../shared/constants/multichain/assets';
import { isBtcMainnetAddress } from '../../../../shared/lib/multichain';
import { BalancesTracker } from './BalancesTracker';
@@ -122,13 +125,17 @@ const balancesControllerMetadata = {
},
};
-const BTC_TESTNET_ASSETS = ['bip122:000000000933ea01ad0ee984209779ba/slip44:0'];
-const BTC_MAINNET_ASSETS = ['bip122:000000000019d6689c085ae165831e93/slip44:0'];
const BTC_AVG_BLOCK_TIME = 10 * 60 * 1000; // 10 minutes in milliseconds
+const SOLANA_AVG_BLOCK_TIME = 400; // 400 milliseconds
// NOTE: We set an interval of half the average block time to mitigate when our interval
// is de-synchronized with the actual block time.
-export const BALANCES_UPDATE_TIME = BTC_AVG_BLOCK_TIME / 2;
+export const BTC_BALANCES_UPDATE_TIME = BTC_AVG_BLOCK_TIME / 2;
+
+const BALANCE_CHECK_INTERVALS = {
+ [BtcAccountType.P2wpkh]: BTC_BALANCES_UPDATE_TIME,
+ [SolAccountType.DataAccount]: SOLANA_AVG_BLOCK_TIME,
+};
/**
* The BalancesController is responsible for fetching and caching account
@@ -165,7 +172,7 @@ export class BalancesController extends BaseController<
// Register all non-EVM accounts into the tracker
for (const account of this.#listAccounts()) {
if (this.#isNonEvmAccount(account)) {
- this.#tracker.track(account.id, BALANCES_UPDATE_TIME);
+ this.#tracker.track(account.id, this.#getBlockTimeFor(account));
}
}
@@ -193,6 +200,23 @@ export class BalancesController extends BaseController<
this.#tracker.stop();
}
+ /**
+ * Gets the block time for a given account.
+ *
+ * @param account - The account to get the block time for.
+ * @returns The block time for the account.
+ */
+ #getBlockTimeFor(account: InternalAccount): number {
+ if (account.type in BALANCE_CHECK_INTERVALS) {
+ return BALANCE_CHECK_INTERVALS[
+ account.type as keyof typeof BALANCE_CHECK_INTERVALS
+ ];
+ }
+ throw new Error(
+ `Unsupported account type for balance tracking: ${account.type}`,
+ );
+ }
+
/**
* Lists the multichain accounts coming from the `AccountsController`.
*
@@ -207,15 +231,16 @@ export class BalancesController extends BaseController<
/**
* Lists the accounts that we should get balances for.
*
- * Currently, we only get balances for P2WPKH accounts, but this will change
- * in the future when we start support other non-EVM account types.
- *
* @returns A list of accounts that we should get balances for.
*/
#listAccounts(): InternalAccount[] {
const accounts = this.#listMultichainAccounts();
- return accounts.filter((account) => account.type === BtcAccountType.P2wpkh);
+ return accounts.filter(
+ (account) =>
+ account.type === SolAccountType.DataAccount ||
+ account.type === BtcAccountType.P2wpkh,
+ );
}
/**
@@ -249,12 +274,13 @@ export class BalancesController extends BaseController<
const partialState: BalancesControllerState = { balances: {} };
if (account.metadata.snap) {
+ const scope = this.#getScopeFrom(account);
+ const assetTypes = MULTICHAIN_NETWORK_TO_ASSET_TYPES[scope];
+
partialState.balances[account.id] = await this.#getBalances(
account.id,
account.metadata.snap.id,
- isBtcMainnetAddress(account.address)
- ? BTC_MAINNET_ASSETS
- : BTC_TESTNET_ASSETS,
+ assetTypes,
);
}
@@ -312,7 +338,7 @@ export class BalancesController extends BaseController<
return;
}
- this.#tracker.track(account.id, BTC_AVG_BLOCK_TIME);
+ this.#tracker.track(account.id, this.#getBlockTimeFor(account));
// NOTE: Unfortunately, we cannot update the balance right away here, because
// messenger's events are running synchronously and fetching the balance is
// asynchronous.
@@ -376,4 +402,33 @@ export class BalancesController extends BaseController<
})) as Promise,
});
}
+
+ /**
+ * Gets the network scope for a given account.
+ *
+ * @param account - The account to get the scope for.
+ * @returns The network scope for the account.
+ * @throws If the account type is unknown or unsupported.
+ */
+ #getScopeFrom(account: InternalAccount): MultichainNetworks {
+ // TODO: Use the new `account.scopes` once available in the `keyring-api`.
+
+ // For Bitcoin accounts, we get the scope based on the address format.
+ if (account.type === BtcAccountType.P2wpkh) {
+ if (isBtcMainnetAddress(account.address)) {
+ return MultichainNetworks.BITCOIN;
+ }
+ return MultichainNetworks.BITCOIN_TESTNET;
+ }
+
+ // For Solana accounts, we know we have a `scope` on the account's `options` bag.
+ if (account.type === SolAccountType.DataAccount) {
+ if (!account.options.scope) {
+ throw new Error('Solana account scope is undefined');
+ }
+ return account.options.scope as MultichainNetworks;
+ }
+
+ throw new Error(`Unsupported non-EVM account type: ${account.type}`);
+ }
}
diff --git a/app/scripts/lib/manifestFlags.ts b/app/scripts/lib/manifestFlags.ts
index 93925bf63a0c..5804c7391973 100644
--- a/app/scripts/lib/manifestFlags.ts
+++ b/app/scripts/lib/manifestFlags.ts
@@ -11,6 +11,7 @@ export type ManifestFlags = {
};
sentry?: {
tracesSampleRate?: number;
+ lazyLoadSubSampleRate?: number; // multiply by tracesSampleRate to get the actual probability
forceEnable?: boolean;
};
};
@@ -27,6 +28,14 @@ interface WebExtensionManifestWithFlags
* @returns flags if they exist, otherwise an empty object
*/
export function getManifestFlags(): ManifestFlags {
+ // If this is running in a unit test, there's no manifest, so just return an empty object
+ if (
+ process.env.JEST_WORKER_ID === undefined ||
+ !browser.runtime.getManifest
+ ) {
+ return {};
+ }
+
return (
(browser.runtime.getManifest() as WebExtensionManifestWithFlags)._flags ||
{}
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 548b2f9d940c..55888558c6d6 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -157,7 +157,6 @@ import {
NotificationServicesPushController,
NotificationServicesController,
} from '@metamask/notification-services-controller';
-import { isProduction } from '../../shared/modules/environment';
import {
methodsRequiringNetworkSwitch,
methodsThatCanSwitchNetworkWithoutApproval,
@@ -244,6 +243,7 @@ import { getProviderConfig } from '../../shared/modules/selectors/networks';
import { endTrace, trace } from '../../shared/lib/trace';
// eslint-disable-next-line import/no-restricted-paths
import { isSnapId } from '../../ui/helpers/utils/snaps';
+import { BridgeStatusAction } from '../../shared/types/bridge-status';
import { BalancesController as MultichainBalancesController } from './lib/accounts/BalancesController';
import {
///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
@@ -372,6 +372,8 @@ import {
import createTracingMiddleware from './lib/createTracingMiddleware';
import { PatchStore } from './lib/PatchStore';
import { sanitizeUIState } from './lib/state-utils';
+import BridgeStatusController from './controllers/bridge-status/bridge-status-controller';
+import { BRIDGE_STATUS_CONTROLLER_NAME } from './controllers/bridge-status/constants';
export const METAMASK_CONTROLLER_EVENTS = {
// Fired after state changes that impact the extension badge (unapproved msg count)
@@ -1609,7 +1611,7 @@ export default class MetamaskController extends EventEmitter {
},
},
env: {
- isAccountSyncingEnabled: !isProduction() && isManifestV3,
+ isAccountSyncingEnabled: isManifestV3,
},
messenger: this.controllerMessenger.getRestricted({
name: 'UserStorageController',
@@ -1941,6 +1943,8 @@ export default class MetamaskController extends EventEmitter {
queryEntireHistory: false,
updateTransactions: false,
},
+ isFirstTimeInteractionEnabled: () =>
+ this.preferencesController.state.securityAlertsEnabled,
isMultichainEnabled: process.env.TRANSACTION_MULTICHAIN,
isSimulationEnabled: () =>
this.preferencesController.state.useTransactionSimulations,
@@ -2178,6 +2182,26 @@ export default class MetamaskController extends EventEmitter {
});
this.bridgeController = new BridgeController({
messenger: bridgeControllerMessenger,
+ // TODO: Remove once TransactionController exports this action type
+ getLayer1GasFee: this.txController.getLayer1GasFee.bind(
+ this.txController,
+ ),
+ });
+
+ const bridgeStatusControllerMessenger =
+ this.controllerMessenger.getRestricted({
+ name: BRIDGE_STATUS_CONTROLLER_NAME,
+ allowedActions: [
+ 'AccountsController:getSelectedAccount',
+ 'NetworkController:getNetworkClientById',
+ 'NetworkController:findNetworkClientIdByChainId',
+ 'NetworkController:getState',
+ ],
+ allowedEvents: [],
+ });
+ this.bridgeStatusController = new BridgeStatusController({
+ messenger: bridgeStatusControllerMessenger,
+ state: initState.BridgeStatusController,
});
const smartTransactionsControllerMessenger =
@@ -2419,6 +2443,7 @@ export default class MetamaskController extends EventEmitter {
SignatureController: this.signatureController,
SwapsController: this.swapsController,
BridgeController: this.bridgeController,
+ BridgeStatusController: this.bridgeStatusController,
EnsController: this.ensController,
ApprovalController: this.approvalController,
PPOMController: this.ppomController,
@@ -3020,7 +3045,11 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger.subscribe(
`${this.snapController.name}:snapInstalled`,
- (truncatedSnap, origin) => {
+ (truncatedSnap, origin, preinstalled) => {
+ if (preinstalled) {
+ return;
+ }
+
const snapId = truncatedSnap.id;
const snapCategory = this._getSnapMetadata(snapId)?.category;
this.metaMetricsController.trackEvent({
@@ -3038,7 +3067,11 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger.subscribe(
`${this.snapController.name}:snapUpdated`,
- (newSnap, oldVersion, origin) => {
+ (newSnap, oldVersion, origin, preinstalled) => {
+ if (preinstalled) {
+ return;
+ }
+
const snapId = newSnap.id;
const snapCategory = this._getSnapMetadata(snapId)?.category;
this.metaMetricsController.trackEvent({
@@ -4002,6 +4035,13 @@ export default class MetamaskController extends EventEmitter {
`${BRIDGE_CONTROLLER_NAME}:${BridgeUserAction.UPDATE_QUOTE_PARAMS}`,
),
+ // Bridge Status
+ [BridgeStatusAction.START_POLLING_FOR_BRIDGE_TX_STATUS]:
+ this.controllerMessenger.call.bind(
+ this.controllerMessenger,
+ `${BRIDGE_STATUS_CONTROLLER_NAME}:${BridgeStatusAction.START_POLLING_FOR_BRIDGE_TX_STATUS}`,
+ ),
+
// Smart Transactions
fetchSmartTransactionFees: smartTransactionsController.getFees.bind(
smartTransactionsController,
@@ -4992,6 +5032,10 @@ export default class MetamaskController extends EventEmitter {
address: selectedAddress,
ignoreNetwork: false,
});
+ this.bridgeStatusController.wipeBridgeStatus({
+ address: selectedAddress,
+ ignoreNetwork: false,
+ });
this.networkController.resetConnection();
return selectedAddress;
@@ -6085,6 +6129,12 @@ export default class MetamaskController extends EventEmitter {
origin,
...args,
).state,
+ getInterfaceContext: (...args) =>
+ this.controllerMessenger.call(
+ 'SnapInterfaceController:getInterface',
+ origin,
+ ...args,
+ ).context,
createInterface: this.controllerMessenger.call.bind(
this.controllerMessenger,
'SnapInterfaceController:createInterface',
diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js
index 0cd4fba34589..0e08a0ac27c0 100644
--- a/app/scripts/metamask-controller.test.js
+++ b/app/scripts/metamask-controller.test.js
@@ -41,10 +41,11 @@ import { createMockInternalAccount } from '../../test/jest/mocks';
import { mockNetworkState } from '../../test/stub/networks';
import {
BalancesController as MultichainBalancesController,
- BALANCES_UPDATE_TIME as MULTICHAIN_BALANCES_UPDATE_TIME,
+ BTC_BALANCES_UPDATE_TIME as MULTICHAIN_BALANCES_UPDATE_TIME,
} from './lib/accounts/BalancesController';
import { BalancesTracker as MultichainBalancesTracker } from './lib/accounts/BalancesTracker';
import { deferredPromise } from './lib/util';
+import { METAMASK_COOKIE_HANDLER } from './constants/stream';
import MetaMaskController, {
ONE_KEY_VIA_TREZOR_MINOR_VERSION,
} from './metamask-controller';
@@ -1273,6 +1274,129 @@ describe('MetaMaskController', () => {
expect(mockKeyring.destroy).toHaveBeenCalledTimes(1);
});
});
+ describe('#setupPhishingCommunication', () => {
+ beforeEach(() => {
+ jest.spyOn(metamaskController, 'safelistPhishingDomain');
+ jest.spyOn(metamaskController, 'backToSafetyPhishingWarning');
+ metamaskController.preferencesController.setUsePhishDetect(true);
+ });
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+ it('creates a phishing stream with safelistPhishingDomain and backToSafetyPhishingWarning handler', async () => {
+ const safelistPhishingDomainRequest = {
+ name: 'metamask-phishing-safelist',
+ data: {
+ id: 1,
+ method: 'safelistPhishingDomain',
+ params: ['mockHostname'],
+ },
+ };
+ const backToSafetyPhishingWarningRequest = {
+ name: 'metamask-phishing-safelist',
+ data: { id: 2, method: 'backToSafetyPhishingWarning', params: [] },
+ };
+
+ const { promise, resolve } = deferredPromise();
+ const { promise: promiseStream, resolve: resolveStream } =
+ deferredPromise();
+ const streamTest = createThroughStream((chunk, _, cb) => {
+ if (chunk.name !== 'metamask-phishing-safelist') {
+ cb();
+ return;
+ }
+ resolve();
+ cb(null, chunk);
+ });
+
+ metamaskController.setupPhishingCommunication({
+ connectionStream: streamTest,
+ });
+
+ streamTest.write(safelistPhishingDomainRequest, null, () => {
+ expect(
+ metamaskController.safelistPhishingDomain,
+ ).toHaveBeenCalledWith('mockHostname');
+ });
+ streamTest.write(backToSafetyPhishingWarningRequest, null, () => {
+ expect(
+ metamaskController.backToSafetyPhishingWarning,
+ ).toHaveBeenCalled();
+ resolveStream();
+ });
+
+ await promise;
+ streamTest.end();
+ await promiseStream;
+ });
+ });
+
+ describe('#setUpCookieHandlerCommunication', () => {
+ let localMetaMaskController;
+ beforeEach(() => {
+ localMetaMaskController = new MetaMaskController({
+ showUserConfirmation: noop,
+ encryptor: mockEncryptor,
+ initState: {
+ ...cloneDeep(firstTimeState),
+ MetaMetricsController: {
+ metaMetricsId: 'MOCK_METRICS_ID',
+ participateInMetaMetrics: true,
+ dataCollectionForMarketing: true,
+ },
+ },
+ initLangCode: 'en_US',
+ platform: {
+ showTransactionNotification: () => undefined,
+ getVersion: () => 'foo',
+ },
+ browser: browserPolyfillMock,
+ infuraProjectId: 'foo',
+ isFirstMetaMaskControllerSetup: true,
+ });
+ jest.spyOn(localMetaMaskController, 'getCookieFromMarketingPage');
+ });
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+ it('creates a cookie handler communication stream with getCookieFromMarketingPage handler', async () => {
+ const attributionRequest = {
+ name: METAMASK_COOKIE_HANDLER,
+ data: {
+ id: 1,
+ method: 'getCookieFromMarketingPage',
+ params: [{ ga_client_id: 'XYZ.ABC' }],
+ },
+ };
+
+ const { promise, resolve } = deferredPromise();
+ const { promise: promiseStream, resolve: resolveStream } =
+ deferredPromise();
+ const streamTest = createThroughStream((chunk, _, cb) => {
+ if (chunk.name !== METAMASK_COOKIE_HANDLER) {
+ cb();
+ return;
+ }
+ resolve();
+ cb(null, chunk);
+ });
+
+ localMetaMaskController.setUpCookieHandlerCommunication({
+ connectionStream: streamTest,
+ });
+
+ streamTest.write(attributionRequest, null, () => {
+ expect(
+ localMetaMaskController.getCookieFromMarketingPage,
+ ).toHaveBeenCalledWith({ ga_client_id: 'XYZ.ABC' });
+ resolveStream();
+ });
+
+ await promise;
+ streamTest.end();
+ await promiseStream;
+ });
+ });
describe('#setupUntrustedCommunicationEip1193', () => {
const mockTxParams = { from: TEST_ADDRESS };
diff --git a/app/scripts/migrations/088.ts b/app/scripts/migrations/088.ts
index 274b93624a85..27f324228790 100644
--- a/app/scripts/migrations/088.ts
+++ b/app/scripts/migrations/088.ts
@@ -1,5 +1,5 @@
import { hasProperty, Hex, isObject, isStrictHexString } from '@metamask/utils';
-import { BN } from 'ethereumjs-util';
+import BN from 'bn.js';
import { cloneDeep, mapKeys } from 'lodash';
import log from 'loglevel';
@@ -302,6 +302,6 @@ function toHex(value: number | string | BN): Hex {
}
const hexString = BN.isBN(value)
? value.toString(16)
- : new BN(value.toString(), 10).toString(16);
+ : new BN(value.toString(10), 10).toString(16);
return `0x${hexString}`;
}
diff --git a/app/scripts/migrations/132.test.ts b/app/scripts/migrations/132.test.ts
new file mode 100644
index 000000000000..fb53f90a38fb
--- /dev/null
+++ b/app/scripts/migrations/132.test.ts
@@ -0,0 +1,73 @@
+import { migrate, version } from './132';
+
+const oldVersion = 131;
+
+describe('migration #132', () => {
+ it('updates the version metadata', async () => {
+ const oldStorage = {
+ meta: { version: oldVersion },
+ data: {},
+ };
+
+ const newStorage = await migrate(oldStorage);
+
+ expect(newStorage.meta).toStrictEqual({ version });
+ });
+
+ it('does nothing if no preferences controller state is set', async () => {
+ const oldState = {
+ OtherController: {},
+ };
+
+ const transformedState = await migrate({
+ meta: { version: oldVersion },
+ data: oldState,
+ });
+
+ expect(transformedState.data).toEqual(oldState);
+ });
+
+ it('adds preferences property to the controller if it is not set and set the preference to true if migration runs', async () => {
+ const oldState = { PreferencesController: {} };
+
+ const expectedState = {
+ PreferencesController: {
+ preferences: {
+ redesignedTransactionsEnabled: true,
+ },
+ },
+ };
+
+ const transformedState = await migrate({
+ meta: { version: oldVersion },
+ data: oldState,
+ });
+
+ expect(transformedState.data).toEqual(expectedState);
+ });
+
+ it('changes property to true if migration runs', async () => {
+ const oldState = {
+ PreferencesController: {
+ preferences: {
+ redesignedTransactionsEnabled: false,
+ },
+ },
+ };
+
+ const expectedState = {
+ PreferencesController: {
+ preferences: {
+ redesignedTransactionsEnabled: true,
+ },
+ },
+ };
+
+ const transformedState = await migrate({
+ meta: { version: oldVersion },
+ data: oldState,
+ });
+
+ expect(transformedState.data).toEqual(expectedState);
+ });
+});
diff --git a/app/scripts/migrations/132.ts b/app/scripts/migrations/132.ts
new file mode 100644
index 000000000000..ec12595d389a
--- /dev/null
+++ b/app/scripts/migrations/132.ts
@@ -0,0 +1,58 @@
+import { isObject } from '@metamask/utils';
+import { cloneDeep } from 'lodash';
+
+type VersionedData = {
+ meta: { version: number };
+ data: Record;
+};
+
+export const version = 132;
+
+/**
+ * This migration sets `redesignedTransactionsEnabled` as true by default in preferences in PreferencesController.
+ *
+ * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist.
+ * @param originalVersionedData.meta - State metadata.
+ * @param originalVersionedData.meta.version - The current state version.
+ * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller.
+ * @returns Updated versioned MetaMask extension state.
+ */
+export async function migrate(
+ originalVersionedData: VersionedData,
+): Promise {
+ const versionedData = cloneDeep(originalVersionedData);
+ versionedData.meta.version = version;
+ transformState(versionedData.data);
+ return versionedData;
+}
+
+function transformState(
+ state: Record,
+): Record {
+ if (!isObject(state?.PreferencesController)) {
+ return state;
+ }
+
+ if (!isObject(state.PreferencesController?.preferences)) {
+ state.PreferencesController = {
+ ...state.PreferencesController,
+ preferences: {},
+ };
+ }
+
+ const preferencesControllerState = state.PreferencesController as Record<
+ string,
+ unknown
+ >;
+
+ const preferences = preferencesControllerState.preferences as Record<
+ string,
+ unknown
+ >;
+
+ // `redesignedTransactionsEnabled` was previously set to `false` by
+ // default in `124.ts`
+ preferences.redesignedTransactionsEnabled = true;
+
+ return state;
+}
diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js
index d2c63eb2e35c..6cde292ba55d 100644
--- a/app/scripts/migrations/index.js
+++ b/app/scripts/migrations/index.js
@@ -152,6 +152,7 @@ const migrations = [
require('./129'),
require('./130'),
require('./131'),
+ require('./132'),
];
export default migrations;
diff --git a/app/scripts/streams/provider-stream.ts b/app/scripts/streams/provider-stream.ts
index 9616253815e8..82b159130242 100644
--- a/app/scripts/streams/provider-stream.ts
+++ b/app/scripts/streams/provider-stream.ts
@@ -33,7 +33,7 @@ let legacyExtMux: ObjectMultiplex,
let extensionMux: ObjectMultiplex,
extensionChannel: Substream,
- extensionPort: browser.Runtime.Port,
+ extensionPort: browser.Runtime.Port | null,
extensionStream: PortStream | null,
pageMux: ObjectMultiplex,
pageChannel: Substream;
@@ -65,7 +65,7 @@ const setupPageStreams = () => {
// The field below is used to ensure that replay is done only once for each restart.
let METAMASK_EXTENSION_CONNECT_SENT = false;
-const setupExtensionStreams = () => {
+export const setupExtensionStreams = () => {
METAMASK_EXTENSION_CONNECT_SENT = true;
extensionPort = browser.runtime.connect({ name: CONTENT_SCRIPT });
extensionStream = new PortStream(extensionPort);
@@ -226,19 +226,35 @@ const onMessageSetUpExtensionStreams = (msg: MessageType) => {
return undefined;
};
+/**
+ * Ends two-way communication streams between browser extension and
+ * the local per-page browser context.
+ */
+export function destroyStreams() {
+ if (!extensionPort) {
+ return;
+ }
+ extensionPort.onDisconnect.removeListener(onDisconnectDestroyStreams);
+
+ destroyExtensionStreams();
+ destroyLegacyExtensionStreams();
+
+ extensionPort.disconnect();
+ extensionPort = null;
+
+ METAMASK_EXTENSION_CONNECT_SENT = false;
+}
+
/**
* This listener destroys the extension streams when the extension port is disconnected,
* so that streams may be re-established later when the extension port is reconnected.
*
* @param [err] - Stream connection error
*/
-export const onDisconnectDestroyStreams = (err: unknown) => {
+export function onDisconnectDestroyStreams(err: unknown) {
const lastErr = err || checkForLastError();
- extensionPort.onDisconnect.removeListener(onDisconnectDestroyStreams);
-
- destroyExtensionStreams();
- destroyLegacyExtensionStreams();
+ destroyStreams();
/**
* If an error is found, reset the streams. When running two or more dapps, resetting the service
@@ -251,7 +267,7 @@ export const onDisconnectDestroyStreams = (err: unknown) => {
console.warn(`${lastErr} Resetting the streams.`);
setTimeout(setupExtensionStreams, 1000);
}
-};
+}
/**
* Initializes two-way communication streams between the browser extension and
diff --git a/builds.yml b/builds.yml
index 2857b9b9f5ad..c27d79a9a034 100644
--- a/builds.yml
+++ b/builds.yml
@@ -298,6 +298,9 @@ env:
# Enables the notifications feature within the build:
- NOTIFICATIONS: ''
+ # This will be defined if running a unit test
+ - JEST_WORKER_ID: undefined
+
- METAMASK_RAMP_API_CONTENT_BASE_URL: https://on-ramp-content.api.cx.metamask.io
###
diff --git a/jest.integration.config.js b/jest.integration.config.js
index d7236b832aed..685080330fb3 100644
--- a/jest.integration.config.js
+++ b/jest.integration.config.js
@@ -25,7 +25,8 @@ module.exports = {
setupFilesAfterEnv: ['/test/integration/config/setupAfter.js'],
testMatch: ['/test/integration/**/*.test.(js|ts|tsx)'],
testPathIgnorePatterns: ['/test/integration/config/*'],
- testTimeout: 5500,
+ // This was increased from 5500 to 10000 to when lazy loading was introduced
+ testTimeout: 10000,
// We have to specify the environment we are running in, which is jsdom. The
// default is 'node'. This can be modified *per file* using a comment at the
// head of the file. So it may be worthwhile to switch to 'node' in any
diff --git a/package.json b/package.json
index 0f963c3aab2b..b7adb1fcdcee 100644
--- a/package.json
+++ b/package.json
@@ -180,11 +180,6 @@
"eslint@npm:^8.7.0": "patch:eslint@npm%3A8.57.0#~/.yarn/patches/eslint-npm-8.57.0-4286e12a3a.patch",
"eth-query@^2.1.2": "patch:eth-query@npm%3A2.1.2#./.yarn/patches/eth-query-npm-2.1.2-7c6adc825f.patch",
"eth-query@^2.1.0": "patch:eth-query@npm%3A2.1.2#./.yarn/patches/eth-query-npm-2.1.2-7c6adc825f.patch",
- "ethereumjs-util@^5.1.1": "patch:ethereumjs-util@npm%3A5.2.1#./.yarn/patches/ethereumjs-util-npm-5.2.1-72b39f4e7e.patch",
- "ethereumjs-util@^5.1.2": "patch:ethereumjs-util@npm%3A5.2.1#./.yarn/patches/ethereumjs-util-npm-5.2.1-72b39f4e7e.patch",
- "ethereumjs-util@^5.1.5": "patch:ethereumjs-util@npm%3A5.2.1#./.yarn/patches/ethereumjs-util-npm-5.2.1-72b39f4e7e.patch",
- "ethereumjs-util@^5.0.0": "patch:ethereumjs-util@npm%3A5.2.1#./.yarn/patches/ethereumjs-util-npm-5.2.1-72b39f4e7e.patch",
- "ethereumjs-util@^5.2.0": "patch:ethereumjs-util@npm%3A5.2.1#./.yarn/patches/ethereumjs-util-npm-5.2.1-72b39f4e7e.patch",
"ethereumjs-util@^7.0.10": "patch:ethereumjs-util@npm%3A7.1.5#./.yarn/patches/ethereumjs-util-npm-7.1.5-5bb4d00000.patch",
"ethereumjs-util@^7.1.5": "patch:ethereumjs-util@npm%3A7.1.5#./.yarn/patches/ethereumjs-util-npm-7.1.5-5bb4d00000.patch",
"ethereumjs-util@^7.1.4": "patch:ethereumjs-util@npm%3A7.1.5#./.yarn/patches/ethereumjs-util-npm-7.1.5-5bb4d00000.patch",
@@ -234,7 +229,7 @@
"semver@7.3.8": "^7.5.4",
"@trezor/schema-utils@npm:1.0.2": "patch:@trezor/schema-utils@npm%3A1.0.2#~/.yarn/patches/@trezor-schema-utils-npm-1.0.2-7dd48689b2.patch",
"lavamoat-core@npm:^15.1.1": "patch:lavamoat-core@npm%3A15.1.1#~/.yarn/patches/lavamoat-core-npm-15.1.1-51fbe39988.patch",
- "@metamask/snaps-sdk": "^6.11.0",
+ "@metamask/snaps-sdk": "^6.12.0",
"@swc/types@0.1.5": "^0.1.6",
"@babel/core": "patch:@babel/core@npm%3A7.25.9#~/.yarn/patches/@babel-core-npm-7.25.9-4ae3bff7f3.patch",
"@babel/runtime": "patch:@babel/runtime@npm%3A7.25.9#~/.yarn/patches/@babel-runtime-npm-7.25.9-fe8c62510a.patch",
@@ -294,7 +289,7 @@
"@metamask/address-book-controller": "^6.0.0",
"@metamask/announcement-controller": "^7.0.0",
"@metamask/approval-controller": "^7.0.0",
- "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A45.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch",
+ "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch",
"@metamask/base-controller": "^7.0.0",
"@metamask/bitcoin-wallet-snap": "^0.8.2",
"@metamask/browser-passworder": "^4.3.0",
@@ -349,13 +344,13 @@
"@metamask/selected-network-controller": "^18.0.2",
"@metamask/signature-controller": "^23.0.0",
"@metamask/smart-transactions-controller": "^13.0.0",
- "@metamask/snaps-controllers": "^9.13.0",
+ "@metamask/snaps-controllers": "^9.14.0",
"@metamask/snaps-execution-environments": "^6.10.0",
- "@metamask/snaps-rpc-methods": "^11.5.1",
- "@metamask/snaps-sdk": "^6.11.0",
+ "@metamask/snaps-rpc-methods": "^11.6.0",
+ "@metamask/snaps-sdk": "^6.12.0",
"@metamask/snaps-utils": "^8.6.0",
"@metamask/solana-wallet-snap": "^0.1.9",
- "@metamask/transaction-controller": "^40.0.0",
+ "@metamask/transaction-controller": "^40.1.0",
"@metamask/user-operation-controller": "^13.0.0",
"@metamask/utils": "^10.0.1",
"@ngraveio/bc-ur": "^1.1.12",
@@ -757,7 +752,8 @@
"resolve-url-loader>es6-iterator>d>es5-ext": false,
"resolve-url-loader>es6-iterator>d>es5-ext>esniff>es5-ext": false,
"level>classic-level": false,
- "jest-preview": false
+ "jest-preview": false,
+ "@metamask/solana-wallet-snap>@solana/web3.js>bigint-buffer": false
}
},
"packageManager": "yarn@4.5.1"
diff --git a/privacy-snapshot.json b/privacy-snapshot.json
index b31462ad4bf7..36249b132bca 100644
--- a/privacy-snapshot.json
+++ b/privacy-snapshot.json
@@ -30,6 +30,7 @@
"goerli.infura.io",
"lattice.gridplus.io",
"linea-mainnet.infura.io",
+ "linea-sepolia.infura.io",
"localhost:8000",
"localhost:8545",
"mainnet.infura.io",
diff --git a/shared/constants/bridge.ts b/shared/constants/bridge.ts
index 10f2587d3fbd..8ad27dce4944 100644
--- a/shared/constants/bridge.ts
+++ b/shared/constants/bridge.ts
@@ -26,3 +26,7 @@ export const BRIDGE_CLIENT_ID = 'extension';
export const ETH_USDT_ADDRESS = '0xdac17f958d2ee523a2206206994597c13d831ec7';
export const METABRIDGE_ETHEREUM_ADDRESS =
'0x0439e60F02a8900a951603950d8D4527f400C3f1';
+export const BRIDGE_QUOTE_MAX_ETA_SECONDS = 60 * 60; // 1 hour
+export const BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE = 0.8; // if a quote returns in x times less return than the best quote, ignore it
+
+export const BRIDGE_PREFERRED_GAS_ESTIMATE = 'medium';
diff --git a/shared/constants/multichain/assets.ts b/shared/constants/multichain/assets.ts
index 23462d57d05a..58e0869ff045 100644
--- a/shared/constants/multichain/assets.ts
+++ b/shared/constants/multichain/assets.ts
@@ -1,3 +1,4 @@
+import { CaipAssetType } from '@metamask/keyring-api';
import { MultichainNetworks } from './networks';
export const MULTICHAIN_NATIVE_CURRENCY_TO_CAIP19 = {
@@ -13,3 +14,20 @@ export enum MultichainNativeAssets {
SOLANA_DEVNET = `${MultichainNetworks.SOLANA_DEVNET}/slip44:501`,
SOLANA_TESTNET = `${MultichainNetworks.SOLANA_TESTNET}/slip44:501`,
}
+
+/**
+ * Maps network identifiers to their corresponding native asset types.
+ * Each network is mapped to an array containing its native asset for consistency.
+ */
+export const MULTICHAIN_NETWORK_TO_ASSET_TYPES: Record<
+ MultichainNetworks,
+ CaipAssetType[]
+> = {
+ [MultichainNetworks.SOLANA]: [MultichainNativeAssets.SOLANA],
+ [MultichainNetworks.SOLANA_TESTNET]: [MultichainNativeAssets.SOLANA_TESTNET],
+ [MultichainNetworks.SOLANA_DEVNET]: [MultichainNativeAssets.SOLANA_DEVNET],
+ [MultichainNetworks.BITCOIN]: [MultichainNativeAssets.BITCOIN],
+ [MultichainNetworks.BITCOIN_TESTNET]: [
+ MultichainNativeAssets.BITCOIN_TESTNET,
+ ],
+};
diff --git a/shared/constants/multichain/networks.ts b/shared/constants/multichain/networks.ts
index f5a45138d88a..659228ba1199 100644
--- a/shared/constants/multichain/networks.ts
+++ b/shared/constants/multichain/networks.ts
@@ -1,4 +1,5 @@
import { CaipChainId } from '@metamask/utils';
+import { BtcAccountType, SolAccountType } from '@metamask/keyring-api';
import {
isBtcMainnetAddress,
isBtcTestnetAddress,
@@ -33,6 +34,11 @@ export enum MultichainNetworks {
SOLANA_TESTNET = 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z',
}
+export const MULTICHAIN_ACCOUNT_TYPE_TO_MAINNET = {
+ [BtcAccountType.P2wpkh]: MultichainNetworks.BITCOIN,
+ [SolAccountType.DataAccount]: MultichainNetworks.SOLANA,
+} as const;
+
export const BITCOIN_TOKEN_IMAGE_URL = './images/bitcoin-logo.svg';
export const SOLANA_TOKEN_IMAGE_URL = './images/solana-logo.svg';
diff --git a/shared/constants/network.ts b/shared/constants/network.ts
index 3fca971c338e..41f1d9fd0d95 100644
--- a/shared/constants/network.ts
+++ b/shared/constants/network.ts
@@ -301,7 +301,6 @@ export const CURRENCY_SYMBOLS = {
AVALANCHE: 'AVAX',
BNB: 'BNB',
BUSD: 'BUSD',
- BTC: 'BTC', // Do we wanna mix EVM and non-EVM here?
CELO: 'CELO',
DAI: 'DAI',
GNOSIS: 'XDAI',
@@ -322,8 +321,15 @@ export const CURRENCY_SYMBOLS = {
ONE: 'ONE',
} as const;
+// Non-EVM currency symbols
+export const NON_EVM_CURRENCY_SYMBOLS = {
+ BTC: 'BTC',
+ SOL: 'SOL',
+} as const;
+
const CHAINLIST_CURRENCY_SYMBOLS_MAP = {
...CURRENCY_SYMBOLS,
+ ...NON_EVM_CURRENCY_SYMBOLS,
BASE: 'ETH',
LINEA_MAINNET: 'ETH',
OPBNB: 'BNB',
diff --git a/shared/constants/transaction.ts b/shared/constants/transaction.ts
index 24b89140f941..38311de3be76 100644
--- a/shared/constants/transaction.ts
+++ b/shared/constants/transaction.ts
@@ -113,6 +113,11 @@ export enum TransactionGroupCategory {
* Transaction group representing a token swap through MetaMask Swaps, where the final token is sent to another address.
*/
swapAndSend = 'swapAndSend',
+ /**
+ * Transaction group representing a token bridge through MetaMask Bridge,
+ * where the final token is sent to another chain.
+ */
+ bridge = 'bridge',
}
/**
diff --git a/shared/lib/multichain.ts b/shared/lib/multichain.ts
index 815d9d9e6763..26111a0970a2 100644
--- a/shared/lib/multichain.ts
+++ b/shared/lib/multichain.ts
@@ -1,6 +1,12 @@
-import { CaipNamespace, KnownCaipNamespace } from '@metamask/utils';
+import {
+ CaipNamespace,
+ isCaipChainId,
+ KnownCaipNamespace,
+ parseCaipChainId,
+} from '@metamask/utils';
import { validate, Network } from 'bitcoin-address-validation';
import { isAddress } from '@solana/addresses';
+import { InternalAccount, isEvmAccountType } from '@metamask/keyring-api';
/**
* Returns whether an address is on the Bitcoin mainnet.
@@ -59,3 +65,21 @@ export function getCaipNamespaceFromAddress(address: string): CaipNamespace {
// Defaults to "Ethereum" for all other cases for now.
return KnownCaipNamespace.Eip155;
}
+
+export function isCurrentChainCompatibleWithAccount(
+ chainId: string,
+ account: InternalAccount,
+): boolean {
+ if (!chainId) {
+ return false;
+ }
+
+ if (isCaipChainId(chainId)) {
+ const { namespace } = parseCaipChainId(chainId);
+ return namespace === getCaipNamespaceFromAddress(account.address);
+ }
+
+ // For EVM accounts, we do not check the chain ID format, but we just expect it
+ // to be defined.
+ return isEvmAccountType(account.type);
+}
diff --git a/shared/lib/trace.ts b/shared/lib/trace.ts
index 0d58ddcdcfcc..4c05b098f120 100644
--- a/shared/lib/trace.ts
+++ b/shared/lib/trace.ts
@@ -18,6 +18,7 @@ export enum TraceName {
FirstRender = 'First Render',
GetState = 'Get State',
InitialActions = 'Initial Actions',
+ LazyLoadComponent = 'Lazy Load Component',
LoadScripts = 'Load Scripts',
Middleware = 'Middleware',
NestedTest1 = 'Nested Test 1',
diff --git a/shared/modules/conversion.utils.ts b/shared/modules/conversion.utils.ts
index 75da336eb8e6..ad83e9e8c634 100644
--- a/shared/modules/conversion.utils.ts
+++ b/shared/modules/conversion.utils.ts
@@ -1,6 +1,7 @@
import { Hex } from '@metamask/utils';
import { BigNumber } from 'bignumber.js';
-import { addHexPrefix, BN } from 'ethereumjs-util';
+import BN from 'bn.js';
+import { addHexPrefix } from 'ethereumjs-util';
import { EtherDenomination } from '../constants/common';
import { Numeric, NumericValue } from './Numeric';
@@ -184,6 +185,12 @@ export function decimalToHex(decimal: number | string | BigNumber | BN) {
return new Numeric(decimal, 10).toBase(16).toString();
}
+export function decimalToPrefixedHex(
+ decimal: number | string | BigNumber | BN,
+): Hex {
+ return new Numeric(decimal, 10).toPrefixedHexString() as Hex;
+}
+
export function hexToDecimal(hexValue: number | string | BigNumber | BN) {
return new Numeric(hexValue, 16).toBase(10).toString();
}
diff --git a/shared/modules/metametrics.test.ts b/shared/modules/metametrics.test.ts
index 9d6d17bc8040..b6fe2f8ac0a2 100644
--- a/shared/modules/metametrics.test.ts
+++ b/shared/modules/metametrics.test.ts
@@ -92,7 +92,6 @@ describe('getSmartTransactionMetricsProperties', () => {
cancellationReason: 'not_cancelled',
deadlineRatio: 0.6400288486480713,
minedHash: txHash,
- duplicated: true,
timedOut: true,
proxied: true,
minedTx: 'success',
@@ -112,7 +111,6 @@ describe('getSmartTransactionMetricsProperties', () => {
expect(result).toStrictEqual({
gas_included: true,
is_smart_transaction: true,
- smart_transaction_duplicated: true,
smart_transaction_proxied: true,
smart_transaction_timed_out: true,
});
diff --git a/shared/modules/metametrics.ts b/shared/modules/metametrics.ts
index b689891da1fb..389a2778a5b8 100644
--- a/shared/modules/metametrics.ts
+++ b/shared/modules/metametrics.ts
@@ -6,7 +6,6 @@ import { TransactionMetricsRequest } from '../../app/scripts/lib/transaction/met
type SmartTransactionMetricsProperties = {
is_smart_transaction: boolean;
gas_included: boolean;
- smart_transaction_duplicated?: boolean;
smart_transaction_timed_out?: boolean;
smart_transaction_proxied?: boolean;
};
@@ -31,8 +30,6 @@ export const getSmartTransactionMetricsProperties = (
if (!smartTransactionStatusMetadata) {
return properties;
}
- properties.smart_transaction_duplicated =
- smartTransactionStatusMetadata.duplicated;
properties.smart_transaction_timed_out =
smartTransactionStatusMetadata.timedOut;
properties.smart_transaction_proxied = smartTransactionStatusMetadata.proxied;
diff --git a/shared/types/bridge-status.ts b/shared/types/bridge-status.ts
new file mode 100644
index 000000000000..601a2209aaf9
--- /dev/null
+++ b/shared/types/bridge-status.ts
@@ -0,0 +1,146 @@
+// eslint-disable-next-line import/no-restricted-paths
+import { ChainId, Quote, QuoteResponse } from '../../ui/pages/bridge/types';
+
+// All fields need to be types not interfaces, same with their children fields
+// o/w you get a type error
+
+export enum StatusTypes {
+ UNKNOWN = 'UNKNOWN',
+ FAILED = 'FAILED',
+ PENDING = 'PENDING',
+ COMPLETE = 'COMPLETE',
+}
+
+export type StatusRequest = {
+ bridgeId: string; // lifi, socket, squid
+ srcTxHash: string; // lifi, socket, squid
+ bridge: string; // lifi, socket, squid
+ srcChainId: ChainId; // lifi, socket, squid
+ destChainId: ChainId; // lifi, socket, squid
+ quote?: Quote; // squid
+ refuel?: boolean; // lifi
+};
+
+export type Asset = {
+ chainId: ChainId;
+ address: string;
+ symbol: string;
+ name: string;
+ decimals: number;
+ icon?: string;
+};
+
+export type SrcChainStatus = {
+ chainId: ChainId;
+ txHash: string;
+ amount?: string;
+ token?: Asset;
+};
+
+export type DestChainStatus = {
+ chainId: ChainId;
+ txHash?: string;
+ amount?: string;
+ token?: Record | Asset;
+};
+
+export enum BridgeId {
+ HOP = 'hop',
+ CELER = 'celer',
+ CELERCIRCLE = 'celercircle',
+ CONNEXT = 'connext',
+ POLYGON = 'polygon',
+ AVALANCHE = 'avalanche',
+ MULTICHAIN = 'multichain',
+ AXELAR = 'axelar',
+ ACROSS = 'across',
+ STARGATE = 'stargate',
+}
+
+export enum FeeType {
+ METABRIDGE = 'metabridge',
+ REFUEL = 'refuel',
+}
+
+export type FeeData = {
+ amount: string;
+ asset: Asset;
+};
+
+export type Protocol = {
+ displayName?: string;
+ icon?: string;
+ name?: string; // for legacy quotes
+};
+
+export enum ActionTypes {
+ BRIDGE = 'bridge',
+ SWAP = 'swap',
+ REFUEL = 'refuel',
+}
+
+export type Step = {
+ action: ActionTypes;
+ srcChainId: ChainId;
+ destChainId?: ChainId;
+ srcAsset: Asset;
+ destAsset: Asset;
+ srcAmount: string;
+ destAmount: string;
+ protocol: Protocol;
+};
+
+export type StatusResponse = {
+ status: StatusTypes;
+ srcChain: SrcChainStatus;
+ destChain?: DestChainStatus;
+ bridge?: BridgeId;
+ isExpectedToken?: boolean;
+ isUnrecognizedRouterAddress?: boolean;
+ refuel?: RefuelStatusResponse;
+};
+
+export type RefuelStatusResponse = object & StatusResponse;
+
+export type RefuelData = object & Step;
+
+export type BridgeHistoryItem = {
+ quote: Quote;
+ status: StatusResponse;
+ startTime?: number;
+ estimatedProcessingTimeInSeconds: number;
+ slippagePercentage: number;
+ completionTime?: number;
+ pricingData?: {
+ quotedGasInUsd: number;
+ quotedReturnInUsd: number;
+ amountSentInUsd: number;
+ quotedRefuelSrcAmountInUsd?: number;
+ quotedRefuelDestAmountInUsd?: number;
+ };
+ initialDestAssetBalance?: number;
+ targetContractAddress?: string;
+ account: string;
+};
+
+export enum BridgeStatusAction {
+ START_POLLING_FOR_BRIDGE_TX_STATUS = 'startPollingForBridgeTxStatus',
+ WIPE_BRIDGE_STATUS = 'wipeBridgeStatus',
+ GET_STATE = 'getState',
+}
+
+export type StartPollingForBridgeTxStatusArgs = {
+ statusRequest: StatusRequest;
+ quoteResponse: QuoteResponse;
+ startTime?: BridgeHistoryItem['startTime'];
+ slippagePercentage: BridgeHistoryItem['slippagePercentage'];
+ pricingData?: BridgeHistoryItem['pricingData'];
+ initialDestAssetBalance?: BridgeHistoryItem['initialDestAssetBalance'];
+ targetContractAddress?: BridgeHistoryItem['targetContractAddress'];
+};
+
+export type SourceChainTxHash = string;
+
+export type BridgeStatusControllerState = {
+ txHistory: Record;
+};
diff --git a/test/data/bridge/mock-quotes-erc20-native.json b/test/data/bridge/mock-quotes-erc20-native.json
new file mode 100644
index 000000000000..cd4a1963c6fc
--- /dev/null
+++ b/test/data/bridge/mock-quotes-erc20-native.json
@@ -0,0 +1,894 @@
+[
+ {
+ "quote": {
+ "requestId": "a63df72a-75ae-4416-a8ab-aff02596c75c",
+ "srcChainId": 10,
+ "srcTokenAmount": "991250000000000000",
+ "srcAsset": {
+ "address": "0x4200000000000000000000000000000000000006",
+ "chainId": 10,
+ "symbol": "WETH",
+ "decimals": 18,
+ "name": "Wrapped ETH",
+ "coinKey": "WETH",
+ "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png",
+ "priceUSD": "3136",
+ "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png"
+ },
+ "destChainId": 42161,
+ "destTokenAmount": "991225000000000000",
+ "destAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3135.46",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "feeData": {
+ "metabridge": {
+ "amount": "8750000000000000",
+ "asset": {
+ "address": "0x4200000000000000000000000000000000000006",
+ "chainId": 10,
+ "symbol": "WETH",
+ "decimals": 18,
+ "name": "Wrapped ETH",
+ "coinKey": "WETH",
+ "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png",
+ "priceUSD": "3136",
+ "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png"
+ }
+ }
+ },
+ "bridgeId": "lifi",
+ "bridges": ["stargate"],
+ "steps": [
+ {
+ "action": "bridge",
+ "srcChainId": 10,
+ "destChainId": 42161,
+ "protocol": {
+ "name": "stargate",
+ "displayName": "StargateV2 (Fast mode)",
+ "icon": "https://raw.githubusercontent.com/lifinance/types/5685c638772f533edad80fcb210b4bb89e30a50f/src/assets/icons/bridges/stargate.png"
+ },
+ "srcAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 10,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3136",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "destAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3135.46",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "srcAmount": "991250000000000000",
+ "destAmount": "991225000000000000"
+ }
+ ]
+ },
+ "approval": {
+ "chainId": 10,
+ "to": "0x4200000000000000000000000000000000000006",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x00",
+ "data": "0x095ea7b3000000000000000000000000b90357f2b86dbfd59c3502215d4060f71df8ca0e0000000000000000000000000000000000000000000000000de0b6b3a7640000",
+ "gasLimit": 29122
+ },
+ "trade": {
+ "chainId": 10,
+ "to": "0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x1c8598b5db2e",
+ "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c00000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a51870000000000000000000000000000000000000000000000000000000000000564a6010a660000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003804bdedbea3f94faf8c8fac5ec841251d96cf5e64e8706ada4688877885e5249520000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000000000000000000000000000000dc1a09f859b2000000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7374617267617465563200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6d6574616d61736b2d6272696467650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000005215e9fd223bc909083fbdb2860213873046e45d0000000000000000000000005215e9fd223bc909083fbdb2860213873046e45d000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000043ccfd60b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000001c8598b5db2e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838000000000000000000000000000000000000000000000000000000000000759e000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000000000000000000000000000000dc1a09f859b2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c83dc7c11df600d7293f778cb365d3dfcc1ffa2221cf5447a8f2ea407a97792135d9f585ecb68916479dfa1f071f169cbe1cfec831b5ad01f4e4caa09204e5181c",
+ "gasLimit": 641446
+ },
+ "estimatedProcessingTimeInSeconds": 64
+ },
+ {
+ "quote": {
+ "requestId": "aad73198-a64d-4310-b12d-9dcc81c412e2",
+ "srcChainId": 10,
+ "srcTokenAmount": "991250000000000000",
+ "srcAsset": {
+ "address": "0x4200000000000000000000000000000000000006",
+ "chainId": 10,
+ "symbol": "WETH",
+ "decimals": 18,
+ "name": "Wrapped ETH",
+ "coinKey": "WETH",
+ "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png",
+ "priceUSD": "3136",
+ "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png"
+ },
+ "destChainId": 42161,
+ "destTokenAmount": "991147696728676903",
+ "destAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3135.46",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "feeData": {
+ "metabridge": {
+ "amount": "8750000000000000",
+ "asset": {
+ "address": "0x4200000000000000000000000000000000000006",
+ "chainId": 10,
+ "symbol": "WETH",
+ "decimals": 18,
+ "name": "Wrapped ETH",
+ "coinKey": "WETH",
+ "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png",
+ "priceUSD": "3136",
+ "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png"
+ }
+ }
+ },
+ "bridgeId": "lifi",
+ "bridges": ["celer"],
+ "steps": [
+ {
+ "action": "bridge",
+ "srcChainId": 10,
+ "destChainId": 42161,
+ "protocol": {
+ "name": "celer",
+ "displayName": "Celer cBridge",
+ "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/cbridge.svg"
+ },
+ "srcAsset": {
+ "address": "0x4200000000000000000000000000000000000006",
+ "chainId": 10,
+ "symbol": "WETH",
+ "decimals": 18,
+ "name": "Wrapped ETH",
+ "coinKey": "WETH",
+ "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png",
+ "priceUSD": "3136",
+ "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png"
+ },
+ "destAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3135.46",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "srcAmount": "991250000000000000",
+ "destAmount": "991147696728676903"
+ }
+ ]
+ },
+ "approval": {
+ "chainId": 10,
+ "to": "0x4200000000000000000000000000000000000006",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x00",
+ "data": "0x095ea7b3000000000000000000000000b90357f2b86dbfd59c3502215d4060f71df8ca0e0000000000000000000000000000000000000000000000000de0b6b3a7640000",
+ "gasLimit": 29122
+ },
+ "trade": {
+ "chainId": 10,
+ "to": "0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x00",
+ "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000e7bf43c55551b1036e796e7fd3b125d1f9903e2e000000000000000000000000e7bf43c55551b1036e796e7fd3b125d1f9903e2e000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a51870000000000000000000000000000000000000000000000000000000000000050f68486970f93a855b27794b8141d32a89a1e0a5ef360034a2f60a4b917c188380000a4b1420000000000000000000000000000000000000600000000000000000dc1a09f859b20002c03873900002777000000000000000000000000000000002d68122053030bf8df41a8bb8c6f0a9de411c7d94eed376b7d91234e1585fd9f77dcf974dd25160d0c2c16c8382d8aa85b0edd429edff19b4d4cdcf50d0a9d4d1c",
+ "gasLimit": 203352
+ },
+ "estimatedProcessingTimeInSeconds": 53
+ },
+ {
+ "quote": {
+ "requestId": "6cfd4952-c9b2-4aec-9349-af39c212f84b",
+ "srcChainId": 10,
+ "srcTokenAmount": "991250000000000000",
+ "srcAsset": {
+ "address": "0x4200000000000000000000000000000000000006",
+ "chainId": 10,
+ "symbol": "WETH",
+ "decimals": 18,
+ "name": "Wrapped ETH",
+ "coinKey": "WETH",
+ "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png",
+ "priceUSD": "3136",
+ "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png"
+ },
+ "destChainId": 42161,
+ "destTokenAmount": "991112862890876485",
+ "destAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3135.46",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "feeData": {
+ "metabridge": {
+ "amount": "8750000000000000",
+ "asset": {
+ "address": "0x4200000000000000000000000000000000000006",
+ "chainId": 10,
+ "symbol": "WETH",
+ "decimals": 18,
+ "name": "Wrapped ETH",
+ "coinKey": "WETH",
+ "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png",
+ "priceUSD": "3136",
+ "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png"
+ }
+ }
+ },
+ "bridgeId": "lifi",
+ "bridges": ["across"],
+ "steps": [
+ {
+ "action": "bridge",
+ "srcChainId": 10,
+ "destChainId": 42161,
+ "protocol": {
+ "name": "across",
+ "displayName": "Across",
+ "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png"
+ },
+ "srcAsset": {
+ "address": "0x4200000000000000000000000000000000000006",
+ "chainId": 10,
+ "symbol": "WETH",
+ "decimals": 18,
+ "name": "Wrapped ETH",
+ "coinKey": "WETH",
+ "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png",
+ "priceUSD": "3136",
+ "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png"
+ },
+ "destAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3135.46",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "srcAmount": "991250000000000000",
+ "destAmount": "991112862890876485"
+ }
+ ]
+ },
+ "approval": {
+ "chainId": 10,
+ "to": "0x4200000000000000000000000000000000000006",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x00",
+ "data": "0x095ea7b3000000000000000000000000b90357f2b86dbfd59c3502215d4060f71df8ca0e0000000000000000000000000000000000000000000000000de0b6b3a7640000",
+ "gasLimit": 29122
+ },
+ "trade": {
+ "chainId": 10,
+ "to": "0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x00",
+ "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a518700000000000000000000000000000000000000000000000000000000000000902340ab8f6a57ef0c43231b98141d32a89a1e0a5ef360034a2f60a4b917c18838420000000000000000000000000000000000000600000000000000000dc1a09f859b20000000a4b100007dd39298f9ad673645ebffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b710000000000000000000000000000000088d06e7971021eee573a0ab6bc3e22039fc1c5ded5d12c4cf2b6311f47f909e06197aa8b2f647ae78ae33a6ea5d23f7c951c0e1686abecd01d7c796990d56f391c",
+ "gasLimit": 177423
+ },
+ "estimatedProcessingTimeInSeconds": 15
+ },
+ {
+ "quote": {
+ "requestId": "2c2ba7d8-3922-4081-9f27-63b7d5cc1986",
+ "srcChainId": 10,
+ "srcTokenAmount": "991250000000000000",
+ "srcAsset": {
+ "address": "0x4200000000000000000000000000000000000006",
+ "chainId": 10,
+ "symbol": "WETH",
+ "decimals": 18,
+ "name": "Wrapped ETH",
+ "coinKey": "WETH",
+ "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png",
+ "priceUSD": "3136",
+ "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png"
+ },
+ "destChainId": 42161,
+ "destTokenAmount": "990221346602370184",
+ "destAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3135.46",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "feeData": {
+ "metabridge": {
+ "amount": "8750000000000000",
+ "asset": {
+ "address": "0x4200000000000000000000000000000000000006",
+ "chainId": 10,
+ "symbol": "WETH",
+ "decimals": 18,
+ "name": "Wrapped ETH",
+ "coinKey": "WETH",
+ "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png",
+ "priceUSD": "3136",
+ "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png"
+ }
+ }
+ },
+ "bridgeId": "lifi",
+ "bridges": ["hop"],
+ "steps": [
+ {
+ "action": "bridge",
+ "srcChainId": 10,
+ "destChainId": 42161,
+ "protocol": {
+ "name": "hop",
+ "displayName": "Hop",
+ "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/hop.png"
+ },
+ "srcAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 10,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3136",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "destAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3135.46",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "srcAmount": "991250000000000000",
+ "destAmount": "990221346602370184"
+ }
+ ]
+ },
+ "approval": {
+ "chainId": 10,
+ "to": "0x4200000000000000000000000000000000000006",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x00",
+ "data": "0x095ea7b3000000000000000000000000b90357f2b86dbfd59c3502215d4060f71df8ca0e0000000000000000000000000000000000000000000000000de0b6b3a7640000",
+ "gasLimit": 29122
+ },
+ "trade": {
+ "chainId": 10,
+ "to": "0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x00",
+ "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e00000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a51870000000000000000000000000000000000000000000000000000000000000484ca360ae0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000001168a464edd170000000000000000000000000000000000000000000000000dac6213fc70c84400000000000000000000000000000000000000000000000000000000673a3b080000000000000000000000000000000000000000000000000dac6213fc70c84400000000000000000000000000000000000000000000000000000000673a3b0800000000000000000000000086ca30bef97fb651b8d866d45503684b90cb3312000000000000000000000000710bda329b2a6224e4b44833de30f38e7f81d5640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067997b63db4b9059d22e50750707b46a6d48dfbb32e50d85fc3bff1170ed9ca30000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000000000000000000000000000000dc1a09f859b2000000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003686f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6d6574616d61736b2d6272696467650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000005215e9fd223bc909083fbdb2860213873046e45d0000000000000000000000005215e9fd223bc909083fbdb2860213873046e45d000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000043ccfd60b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000099d00cde1f22e8afd37d7f103ec3c6c1eb835ace46e502ec8c5ab51413e539461b89c0e26892efd1de1cbfe4222b5589e76231080252197507cce4fb72a30b031b",
+ "gasLimit": 547501
+ },
+ "estimatedProcessingTimeInSeconds": 24.159
+ },
+ {
+ "quote": {
+ "requestId": "a77bc7b2-e8c8-4463-89db-5dd239d6aacc",
+ "srcChainId": 10,
+ "srcAsset": {
+ "chainId": 10,
+ "address": "0x4200000000000000000000000000000000000006",
+ "symbol": "WETH",
+ "name": "Wrapped Ether",
+ "decimals": 18,
+ "icon": "https://media.socket.tech/tokens/all/WETH",
+ "logoURI": "https://media.socket.tech/tokens/all/WETH",
+ "chainAgnosticId": "ETH"
+ },
+ "srcTokenAmount": "991250000000000000",
+ "destChainId": 42161,
+ "destAsset": {
+ "chainId": 42161,
+ "address": "0x0000000000000000000000000000000000000000",
+ "symbol": "ETH",
+ "name": "Ethereum",
+ "decimals": 18,
+ "icon": "https://media.socket.tech/tokens/all/ETH",
+ "logoURI": "https://media.socket.tech/tokens/all/ETH",
+ "chainAgnosticId": null
+ },
+ "destTokenAmount": "991147696728676903",
+ "feeData": {
+ "metabridge": {
+ "amount": "8750000000000000",
+ "asset": {
+ "chainId": 10,
+ "address": "0x4200000000000000000000000000000000000006",
+ "symbol": "WETH",
+ "name": "Wrapped Ether",
+ "decimals": 18,
+ "icon": "https://media.socket.tech/tokens/all/WETH",
+ "logoURI": "https://media.socket.tech/tokens/all/WETH",
+ "chainAgnosticId": "ETH"
+ }
+ }
+ },
+ "bridgeId": "socket",
+ "bridges": ["celer"],
+ "steps": [
+ {
+ "action": "bridge",
+ "srcChainId": 10,
+ "destChainId": 42161,
+ "protocol": {
+ "name": "celer",
+ "displayName": "Celer",
+ "icon": "https://socketicons.s3.amazonaws.com/Celer+Light.png"
+ },
+ "srcAsset": {
+ "chainId": 10,
+ "address": "0x4200000000000000000000000000000000000006",
+ "symbol": "WETH",
+ "name": "Wrapped Ether",
+ "decimals": 18,
+ "icon": "https://media.socket.tech/tokens/all/WETH",
+ "logoURI": "https://media.socket.tech/tokens/all/WETH",
+ "chainAgnosticId": "ETH"
+ },
+ "destAsset": {
+ "chainId": 42161,
+ "address": "0x0000000000000000000000000000000000000000",
+ "symbol": "ETH",
+ "name": "Ethereum",
+ "decimals": 18,
+ "icon": "https://media.socket.tech/tokens/all/ETH",
+ "logoURI": "https://media.socket.tech/tokens/all/ETH",
+ "chainAgnosticId": null
+ },
+ "srcAmount": "991250000000000000",
+ "destAmount": "991147696728676903"
+ }
+ ]
+ },
+ "approval": {
+ "chainId": 10,
+ "to": "0x4200000000000000000000000000000000000006",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x00",
+ "data": "0x095ea7b3000000000000000000000000b90357f2b86dbfd59c3502215d4060f71df8ca0e0000000000000000000000000000000000000000000000000de0b6b3a7640000",
+ "gasLimit": 29122
+ },
+ "trade": {
+ "chainId": 10,
+ "to": "0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x00",
+ "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000f736f636b6574416461707465725632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000003a23f943181408eac424116af7b7790c94cb97a50000000000000000000000003a23f943181408eac424116af7b7790c94cb97a5000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a5187000000000000000000000000000000000000000000000000000000000000004c0000001252106ce9141d32a89a1e0a5ef360034a2f60a4b917c18838420000000000000000000000000000000000000600000000000000000dc1a09f859b20000000a4b1245fa5dd00002777000000000000000000000000000000000000000022be703a074ef6089a301c364c2bbf391d51067ea5cd91515c9ec5421cdaabb23451cd2086f3ebe3e19ff138f3a9be154dcae6033838cc5fabeeb0d260b075cb1c",
+ "gasLimit": 182048
+ },
+ "estimatedProcessingTimeInSeconds": 360
+ },
+ {
+ "quote": {
+ "requestId": "4f2154d9b330221b2ad461adf63acc2c",
+ "srcChainId": 10,
+ "srcTokenAmount": "991250000000000000",
+ "srcAsset": {
+ "id": "10_0x4200000000000000000000000000000000000006",
+ "symbol": "WETH",
+ "address": "0x4200000000000000000000000000000000000006",
+ "chainId": 10,
+ "name": "Wrapped ETH",
+ "decimals": 18,
+ "usdPrice": 3135.9632118339764,
+ "coingeckoId": "weth",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/weth.svg",
+ "volatility": 2,
+ "axelarNetworkSymbol": "WETH",
+ "subGraphIds": [],
+ "enabled": true,
+ "subGraphOnly": false,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/weth.svg"
+ },
+ "destChainId": 42161,
+ "destTokenAmount": "989989428114299041",
+ "destAsset": {
+ "id": "42161_0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
+ "symbol": "ETH",
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 42161,
+ "name": "ETH",
+ "decimals": 18,
+ "usdPrice": 3133.259355489038,
+ "coingeckoId": "ethereum",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/eth.svg",
+ "volatility": 2,
+ "axelarNetworkSymbol": "ETH",
+ "subGraphIds": ["chainflip-bridge"],
+ "enabled": true,
+ "subGraphOnly": false,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/eth.svg"
+ },
+ "feeData": {
+ "metabridge": {
+ "amount": "8750000000000000",
+ "asset": {
+ "id": "10_0x4200000000000000000000000000000000000006",
+ "symbol": "WETH",
+ "address": "0x4200000000000000000000000000000000000006",
+ "chainId": 10,
+ "name": "Wrapped ETH",
+ "decimals": 18,
+ "usdPrice": 3135.9632118339764,
+ "coingeckoId": "weth",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/weth.svg",
+ "volatility": 2,
+ "axelarNetworkSymbol": "WETH",
+ "subGraphIds": [],
+ "enabled": true,
+ "subGraphOnly": false,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/weth.svg"
+ }
+ }
+ },
+ "bridgeId": "squid",
+ "bridges": ["axelar"],
+ "steps": [
+ {
+ "action": "swap",
+ "srcChainId": 10,
+ "destChainId": 10,
+ "protocol": {
+ "name": "Uniswap V3",
+ "displayName": "Uniswap V3"
+ },
+ "srcAsset": {
+ "id": "10_0x4200000000000000000000000000000000000006",
+ "symbol": "WETH",
+ "address": "0x4200000000000000000000000000000000000006",
+ "chainId": 10,
+ "name": "Wrapped ETH",
+ "decimals": 18,
+ "usdPrice": 3135.9632118339764,
+ "coingeckoId": "weth",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/weth.svg",
+ "axelarNetworkSymbol": "WETH",
+ "subGraphIds": [],
+ "enabled": true,
+ "subGraphOnly": false,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/weth.svg"
+ },
+ "destAsset": {
+ "id": "10_0x0b2c639c533813f4aa9d7837caf62653d097ff85",
+ "symbol": "USDC",
+ "address": "0x0b2c639c533813f4aa9d7837caf62653d097ff85",
+ "chainId": 10,
+ "name": "USDC",
+ "decimals": 6,
+ "usdPrice": 1.0003003590332982,
+ "coingeckoId": "usd-coin",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg",
+ "axelarNetworkSymbol": "USDC",
+ "subGraphOnly": false,
+ "subGraphIds": ["uusdc", "cctp-uusdc-optimism-to-noble"],
+ "enabled": true,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg"
+ },
+ "srcAmount": "991250000000000000",
+ "destAmount": "3100880215"
+ },
+ {
+ "action": "swap",
+ "srcChainId": 10,
+ "destChainId": 10,
+ "protocol": {
+ "name": "Uniswap V3",
+ "displayName": "Uniswap V3"
+ },
+ "srcAsset": {
+ "id": "10_0x0b2c639c533813f4aa9d7837caf62653d097ff85",
+ "symbol": "USDC",
+ "address": "0x0b2c639c533813f4aa9d7837caf62653d097ff85",
+ "chainId": 10,
+ "name": "USDC",
+ "decimals": 6,
+ "usdPrice": 1.0003003590332982,
+ "coingeckoId": "usd-coin",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg",
+ "axelarNetworkSymbol": "USDC",
+ "subGraphOnly": false,
+ "subGraphIds": ["uusdc", "cctp-uusdc-optimism-to-noble"],
+ "enabled": true,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg"
+ },
+ "destAsset": {
+ "id": "10_0x7f5c764cbc14f9669b88837ca1490cca17c31607",
+ "symbol": "USDC.e",
+ "address": "0x7f5c764cbc14f9669b88837ca1490cca17c31607",
+ "chainId": 10,
+ "name": "USDC.e",
+ "decimals": 6,
+ "usdPrice": 1.0003003590332982,
+ "coingeckoId": "usd-coin",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg",
+ "axelarNetworkSymbol": "USDC.e",
+ "subGraphIds": [],
+ "enabled": true,
+ "subGraphOnly": false,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg"
+ },
+ "srcAmount": "3100880215",
+ "destAmount": "3101045779"
+ },
+ {
+ "action": "swap",
+ "srcChainId": 10,
+ "destChainId": 10,
+ "protocol": {
+ "name": "Uniswap V3",
+ "displayName": "Uniswap V3"
+ },
+ "srcAsset": {
+ "id": "10_0x7f5c764cbc14f9669b88837ca1490cca17c31607",
+ "symbol": "USDC.e",
+ "address": "0x7f5c764cbc14f9669b88837ca1490cca17c31607",
+ "chainId": 10,
+ "name": "USDC.e",
+ "decimals": 6,
+ "usdPrice": 1.0003003590332982,
+ "coingeckoId": "usd-coin",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg",
+ "axelarNetworkSymbol": "USDC.e",
+ "subGraphIds": [],
+ "enabled": true,
+ "subGraphOnly": false,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg"
+ },
+ "destAsset": {
+ "id": "10_0xeb466342c4d449bc9f53a865d5cb90586f405215",
+ "symbol": "USDC.axl",
+ "address": "0xeb466342c4d449bc9f53a865d5cb90586f405215",
+ "chainId": 10,
+ "name": " USDC (Axelar)",
+ "decimals": 6,
+ "usdPrice": 1.0003003590332982,
+ "interchainTokenId": null,
+ "coingeckoId": "usd-coin",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg",
+ "axelarNetworkSymbol": "axlUSDC",
+ "subGraphOnly": false,
+ "subGraphIds": ["uusdc"],
+ "enabled": true,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg"
+ },
+ "srcAmount": "3101045779",
+ "destAmount": "3101521947"
+ },
+ {
+ "action": "bridge",
+ "srcChainId": 10,
+ "destChainId": 42161,
+ "protocol": {
+ "name": "axelar",
+ "displayName": "Axelar"
+ },
+ "srcAsset": {
+ "id": "10_0xeb466342c4d449bc9f53a865d5cb90586f405215",
+ "symbol": "USDC.axl",
+ "address": "0xeb466342c4d449bc9f53a865d5cb90586f405215",
+ "chainId": 10,
+ "name": " USDC (Axelar)",
+ "decimals": 6,
+ "usdPrice": 1.0003003590332982,
+ "interchainTokenId": null,
+ "coingeckoId": "usd-coin",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg",
+ "axelarNetworkSymbol": "axlUSDC",
+ "subGraphOnly": false,
+ "subGraphIds": ["uusdc"],
+ "enabled": true,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg"
+ },
+ "destAsset": {
+ "id": "42161_0xeb466342c4d449bc9f53a865d5cb90586f405215",
+ "symbol": "USDC.axl",
+ "address": "0xeb466342c4d449bc9f53a865d5cb90586f405215",
+ "chainId": 42161,
+ "name": " USDC (Axelar)",
+ "decimals": 6,
+ "usdPrice": 1.0003003590332982,
+ "interchainTokenId": null,
+ "coingeckoId": "usd-coin",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg",
+ "axelarNetworkSymbol": "axlUSDC",
+ "subGraphOnly": false,
+ "subGraphIds": ["uusdc"],
+ "enabled": true,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg"
+ },
+ "srcAmount": "3101521947",
+ "destAmount": "3101521947"
+ },
+ {
+ "action": "swap",
+ "srcChainId": 42161,
+ "destChainId": 42161,
+ "protocol": {
+ "name": "Pancakeswap V3",
+ "displayName": "Pancakeswap V3"
+ },
+ "srcAsset": {
+ "id": "42161_0xeb466342c4d449bc9f53a865d5cb90586f405215",
+ "symbol": "USDC.axl",
+ "address": "0xeb466342c4d449bc9f53a865d5cb90586f405215",
+ "chainId": 42161,
+ "name": " USDC (Axelar)",
+ "decimals": 6,
+ "usdPrice": 1.0003003590332982,
+ "interchainTokenId": null,
+ "coingeckoId": "usd-coin",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg",
+ "axelarNetworkSymbol": "axlUSDC",
+ "subGraphOnly": false,
+ "subGraphIds": ["uusdc"],
+ "enabled": true,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg"
+ },
+ "destAsset": {
+ "id": "42161_0xaf88d065e77c8cc2239327c5edb3a432268e5831",
+ "symbol": "USDC",
+ "address": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
+ "chainId": 42161,
+ "name": "USDC",
+ "decimals": 6,
+ "usdPrice": 1.0003003590332982,
+ "coingeckoId": "usd-coin",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg",
+ "axelarNetworkSymbol": "USDC",
+ "subGraphOnly": false,
+ "subGraphIds": [
+ "uusdc",
+ "cctp-uusdc-arbitrum-to-noble",
+ "chainflip-bridge"
+ ],
+ "enabled": true,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg"
+ },
+ "srcAmount": "3101521947",
+ "destAmount": "3100543869"
+ },
+ {
+ "action": "swap",
+ "srcChainId": 42161,
+ "destChainId": 42161,
+ "protocol": {
+ "name": "Uniswap V3",
+ "displayName": "Uniswap V3"
+ },
+ "srcAsset": {
+ "id": "42161_0xaf88d065e77c8cc2239327c5edb3a432268e5831",
+ "symbol": "USDC",
+ "address": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
+ "chainId": 42161,
+ "name": "USDC",
+ "decimals": 6,
+ "usdPrice": 1.0003003590332982,
+ "coingeckoId": "usd-coin",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg",
+ "axelarNetworkSymbol": "USDC",
+ "subGraphOnly": false,
+ "subGraphIds": [
+ "uusdc",
+ "cctp-uusdc-arbitrum-to-noble",
+ "chainflip-bridge"
+ ],
+ "enabled": true,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg"
+ },
+ "destAsset": {
+ "id": "42161_0x82af49447d8a07e3bd95bd0d56f35241523fbab1",
+ "symbol": "WETH",
+ "address": "0x82af49447d8a07e3bd95bd0d56f35241523fbab1",
+ "chainId": 42161,
+ "name": "Wrapped ETH",
+ "decimals": 18,
+ "usdPrice": 3135.9632118339764,
+ "interchainTokenId": null,
+ "coingeckoId": "weth",
+ "type": "evm",
+ "logoURI": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/weth.svg",
+ "axelarNetworkSymbol": "WETH",
+ "subGraphOnly": false,
+ "subGraphIds": ["arbitrum-weth-wei"],
+ "enabled": true,
+ "active": true,
+ "icon": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/weth.svg"
+ },
+ "srcAmount": "3100543869",
+ "destAmount": "989989428114299041"
+ }
+ ]
+ },
+ "approval": {
+ "chainId": 10,
+ "to": "0x4200000000000000000000000000000000000006",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x00",
+ "data": "0x095ea7b3000000000000000000000000b90357f2b86dbfd59c3502215d4060f71df8ca0e0000000000000000000000000000000000000000000000000de0b6b3a7640000",
+ "gasLimit": 29122
+ },
+ "trade": {
+ "chainId": 10,
+ "to": "0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x4653ce53e6b1",
+ "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000e73717569644164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b60000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a51870000000000000000000000000000000000000000000000000000000000001a14846a1bc600000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000ce00000000000000000000000000000000000000000000000000000000000000d200000000000000000000000000000000000000000000000000000000000000d600000000000000000000000000000000000000000000000000000000000000dc0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000005e00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000098000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000e404e45aaf00000000000000000000000042000000000000000000000000000000000000060000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff8500000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000ea749fd6ba492dbc14c24fe8a3d08769229b896c0000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000000000000000000000000000000000000b8833d8e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff85000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff850000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000e404e45aaf0000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff850000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c316070000000000000000000000000000000000000000000000000000000000000064000000000000000000000000ea749fd6ba492dbc14c24fe8a3d08769229b896c00000000000000000000000000000000000000000000000000000000b8d3ad5700000000000000000000000000000000000000000000000000000000b8c346b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff85000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c31607000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c316070000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000e404e45aaf0000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c31607000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f4052150000000000000000000000000000000000000000000000000000000000000064000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d66600000000000000000000000000000000000000000000000000000000b8d6341300000000000000000000000000000000000000000000000000000000b8ca89fa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c316070000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000761786c55534443000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008417262697472756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a307863653136463639333735353230616230313337376365374238386635424138433438463844363636000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c100000000000000000000000000000000000000000000000000000000000000040000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000580000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000009200000000000000000000000000000000000000000000000000000000000000a8000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f4052150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f405215000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000032226588378236fd0c7c4053999f88ac0e5cac77ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f4052150000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000032226588378236fd0c7c4053999f88ac0e5cac77000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000e404e45aaf000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f405215000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000000000000000064000000000000000000000000ea749fd6ba492dbc14c24fe8a3d08769229b896c00000000000000000000000000000000000000000000000000000000b8dd781b00000000000000000000000000000000000000000000000000000000b8bb9ee30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f40521500000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000e404e45aaf000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab100000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000ea749fd6ba492dbc14c24fe8a3d08769229b896c00000000000000000000000000000000000000000000000000000000b8ce8b7d0000000000000000000000000000000000000000000000000db72b79f837011c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000242e1a7d4d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000000004f2154d9b330221b2ad461adf63acc2c000000000000000000000000000000004f2154d9b330221b2ad461adf63acc2c0000000000000000000000003c17c95cdb5887c334bfae85750ce00e1a720a76eff35e60db6c9f3b8384a6d63db3c56f1ce6545b50ba2f250429055ca77e7e6203ddd65a7a4d89ae1af3d61b1c",
+ "gasLimit": 710342
+ },
+ "estimatedProcessingTimeInSeconds": 20
+ }
+]
diff --git a/test/data/bridge/mock-quotes-native-erc20-eth.json b/test/data/bridge/mock-quotes-native-erc20-eth.json
new file mode 100644
index 000000000000..0afd77760e75
--- /dev/null
+++ b/test/data/bridge/mock-quotes-native-erc20-eth.json
@@ -0,0 +1,258 @@
+[
+ {
+ "quote": {
+ "requestId": "34c4136d-8558-4d87-bdea-eef8d2d30d6d",
+ "srcChainId": 1,
+ "srcTokenAmount": "991250000000000000",
+ "srcAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 1,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3145.41",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "destChainId": 42161,
+ "destTokenAmount": "3104367033",
+ "destAsset": {
+ "address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
+ "chainId": 42161,
+ "symbol": "USDC",
+ "decimals": 6,
+ "name": "USD Coin",
+ "coinKey": "USDC",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
+ "priceUSD": "0.9998000399920016",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
+ },
+ "feeData": {
+ "metabridge": {
+ "amount": "8750000000000000",
+ "asset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 1,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3145.41",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ }
+ }
+ },
+ "bridgeId": "lifi",
+ "bridges": ["across"],
+ "steps": [
+ {
+ "action": "swap",
+ "srcChainId": 1,
+ "destChainId": 1,
+ "protocol": {
+ "name": "0x",
+ "displayName": "0x",
+ "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/exchanges/zerox.png"
+ },
+ "srcAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 1,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3145.41",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "destAsset": {
+ "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
+ "chainId": 1,
+ "symbol": "USDC",
+ "decimals": 6,
+ "name": "USD Coin",
+ "coinKey": "USDC",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
+ "priceUSD": "0.9997000899730081",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
+ },
+ "srcAmount": "991250000000000000",
+ "destAmount": "3104701473"
+ },
+ {
+ "action": "bridge",
+ "srcChainId": 1,
+ "destChainId": 42161,
+ "protocol": {
+ "name": "across",
+ "displayName": "Across",
+ "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png"
+ },
+ "srcAsset": {
+ "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
+ "chainId": 1,
+ "symbol": "USDC",
+ "decimals": 6,
+ "name": "USD Coin",
+ "coinKey": "USDC",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
+ "priceUSD": "0.9997000899730081",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
+ },
+ "destAsset": {
+ "address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
+ "chainId": 42161,
+ "symbol": "USDC",
+ "decimals": 6,
+ "name": "USD Coin",
+ "coinKey": "USDC",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
+ "priceUSD": "0.9998000399920016",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
+ },
+ "srcAmount": "3104701473",
+ "destAmount": "3104367033"
+ }
+ ]
+ },
+ "trade": {
+ "chainId": 1,
+ "to": "0x0439e60F02a8900a951603950d8D4527f400C3f1",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x0de0b6b3a7640000",
+ "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c696669416461707465725632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b400000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000e6b738da243e8fa2a0ed5915645789add5de51520000000000000000000000000000000000000000000000000000000000000a003a3f733200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000094027363a1fac5600d1f7e8a4c50087ff1f32a09359512d2379d46b331c6033cc7b000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c1883800000000000000000000000000000000000000000000000000000000b8211d6e000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066163726f73730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6d6574616d61736b2d6272696467650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000001ff3684f28c67538d4d072c227340000000000000000000000000000000000001ff3684f28c67538d4d072c227340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000005c42213bc0b00000000000000000000000070bf6634ee8cb27d04478f184b9b8bb13e5f471000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000070bf6634ee8cb27d04478f184b9b8bb13e5f471000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000004e41fff991f0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b909399a00000000000000000000000000000000000000000000000000000000000000a094cc69295a8f2a3016ede239627ab300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024d0e30db00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e48d68a15600000000000000000000000070bf6634ee8cb27d04478f184b9b8bb13e5f4710000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2010001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012438c9c147000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000005000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000ad01c20d5886137e056775af56915de824c8fce50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000620541d325b000000000000000000000000000000000000000000000000000000000673656d70000000000000000000000000000000000000000000000000000000000000080ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000d00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b71dcbfe555f9a744b18195d9b52032871d6f3c5a558275c08a71c2b6214801f5161be976f49181b854a3ebcbe1f2b896133b03314a5ff2746e6494c43e59d0c9ee1c",
+ "gasLimit": 540076
+ },
+ "estimatedProcessingTimeInSeconds": 45
+ },
+ {
+ "quote": {
+ "requestId": "5bf0f2f0-655c-4e13-a545-1ebad6f9d2bc",
+ "srcChainId": 1,
+ "srcTokenAmount": "991250000000000000",
+ "srcAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 1,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3145.41",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "destChainId": 42161,
+ "destTokenAmount": "3104601473",
+ "destAsset": {
+ "address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
+ "chainId": 42161,
+ "symbol": "USDC",
+ "decimals": 6,
+ "name": "USD Coin",
+ "coinKey": "USDC",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
+ "priceUSD": "0.9998000399920016",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
+ },
+ "feeData": {
+ "metabridge": {
+ "amount": "8750000000000000",
+ "asset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 1,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3145.41",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ }
+ }
+ },
+ "bridgeId": "lifi",
+ "bridges": ["celercircle"],
+ "steps": [
+ {
+ "action": "swap",
+ "srcChainId": 1,
+ "destChainId": 1,
+ "protocol": {
+ "name": "0x",
+ "displayName": "0x",
+ "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/exchanges/zerox.png"
+ },
+ "srcAsset": {
+ "address": "0x0000000000000000000000000000000000000000",
+ "chainId": 1,
+ "symbol": "ETH",
+ "decimals": 18,
+ "name": "ETH",
+ "coinKey": "ETH",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "priceUSD": "3145.41",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
+ },
+ "destAsset": {
+ "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
+ "chainId": 1,
+ "symbol": "USDC",
+ "decimals": 6,
+ "name": "USD Coin",
+ "coinKey": "USDC",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
+ "priceUSD": "0.9997000899730081",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
+ },
+ "srcAmount": "991250000000000000",
+ "destAmount": "3104701473"
+ },
+ {
+ "action": "bridge",
+ "srcChainId": 1,
+ "destChainId": 42161,
+ "protocol": {
+ "name": "celercircle",
+ "displayName": "Circle CCTP",
+ "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/circle.png"
+ },
+ "srcAsset": {
+ "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
+ "chainId": 1,
+ "symbol": "USDC",
+ "decimals": 6,
+ "name": "USD Coin",
+ "coinKey": "USDC",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
+ "priceUSD": "0.9997000899730081",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
+ },
+ "destAsset": {
+ "address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
+ "chainId": 42161,
+ "symbol": "USDC",
+ "decimals": 6,
+ "name": "USD Coin",
+ "coinKey": "USDC",
+ "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
+ "priceUSD": "0.9998000399920016",
+ "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
+ },
+ "srcAmount": "3104701473",
+ "destAmount": "3104601473"
+ }
+ ]
+ },
+ "trade": {
+ "chainId": 1,
+ "to": "0x0439e60F02a8900a951603950d8D4527f400C3f1",
+ "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838",
+ "value": "0x0de0b6b3a7640000",
+ "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c696669416461707465725632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000e6b738da243e8fa2a0ed5915645789add5de515200000000000000000000000000000000000000000000000000000000000009248fab066300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000200b431adcab44c6fe13ade53dbd3b714f57922ab5b776924a913685ad0fe680f6c000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c1883800000000000000000000000000000000000000000000000000000000b8211d6e000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b63656c6572636972636c65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6d6574616d61736b2d6272696467650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000001ff3684f28c67538d4d072c227340000000000000000000000000000000000001ff3684f28c67538d4d072c227340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000005c42213bc0b00000000000000000000000070bf6634ee8cb27d04478f184b9b8bb13e5f471000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000070bf6634ee8cb27d04478f184b9b8bb13e5f471000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000004e41fff991f0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b909399a00000000000000000000000000000000000000000000000000000000000000a0c0452b52ecb7cf70409b16cd627ab300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024d0e30db00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e48d68a15600000000000000000000000070bf6634ee8cb27d04478f184b9b8bb13e5f4710000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2010001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012438c9c147000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000005000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000ad01c20d5886137e056775af56915de824c8fce50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047896dca097909ba9db4c9631bce0e53090bce14a9b7d203e21fa80cee7a16fa049aa1ef7d663c2ec3148e698e01774b62ddedc9c2dcd21994e549cd6f318f971b",
+ "gasLimit": 682910
+ },
+ "estimatedProcessingTimeInSeconds": 1029.717
+ }
+]
diff --git a/test/data/bridge/mock-quotes-native-erc20.json b/test/data/bridge/mock-quotes-native-erc20.json
index fb6ecfcc0b73..f7efe7950ba0 100644
--- a/test/data/bridge/mock-quotes-native-erc20.json
+++ b/test/data/bridge/mock-quotes-native-erc20.json
@@ -289,6 +289,6 @@
"data": "0x3ce33bff00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002714711487800000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000f736f636b657441646170746572563200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc00000000000000000000000003a23f943181408eac424116af7b7790c94cb97a50000000000000000000000003a23f943181408eac424116af7b7790c94cb97a5000000000000000000000000000000000000000000000000000000000000008900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c33590000000000000000000000000000000000000000000000000023375dc1560800000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000004f94ae6af800000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a51870000000000000000000000000000000000000000000000000000000000000c6437c6145a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000bc4123506490000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001960000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000018c0000000000000000000000000000000000000000000000000000000000000ac00000000000000000000000000000000000000000000000000000000000000084ad69fa4f00000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c1883800000000000000000000000000000000000000000000000000000000000000890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000904ee8f0b86000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff850000000000000000000000000000000000000000000000000023375dc156080000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000828415565b0000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff850000000000000000000000000000000000000000000000000023375dc15608000000000000000000000000000000000000000000000000000000000001734d0800000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000005e0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000023375dc15608000000000000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff8500000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000023375dc1560800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000012556e69737761705633000000000000000000000000000000000000000000000000000000000000000023375dc1560800000000000000000000000000000000000000000000000000000000000173dbd3000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000e592427a0aece92de3edee1f18e0157c0586156400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002b42000000000000000000000000000000000000060001f40b2c639c533813f4aa9d7837caf62653d097ff85000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff850000000000000000000000000000000000000000000000000000000000008ecb000000000000000000000000ad01c20d5886137e056775af56915de824c8fce5000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000004200000000000000000000000000000000000006000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000000000869584cd00000000000000000000000010000000000000000000000000000000000000110000000000000000000000000000000000000000974132b87a5cb75e32f034280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff85000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000890000000000000000000000000000000000000000000000000000000000030d4000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003f9e43204a24f476db20f2518722627a122d31a1bc7c63fc15412e6a327295a9460b76bea5bb53b1f73fa6a15811055f6bada592d2e9e6c8cf48a855ce6968951c",
"gasLimit": 664389
},
- "estimatedProcessingTimeInSeconds": 1560
+ "estimatedProcessingTimeInSeconds": 15
}
]
diff --git a/test/data/mock-state.json b/test/data/mock-state.json
index 734845f0ca9a..b315dfa203eb 100644
--- a/test/data/mock-state.json
+++ b/test/data/mock-state.json
@@ -2035,6 +2035,9 @@
}
}
}
+ },
+ "bridgeStatusState": {
+ "txHistory": {}
}
},
"ramps": {
diff --git a/test/e2e/flask/btc/common-btc.ts b/test/e2e/flask/btc/common-btc.ts
index 452f9ad44f6b..05f382281e29 100644
--- a/test/e2e/flask/btc/common-btc.ts
+++ b/test/e2e/flask/btc/common-btc.ts
@@ -1,6 +1,6 @@
import { Mockttp } from 'mockttp';
import FixtureBuilder from '../../fixture-builder';
-import { withFixtures, unlockWallet } from '../../helpers';
+import { withFixtures } from '../../helpers';
import {
DEFAULT_BTC_ACCOUNT,
DEFAULT_BTC_BALANCE,
@@ -11,7 +11,9 @@ import {
} from '../../constants';
import { MultichainNetworks } from '../../../../shared/constants/multichain/networks';
import { Driver } from '../../webdriver/driver';
-import messages from '../../../../app/_locales/en/messages.json';
+import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow';
+import AccountListPage from '../../page-objects/pages/account-list-page';
+import HeaderNavbar from '../../page-objects/pages/header-navbar';
const QUICKNODE_URL_REGEX = /^https:\/\/.*\.btc.*\.quiknode\.pro(\/|$)/u;
@@ -21,27 +23,6 @@ export enum SendFlowPlaceHolders {
LOADING = 'Preparing transaction',
}
-export async function createBtcAccount(driver: Driver) {
- await driver.clickElement('[data-testid="account-menu-icon"]');
- await driver.clickElement(
- '[data-testid="multichain-account-menu-popover-action-button"]',
- );
- await driver.clickElement({
- text: messages.addNewBitcoinAccount.message,
- tag: 'button',
- });
- await driver.clickElementAndWaitToDisappear(
- {
- text: 'Add account',
- tag: 'button',
- },
- // Longer timeout than usual, this reduces the flakiness
- // around Bitcoin account creation (mainly required for
- // Firefox)
- 5000,
- );
-}
-
export function btcToSats(btc: number): number {
// Watchout, we're not using BigNumber(s) here (but that's ok for test purposes)
return btc * SATS_IN_1_BTC;
@@ -231,8 +212,12 @@ export async function withBtcAccountSnap(
],
},
async ({ driver, mockServer }: { driver: Driver; mockServer: Mockttp }) => {
- await unlockWallet(driver);
- await createBtcAccount(driver);
+ await loginWithBalanceValidation(driver);
+ // create one BTC account
+ await new HeaderNavbar(driver).openAccountMenu();
+ const accountListPage = new AccountListPage(driver);
+ await accountListPage.check_pageIsLoaded();
+ await accountListPage.addNewBtcAccount();
await test(driver, mockServer);
},
);
diff --git a/test/e2e/flask/btc/create-btc-account.spec.ts b/test/e2e/flask/btc/create-btc-account.spec.ts
index 1b10599bf5ca..0dfb24a8a931 100644
--- a/test/e2e/flask/btc/create-btc-account.spec.ts
+++ b/test/e2e/flask/btc/create-btc-account.spec.ts
@@ -1,26 +1,22 @@
import { strict as assert } from 'assert';
import { Suite } from 'mocha';
-import messages from '../../../../app/_locales/en/messages.json';
-
-import {
- WALLET_PASSWORD,
- completeSRPRevealQuiz,
- getSelectedAccountAddress,
- openSRPRevealQuiz,
- removeSelectedAccount,
- tapAndHoldToRevealSRP,
-} from '../../helpers';
-import { createBtcAccount, withBtcAccountSnap } from './common-btc';
+import { WALLET_PASSWORD } from '../../helpers';
+import AccountListPage from '../../page-objects/pages/account-list-page';
+import HeaderNavbar from '../../page-objects/pages/header-navbar';
+import LoginPage from '../../page-objects/pages/login-page';
+import PrivacySettings from '../../page-objects/pages/settings/privacy-settings';
+import ResetPasswordPage from '../../page-objects/pages/reset-password-page';
+import SettingsPage from '../../page-objects/pages/settings/settings-page';
+import { withBtcAccountSnap } from './common-btc';
describe('Create BTC Account', function (this: Suite) {
it('create BTC account from the menu', async function () {
await withBtcAccountSnap(
{ title: this.test?.fullTitle() },
async (driver) => {
- await driver.findElement({
- css: '[data-testid="account-menu-icon"]',
- text: 'Bitcoin Account',
- });
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.check_accountLabel('Bitcoin Account');
},
);
});
@@ -29,27 +25,23 @@ describe('Create BTC Account', function (this: Suite) {
await withBtcAccountSnap(
{ title: this.test?.fullTitle() },
async (driver) => {
- await driver.delay(500);
- await driver.clickElement('[data-testid="account-menu-icon"]');
- await driver.clickElement(
- '[data-testid="multichain-account-menu-popover-action-button"]',
- );
-
- const createButton = await driver.findElement({
- text: messages.addNewBitcoinAccount.message,
- tag: 'button',
+ // check that we have one BTC account
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.check_accountLabel('Bitcoin Account');
+
+ // check user cannot create second BTC account
+ await headerNavbar.openAccountMenu();
+ const accountListPage = new AccountListPage(driver);
+ await accountListPage.check_pageIsLoaded();
+ await accountListPage.addNewBtcAccount({
+ btcAccountCreationEnabled: false,
});
- assert.equal(await createButton.isEnabled(), false);
- // modal will still be here
- await driver.clickElement('.mm-box button[aria-label="Close"]');
-
- // check the number of accounts. it should only be 2.
- await driver.clickElement('[data-testid="account-menu-icon"]');
- const menuItems = await driver.findElements(
- '.multichain-account-list-item',
- );
- assert.equal(menuItems.length, 2);
+ // check the number of available accounts is 2
+ await headerNavbar.openAccountMenu();
+ await accountListPage.check_pageIsLoaded();
+ await accountListPage.check_numberOfAvailableAccounts(2);
},
);
});
@@ -58,29 +50,22 @@ describe('Create BTC Account', function (this: Suite) {
await withBtcAccountSnap(
{ title: this.test?.fullTitle() },
async (driver) => {
- await driver.findElement({
- css: '[data-testid="account-menu-icon"]',
- text: 'Bitcoin Account',
- });
-
- await driver.clickElement('[data-testid="account-menu-icon"]');
- await driver.clickElement(
- '.multichain-account-list-item--selected [data-testid="account-list-item-menu-button"]',
- );
- await driver.clickElement('[data-testid="account-list-menu-remove"]');
- await driver.clickElement({ text: 'Nevermind', tag: 'button' });
-
- await driver.findElement({
- css: '[data-testid="account-menu-icon"]',
- text: 'Bitcoin Account',
- });
-
- // check the number of accounts. it should only be 2.
- await driver.clickElement('[data-testid="account-menu-icon"]');
- const menuItems = await driver.findElements(
- '.multichain-account-list-item',
- );
- assert.equal(menuItems.length, 2);
+ // check that we have one BTC account
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.check_accountLabel('Bitcoin Account');
+
+ // check user can cancel the removal of the BTC account
+ await headerNavbar.openAccountMenu();
+ const accountListPage = new AccountListPage(driver);
+ await accountListPage.check_pageIsLoaded();
+ await accountListPage.removeAccount('Bitcoin Account', false);
+ await headerNavbar.check_accountLabel('Bitcoin Account');
+
+ // check the number of accounts. it should be 2.
+ await headerNavbar.openAccountMenu();
+ await accountListPage.check_pageIsLoaded();
+ await accountListPage.check_numberOfAvailableAccounts(2);
},
);
});
@@ -89,22 +74,33 @@ describe('Create BTC Account', function (this: Suite) {
await withBtcAccountSnap(
{ title: this.test?.fullTitle() },
async (driver) => {
- await driver.findElement({
- css: '[data-testid="account-menu-icon"]',
- text: 'Bitcoin Account',
- });
-
- const accountAddress = await getSelectedAccountAddress(driver);
- await removeSelectedAccount(driver);
-
- // Recreate account
- await createBtcAccount(driver);
- await driver.findElement({
- css: '[data-testid="account-menu-icon"]',
- text: 'Bitcoin Account',
- });
+ // check that we have one BTC account
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.check_accountLabel('Bitcoin Account');
+
+ // get the address of the BTC account and remove it
+ await headerNavbar.openAccountMenu();
+ const accountListPage = new AccountListPage(driver);
+ await accountListPage.check_pageIsLoaded();
+ const accountAddress = await accountListPage.getAccountAddress(
+ 'Bitcoin Account',
+ );
+ await headerNavbar.openAccountMenu();
+ await accountListPage.removeAccount('Bitcoin Account');
+
+ // Recreate account and check that the address is the same
+ await headerNavbar.openAccountMenu();
+ await accountListPage.check_pageIsLoaded();
+ await accountListPage.addNewBtcAccount();
+ await headerNavbar.check_accountLabel('Bitcoin Account');
+
+ await headerNavbar.openAccountMenu();
+ await accountListPage.check_pageIsLoaded();
+ const recreatedAccountAddress = await accountListPage.getAccountAddress(
+ 'Bitcoin Account',
+ );
- const recreatedAccountAddress = await getSelectedAccountAddress(driver);
assert(accountAddress === recreatedAccountAddress);
},
);
@@ -114,62 +110,50 @@ describe('Create BTC Account', function (this: Suite) {
await withBtcAccountSnap(
{ title: this.test?.fullTitle() },
async (driver) => {
- await driver.findElement({
- css: '[data-testid="account-menu-icon"]',
- text: 'Bitcoin Account',
- });
-
- const accountAddress = await getSelectedAccountAddress(driver);
-
- await openSRPRevealQuiz(driver);
- await completeSRPRevealQuiz(driver);
- await driver.fill('[data-testid="input-password"]', WALLET_PASSWORD);
- await driver.press('[data-testid="input-password"]', driver.Key.ENTER);
- await tapAndHoldToRevealSRP(driver);
- const seedPhrase = await (
- await driver.findElement('[data-testid="srp_text"]')
- ).getText();
-
- // Reset wallet
- await driver.clickElement(
- '[data-testid="account-options-menu-button"]',
- );
- await driver.clickElement({
- css: '[data-testid="global-menu-lock"]',
- text: 'Lock MetaMask',
- });
-
- await driver.clickElement({
- text: 'Forgot password?',
- tag: 'a',
- });
-
- await driver.pasteIntoField(
- '[data-testid="import-srp__srp-word-0"]',
- seedPhrase,
+ // check that we have one BTC account
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.check_accountLabel('Bitcoin Account');
+
+ await headerNavbar.openAccountMenu();
+ const accountListPage = new AccountListPage(driver);
+ await accountListPage.check_pageIsLoaded();
+ const accountAddress = await accountListPage.getAccountAddress(
+ 'Bitcoin Account',
);
- await driver.fill(
- '[data-testid="create-vault-password"]',
- WALLET_PASSWORD,
+ // go to privacy settings page and get the SRP
+ await headerNavbar.openSettingsPage();
+ const settingsPage = new SettingsPage(driver);
+ await settingsPage.check_pageIsLoaded();
+ await settingsPage.goToPrivacySettings();
+
+ const privacySettings = new PrivacySettings(driver);
+ await privacySettings.check_pageIsLoaded();
+ await privacySettings.openRevealSrpQuiz();
+ await privacySettings.completeRevealSrpQuiz();
+ await privacySettings.fillPasswordToRevealSrp(WALLET_PASSWORD);
+ const seedPhrase = await privacySettings.getSrpInRevealSrpDialog();
+
+ // lock metamask and reset wallet by clicking forgot password button
+ await headerNavbar.lockMetaMask();
+ await new LoginPage(driver).gotoResetPasswordPage();
+ const resetPasswordPage = new ResetPasswordPage(driver);
+ await resetPasswordPage.check_pageIsLoaded();
+ await resetPasswordPage.resetPassword(seedPhrase, WALLET_PASSWORD);
+
+ // create a BTC account and check that the address is the same
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.openAccountMenu();
+ await accountListPage.check_pageIsLoaded();
+ await accountListPage.addNewBtcAccount();
+ await headerNavbar.check_accountLabel('Bitcoin Account');
+
+ await headerNavbar.openAccountMenu();
+ await accountListPage.check_pageIsLoaded();
+ const recreatedAccountAddress = await accountListPage.getAccountAddress(
+ 'Bitcoin Account',
);
- await driver.fill(
- '[data-testid="create-vault-confirm-password"]',
- WALLET_PASSWORD,
- );
-
- await driver.clickElement({
- text: 'Restore',
- tag: 'button',
- });
-
- await createBtcAccount(driver);
- await driver.findElement({
- css: '[data-testid="account-menu-icon"]',
- text: 'Bitcoin Account',
- });
-
- const recreatedAccountAddress = await getSelectedAccountAddress(driver);
assert(accountAddress === recreatedAccountAddress);
},
);
diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js
index ae86720da46f..75eefb12f7c1 100644
--- a/test/e2e/helpers.js
+++ b/test/e2e/helpers.js
@@ -5,7 +5,6 @@ const mockttp = require('mockttp');
const detectPort = require('detect-port');
const { difference } = require('lodash');
const createStaticServer = require('../../development/create-static-server');
-const { tEn } = require('../lib/i18n-helpers');
const { setupMocking } = require('./mock-e2e');
const { Ganache } = require('./seeder/ganache');
const FixtureServer = require('./fixture-server');
@@ -385,51 +384,6 @@ const getWindowHandles = async (driver, handlesCount) => {
return { extension, dapp, popup };
};
-const openSRPRevealQuiz = async (driver) => {
- // navigate settings to reveal SRP
- await driver.clickElement('[data-testid="account-options-menu-button"]');
-
- // fix race condition with mmi build
- if (process.env.MMI) {
- await driver.waitForSelector('[data-testid="global-menu-mmi-portfolio"]');
- }
-
- await driver.clickElement({ text: 'Settings', tag: 'div' });
- await driver.clickElement({ text: 'Security & privacy', tag: 'div' });
- await driver.clickElement('[data-testid="reveal-seed-words"]');
-};
-
-/**
- * @deprecated Please use page object functions in `test/e2e/page-objects/pages/settings/privacy-settings.ts`.
- * @param driver
- */
-const completeSRPRevealQuiz = async (driver) => {
- // start quiz
- await driver.clickElement('[data-testid="srp-quiz-get-started"]');
-
- // tap correct answer 1
- await driver.clickElement('[data-testid="srp-quiz-right-answer"]');
-
- // tap Continue 1
- await driver.clickElement('[data-testid="srp-quiz-continue"]');
-
- // tap correct answer 2
- await driver.clickElement('[data-testid="srp-quiz-right-answer"]');
-
- // tap Continue 2
- await driver.clickElement('[data-testid="srp-quiz-continue"]');
-};
-
-const tapAndHoldToRevealSRP = async (driver) => {
- await driver.holdMouseDownOnElement(
- {
- text: tEn('holdToRevealSRP'),
- tag: 'span',
- },
- 3000,
- );
-};
-
const DAPP_HOST_ADDRESS = '127.0.0.1:8080';
const DAPP_URL = `http://${DAPP_HOST_ADDRESS}`;
const DAPP_ONE_URL = 'http://127.0.0.1:8081';
@@ -661,30 +615,27 @@ const locateAccountBalanceDOM = async (
const WALLET_PASSWORD = 'correct horse battery staple';
/**
- * Unlock the wallet with the default password.
+ * Unlocks the wallet using the provided password.
* This method is intended to replace driver.navigate and should not be called after driver.navigate.
*
* @param {WebDriver} driver - The webdriver instance
- * @param {object} options - Options for unlocking the wallet
- * @param {boolean} options.navigate - Whether to navigate to the root page prior to unlocking. Defaults to true.
- * @param {boolean} options.waitLoginSuccess - Whether to wait for the login to succeed. Defaults to true.
+ * @param {object} [options] - Options for unlocking the wallet
+ * @param {boolean} [options.navigate] - Whether to navigate to the root page prior to unlocking - defaults to true
+ * @param {boolean} [options.waitLoginSuccess] - Whether to wait for the login to succeed - defaults to true
+ * @param {string} [options.password] - Password to unlock wallet - defaults to shared WALLET_PASSWORD
*/
async function unlockWallet(
driver,
- options = {
- navigate: true,
- waitLoginSuccess: true,
- },
+ { navigate = true, waitLoginSuccess = true, password = WALLET_PASSWORD } = {},
) {
- if (options.navigate !== false) {
+ if (navigate) {
await driver.navigate();
}
- await driver.fill('#password', WALLET_PASSWORD);
+ await driver.fill('#password', password);
await driver.press('#password', driver.Key.ENTER);
- if (options.waitLoginSuccess !== false) {
- // No guard is necessary here, because it goes from present to absent
+ if (waitLoginSuccess) {
await driver.assertElementNotPresent('[data-testid="unlock-page"]');
}
}
@@ -872,41 +823,48 @@ async function initBundler(bundlerServer, ganacheServer, usePaymaster) {
}
/**
- * @deprecated Please use page object functions in `pages/account-list-page`.
+ * Rather than using the FixtureBuilder#withPreferencesController to set the setting
+ * we need to manually set the setting because the migration #122 overrides this.
+ * We should be able to remove this when we delete the redesignedConfirmationsEnabled setting.
+ *
* @param driver
*/
-async function removeSelectedAccount(driver) {
- await driver.clickElement('[data-testid="account-menu-icon"]');
- await driver.clickElement(
- '.multichain-account-list-item--selected [data-testid="account-list-item-menu-button"]',
- );
- await driver.clickElement('[data-testid="account-list-menu-remove"]');
- await driver.clickElement({ text: 'Remove', tag: 'button' });
-}
+async function tempToggleSettingRedesignedConfirmations(driver) {
+ // Ensure we are on the extension window
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView);
-/**
- * @deprecated Please use page object functions in `pages/account-list-page`.
- * @param driver
- */
-async function getSelectedAccountAddress(driver) {
+ // Open settings menu button
await driver.clickElement('[data-testid="account-options-menu-button"]');
- await driver.clickElement('[data-testid="account-list-menu-details"]');
- const accountAddress = await (
- await driver.findElement('[data-testid="address-copy-button-text"]')
- ).getText();
- await driver.clickElement('.mm-box button[aria-label="Close"]');
- return accountAddress;
+ // fix race condition with mmi build
+ if (process.env.MMI) {
+ await driver.waitForSelector('[data-testid="global-menu-mmi-portfolio"]');
+ }
+
+ // Click settings from dropdown menu
+ await driver.clickElement('[data-testid="global-menu-settings"]');
+
+ // Click Experimental tab
+ const experimentalTabRawLocator = {
+ text: 'Experimental',
+ tag: 'div',
+ };
+ await driver.clickElement(experimentalTabRawLocator);
+
+ // Click redesignedConfirmationsEnabled toggle
+ await driver.clickElement(
+ '[data-testid="toggle-redesigned-confirmations-container"]',
+ );
}
/**
* Rather than using the FixtureBuilder#withPreferencesController to set the setting
- * we need to manually set the setting because the migration #122 overrides this.
- * We should be able to remove this when we delete the redesignedConfirmationsEnabled setting.
+ * we need to manually set the setting because the migration #132 overrides this.
+ * We should be able to remove this when we delete the redesignedTransactionsEnabled setting.
*
* @param driver
*/
-async function tempToggleSettingRedesignedConfirmations(driver) {
+async function tempToggleSettingRedesignedTransactionConfirmations(driver) {
// Ensure we are on the extension window
await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView);
@@ -928,9 +886,14 @@ async function tempToggleSettingRedesignedConfirmations(driver) {
};
await driver.clickElement(experimentalTabRawLocator);
- // Click redesignedConfirmationsEnabled toggle
+ // Click redesigned transactions toggle
await driver.clickElement(
- '[data-testid="toggle-redesigned-confirmations-container"]',
+ '[data-testid="toggle-redesigned-transactions-container"]',
+ );
+
+ // Close settings page
+ await driver.clickElement(
+ '.settings-page__header__title-container__close-button',
);
}
@@ -970,9 +933,6 @@ module.exports = {
largeDelayMs,
veryLargeDelayMs,
withFixtures,
- openSRPRevealQuiz,
- completeSRPRevealQuiz,
- tapAndHoldToRevealSRP,
createDownloadFolder,
openDapp,
openDappConnectionsPage,
@@ -1004,9 +964,8 @@ module.exports = {
getCleanAppState,
editGasFeeForm,
clickNestedButton,
- removeSelectedAccount,
- getSelectedAccountAddress,
tempToggleSettingRedesignedConfirmations,
+ tempToggleSettingRedesignedTransactionConfirmations,
openMenuSafe,
sentryRegEx,
};
diff --git a/test/e2e/json-rpc/eth_sendTransaction.spec.js b/test/e2e/json-rpc/eth_sendTransaction.spec.js
index c17421cca042..ed740d17bfc1 100644
--- a/test/e2e/json-rpc/eth_sendTransaction.spec.js
+++ b/test/e2e/json-rpc/eth_sendTransaction.spec.js
@@ -4,6 +4,7 @@ const {
unlockWallet,
WINDOW_TITLES,
generateGanacheOptions,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../helpers');
const FixtureBuilder = require('../fixture-builder');
@@ -68,6 +69,8 @@ describe('eth_sendTransaction', function () {
async ({ driver }) => {
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// eth_sendTransaction
await driver.openNewPage(`http://127.0.0.1:8080`);
const request = JSON.stringify({
diff --git a/test/e2e/json-rpc/switchEthereumChain.spec.js b/test/e2e/json-rpc/switchEthereumChain.spec.js
index 60ba4eb9aacb..6a8576b3f136 100644
--- a/test/e2e/json-rpc/switchEthereumChain.spec.js
+++ b/test/e2e/json-rpc/switchEthereumChain.spec.js
@@ -8,509 +8,669 @@ const {
unlockWallet,
switchToNotificationWindow,
WINDOW_TITLES,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../helpers');
const FixtureBuilder = require('../fixture-builder');
const { isManifestV3 } = require('../../../shared/modules/mv3.utils');
describe('Switch Ethereum Chain for two dapps', function () {
- it('switches the chainId of two dapps when switchEthereumChain of one dapp is confirmed', async function () {
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .build(),
- dappOptions: { numberOfDapps: 2 },
-
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [{ port: 8546, chainId: 1338 }],
+ describe('Old confirmation screens', function () {
+ it('queues send tx after switchEthereum request with a warning, if switchEthereum request is cancelled should show pending tx', async function () {
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [{ port: 8546, chainId: 1338 }],
+ },
+ title: this.test.fullTitle(),
},
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
-
- // Open settings menu button
- const accountOptionsMenuSelector =
- '[data-testid="account-options-menu-button"]';
- await driver.waitForSelector(accountOptionsMenuSelector);
- await driver.clickElement(accountOptionsMenuSelector);
-
- // Click settings from dropdown menu
- const globalMenuSettingsSelector =
- '[data-testid="global-menu-settings"]';
- await driver.waitForSelector(globalMenuSettingsSelector);
- await driver.clickElement(globalMenuSettingsSelector);
-
- // Click Experimental tab
- const experimentalTabRawLocator = {
- text: 'Experimental',
- tag: 'div',
- };
- await driver.clickElement(experimentalTabRawLocator);
-
- // Toggle off request queue setting (on by default now)
- await driver.clickElement(
- '[data-testid="experimental-setting-toggle-request-queue"]',
- );
-
- // open two dapps
- const dappOne = await openDapp(driver, undefined, DAPP_URL);
- const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL);
-
- // switchEthereumChain request
- const switchEthereumChainRequest = JSON.stringify({
- jsonrpc: '2.0',
- method: 'wallet_switchEthereumChain',
- params: [{ chainId: '0x53a' }],
- });
-
- // Initiate switchEthereumChain on Dapp Two
- await driver.executeScript(
- `window.ethereum.request(${switchEthereumChainRequest})`,
- );
-
- // Confirm switchEthereumChain
- await switchToNotificationWindow(driver, 4);
- await driver.findClickableElements({
- text: 'Confirm',
- tag: 'button',
- });
- await driver.clickElement({ text: 'Confirm', tag: 'button' });
-
- // Switch to Dapp One
- await driver.switchToWindow(dappOne);
- assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`);
-
- // Wait for chain id element to change, there's a page reload.
- await driver.waitForSelector({
- css: '#chainId',
- text: '0x53a',
- });
-
- // Dapp One ChainId assertion
- await driver.findElement({ css: '#chainId', text: '0x53a' });
-
- // Switch to Dapp Two
- await driver.switchToWindow(dappTwo);
- assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`);
-
- // Dapp Two ChainId Assertion
- await driver.findElement({ css: '#chainId', text: '0x53a' });
- },
- );
- });
-
- it('queues switchEthereumChain request from second dapp after send tx request', async function () {
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .build(),
- dappOptions: { numberOfDapps: 2 },
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [{ port: 8546, chainId: 1338 }],
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ // Open settings menu button
+ const accountOptionsMenuSelector =
+ '[data-testid="account-options-menu-button"]';
+ await driver.waitForSelector(accountOptionsMenuSelector);
+ await driver.clickElement(accountOptionsMenuSelector);
+
+ // Click settings from dropdown menu
+ const globalMenuSettingsSelector =
+ '[data-testid="global-menu-settings"]';
+ await driver.waitForSelector(globalMenuSettingsSelector);
+ await driver.clickElement(globalMenuSettingsSelector);
+
+ // Click Experimental tab
+ const experimentalTabRawLocator = {
+ text: 'Experimental',
+ tag: 'div',
+ };
+ await driver.clickElement(experimentalTabRawLocator);
+
+ // Toggle off request queue setting (on by default now)
+ await driver.clickElement(
+ '[data-testid="experimental-setting-toggle-request-queue"]',
+ );
+
+ // open two dapps
+ const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL);
+ const dappOne = await openDapp(driver, undefined, DAPP_URL);
+
+ // Connect Dapp One
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // Switch and connect Dapp Two
+ await driver.switchToWindow(dappTwo);
+ assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`);
+
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ const editButtons = await driver.findElements('[data-testid="edit"]');
+
+ // Click the edit button for networks
+ await editButtons[1].click();
+
+ // Disconnect Mainnet
+ await driver.clickElement({
+ text: 'Localhost 8545',
+ tag: 'p',
+ });
+
+ await driver.clickElement(
+ '[data-testid="connect-more-chains-button"]',
+ );
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+ await driver.switchToWindow(dappTwo);
+ assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`);
+
+ // switchEthereumChain request
+ const switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x539' }],
+ });
+
+ // Initiate switchEthereumChain on Dapp Two
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+
+ // Switch to notification of switchEthereumChain
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.findClickableElements({
+ text: 'Confirm',
+ tag: 'button',
+ });
+
+ // Switch back to dapp one
+ await driver.switchToWindow(dappOne);
+ assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`);
+
+ // Initiate send tx on dapp one
+ await driver.clickElement('#sendButton');
+ await driver.delay(2000);
+
+ // Switch to notification that should still be switchEthereumChain request but with an warning.
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // THIS IS BROKEN
+ // await driver.findElement({
+ // span: 'span',
+ // text: 'Switching networks will cancel all pending confirmations',
+ // });
+
+ // Cancel switchEthereumChain with queued pending tx
+ await driver.clickElement({ text: 'Cancel', tag: 'button' });
+
+ // Delay for second notification of the pending tx
+ await driver.delay(1000);
+
+ // Switch to new pending tx notification
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.findElement({
+ text: 'Sending ETH',
+ tag: 'span',
+ });
+
+ // Confirm pending tx
+ await driver.findClickableElements({
+ text: 'Confirm',
+ tag: 'button',
+ });
+ await driver.clickElement({
+ text: 'Confirm',
+ tag: 'button',
+ });
},
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
-
- // Open settings menu button
- const accountOptionsMenuSelector =
- '[data-testid="account-options-menu-button"]';
- await driver.waitForSelector(accountOptionsMenuSelector);
- await driver.clickElement(accountOptionsMenuSelector);
-
- // Click settings from dropdown menu
- const globalMenuSettingsSelector =
- '[data-testid="global-menu-settings"]';
- await driver.waitForSelector(globalMenuSettingsSelector);
- await driver.clickElement(globalMenuSettingsSelector);
-
- // Click Experimental tab
- const experimentalTabRawLocator = {
- text: 'Experimental',
- tag: 'div',
- };
- await driver.clickElement(experimentalTabRawLocator);
-
- // Toggle off request queue setting (on by default now)
- await driver.clickElement(
- '[data-testid="experimental-setting-toggle-request-queue"]',
- );
-
- // open two dapps
- await openDapp(driver, undefined, DAPP_URL);
- await openDapp(driver, undefined, DAPP_ONE_URL);
-
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
-
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
-
- // Switch to Dapp One and connect it
- await driver.switchToWindowWithUrl(DAPP_URL);
- await driver.findClickableElement({
- text: 'Connect',
- tag: 'button',
- });
- await driver.clickElement('#connectButton');
-
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- const editButtons = await driver.findElements('[data-testid="edit"]');
-
- await editButtons[1].click();
-
- // Disconnect Localhost 8545
- await driver.clickElement({
- text: 'Localhost 8545',
- tag: 'p',
- });
-
- await driver.clickElement('[data-testid="connect-more-chains-button"]');
-
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
-
- // Switch to Dapp Two
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
- // Initiate send transaction on Dapp two
- await driver.clickElement('#sendButton');
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.findClickableElements({
- text: 'Confirm',
- tag: 'button',
- });
-
- // Switch to Dapp One
- await driver.switchToWindowWithUrl(DAPP_URL);
-
- // Switch Ethereum chain request
- const switchEthereumChainRequest = JSON.stringify({
- jsonrpc: '2.0',
- method: 'wallet_switchEthereumChain',
- params: [{ chainId: '0x539' }],
- });
-
- // Initiate switchEthereumChain on Dapp One
- await driver.executeScript(
- `window.ethereum.request(${switchEthereumChainRequest})`,
- );
- await switchToNotificationWindow(driver, 4);
- await driver.findClickableElements({
- text: 'Confirm',
- tag: 'button',
- });
- await driver.clickElement({
- text: 'Confirm',
- tag: 'button',
- });
- // Delay here after notification for second notification popup for switchEthereumChain
- await driver.delay(1000);
-
- // Switch and confirm to queued notification for switchEthereumChain
- await switchToNotificationWindow(driver, 4);
-
- await driver.findClickableElements({
- text: 'Confirm',
- tag: 'button',
- });
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Confirm',
- tag: 'button',
- });
- await driver.switchToWindowWithUrl(DAPP_URL);
- await driver.findElement({ css: '#chainId', text: '0x539' });
- },
- );
+ );
+ });
});
- it('queues send tx after switchEthereum request with a warning, confirming removes pending tx', async function () {
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .build(),
- dappOptions: { numberOfDapps: 2 },
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [{ port: 8546, chainId: 1338 }],
+ describe('Redesigned confirmation screens', function () {
+ it('switches the chainId of two dapps when switchEthereumChain of one dapp is confirmed', async function () {
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [{ port: 8546, chainId: 1338 }],
+ },
+ title: this.test.fullTitle(),
},
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
-
- // Open settings menu button
- const accountOptionsMenuSelector =
- '[data-testid="account-options-menu-button"]';
- await driver.waitForSelector(accountOptionsMenuSelector);
- await driver.clickElement(accountOptionsMenuSelector);
-
- // Click settings from dropdown menu
- const globalMenuSettingsSelector =
- '[data-testid="global-menu-settings"]';
- await driver.waitForSelector(globalMenuSettingsSelector);
- await driver.clickElement(globalMenuSettingsSelector);
-
- // Click Experimental tab
- const experimentalTabRawLocator = {
- text: 'Experimental',
- tag: 'div',
- };
- await driver.clickElement(experimentalTabRawLocator);
-
- // Toggle off request queue setting (on by default now)
- await driver.clickElement(
- '[data-testid="experimental-setting-toggle-request-queue"]',
- );
-
- // open two dapps
- const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL);
- const dappOne = await openDapp(driver, undefined, DAPP_URL);
-
- // Connect Dapp One
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
-
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
-
- // Switch and connect Dapp Two
-
- await driver.switchToWindow(dappTwo);
- assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`);
-
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
-
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- const editButtons = await driver.findElements('[data-testid="edit"]');
-
- // Click the edit button for networks
- await editButtons[1].click();
-
- // Disconnect Mainnet
- await driver.clickElement({
- text: 'Localhost 8545',
- tag: 'p',
- });
-
- await driver.clickElement('[data-testid="connect-more-chains-button"]');
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
-
- await driver.switchToWindow(dappTwo);
- assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`);
-
- // switchEthereumChain request
- const switchEthereumChainRequest = JSON.stringify({
- jsonrpc: '2.0',
- method: 'wallet_switchEthereumChain',
- params: [{ chainId: '0x539' }],
- });
-
- // Initiate switchEthereumChain on Dapp Two
- await driver.executeScript(
- `window.ethereum.request(${switchEthereumChainRequest})`,
- );
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.findClickableElements({
- text: 'Confirm',
- tag: 'button',
- });
- // Switch back to dapp one
- await driver.switchToWindow(dappOne);
- assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`);
-
- // Initiate send tx on dapp one
- await driver.clickElement('#sendButton');
- await driver.delay(2000);
-
- // Switch to notification that should still be switchEthereumChain request but with a warning.
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- // THIS IS BROKEN
- // await driver.findElement({
- // span: 'span',
- // text: 'Switching networks will cancel all pending confirmations',
- // });
-
- // Confirm switchEthereumChain with queued pending tx
- await driver.clickElement({ text: 'Confirm', tag: 'button' });
-
- // Window handles should only be expanded mm, dapp one, dapp 2, and the offscreen document
- // if this is an MV3 build(3 or 4 total)
- await driver.wait(async () => {
- const windowHandles = await driver.getAllWindowHandles();
- const numberOfWindowHandlesToExpect = isManifestV3 ? 4 : 3;
- return windowHandles.length === numberOfWindowHandlesToExpect;
- });
- },
- );
- });
-
- it('queues send tx after switchEthereum request with a warning, if switchEthereum request is cancelled should show pending tx', async function () {
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .build(),
- dappOptions: { numberOfDapps: 2 },
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [{ port: 8546, chainId: 1338 }],
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open settings menu button
+ const accountOptionsMenuSelector =
+ '[data-testid="account-options-menu-button"]';
+ await driver.waitForSelector(accountOptionsMenuSelector);
+ await driver.clickElement(accountOptionsMenuSelector);
+
+ // Click settings from dropdown menu
+ const globalMenuSettingsSelector =
+ '[data-testid="global-menu-settings"]';
+ await driver.waitForSelector(globalMenuSettingsSelector);
+ await driver.clickElement(globalMenuSettingsSelector);
+
+ // Click Experimental tab
+ const experimentalTabRawLocator = {
+ text: 'Experimental',
+ tag: 'div',
+ };
+ await driver.clickElement(experimentalTabRawLocator);
+
+ // Toggle off request queue setting (on by default now)
+ await driver.clickElement(
+ '[data-testid="experimental-setting-toggle-request-queue"]',
+ );
+
+ // open two dapps
+ const dappOne = await openDapp(driver, undefined, DAPP_URL);
+ const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ // switchEthereumChain request
+ const switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x53a' }],
+ });
+
+ // Initiate switchEthereumChain on Dapp Two
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+
+ // Confirm switchEthereumChain
+ await switchToNotificationWindow(driver, 4);
+ await driver.findClickableElements({
+ text: 'Confirm',
+ tag: 'button',
+ });
+ await driver.clickElement({ text: 'Confirm', tag: 'button' });
+
+ // Switch to Dapp One
+ await driver.switchToWindow(dappOne);
+ assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`);
+
+ // Wait for chain id element to change, there's a page reload.
+ await driver.waitForSelector({
+ css: '#chainId',
+ text: '0x53a',
+ });
+
+ // Dapp One ChainId assertion
+ await driver.findElement({ css: '#chainId', text: '0x53a' });
+
+ // Switch to Dapp Two
+ await driver.switchToWindow(dappTwo);
+ assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`);
+
+ // Dapp Two ChainId Assertion
+ await driver.findElement({ css: '#chainId', text: '0x53a' });
+ },
+ );
+ });
+
+ it('queues switchEthereumChain request from second dapp after send tx request', async function () {
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [{ port: 8546, chainId: 1338 }],
+ },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open settings menu button
+ const accountOptionsMenuSelector =
+ '[data-testid="account-options-menu-button"]';
+ await driver.waitForSelector(accountOptionsMenuSelector);
+ await driver.clickElement(accountOptionsMenuSelector);
+
+ // Click settings from dropdown menu
+ const globalMenuSettingsSelector =
+ '[data-testid="global-menu-settings"]';
+ await driver.waitForSelector(globalMenuSettingsSelector);
+ await driver.clickElement(globalMenuSettingsSelector);
+
+ // Click Experimental tab
+ const experimentalTabRawLocator = {
+ text: 'Experimental',
+ tag: 'div',
+ };
+ await driver.clickElement(experimentalTabRawLocator);
+
+ // Toggle off request queue setting (on by default now)
+ await driver.clickElement(
+ '[data-testid="experimental-setting-toggle-request-queue"]',
+ );
+
+ // open two dapps
+ await openDapp(driver, undefined, DAPP_URL);
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // Switch to Dapp One and connect it
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.findClickableElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ const editButtons = await driver.findElements('[data-testid="edit"]');
+
+ await editButtons[1].click();
+
+ // Disconnect Localhost 8545
+ await driver.clickElement({
+ text: 'Localhost 8545',
+ tag: 'p',
+ });
+
+ await driver.clickElement(
+ '[data-testid="connect-more-chains-button"]',
+ );
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // Switch to Dapp Two
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ // Initiate send transaction on Dapp two
+ await driver.clickElement('#sendButton');
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.findClickableElements({
+ text: 'Confirm',
+ tag: 'button',
+ });
+
+ // Switch to Dapp One
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ // Switch Ethereum chain request
+ const switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x539' }],
+ });
+
+ // Initiate switchEthereumChain on Dapp One
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+ await switchToNotificationWindow(driver, 4);
+ await driver.findClickableElements({
+ text: 'Confirm',
+ tag: 'button',
+ });
+ await driver.clickElement({
+ text: 'Confirm',
+ tag: 'button',
+ });
+ // Delay here after notification for second notification popup for switchEthereumChain
+ await driver.delay(1000);
+
+ // Switch and confirm to queued notification for switchEthereumChain
+ await switchToNotificationWindow(driver, 4);
+
+ await driver.findClickableElements({
+ text: 'Confirm',
+ tag: 'button',
+ });
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Confirm',
+ tag: 'button',
+ });
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.findElement({ css: '#chainId', text: '0x539' });
+ },
+ );
+ });
+
+ it('queues send tx after switchEthereum request with a warning, confirming removes pending tx', async function () {
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [{ port: 8546, chainId: 1338 }],
+ },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open settings menu button
+ const accountOptionsMenuSelector =
+ '[data-testid="account-options-menu-button"]';
+ await driver.waitForSelector(accountOptionsMenuSelector);
+ await driver.clickElement(accountOptionsMenuSelector);
+
+ // Click settings from dropdown menu
+ const globalMenuSettingsSelector =
+ '[data-testid="global-menu-settings"]';
+ await driver.waitForSelector(globalMenuSettingsSelector);
+ await driver.clickElement(globalMenuSettingsSelector);
+
+ // Click Experimental tab
+ const experimentalTabRawLocator = {
+ text: 'Experimental',
+ tag: 'div',
+ };
+ await driver.clickElement(experimentalTabRawLocator);
+
+ // Toggle off request queue setting (on by default now)
+ await driver.clickElement(
+ '[data-testid="experimental-setting-toggle-request-queue"]',
+ );
+
+ // open two dapps
+ const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL);
+ const dappOne = await openDapp(driver, undefined, DAPP_URL);
+
+ // Connect Dapp One
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // Switch and connect Dapp Two
+
+ await driver.switchToWindow(dappTwo);
+ assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`);
+
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ const editButtons = await driver.findElements('[data-testid="edit"]');
+
+ // Click the edit button for networks
+ await editButtons[1].click();
+
+ // Disconnect Mainnet
+ await driver.clickElement({
+ text: 'Localhost 8545',
+ tag: 'p',
+ });
+
+ await driver.clickElement(
+ '[data-testid="connect-more-chains-button"]',
+ );
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindow(dappTwo);
+ assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`);
+
+ // switchEthereumChain request
+ const switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x539' }],
+ });
+
+ // Initiate switchEthereumChain on Dapp Two
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.findClickableElements({
+ text: 'Confirm',
+ tag: 'button',
+ });
+ // Switch back to dapp one
+ await driver.switchToWindow(dappOne);
+ assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`);
+
+ // Initiate send tx on dapp one
+ await driver.clickElement('#sendButton');
+ await driver.delay(2000);
+
+ // Switch to notification that should still be switchEthereumChain request but with a warning.
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // THIS IS BROKEN
+ // await driver.findElement({
+ // span: 'span',
+ // text: 'Switching networks will cancel all pending confirmations',
+ // });
+
+ // Confirm switchEthereumChain with queued pending tx
+ await driver.clickElement({ text: 'Confirm', tag: 'button' });
+
+ // Window handles should only be expanded mm, dapp one, dapp 2, and the offscreen document
+ // if this is an MV3 build(3 or 4 total)
+ await driver.wait(async () => {
+ const windowHandles = await driver.getAllWindowHandles();
+ const numberOfWindowHandlesToExpect = isManifestV3 ? 4 : 3;
+ return windowHandles.length === numberOfWindowHandlesToExpect;
+ });
+ },
+ );
+ });
+
+ it('queues send tx after switchEthereum request with a warning, if switchEthereum request is cancelled should show pending tx', async function () {
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [{ port: 8546, chainId: 1338 }],
+ },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open settings menu button
+ const accountOptionsMenuSelector =
+ '[data-testid="account-options-menu-button"]';
+ await driver.waitForSelector(accountOptionsMenuSelector);
+ await driver.clickElement(accountOptionsMenuSelector);
+
+ // Click settings from dropdown menu
+ const globalMenuSettingsSelector =
+ '[data-testid="global-menu-settings"]';
+ await driver.waitForSelector(globalMenuSettingsSelector);
+ await driver.clickElement(globalMenuSettingsSelector);
+
+ // Click Experimental tab
+ const experimentalTabRawLocator = {
+ text: 'Experimental',
+ tag: 'div',
+ };
+ await driver.clickElement(experimentalTabRawLocator);
+
+ // Toggle off request queue setting (on by default now)
+ await driver.clickElement(
+ '[data-testid="experimental-setting-toggle-request-queue"]',
+ );
+
+ // open two dapps
+ const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL);
+ const dappOne = await openDapp(driver, undefined, DAPP_URL);
+
+ // Connect Dapp One
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // Switch and connect Dapp Two
+ await driver.switchToWindow(dappTwo);
+ assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`);
+
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ const editButtons = await driver.findElements('[data-testid="edit"]');
+
+ // Click the edit button for networks
+ await editButtons[1].click();
+
+ // Disconnect Mainnet
+ await driver.clickElement({
+ text: 'Localhost 8545',
+ tag: 'p',
+ });
+
+ await driver.clickElement(
+ '[data-testid="connect-more-chains-button"]',
+ );
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+ await driver.switchToWindow(dappTwo);
+ assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`);
+
+ // switchEthereumChain request
+ const switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x539' }],
+ });
+
+ // Initiate switchEthereumChain on Dapp Two
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+
+ // Switch to notification of switchEthereumChain
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.findClickableElements({
+ text: 'Confirm',
+ tag: 'button',
+ });
+
+ // Switch back to dapp one
+ await driver.switchToWindow(dappOne);
+ assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`);
+
+ // Initiate send tx on dapp one
+ await driver.clickElement('#sendButton');
+ await driver.delay(2000);
+
+ // Switch to notification that should still be switchEthereumChain request but with an warning.
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // Cancel switchEthereumChain with queued pending tx
+ await driver.clickElement({ text: 'Cancel', tag: 'button' });
+
+ // Delay for second notification of the pending tx
+ await driver.delay(1000);
+
+ // Switch to new pending tx notification
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.findElement({
+ text: 'Transfer request',
+ tag: 'h3',
+ });
+
+ await driver.findElement({
+ text: '0 ETH',
+ tag: 'h2',
+ });
+
+ // Confirm pending tx
+ await driver.findClickableElements({
+ text: 'Confirm',
+ tag: 'button',
+ });
+ await driver.clickElement({
+ text: 'Confirm',
+ tag: 'button',
+ });
},
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
-
- // Open settings menu button
- const accountOptionsMenuSelector =
- '[data-testid="account-options-menu-button"]';
- await driver.waitForSelector(accountOptionsMenuSelector);
- await driver.clickElement(accountOptionsMenuSelector);
-
- // Click settings from dropdown menu
- const globalMenuSettingsSelector =
- '[data-testid="global-menu-settings"]';
- await driver.waitForSelector(globalMenuSettingsSelector);
- await driver.clickElement(globalMenuSettingsSelector);
-
- // Click Experimental tab
- const experimentalTabRawLocator = {
- text: 'Experimental',
- tag: 'div',
- };
- await driver.clickElement(experimentalTabRawLocator);
-
- // Toggle off request queue setting (on by default now)
- await driver.clickElement(
- '[data-testid="experimental-setting-toggle-request-queue"]',
- );
-
- // open two dapps
- const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL);
- const dappOne = await openDapp(driver, undefined, DAPP_URL);
-
- // Connect Dapp One
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
-
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
-
- // Switch and connect Dapp Two
- await driver.switchToWindow(dappTwo);
- assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`);
-
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
-
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- const editButtons = await driver.findElements('[data-testid="edit"]');
-
- // Click the edit button for networks
- await editButtons[1].click();
-
- // Disconnect Mainnet
- await driver.clickElement({
- text: 'Localhost 8545',
- tag: 'p',
- });
-
- await driver.clickElement('[data-testid="connect-more-chains-button"]');
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
- await driver.switchToWindow(dappTwo);
- assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`);
-
- // switchEthereumChain request
- const switchEthereumChainRequest = JSON.stringify({
- jsonrpc: '2.0',
- method: 'wallet_switchEthereumChain',
- params: [{ chainId: '0x539' }],
- });
-
- // Initiate switchEthereumChain on Dapp Two
- await driver.executeScript(
- `window.ethereum.request(${switchEthereumChainRequest})`,
- );
-
- // Switch to notification of switchEthereumChain
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.findClickableElements({
- text: 'Confirm',
- tag: 'button',
- });
-
- // Switch back to dapp one
- await driver.switchToWindow(dappOne);
- assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`);
-
- // Initiate send tx on dapp one
- await driver.clickElement('#sendButton');
- await driver.delay(2000);
-
- // Switch to notification that should still be switchEthereumChain request but with an warning.
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- // THIS IS BROKEN
- // await driver.findElement({
- // span: 'span',
- // text: 'Switching networks will cancel all pending confirmations',
- // });
-
- // Cancel switchEthereumChain with queued pending tx
- await driver.clickElement({ text: 'Cancel', tag: 'button' });
-
- // Delay for second notification of the pending tx
- await driver.delay(1000);
-
- // Switch to new pending tx notification
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.findElement({
- text: 'Sending ETH',
- tag: 'span',
- });
-
- // Confirm pending tx
- await driver.findClickableElements({
- text: 'Confirm',
- tag: 'button',
- });
- await driver.clickElement({
- text: 'Confirm',
- tag: 'button',
- });
- },
- );
+ );
+ });
});
});
diff --git a/test/e2e/mock-cdn/cdn-stale-diff-res-headers.json b/test/e2e/mock-cdn/cdn-stale-diff-res-headers.json
index 0fb0ec0f7d89..786436052a14 100644
--- a/test/e2e/mock-cdn/cdn-stale-diff-res-headers.json
+++ b/test/e2e/mock-cdn/cdn-stale-diff-res-headers.json
@@ -1,4 +1,4 @@
{
"Content-Type": "text/plain",
- "Etag": "W/\"5ae8a43f84ccd89e8ddc79b1dfed0035\""
+ "Etag": "W/\"24396be8aa89668e00ba9f795d7753e6\""
}
diff --git a/test/e2e/mock-cdn/cdn-stale-diff.txt b/test/e2e/mock-cdn/cdn-stale-diff.txt
index 44eb67f85fa4..9d9b4e83e97b 100644
Binary files a/test/e2e/mock-cdn/cdn-stale-diff.txt and b/test/e2e/mock-cdn/cdn-stale-diff.txt differ
diff --git a/test/e2e/mock-cdn/ppom-version-headers.json b/test/e2e/mock-cdn/ppom-version-headers.json
index ad50d161d1dd..1ab52017d4a0 100644
--- a/test/e2e/mock-cdn/ppom-version-headers.json
+++ b/test/e2e/mock-cdn/ppom-version-headers.json
@@ -1,3 +1,3 @@
{
- "Etag": "W/\"7aa74f7c18a5cb2601e4fc6afcadc9cc\""
+ "Etag": "W/\"0b264c1a98f3bb20ea7741b37850b69a\""
}
diff --git a/test/e2e/mock-cdn/ppom-version.json b/test/e2e/mock-cdn/ppom-version.json
index b529f71a0f1c..59216b0ce281 100644
--- a/test/e2e/mock-cdn/ppom-version.json
+++ b/test/e2e/mock-cdn/ppom-version.json
@@ -152,302 +152,302 @@
{
"name": "stale_diff",
"chainId": "0x38",
- "version": "0.0.482",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0x38/0.0.482",
- "timestamp": "2024-10-27T12:49:57.410484"
+ "version": "0.0.483",
+ "checksum": "b979504b7de98aad15400df3e366ab734725c11a30d4f893c7dc9ac07bdc230a",
+ "signature": "1333191445c23cebd097f6405d50298f7866fda9f209522054292d66f2c01d227f9663597976af133dfcfe16fcd325d8c65ac51345863a5412211b979f1fc80e",
+ "hashSignature": "ce019245ed127bbb5c0cff6c06d7485ed4eb79df1870b4f513e75e230ee6719637bdcb7fc22046315ccb8d88996ab77edd7cc53ffa359740d66f3ef4f6260e00",
+ "filePath": "stale_diff/0x38/0.0.483",
+ "timestamp": "2024-10-28T13:04:21.808185"
},
{
"name": "config",
"chainId": "0x38",
- "version": "0.0.482",
+ "version": "0.0.483",
"checksum": "e29783f8be2392ee395055382aa7b19a78564f99915bb749492577712c18c6f0",
"signature": "0e4e28c1e10d8e0243fd6009787dc5742a76cc5460c2565459094aa112fe2cbddb62001b8d5771ed75e836d842f87baf2a8bdc099003797ebc6c7a80079f4701",
"hashSignature": "ac2ba4a855e4e271f1ce7f4f6b67ac1b10a56378ab3f31a638aff4d5f5ccccc9385d976eec2fb304427810995ed22bd755adac62d15d4fcf6fd4934ee3883d00",
- "filePath": "config/0x38/0.0.482",
- "timestamp": "2024-10-27T12:49:57.410737"
+ "filePath": "config/0x38/0.0.483",
+ "timestamp": "2024-10-28T13:04:21.808484"
},
{
"name": "stale_diff",
"chainId": "0x1",
- "version": "0.0.525",
- "checksum": "ae3059765d220e8cda72aa43638916b9baac84f264a39a1d147a5b144378df62",
- "signature": "3ed03cfa8bee704816a9ceb270fda86d7b007f0fe510d6acc40f96b15819c114fbd768d8845d75ab803c981eb151b4b0a24af705a27e1f96381bdc6dc5e3b50f",
- "hashSignature": "9394bc16a948ab41ee74c0270a900092cbb8707fe72d3576fd75f0b87c698089c0a10b45a20ea47691a90cee427e605f81838b87424291902a9b54fec19e0709",
- "filePath": "stale_diff/0x1/0.0.525",
- "timestamp": "2024-10-27T12:50:03.871663"
+ "version": "0.0.526",
+ "checksum": "d0182b5ad9ce2bdf7c237fdf344e25c83eaab132e770db5d259f50c98894249e",
+ "signature": "51403083c28a827d20a2d998a8f25e6ed64e79fa8a330e7760a4090987c0162ab14b0ed12c4bb9c82dfe2d198e242e4211b7c882ae64bce4bdbcc37cca68990e",
+ "hashSignature": "b6196a6ef87f47fcbf91d84367a26e05491ef1ca7d7204f7cd4e2f78edbdacff842b3189ff4a5c73ff7eae3f10f1d0d2f677954658556c5af2add0176daa1b00",
+ "filePath": "stale_diff/0x1/0.0.526",
+ "timestamp": "2024-10-28T13:04:33.812986"
},
{
"name": "config",
"chainId": "0x1",
- "version": "0.0.525",
+ "version": "0.0.526",
"checksum": "abe69e1c8f6084d26b1d556bb3ae4919628ac4bf3b468bea95e3c14254bdf290",
"signature": "52ffaf9e1a543f8164ea93558467f7f4e02c15650daf92f1a1e374848c53b91dcca96037fd6d7bd63b13e7fcf88a1bcc9fe7c7915d8d6949bd153e6bf6b1a403",
"hashSignature": "83c1edb28635049e4c99d8610782506818ef071de95df49f9242f229991924b4ea829782b0ac125de3f763fc7415baaebf3732a920fb4d01861e1fdd5cb86207",
- "filePath": "config/0x1/0.0.525",
- "timestamp": "2024-10-27T12:50:03.871914"
+ "filePath": "config/0x1/0.0.526",
+ "timestamp": "2024-10-28T13:04:33.813277"
},
{
"name": "stale_diff",
"chainId": "0x89",
- "version": "0.0.481",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0x89/0.0.481",
- "timestamp": "2024-10-27T12:50:09.561729"
+ "version": "0.0.482",
+ "checksum": "5cd3b461f37480a0f84c5769156ed4d362f0dade06c1bf3fc266ed3de7fadb6d",
+ "signature": "5f049aab6c8a5c18dcdf1d730b1c306ae3728f288af4bcc22e3cc6a243bbe2673444a6e4316b2dbd084e471aec85f102f864011c1e7438367b243b43d1b3e604",
+ "hashSignature": "78ae36e78e6c3c3d2aa48a86c19cbd667b907c1ada62aaac8a34e3982be16ec9727041264bf61d445469de2fd56a94f8c4289e17a560f25cb31240c8d41ca303",
+ "filePath": "stale_diff/0x89/0.0.482",
+ "timestamp": "2024-10-28T13:04:41.430575"
},
{
"name": "config",
"chainId": "0x89",
- "version": "0.0.481",
+ "version": "0.0.482",
"checksum": "eb3b41ae8c3bbf9595dcded8e1b9090c8ed2427e236375652f6fd701c6e134b4",
"signature": "79552c7fa525e05c9c086fe8d8ecb49375be796176877e17332fa1137d4c653f224873bdac2b6fd4fe63fcfe6d404778684116e98fdb0563f63ae1efcfafe60d",
"hashSignature": "9c8a84e430578290eceea60ba7dec3695e0cfd343f7ac3e2667c8634afa3dd65d6ec25b112ac8260dbe3e6bc9e00ba906c4212975652354b1423f6ee0ec7b20e",
- "filePath": "config/0x89/0.0.481",
- "timestamp": "2024-10-27T12:50:09.561989"
+ "filePath": "config/0x89/0.0.482",
+ "timestamp": "2024-10-28T13:04:41.430884"
},
{
"name": "stale_diff",
"chainId": "0xa",
- "version": "0.0.481",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0xa/0.0.481",
- "timestamp": "2024-10-27T12:50:15.189820"
+ "version": "0.0.482",
+ "checksum": "8f41a8616fc5fb1e55de136d6e0aef1e834049a1452e148325bbbb059233acb8",
+ "signature": "e7e1a80c48a32902cc526dae4ae8e05980f059de0447bfbfe533c26ff03f8721c004e4f35888708964a19bebc5f0d91753f741dabe0418a0d8a8af38f71e0406",
+ "hashSignature": "056b583909986fee8cb115d2a0827a704e391b8a3f3392d9d42be17e60344b397fab2ae31561c4211a67dfcf6af86b1e22facac7c47393cb3b978a9396888d0a",
+ "filePath": "stale_diff/0xa/0.0.482",
+ "timestamp": "2024-10-28T13:04:49.257520"
},
{
"name": "config",
"chainId": "0xa",
- "version": "0.0.481",
+ "version": "0.0.482",
"checksum": "eb3b41ae8c3bbf9595dcded8e1b9090c8ed2427e236375652f6fd701c6e134b4",
"signature": "79552c7fa525e05c9c086fe8d8ecb49375be796176877e17332fa1137d4c653f224873bdac2b6fd4fe63fcfe6d404778684116e98fdb0563f63ae1efcfafe60d",
"hashSignature": "9c8a84e430578290eceea60ba7dec3695e0cfd343f7ac3e2667c8634afa3dd65d6ec25b112ac8260dbe3e6bc9e00ba906c4212975652354b1423f6ee0ec7b20e",
- "filePath": "config/0xa/0.0.481",
- "timestamp": "2024-10-27T12:50:15.190077"
+ "filePath": "config/0xa/0.0.482",
+ "timestamp": "2024-10-28T13:04:49.257852"
},
{
"name": "stale_diff",
"chainId": "0xa4b1",
- "version": "0.0.481",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0xa4b1/0.0.481",
- "timestamp": "2024-10-27T12:50:21.791169"
+ "version": "0.0.482",
+ "checksum": "d7de3157af44ddbb82ca8913f6396a4690e61f6d7179ed65b0cc2bf591b6da3a",
+ "signature": "d3c8a37b8778f2db1e91a49839f1b0b24f2c96321e7b0c659d947c360f68dd61ea83e3983ee116fd2fc23fb78b8fee1dc9e00e89463c86b8d5c06dd36cd0110e",
+ "hashSignature": "327ca5b7af1931368881c98f762741f54152c75e06878e89bb341eed1095a58ca88fccd04eb6a1018c7d539947b0bbec37bb60a5c6eadb0221865b1d59d4030f",
+ "filePath": "stale_diff/0xa4b1/0.0.482",
+ "timestamp": "2024-10-28T13:05:00.218564"
},
{
"name": "config",
"chainId": "0xa4b1",
- "version": "0.0.481",
+ "version": "0.0.482",
"checksum": "eb3b41ae8c3bbf9595dcded8e1b9090c8ed2427e236375652f6fd701c6e134b4",
"signature": "79552c7fa525e05c9c086fe8d8ecb49375be796176877e17332fa1137d4c653f224873bdac2b6fd4fe63fcfe6d404778684116e98fdb0563f63ae1efcfafe60d",
"hashSignature": "9c8a84e430578290eceea60ba7dec3695e0cfd343f7ac3e2667c8634afa3dd65d6ec25b112ac8260dbe3e6bc9e00ba906c4212975652354b1423f6ee0ec7b20e",
- "filePath": "config/0xa4b1/0.0.481",
- "timestamp": "2024-10-27T12:50:21.791427"
+ "filePath": "config/0xa4b1/0.0.482",
+ "timestamp": "2024-10-28T13:05:00.218867"
},
{
"name": "stale_diff",
"chainId": "0xa86a",
- "version": "0.0.481",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0xa86a/0.0.481",
- "timestamp": "2024-10-27T12:50:27.602732"
+ "version": "0.0.482",
+ "checksum": "88ab495f0dc39bb9bb0d607b562ef8b16ededfd48aab7b5825126b4f19c02ccf",
+ "signature": "84e1b810e93bd3cd0732ffbdf9f7003140da90758c5ebbefc9516979bb47b3c9a4117e757883c4089c44f165f507d43885700bb50e6126fb037e1f62b8e75708",
+ "hashSignature": "b38ce4c3a012de90e8949df5b32a8cf74a3a9edf2d602197d01ebfd4b1cd8fd32defbb0d363f93abf8241cd7224614723cb7fba7e744fccd82bee3b082138e0b",
+ "filePath": "stale_diff/0xa86a/0.0.482",
+ "timestamp": "2024-10-28T13:05:09.315523"
},
{
"name": "config",
"chainId": "0xa86a",
- "version": "0.0.481",
+ "version": "0.0.482",
"checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4",
"signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e",
"hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302",
- "filePath": "config/0xa86a/0.0.481",
- "timestamp": "2024-10-27T12:50:27.602991"
+ "filePath": "config/0xa86a/0.0.482",
+ "timestamp": "2024-10-28T13:05:09.315795"
},
{
"name": "stale_diff",
"chainId": "0xe708",
- "version": "0.0.435",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0xe708/0.0.435",
- "timestamp": "2024-10-27T12:50:39.077328"
+ "version": "0.0.436",
+ "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754",
+ "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08",
+ "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02",
+ "filePath": "stale_diff/0xe708/0.0.436",
+ "timestamp": "2024-10-28T13:05:28.153528"
},
{
"name": "config",
"chainId": "0xe708",
- "version": "0.0.435",
+ "version": "0.0.436",
"checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4",
"signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e",
"hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302",
- "filePath": "config/0xe708/0.0.435",
- "timestamp": "2024-10-27T12:50:39.077595"
+ "filePath": "config/0xe708/0.0.436",
+ "timestamp": "2024-10-28T13:05:28.153794"
},
{
"name": "stale_diff",
"chainId": "0x2105",
- "version": "0.0.370",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0x2105/0.0.370",
- "timestamp": "2024-10-27T12:50:33.715820"
+ "version": "0.0.371",
+ "checksum": "2ac83e8d092ae95bdcc2e8078e458f0c3f2ea9bf46c00fb40b37b070bb92dc63",
+ "signature": "6c04fcdc49fd49502da3a73d6e3c721004d40eec65f2dec02cfbe8a2a5f253d3747d85253894508bba57d850d647707b26f2c78a5dfbff8038985ae413474f05",
+ "hashSignature": "d791983edf3b111bf4448acc6b54cf4f9072358c7748a6e247768899d0ae36c1cdc70c45caa9e8c6d34f151256792e365f9224eef236610067c4489d48bc840f",
+ "filePath": "stale_diff/0x2105/0.0.371",
+ "timestamp": "2024-10-28T13:05:19.542338"
},
{
"name": "config",
"chainId": "0x2105",
- "version": "0.0.370",
+ "version": "0.0.371",
"checksum": "eb3b41ae8c3bbf9595dcded8e1b9090c8ed2427e236375652f6fd701c6e134b4",
"signature": "79552c7fa525e05c9c086fe8d8ecb49375be796176877e17332fa1137d4c653f224873bdac2b6fd4fe63fcfe6d404778684116e98fdb0563f63ae1efcfafe60d",
"hashSignature": "9c8a84e430578290eceea60ba7dec3695e0cfd343f7ac3e2667c8634afa3dd65d6ec25b112ac8260dbe3e6bc9e00ba906c4212975652354b1423f6ee0ec7b20e",
- "filePath": "config/0x2105/0.0.370",
- "timestamp": "2024-10-27T12:50:33.716077"
+ "filePath": "config/0x2105/0.0.371",
+ "timestamp": "2024-10-28T13:05:19.542688"
},
{
"name": "stale_diff",
"chainId": "0xaa36a7",
- "version": "0.0.289",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0xaa36a7/0.0.289",
- "timestamp": "2024-10-27T12:50:44.187191"
+ "version": "0.0.290",
+ "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754",
+ "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08",
+ "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02",
+ "filePath": "stale_diff/0xaa36a7/0.0.290",
+ "timestamp": "2024-10-28T13:05:35.180841"
},
{
"name": "config",
"chainId": "0xaa36a7",
- "version": "0.0.289",
+ "version": "0.0.290",
"checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4",
"signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e",
"hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302",
- "filePath": "config/0xaa36a7/0.0.289",
- "timestamp": "2024-10-27T12:50:44.187491"
+ "filePath": "config/0xaa36a7/0.0.290",
+ "timestamp": "2024-10-28T13:05:35.181112"
},
{
"name": "stale_diff",
"chainId": "0xcc",
- "version": "0.0.238",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0xcc/0.0.238",
- "timestamp": "2024-10-27T12:50:49.423599"
+ "version": "0.0.239",
+ "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754",
+ "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08",
+ "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02",
+ "filePath": "stale_diff/0xcc/0.0.239",
+ "timestamp": "2024-10-28T13:05:42.918569"
},
{
"name": "config",
"chainId": "0xcc",
- "version": "0.0.238",
+ "version": "0.0.239",
"checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4",
"signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e",
"hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302",
- "filePath": "config/0xcc/0.0.238",
- "timestamp": "2024-10-27T12:50:49.423903"
+ "filePath": "config/0xcc/0.0.239",
+ "timestamp": "2024-10-28T13:05:42.918850"
},
{
"name": "stale_diff",
"chainId": "0x0",
- "version": "0.0.179",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0x0/0.0.179",
- "timestamp": "2024-10-27T12:50:57.113651"
+ "version": "0.0.180",
+ "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754",
+ "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08",
+ "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02",
+ "filePath": "stale_diff/0x0/0.0.180",
+ "timestamp": "2024-10-28T13:05:51.610174"
},
{
"name": "config",
"chainId": "0x0",
- "version": "0.0.179",
+ "version": "0.0.180",
"checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4",
"signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e",
"hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302",
- "filePath": "config/0x0/0.0.179",
- "timestamp": "2024-10-27T12:50:57.113910"
+ "filePath": "config/0x0/0.0.180",
+ "timestamp": "2024-10-28T13:05:51.610489"
},
{
"name": "stale_diff",
"chainId": "0x144",
- "version": "0.0.258",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0x144/0.0.258",
- "timestamp": "2024-10-27T12:51:10.842031"
+ "version": "0.0.259",
+ "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754",
+ "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08",
+ "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02",
+ "filePath": "stale_diff/0x144/0.0.259",
+ "timestamp": "2024-10-28T13:05:59.914265"
},
{
"name": "config",
"chainId": "0x144",
- "version": "0.0.258",
+ "version": "0.0.259",
"checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4",
"signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e",
"hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302",
- "filePath": "config/0x144/0.0.258",
- "timestamp": "2024-10-27T12:51:10.842292"
+ "filePath": "config/0x144/0.0.259",
+ "timestamp": "2024-10-28T13:05:59.914582"
},
{
"name": "stale_diff",
"chainId": "0x82750",
- "version": "0.0.180",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0x82750/0.0.180",
- "timestamp": "2024-10-27T12:51:16.756456"
+ "version": "0.0.181",
+ "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754",
+ "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08",
+ "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02",
+ "filePath": "stale_diff/0x82750/0.0.181",
+ "timestamp": "2024-10-28T13:06:08.538356"
},
{
"name": "config",
"chainId": "0x82750",
- "version": "0.0.180",
+ "version": "0.0.181",
"checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4",
"signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e",
"hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302",
- "filePath": "config/0x82750/0.0.180",
- "timestamp": "2024-10-27T12:51:16.756829"
+ "filePath": "config/0x82750/0.0.181",
+ "timestamp": "2024-10-28T13:06:08.538665"
},
{
"name": "stale_diff",
"chainId": "0x1b58",
- "version": "0.0.180",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0x1b58/0.0.180",
- "timestamp": "2024-10-27T12:51:23.355191"
+ "version": "0.0.181",
+ "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754",
+ "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08",
+ "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02",
+ "filePath": "stale_diff/0x1b58/0.0.181",
+ "timestamp": "2024-10-28T13:06:16.179180"
},
{
"name": "config",
"chainId": "0x1b58",
- "version": "0.0.180",
+ "version": "0.0.181",
"checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4",
"signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e",
"hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302",
- "filePath": "config/0x1b58/0.0.180",
- "timestamp": "2024-10-27T12:51:23.355470"
+ "filePath": "config/0x1b58/0.0.181",
+ "timestamp": "2024-10-28T13:06:16.179453"
},
{
"name": "stale_diff",
"chainId": "0x138d5",
- "version": "0.0.180",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0x138d5/0.0.180",
- "timestamp": "2024-10-27T12:51:29.701505"
+ "version": "0.0.181",
+ "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754",
+ "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08",
+ "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02",
+ "filePath": "stale_diff/0x138d5/0.0.181",
+ "timestamp": "2024-10-28T13:06:24.630573"
},
{
"name": "config",
"chainId": "0x138d5",
- "version": "0.0.180",
+ "version": "0.0.181",
"checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4",
"signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e",
"hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302",
- "filePath": "config/0x138d5/0.0.180",
- "timestamp": "2024-10-27T12:51:29.701771"
+ "filePath": "config/0x138d5/0.0.181",
+ "timestamp": "2024-10-28T13:06:24.630854"
},
{
"name": "stale",
@@ -462,42 +462,42 @@
{
"name": "stale_diff",
"chainId": "0x1b6e6",
- "version": "0.0.114",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0x1b6e6/0.0.114",
- "timestamp": "2024-10-27T12:51:43.249992"
+ "version": "0.0.115",
+ "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754",
+ "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08",
+ "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02",
+ "filePath": "stale_diff/0x1b6e6/0.0.115",
+ "timestamp": "2024-10-28T13:06:41.431736"
},
{
"name": "config",
"chainId": "0x1b6e6",
- "version": "0.0.114",
+ "version": "0.0.115",
"checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4",
"signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e",
"hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302",
- "filePath": "config/0x1b6e6/0.0.114",
- "timestamp": "2024-10-27T12:51:43.250277"
+ "filePath": "config/0x1b6e6/0.0.115",
+ "timestamp": "2024-10-28T13:06:41.432043"
},
{
"name": "stale_diff",
"chainId": "0x138d4",
- "version": "0.0.75",
- "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb",
- "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804",
- "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03",
- "filePath": "stale_diff/0x138d4/0.0.75",
- "timestamp": "2024-10-27T12:51:36.220430"
+ "version": "0.0.76",
+ "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754",
+ "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08",
+ "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02",
+ "filePath": "stale_diff/0x138d4/0.0.76",
+ "timestamp": "2024-10-28T13:06:31.754098"
},
{
"name": "config",
"chainId": "0x138d4",
- "version": "0.0.75",
+ "version": "0.0.76",
"checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4",
"signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e",
"hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302",
- "filePath": "config/0x138d4/0.0.75",
- "timestamp": "2024-10-27T12:51:36.220693"
+ "filePath": "config/0x138d4/0.0.76",
+ "timestamp": "2024-10-28T13:06:31.754438"
},
{
"name": "stale",
diff --git a/test/e2e/page-objects/flows/login.flow.ts b/test/e2e/page-objects/flows/login.flow.ts
index fcd0bcb22d8a..f5ed61946ce8 100644
--- a/test/e2e/page-objects/flows/login.flow.ts
+++ b/test/e2e/page-objects/flows/login.flow.ts
@@ -24,12 +24,12 @@ export const loginWithoutBalanceValidation = async (
* This method unlocks the wallet and verifies that the user lands on the homepage with the expected balance. It is designed to be the initial step in setting up a test environment.
*
* @param driver - The webdriver instance.
- * @param ganacheServer - The ganache server instance
+ * @param localBlockchainServer - The local blockchain server instance
* @param password - The password used to unlock the wallet.
*/
export const loginWithBalanceValidation = async (
driver: Driver,
- ganacheServer?: Ganache,
+ localBlockchainServer?: Ganache,
password?: string,
) => {
await loginWithoutBalanceValidation(driver, password);
@@ -38,8 +38,10 @@ export const loginWithBalanceValidation = async (
await homePage.check_pageIsLoaded();
// Verify the expected balance on the homepage
- if (ganacheServer) {
- await homePage.check_ganacheBalanceIsDisplayed(ganacheServer);
+ if (localBlockchainServer) {
+ await homePage.check_localBlockchainBalanceIsDisplayed(
+ localBlockchainServer,
+ );
} else {
await homePage.check_expectedBalanceIsDisplayed();
}
diff --git a/test/e2e/page-objects/flows/send-transaction.flow.ts b/test/e2e/page-objects/flows/send-transaction.flow.ts
index 3f56076e3934..8af88f01aca6 100644
--- a/test/e2e/page-objects/flows/send-transaction.flow.ts
+++ b/test/e2e/page-objects/flows/send-transaction.flow.ts
@@ -3,6 +3,7 @@ import ConfirmTxPage from '../pages/send/confirm-tx-page';
import SendTokenPage from '../pages/send/send-token-page';
import { Driver } from '../../webdriver/driver';
import SnapSimpleKeyringPage from '../pages/snap-simple-keyring-page';
+import TransactionConfirmation from '../pages/confirmations/redesign/transaction-confirmation';
/**
* This function initiates the steps required to send a transaction from the homepage to final confirmation.
@@ -47,6 +48,42 @@ export const sendTransactionToAddress = async ({
await confirmTxPage.confirmTx();
};
+/**
+ * This function initiates the steps required to send a transaction from the homepage to final confirmation.
+ *
+ * @param params - An object containing the parameters.
+ * @param params.driver - The webdriver instance.
+ * @param params.recipientAddress - The recipient address.
+ * @param params.amount - The amount of the asset to be sent in the transaction.
+ */
+export const sendRedesignedTransactionToAddress = async ({
+ driver,
+ recipientAddress,
+ amount,
+}: {
+ driver: Driver;
+ recipientAddress: string;
+ amount: string;
+}): Promise => {
+ console.log(
+ `Start flow to send amount ${amount} to recipient ${recipientAddress} on home screen`,
+ );
+ // click send button on homepage to start flow
+ const homePage = new HomePage(driver);
+ await homePage.startSendFlow();
+
+ // user should land on send token screen to fill recipient and amount
+ const sendToPage = new SendTokenPage(driver);
+ await sendToPage.check_pageIsLoaded();
+ await sendToPage.fillRecipient(recipientAddress);
+ await sendToPage.fillAmount(amount);
+ await sendToPage.goToNextScreen();
+
+ // confirm transaction when user lands on confirm transaction screen
+ const transactionConfirmationPage = new TransactionConfirmation(driver);
+ await transactionConfirmationPage.clickFooterConfirmButton();
+};
+
/**
* This function initiates the steps required to send a transaction from the homepage to final confirmation.
*
@@ -132,3 +169,38 @@ export const sendTransactionWithSnapAccount = async ({
);
}
};
+
+/**
+ * This function initiates the steps required to send a transaction from snap account on homepage to final confirmation.
+ *
+ * @param params - An object containing the parameters.
+ * @param params.driver - The webdriver instance.
+ * @param params.recipientAddress - The recipient address.
+ * @param params.amount - The amount of the asset to be sent in the transaction.
+ * @param params.isSyncFlow - Indicates whether synchronous approval option is on for the snap. Defaults to true.
+ * @param params.approveTransaction - Indicates whether the transaction should be approved. Defaults to true.
+ */
+export const sendRedesignedTransactionWithSnapAccount = async ({
+ driver,
+ recipientAddress,
+ amount,
+ isSyncFlow = true,
+ approveTransaction = true,
+}: {
+ driver: Driver;
+ recipientAddress: string;
+ amount: string;
+ isSyncFlow?: boolean;
+ approveTransaction?: boolean;
+}): Promise => {
+ await sendRedesignedTransactionToAddress({
+ driver,
+ recipientAddress,
+ amount,
+ });
+ if (!isSyncFlow) {
+ await new SnapSimpleKeyringPage(driver).approveRejectSnapAccountTransaction(
+ approveTransaction,
+ );
+ }
+};
diff --git a/test/e2e/page-objects/pages/account-list-page.ts b/test/e2e/page-objects/pages/account-list-page.ts
index fb7c1232c08c..9f96d70f4972 100644
--- a/test/e2e/page-objects/pages/account-list-page.ts
+++ b/test/e2e/page-objects/pages/account-list-page.ts
@@ -1,9 +1,13 @@
+import { strict as assert } from 'assert';
import { Driver } from '../../webdriver/driver';
import { largeDelayMs } from '../../helpers';
+import messages from '../../../../app/_locales/en/messages.json';
class AccountListPage {
private readonly driver: Driver;
+ private readonly accountAddressText = '.qr-code__address-segments';
+
private readonly accountListBalance =
'[data-testid="second-currency-display"]';
@@ -25,6 +29,11 @@ class AccountListPage {
private readonly addAccountConfirmButton =
'[data-testid="submit-add-account-with-name"]';
+ private readonly addBtcAccountButton = {
+ text: messages.addNewBitcoinAccount.message,
+ tag: 'button',
+ };
+
private readonly addEthereumAccountButton =
'[data-testid="multichain-account-menu-popover-add-account"]';
@@ -93,6 +102,11 @@ class AccountListPage {
tag: 'div',
};
+ private readonly removeAccountNevermindButton = {
+ text: 'Nevermind',
+ tag: 'button',
+ };
+
private readonly saveAccountLabelButton =
'[data-testid="save-account-label-input"]';
@@ -114,15 +128,21 @@ class AccountListPage {
}
/**
- * Adds a new account with a custom label.
+ * Adds a new account with an optional custom label.
*
- * @param customLabel - The custom label for the new account.
+ * @param customLabel - The custom label for the new account. If not provided, a default name will be used.
*/
- async addNewAccountWithCustomLabel(customLabel: string): Promise {
- console.log(`Adding new account with custom label: ${customLabel}`);
+ async addNewAccount(customLabel?: string): Promise {
+ if (customLabel) {
+ console.log(`Adding new account with custom label: ${customLabel}`);
+ } else {
+ console.log(`Adding new account with default name`);
+ }
await this.driver.clickElement(this.createAccountButton);
await this.driver.clickElement(this.addEthereumAccountButton);
- await this.driver.fill(this.accountNameInput, customLabel);
+ if (customLabel) {
+ await this.driver.fill(this.accountNameInput, customLabel);
+ }
// needed to mitigate a race condition with the state update
// there is no condition we can wait for in the UI
await this.driver.delay(largeDelayMs);
@@ -132,19 +152,47 @@ class AccountListPage {
}
/**
- * Adds a new account with default next available name.
+ * Adds a new BTC account with an optional custom name.
*
+ * @param options - Options for adding a new BTC account.
+ * @param [options.btcAccountCreationEnabled] - Indicates if the BTC account creation is expected to be enabled or disabled. Defaults to true.
+ * @param [options.accountName] - The custom name for the BTC account. Defaults to an empty string, which means the default name will be used.
*/
- async addNewAccountWithDefaultName(): Promise {
- console.log(`Adding new account with next available name`);
- await this.driver.clickElement(this.createAccountButton);
- await this.driver.clickElement(this.addEthereumAccountButton);
- // needed to mitigate a race condition with the state update
- // there is no condition we can wait for in the UI
- await this.driver.delay(largeDelayMs);
- await this.driver.clickElementAndWaitToDisappear(
- this.addAccountConfirmButton,
+ async addNewBtcAccount({
+ btcAccountCreationEnabled = true,
+ accountName = '',
+ }: {
+ btcAccountCreationEnabled?: boolean;
+ accountName?: string;
+ } = {}): Promise {
+ console.log(
+ `Adding new BTC account${
+ accountName ? ` with custom name: ${accountName}` : ' with default name'
+ }`,
);
+ await this.driver.clickElement(this.createAccountButton);
+ if (btcAccountCreationEnabled) {
+ await this.driver.clickElement(this.addBtcAccountButton);
+ // needed to mitigate a race condition with the state update
+ // there is no condition we can wait for in the UI
+ await this.driver.delay(largeDelayMs);
+ if (accountName) {
+ await this.driver.fill(this.accountNameInput, accountName);
+ }
+ await this.driver.clickElementAndWaitToDisappear(
+ this.addAccountConfirmButton,
+ // Longer timeout than usual, this reduces the flakiness
+ // around Bitcoin account creation (mainly required for
+ // Firefox)
+ 5000,
+ );
+ } else {
+ const createButton = await this.driver.findElement(
+ this.addBtcAccountButton,
+ );
+ assert.equal(await createButton.isEnabled(), false);
+ await this.driver.clickElement(this.closeAccountModalButton);
+ }
}
/**
@@ -182,6 +230,33 @@ class AccountListPage {
async changeAccountLabel(newLabel: string): Promise {
console.log(`Changing account label to: ${newLabel}`);
await this.driver.clickElement(this.accountMenuButton);
+ await this.changeLabelFromAccountDetailsModal(newLabel);
+ }
+
+ /**
+ * Changes the account label from within an already opened account details modal.
+ * Note: This method assumes the account details modal is already open.
+ *
+ * Recommended usage:
+ * ```typescript
+ * await accountListPage.openAccountDetailsModal('Current Account Name');
+ * await accountListPage.changeLabelFromAccountDetailsModal('New Account Name');
+ * ```
+ *
+ * @param newLabel - The new label to set for the account
+ * @throws Will throw an error if the modal is not open when method is called
+ * @example
+ * // To rename a specific account, first open its details modal:
+ * await accountListPage.openAccountDetailsModal('Current Account Name');
+ * await accountListPage.changeLabelFromAccountDetailsModal('New Account Name');
+ *
+ * // Note: Using changeAccountLabel() alone will only work for the first account
+ */
+ async changeLabelFromAccountDetailsModal(newLabel: string): Promise {
+ await this.driver.waitForSelector(this.editableLabelButton);
+ console.log(
+ `Account details modal opened, changing account label to: ${newLabel}`,
+ );
await this.driver.clickElement(this.editableLabelButton);
await this.driver.fill(this.editableLabelInput, newLabel);
await this.driver.clickElement(this.saveAccountLabelButton);
@@ -195,6 +270,23 @@ class AccountListPage {
);
}
+ /**
+ * Get the address of the specified account.
+ *
+ * @param accountLabel - The label of the account to get the address.
+ */
+ async getAccountAddress(accountLabel: string): Promise {
+ console.log(`Get account address in account list`);
+ await this.openAccountOptionsInAccountList(accountLabel);
+ await this.driver.clickElement(this.accountMenuButton);
+ await this.driver.waitForSelector(this.accountAddressText);
+ const accountAddress = await (
+ await this.driver.findElement(this.accountAddressText)
+ ).getText();
+ await this.driver.clickElement(this.closeAccountModalButton);
+ return accountAddress;
+ }
+
async hideAccount(): Promise {
console.log(`Hide account in account list`);
await this.driver.clickElement(this.hideUnhideAccountButton);
@@ -284,13 +376,23 @@ class AccountListPage {
* Remove the specified account from the account list.
*
* @param accountLabel - The label of the account to remove.
+ * @param confirmRemoval - Whether to confirm the removal of the account. Defaults to true.
*/
- async removeAccount(accountLabel: string): Promise {
+ async removeAccount(
+ accountLabel: string,
+ confirmRemoval: boolean = true,
+ ): Promise {
console.log(`Remove account in account list`);
await this.openAccountOptionsInAccountList(accountLabel);
await this.driver.clickElement(this.removeAccountButton);
await this.driver.waitForSelector(this.removeAccountMessage);
- await this.driver.clickElement(this.removeAccountConfirmButton);
+ if (confirmRemoval) {
+ console.log('Confirm removal of account');
+ await this.driver.clickElement(this.removeAccountConfirmButton);
+ } else {
+ console.log('Click nevermind button to cancel account removal');
+ await this.driver.clickElement(this.removeAccountNevermindButton);
+ }
}
async switchToAccount(expectedLabel: string): Promise {
diff --git a/test/e2e/page-objects/pages/homepage.ts b/test/e2e/page-objects/pages/homepage.ts
index 6a8a916d4349..c5c4d5369d4e 100644
--- a/test/e2e/page-objects/pages/homepage.ts
+++ b/test/e2e/page-objects/pages/homepage.ts
@@ -283,13 +283,15 @@ class HomePage {
);
}
- async check_ganacheBalanceIsDisplayed(
- ganacheServer?: Ganache,
+ async check_localBlockchainBalanceIsDisplayed(
+ localBlockchainServer?: Ganache,
address = null,
): Promise {
let expectedBalance: string;
- if (ganacheServer) {
- expectedBalance = (await ganacheServer.getBalance(address)).toString();
+ if (localBlockchainServer) {
+ expectedBalance = (
+ await localBlockchainServer.getBalance(address)
+ ).toString();
} else {
expectedBalance = '0';
}
diff --git a/test/e2e/provider/bfcache.spec.js b/test/e2e/provider/bfcache.spec.js
new file mode 100644
index 000000000000..4d2c5d2bb1e7
--- /dev/null
+++ b/test/e2e/provider/bfcache.spec.js
@@ -0,0 +1,65 @@
+const { strict: assert } = require('assert');
+const {
+ withFixtures,
+ defaultGanacheOptions,
+ DAPP_URL,
+ openDapp,
+} = require('../helpers');
+const FixtureBuilder = require('../fixture-builder');
+
+const triggerBFCache = async (driver) => {
+ await driver.executeScript(`
+ window.addEventListener('pageshow', (event) => {
+ if (event.persisted) {
+ window.restoredFromBFCache = true
+ }
+ });
+ `);
+
+ await driver.driver.get(`chrome://terms/`);
+
+ await driver.driver.navigate().back();
+
+ const restoredFromBFCache = await driver.executeScript(
+ `return window.restoredFromBFCache`,
+ );
+
+ if (!restoredFromBFCache) {
+ assert.fail(new Error('Failed to trigger BFCache'));
+ }
+};
+
+describe('BFCache', function () {
+ it('has a working provider stream when a dapp is restored from BFCache', async function () {
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder().build(),
+ ganacheOptions: defaultGanacheOptions,
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await openDapp(driver, undefined, DAPP_URL);
+
+ const request = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'eth_chainId',
+ params: [],
+ id: 0,
+ });
+
+ const initialResult = await driver.executeScript(
+ `return window.ethereum.request(${request})`,
+ );
+ assert.equal(initialResult, '0x539');
+
+ await triggerBFCache(driver);
+
+ const bfcacheResult = await driver.executeScript(
+ `return window.ethereum.request(${request})`,
+ );
+ assert.equal(bfcacheResult, '0x539');
+ },
+ );
+ });
+});
diff --git a/test/e2e/snaps/test-snap-txinsights-v2.spec.js b/test/e2e/snaps/test-snap-txinsights-v2.spec.js
index a249e9daa79b..ba2d468a870a 100644
--- a/test/e2e/snaps/test-snap-txinsights-v2.spec.js
+++ b/test/e2e/snaps/test-snap-txinsights-v2.spec.js
@@ -3,166 +3,172 @@ const {
withFixtures,
unlockWallet,
WINDOW_TITLES,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../helpers');
const FixtureBuilder = require('../fixture-builder');
const { TEST_SNAPS_WEBSITE_URL } = require('./enums');
describe('Test Snap TxInsights-v2', function () {
- it('tests tx insights v2 functionality', async function () {
- await withFixtures(
- {
- fixtures: new FixtureBuilder().build(),
- ganacheOptions: defaultGanacheOptions,
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
-
- // navigate to test snaps page and connect
- await driver.openNewPage(TEST_SNAPS_WEBSITE_URL);
-
- // wait for page to load
- await driver.waitForSelector({
- text: 'Installed Snaps',
- tag: 'h2',
- });
-
- // find and scroll to the transaction-insights test snap
- const snapButton1 = await driver.findElement(
- '#connecttransaction-insights',
- );
- await driver.scrollToElement(snapButton1);
-
- // added delay for firefox (deflake)
- await driver.delayFirefox(1000);
-
- // wait for and click connect
- await driver.waitForSelector('#connecttransaction-insights');
- await driver.clickElement('#connecttransaction-insights');
-
- // switch to metamask extension
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- // wait for and click connect
- await driver.waitForSelector({
- text: 'Connect',
- tag: 'button',
- });
- await driver.clickElement({
- text: 'Connect',
- tag: 'button',
- });
-
- // wait for and click connect
- await driver.waitForSelector({ text: 'Confirm' });
- await driver.clickElement({
- text: 'Confirm',
- tag: 'button',
- });
-
- // wait for and click ok and wait for window to close
- await driver.waitForSelector({ text: 'OK' });
- await driver.clickElementAndWaitForWindowToClose({
- text: 'OK',
- tag: 'button',
- });
-
- // switch to test-snaps page
- await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps);
-
- // wait for and click get accounts
- await driver.waitForSelector('#getAccounts');
- await driver.clickElement('#getAccounts');
-
- // switch back to MetaMask window
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- // wait for and click confirm and wait for window to close
- await driver.waitForSelector({
- text: 'Connect',
- tag: 'button',
- });
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
-
- // switch to test-snaps page and send tx
- await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps);
- await driver.clickElement('#sendInsights');
-
- // delay added for rendering (deflake)
- await driver.delay(2000);
-
- // switch back to MetaMask window and switch to tx insights pane
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- // find confirm button
- await driver.findClickableElement({
- text: 'Confirm',
- tag: 'button',
- });
-
- // wait for and click insights snap tab
- await driver.waitForSelector({
- text: 'Insights Example Snap',
- tag: 'button',
- });
- await driver.clickElement({
- text: 'Insights Example Snap',
- tag: 'button',
- });
-
- // check that txinsightstest tab contains the right info
- await driver.waitForSelector({
- css: '.snap-ui-renderer__content',
- text: 'ERC-20',
- });
-
- // click confirm to continue
- await driver.clickElement({
- text: 'Confirm',
- tag: 'button',
- });
-
- // check for warning from txinsights
- await driver.waitForSelector({
- css: '.snap-delineator__header__text',
- text: 'Warning from Insights Example Snap',
- });
-
- // check info in warning
- await driver.waitForSelector({
- css: '.snap-ui-renderer__text',
- text: 'ERC-20',
- });
-
- // click the warning confirm checkbox
- await driver.clickElement('.mm-checkbox__input');
-
- // click confirm button to send transaction
- await driver.clickElement({
- css: '.mm-box--color-error-inverse',
- text: 'Confirm',
- tag: 'button',
- });
-
- // switch back to MetaMask tab
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
-
- // switch to activity pane
- await driver.clickElement({
- tag: 'button',
- text: 'Activity',
- });
- // wait for transaction confirmation
- await driver.waitForSelector({
- css: '.transaction-status-label',
- text: 'Confirmed',
- });
- },
- );
+ describe('Old confirmation screens', function () {
+ it('tests tx insights v2 functionality', async function () {
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder().build(),
+ ganacheOptions: defaultGanacheOptions,
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ // navigate to test snaps page and connect
+ await driver.openNewPage(TEST_SNAPS_WEBSITE_URL);
+
+ // wait for page to load
+ await driver.waitForSelector({
+ text: 'Installed Snaps',
+ tag: 'h2',
+ });
+
+ // find and scroll to the transaction-insights test snap
+ const snapButton1 = await driver.findElement(
+ '#connecttransaction-insights',
+ );
+ await driver.scrollToElement(snapButton1);
+
+ // added delay for firefox (deflake)
+ await driver.delayFirefox(1000);
+
+ // wait for and click connect
+ await driver.waitForSelector('#connecttransaction-insights');
+ await driver.clickElement('#connecttransaction-insights');
+
+ // switch to metamask extension
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // wait for and click connect
+ await driver.waitForSelector({
+ text: 'Connect',
+ tag: 'button',
+ });
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // wait for and click connect
+ await driver.waitForSelector({ text: 'Confirm' });
+ await driver.clickElement({
+ text: 'Confirm',
+ tag: 'button',
+ });
+
+ // wait for and click ok and wait for window to close
+ await driver.waitForSelector({ text: 'OK' });
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'OK',
+ tag: 'button',
+ });
+
+ // switch to test-snaps page
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps);
+
+ // wait for and click get accounts
+ await driver.waitForSelector('#getAccounts');
+ await driver.clickElement('#getAccounts');
+
+ // switch back to MetaMask window
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // wait for and click confirm and wait for window to close
+ await driver.waitForSelector({
+ text: 'Connect',
+ tag: 'button',
+ });
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // switch to test-snaps page and send tx
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps);
+ await driver.clickElement('#sendInsights');
+
+ // delay added for rendering (deflake)
+ await driver.delay(2000);
+
+ // switch back to MetaMask window and switch to tx insights pane
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // find confirm button
+ await driver.findClickableElement({
+ text: 'Confirm',
+ tag: 'button',
+ });
+
+ // wait for and click insights snap tab
+ await driver.waitForSelector({
+ text: 'Insights Example Snap',
+ tag: 'button',
+ });
+ await driver.clickElement({
+ text: 'Insights Example Snap',
+ tag: 'button',
+ });
+
+ // check that txinsightstest tab contains the right info
+ await driver.waitForSelector({
+ css: '.snap-ui-renderer__content',
+ text: 'ERC-20',
+ });
+
+ // click confirm to continue
+ await driver.clickElement({
+ text: 'Confirm',
+ tag: 'button',
+ });
+
+ // check for warning from txinsights
+ await driver.waitForSelector({
+ css: '.snap-delineator__header__text',
+ text: 'Warning from Insights Example Snap',
+ });
+
+ // check info in warning
+ await driver.waitForSelector({
+ css: '.snap-ui-renderer__text',
+ text: 'ERC-20',
+ });
+
+ // click the warning confirm checkbox
+ await driver.clickElement('.mm-checkbox__input');
+
+ // click confirm button to send transaction
+ await driver.clickElement({
+ css: '.mm-box--color-error-inverse',
+ text: 'Confirm',
+ tag: 'button',
+ });
+
+ // switch back to MetaMask tab
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // switch to activity pane
+ await driver.clickElement({
+ tag: 'button',
+ text: 'Activity',
+ });
+
+ // wait for transaction confirmation
+ await driver.waitForSelector({
+ css: '.transaction-status-label',
+ text: 'Confirmed',
+ });
+ },
+ );
+ });
});
});
diff --git a/test/e2e/snaps/test-snap-txinsights.spec.js b/test/e2e/snaps/test-snap-txinsights.spec.js
index 21feafd06cb9..0171759587b5 100644
--- a/test/e2e/snaps/test-snap-txinsights.spec.js
+++ b/test/e2e/snaps/test-snap-txinsights.spec.js
@@ -3,117 +3,229 @@ const {
withFixtures,
unlockWallet,
WINDOW_TITLES,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../helpers');
const FixtureBuilder = require('../fixture-builder');
const { TEST_SNAPS_WEBSITE_URL } = require('./enums');
describe('Test Snap TxInsights', function () {
- it('tests tx insights functionality', async function () {
- await withFixtures(
- {
- fixtures: new FixtureBuilder().build(),
- ganacheOptions: defaultGanacheOptions,
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
-
- // navigate to test snaps page and connect
- await driver.driver.get(TEST_SNAPS_WEBSITE_URL);
-
- // wait for page to load
- await driver.waitForSelector({
- text: 'Installed Snaps',
- tag: 'h2',
- });
-
- // find and scroll to the transaction-insights test snap
- const snapButton1 = await driver.findElement(
- '#connecttransaction-insights',
- );
- await driver.scrollToElement(snapButton1);
-
- // added delay for firefox (deflake)
- await driver.delayFirefox(1000);
-
- // wait for and click connect
- await driver.waitForSelector('#connecttransaction-insights');
- await driver.clickElement('#connecttransaction-insights');
-
- // switch to metamask extension
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- // wait for and click connect
- await driver.waitForSelector({
- text: 'Connect',
- tag: 'button',
- });
- await driver.clickElement({
- text: 'Connect',
- tag: 'button',
- });
-
- // wait for and click confirm
- await driver.waitForSelector({ text: 'Confirm' });
- await driver.clickElement({
- text: 'Confirm',
- tag: 'button',
- });
-
- // wait for and click ok and wait for window to close
- await driver.waitForSelector({ text: 'OK' });
- await driver.clickElementAndWaitForWindowToClose({
- text: 'OK',
- tag: 'button',
- });
-
- // switch to test-snaps page and get accounts
- await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps);
-
- // click get accounts
- await driver.clickElement('#getAccounts');
-
- // switch back to MetaMask window
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- // wait for and click next and wait for window to close
- await driver.waitForSelector({
- text: 'Connect',
- tag: 'button',
- });
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
-
- // switch to test-snaps page
- await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps);
-
- // click send tx
- await driver.clickElement('#sendInsights');
-
- // delay added for rendering (deflake)
- await driver.delay(2000);
-
- // switch back to MetaMask window
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- // wait for and switch to insight snap pane
- await driver.waitForSelector({
- text: 'Insights Example Snap',
- tag: 'button',
- });
- await driver.clickElement({
- text: 'Insights Example Snap',
- tag: 'button',
- });
-
- // check that txinsightstest tab contains the right info
- await driver.waitForSelector({
- css: '.snap-ui-renderer__content',
- text: 'ERC-20',
- });
- },
- );
+ describe('Old confirmation screens', function () {
+ it('tests tx insights functionality', async function () {
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder().build(),
+ ganacheOptions: defaultGanacheOptions,
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ // navigate to test snaps page and connect
+ await driver.driver.get(TEST_SNAPS_WEBSITE_URL);
+
+ // wait for page to load
+ await driver.waitForSelector({
+ text: 'Installed Snaps',
+ tag: 'h2',
+ });
+
+ // find and scroll to the transaction-insights test snap
+ const snapButton1 = await driver.findElement(
+ '#connecttransaction-insights',
+ );
+ await driver.scrollToElement(snapButton1);
+
+ // added delay for firefox (deflake)
+ await driver.delayFirefox(1000);
+
+ // wait for and click connect
+ await driver.waitForSelector('#connecttransaction-insights');
+ await driver.clickElement('#connecttransaction-insights');
+
+ // switch to metamask extension
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // wait for and click connect
+ await driver.waitForSelector({
+ text: 'Connect',
+ tag: 'button',
+ });
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // wait for and click confirm
+ await driver.waitForSelector({ text: 'Confirm' });
+ await driver.clickElement({
+ text: 'Confirm',
+ tag: 'button',
+ });
+
+ // wait for and click ok and wait for window to close
+ await driver.waitForSelector({ text: 'OK' });
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'OK',
+ tag: 'button',
+ });
+
+ // switch to test-snaps page and get accounts
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps);
+
+ // click get accounts
+ await driver.clickElement('#getAccounts');
+
+ // switch back to MetaMask window
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // wait for and click next and wait for window to close
+ await driver.waitForSelector({
+ text: 'Connect',
+ tag: 'button',
+ });
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // switch to test-snaps page
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps);
+
+ // click send tx
+ await driver.clickElement('#sendInsights');
+
+ // delay added for rendering (deflake)
+ await driver.delay(2000);
+
+ // switch back to MetaMask window
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // wait for and switch to insight snap pane
+ await driver.waitForSelector({
+ text: 'Insights Example Snap',
+ tag: 'button',
+ });
+ await driver.clickElement({
+ text: 'Insights Example Snap',
+ tag: 'button',
+ });
+
+ // check that txinsightstest tab contains the right info
+ await driver.waitForSelector({
+ css: '.snap-ui-renderer__content',
+ text: 'ERC-20',
+ });
+ },
+ );
+ });
+ });
+
+ describe('Redesigned confirmation screens', function () {
+ it('tests tx insights functionality', async function () {
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder().build(),
+ ganacheOptions: defaultGanacheOptions,
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // navigate to test snaps page and connect
+ await driver.driver.get(TEST_SNAPS_WEBSITE_URL);
+
+ // wait for page to load
+ await driver.waitForSelector({
+ text: 'Installed Snaps',
+ tag: 'h2',
+ });
+
+ // find and scroll to the transaction-insights test snap
+ const snapButton1 = await driver.findElement(
+ '#connecttransaction-insights',
+ );
+ await driver.scrollToElement(snapButton1);
+
+ // added delay for firefox (deflake)
+ await driver.delayFirefox(1000);
+
+ // wait for and click connect
+ await driver.waitForSelector('#connecttransaction-insights');
+ await driver.clickElement('#connecttransaction-insights');
+
+ // switch to metamask extension
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // wait for and click connect
+ await driver.waitForSelector({
+ text: 'Connect',
+ tag: 'button',
+ });
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // wait for and click confirm
+ await driver.waitForSelector({ text: 'Confirm' });
+ await driver.clickElement({
+ text: 'Confirm',
+ tag: 'button',
+ });
+
+ // wait for and click ok and wait for window to close
+ await driver.waitForSelector({ text: 'OK' });
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'OK',
+ tag: 'button',
+ });
+
+ // switch to test-snaps page and get accounts
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps);
+
+ // click get accounts
+ await driver.clickElement('#getAccounts');
+
+ // switch back to MetaMask window
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // wait for and click next and wait for window to close
+ await driver.waitForSelector({
+ text: 'Connect',
+ tag: 'button',
+ });
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // switch to test-snaps page
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps);
+
+ // click send tx
+ await driver.clickElement('#sendInsights');
+
+ // delay added for rendering (deflake)
+ await driver.delay(2000);
+
+ // switch back to MetaMask window
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // wait for and switch to insight snap pane
+ await driver.waitForSelector({
+ text: 'Insights Example Snap',
+ tag: 'span',
+ });
+
+ // check that txinsightstest tab contains the right info
+ await driver.waitForSelector({
+ css: 'p',
+ text: 'ERC-20',
+ });
+ },
+ );
+ });
});
});
diff --git a/test/e2e/tests/account/account-custom-name.spec.ts b/test/e2e/tests/account/account-custom-name.spec.ts
index 138317b1dcf0..4c0ecbe196f9 100644
--- a/test/e2e/tests/account/account-custom-name.spec.ts
+++ b/test/e2e/tests/account/account-custom-name.spec.ts
@@ -32,7 +32,7 @@ describe('Account Custom Name Persistence', function (this: Suite) {
// Add new account with custom label and verify new added account label
await headerNavbar.openAccountMenu();
await accountListPage.check_pageIsLoaded();
- await accountListPage.addNewAccountWithCustomLabel(anotherAccountLabel);
+ await accountListPage.addNewAccount(anotherAccountLabel);
await headerNavbar.check_accountLabel(anotherAccountLabel);
// Switch back to the first account and verify first custom account persists
diff --git a/test/e2e/tests/account/add-account.spec.ts b/test/e2e/tests/account/add-account.spec.ts
index 3fa65b14e87d..906e01f50b4d 100644
--- a/test/e2e/tests/account/add-account.spec.ts
+++ b/test/e2e/tests/account/add-account.spec.ts
@@ -1,18 +1,19 @@
+import { E2E_SRP } from '../../default-fixture';
+import FixtureBuilder from '../../fixture-builder';
import {
- withFixtures,
WALLET_PASSWORD,
defaultGanacheOptions,
+ tempToggleSettingRedesignedTransactionConfirmations,
+ withFixtures,
} from '../../helpers';
-import { E2E_SRP } from '../../default-fixture';
-import FixtureBuilder from '../../fixture-builder';
+import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow';
+import { completeImportSRPOnboardingFlow } from '../../page-objects/flows/onboarding.flow';
+import { sendTransactionToAccount } from '../../page-objects/flows/send-transaction.flow';
import AccountListPage from '../../page-objects/pages/account-list-page';
import HeaderNavbar from '../../page-objects/pages/header-navbar';
import HomePage from '../../page-objects/pages/homepage';
import LoginPage from '../../page-objects/pages/login-page';
import ResetPasswordPage from '../../page-objects/pages/reset-password-page';
-import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow';
-import { completeImportSRPOnboardingFlow } from '../../page-objects/flows/onboarding.flow';
-import { sendTransactionToAccount } from '../../page-objects/flows/send-transaction.flow';
describe('Add account', function () {
it('should not affect public address when using secret recovery phrase to recover account with non-zero balance @no-mmi', async function () {
@@ -24,17 +25,21 @@ describe('Add account', function () {
},
async ({ driver, ganacheServer }) => {
await completeImportSRPOnboardingFlow({ driver });
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
const homePage = new HomePage(driver);
await homePage.check_pageIsLoaded();
- await homePage.check_ganacheBalanceIsDisplayed(ganacheServer);
+ await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer);
const headerNavbar = new HeaderNavbar(driver);
await headerNavbar.openAccountMenu();
- // Create new account with default name Account 2
+ // Create new account with default name `newAccountName`
+ const newAccountName = 'Account 2';
const accountListPage = new AccountListPage(driver);
await accountListPage.check_pageIsLoaded();
- await accountListPage.addNewAccountWithDefaultName();
- await headerNavbar.check_accountLabel('Account 2');
+ await accountListPage.addNewAccount();
+ await headerNavbar.check_accountLabel(newAccountName);
await homePage.check_expectedBalanceIsDisplayed();
// Switch back to the first account and transfer some balance to 2nd account so they will not be removed after recovering SRP
@@ -43,10 +48,10 @@ describe('Add account', function () {
await accountListPage.check_accountDisplayedInAccountList('Account 1');
await accountListPage.switchToAccount('Account 1');
await headerNavbar.check_accountLabel('Account 1');
- await homePage.check_ganacheBalanceIsDisplayed(ganacheServer);
+ await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer);
await sendTransactionToAccount({
driver,
- recipientAccount: 'Account 2',
+ recipientAccount: newAccountName,
amount: '2.8',
gasFee: '0.000042',
totalFee: '2.800042',
@@ -64,12 +69,14 @@ describe('Add account', function () {
// Check wallet balance for both accounts
await homePage.check_pageIsLoaded();
- await homePage.check_ganacheBalanceIsDisplayed(ganacheServer);
+ await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer);
await headerNavbar.openAccountMenu();
await accountListPage.check_pageIsLoaded();
- await accountListPage.check_accountDisplayedInAccountList('Account 2');
- await accountListPage.switchToAccount('Account 2');
- await headerNavbar.check_accountLabel('Account 2');
+ await accountListPage.check_accountDisplayedInAccountList(
+ newAccountName,
+ );
+ await accountListPage.switchToAccount(newAccountName);
+ await headerNavbar.check_accountLabel(newAccountName);
await homePage.check_expectedBalanceIsDisplayed('2.8');
},
);
@@ -93,7 +100,7 @@ describe('Add account', function () {
// Create new account with default name Account 2
const accountListPage = new AccountListPage(driver);
await accountListPage.check_pageIsLoaded();
- await accountListPage.addNewAccountWithDefaultName();
+ await accountListPage.addNewAccount();
await headerNavbar.check_accountLabel('Account 2');
await homePage.check_expectedBalanceIsDisplayed();
diff --git a/test/e2e/tests/account/snap-account-transfers.spec.ts b/test/e2e/tests/account/snap-account-transfers.spec.ts
index ee2466cf28e1..af2a61a62a39 100644
--- a/test/e2e/tests/account/snap-account-transfers.spec.ts
+++ b/test/e2e/tests/account/snap-account-transfers.spec.ts
@@ -2,6 +2,7 @@ import { Suite } from 'mocha';
import {
multipleGanacheOptions,
PRIVATE_KEY_TWO,
+ tempToggleSettingRedesignedTransactionConfirmations,
WINDOW_TITLES,
withFixtures,
} from '../../helpers';
@@ -15,151 +16,311 @@ import HomePage from '../../page-objects/pages/homepage';
import SnapSimpleKeyringPage from '../../page-objects/pages/snap-simple-keyring-page';
import { installSnapSimpleKeyring } from '../../page-objects/flows/snap-simple-keyring.flow';
import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow';
-import { sendTransactionWithSnapAccount } from '../../page-objects/flows/send-transaction.flow';
+import {
+ sendRedesignedTransactionWithSnapAccount,
+ sendTransactionWithSnapAccount,
+} from '../../page-objects/flows/send-transaction.flow';
describe('Snap Account Transfers @no-mmi', function (this: Suite) {
- it('can import a private key and transfer 1 ETH (sync flow)', async function () {
- await withFixtures(
- {
- fixtures: new FixtureBuilder().build(),
- ganacheOptions: multipleGanacheOptions,
- title: this.test?.fullTitle(),
- },
- async ({
- driver,
- ganacheServer,
- }: {
- driver: Driver;
- ganacheServer?: Ganache;
- }) => {
- await loginWithBalanceValidation(driver, ganacheServer);
- await installSnapSimpleKeyring(driver);
- const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver);
-
- // import snap account with private key on snap simple keyring page.
- await snapSimpleKeyringPage.importAccountWithPrivateKey(
- PRIVATE_KEY_TWO,
- );
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
- const headerNavbar = new HeaderNavbar(driver);
- await headerNavbar.check_accountLabel('SSK Account');
-
- // send 1 ETH from snap account to account 1
- await sendTransactionWithSnapAccount({
+ // TODO: Remove the old confirmations screen tests once migration has been complete.
+ // See: https://github.com/MetaMask/MetaMask-planning/issues/3030
+ describe('Old confirmation screens', function () {
+ it('can import a private key and transfer 1 ETH (sync flow)', async function () {
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder().build(),
+ ganacheOptions: multipleGanacheOptions,
+ title: this.test?.fullTitle(),
+ },
+ async ({
driver,
- recipientAddress: DEFAULT_FIXTURE_ACCOUNT,
- amount: '1',
- gasFee: '0.000042',
- totalFee: '1.000042',
- });
- await headerNavbar.check_pageIsLoaded();
- await headerNavbar.openAccountMenu();
- const accountList = new AccountListPage(driver);
- await accountList.check_pageIsLoaded();
-
- // check the balance of the 2 accounts are updated
- await accountList.check_accountBalanceDisplayed('26');
- await accountList.check_accountBalanceDisplayed('24');
- },
- );
- });
+ ganacheServer,
+ }: {
+ driver: Driver;
+ ganacheServer?: Ganache;
+ }) => {
+ await loginWithBalanceValidation(driver, ganacheServer);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ await installSnapSimpleKeyring(driver);
+ const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver);
+
+ // import snap account with private key on snap simple keyring page.
+ await snapSimpleKeyringPage.importAccountWithPrivateKey(
+ PRIVATE_KEY_TWO,
+ );
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_accountLabel('SSK Account');
+
+ // send 1 ETH from snap account to account 1
+ await sendTransactionWithSnapAccount({
+ driver,
+ recipientAddress: DEFAULT_FIXTURE_ACCOUNT,
+ amount: '1',
+ gasFee: '0.000042',
+ totalFee: '1.000042',
+ });
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.openAccountMenu();
+ const accountList = new AccountListPage(driver);
+ await accountList.check_pageIsLoaded();
+
+ // check the balance of the 2 accounts are updated
+ await accountList.check_accountBalanceDisplayed('26');
+ await accountList.check_accountBalanceDisplayed('24');
+ },
+ );
+ });
+
+ it('can import a private key and transfer 1 ETH (async flow approve)', async function () {
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder().build(),
+ ganacheOptions: multipleGanacheOptions,
+ title: this.test?.fullTitle(),
+ },
+ async ({
+ driver,
+ ganacheServer,
+ }: {
+ driver: Driver;
+ ganacheServer?: Ganache;
+ }) => {
+ await loginWithBalanceValidation(driver, ganacheServer);
- it('can import a private key and transfer 1 ETH (async flow approve)', async function () {
- await withFixtures(
- {
- fixtures: new FixtureBuilder().build(),
- ganacheOptions: multipleGanacheOptions,
- title: this.test?.fullTitle(),
- },
- async ({
- driver,
- ganacheServer,
- }: {
- driver: Driver;
- ganacheServer?: Ganache;
- }) => {
- await loginWithBalanceValidation(driver, ganacheServer);
- await installSnapSimpleKeyring(driver, false);
- const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver);
-
- // import snap account with private key on snap simple keyring page.
- await snapSimpleKeyringPage.importAccountWithPrivateKey(
- PRIVATE_KEY_TWO,
- );
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
- const headerNavbar = new HeaderNavbar(driver);
- await headerNavbar.check_accountLabel('SSK Account');
-
- // send 1 ETH from snap account to account 1 and approve the transaction
- await sendTransactionWithSnapAccount({
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ await installSnapSimpleKeyring(driver, false);
+ const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver);
+
+ // import snap account with private key on snap simple keyring page.
+ await snapSimpleKeyringPage.importAccountWithPrivateKey(
+ PRIVATE_KEY_TWO,
+ );
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_accountLabel('SSK Account');
+
+ // send 1 ETH from snap account to account 1 and approve the transaction
+ await sendTransactionWithSnapAccount({
+ driver,
+ recipientAddress: DEFAULT_FIXTURE_ACCOUNT,
+ amount: '1',
+ gasFee: '0.000042',
+ totalFee: '1.000042',
+ isSyncFlow: false,
+ });
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.openAccountMenu();
+ const accountList = new AccountListPage(driver);
+ await accountList.check_pageIsLoaded();
+
+ // check the balance of the 2 accounts are updated
+ await accountList.check_accountBalanceDisplayed('26');
+ await accountList.check_accountBalanceDisplayed('24');
+ },
+ );
+ });
+
+ it('can import a private key and transfer 1 ETH (async flow reject)', async function () {
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder().build(),
+ ganacheOptions: multipleGanacheOptions,
+ title: this.test?.fullTitle(),
+ ignoredConsoleErrors: ['Request rejected by user or snap.'],
+ },
+ async ({
driver,
- recipientAddress: DEFAULT_FIXTURE_ACCOUNT,
- amount: '1',
- gasFee: '0.000042',
- totalFee: '1.000042',
- isSyncFlow: false,
- });
- await headerNavbar.check_pageIsLoaded();
- await headerNavbar.openAccountMenu();
- const accountList = new AccountListPage(driver);
- await accountList.check_pageIsLoaded();
-
- // check the balance of the 2 accounts are updated
- await accountList.check_accountBalanceDisplayed('26');
- await accountList.check_accountBalanceDisplayed('24');
- },
- );
+ ganacheServer,
+ }: {
+ driver: Driver;
+ ganacheServer?: Ganache;
+ }) => {
+ await loginWithBalanceValidation(driver, ganacheServer);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ await installSnapSimpleKeyring(driver, false);
+ const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver);
+
+ // Import snap account with private key on snap simple keyring page.
+ await snapSimpleKeyringPage.importAccountWithPrivateKey(
+ PRIVATE_KEY_TWO,
+ );
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_accountLabel('SSK Account');
+
+ // send 1 ETH from snap account to account 1 and reject the transaction
+ await sendTransactionWithSnapAccount({
+ driver,
+ recipientAddress: DEFAULT_FIXTURE_ACCOUNT,
+ amount: '1',
+ gasFee: '0.000042',
+ totalFee: '1.000042',
+ isSyncFlow: false,
+ approveTransaction: false,
+ });
+
+ // check the transaction is failed in MetaMask activity list
+ const homepage = new HomePage(driver);
+ await homepage.check_pageIsLoaded();
+ await homepage.check_failedTxNumberDisplayedInActivity();
+ },
+ );
+ });
});
- it('can import a private key and transfer 1 ETH (async flow reject)', async function () {
- await withFixtures(
- {
- fixtures: new FixtureBuilder().build(),
- ganacheOptions: multipleGanacheOptions,
- title: this.test?.fullTitle(),
- ignoredConsoleErrors: ['Request rejected by user or snap.'],
- },
- async ({
- driver,
- ganacheServer,
- }: {
- driver: Driver;
- ganacheServer?: Ganache;
- }) => {
- await loginWithBalanceValidation(driver, ganacheServer);
- await installSnapSimpleKeyring(driver, false);
- const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver);
-
- // Import snap account with private key on snap simple keyring page.
- await snapSimpleKeyringPage.importAccountWithPrivateKey(
- PRIVATE_KEY_TWO,
- );
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
- const headerNavbar = new HeaderNavbar(driver);
- await headerNavbar.check_accountLabel('SSK Account');
-
- // send 1 ETH from snap account to account 1 and reject the transaction
- await sendTransactionWithSnapAccount({
+ describe('Redesigned confirmation screens', function () {
+ it('can import a private key and transfer 1 ETH (sync flow)', async function () {
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder().build(),
+ ganacheOptions: multipleGanacheOptions,
+ title: this.test?.fullTitle(),
+ },
+ async ({
driver,
- recipientAddress: DEFAULT_FIXTURE_ACCOUNT,
- amount: '1',
- gasFee: '0.000042',
- totalFee: '1.000042',
- isSyncFlow: false,
- approveTransaction: false,
- });
-
- // check the transaction is failed in MetaMask activity list
- const homepage = new HomePage(driver);
- await homepage.check_pageIsLoaded();
- await homepage.check_failedTxNumberDisplayedInActivity();
- },
- );
+ ganacheServer,
+ }: {
+ driver: Driver;
+ ganacheServer?: Ganache;
+ }) => {
+ await loginWithBalanceValidation(driver, ganacheServer);
+
+ await installSnapSimpleKeyring(driver);
+ const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver);
+
+ // import snap account with private key on snap simple keyring page.
+ await snapSimpleKeyringPage.importAccountWithPrivateKey(
+ PRIVATE_KEY_TWO,
+ );
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_accountLabel('SSK Account');
+
+ // send 1 ETH from snap account to account 1
+ await sendRedesignedTransactionWithSnapAccount({
+ driver,
+ recipientAddress: DEFAULT_FIXTURE_ACCOUNT,
+ amount: '1',
+ });
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.openAccountMenu();
+ const accountList = new AccountListPage(driver);
+ await accountList.check_pageIsLoaded();
+
+ // check the balance of the 2 accounts are updated
+ await accountList.check_accountBalanceDisplayed('26');
+ await accountList.check_accountBalanceDisplayed('24');
+ },
+ );
+ });
+
+ it('can import a private key and transfer 1 ETH (async flow approve)', async function () {
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder().build(),
+ ganacheOptions: multipleGanacheOptions,
+ title: this.test?.fullTitle(),
+ },
+ async ({
+ driver,
+ ganacheServer,
+ }: {
+ driver: Driver;
+ ganacheServer?: Ganache;
+ }) => {
+ await loginWithBalanceValidation(driver, ganacheServer);
+
+ await installSnapSimpleKeyring(driver, false);
+ const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver);
+
+ // import snap account with private key on snap simple keyring page.
+ await snapSimpleKeyringPage.importAccountWithPrivateKey(
+ PRIVATE_KEY_TWO,
+ );
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_accountLabel('SSK Account');
+
+ // send 1 ETH from snap account to account 1 and approve the transaction
+ await sendRedesignedTransactionWithSnapAccount({
+ driver,
+ recipientAddress: DEFAULT_FIXTURE_ACCOUNT,
+ amount: '1',
+ isSyncFlow: false,
+ });
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.openAccountMenu();
+ const accountList = new AccountListPage(driver);
+ await accountList.check_pageIsLoaded();
+
+ // check the balance of the 2 accounts are updated
+ await accountList.check_accountBalanceDisplayed('26');
+ await accountList.check_accountBalanceDisplayed('24');
+ },
+ );
+ });
+
+ it('can import a private key and transfer 1 ETH (async flow reject)', async function () {
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder().build(),
+ ganacheOptions: multipleGanacheOptions,
+ title: this.test?.fullTitle(),
+ ignoredConsoleErrors: ['Request rejected by user or snap.'],
+ },
+ async ({
+ driver,
+ ganacheServer,
+ }: {
+ driver: Driver;
+ ganacheServer?: Ganache;
+ }) => {
+ await loginWithBalanceValidation(driver, ganacheServer);
+
+ await installSnapSimpleKeyring(driver, false);
+ const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver);
+
+ // Import snap account with private key on snap simple keyring page.
+ await snapSimpleKeyringPage.importAccountWithPrivateKey(
+ PRIVATE_KEY_TWO,
+ );
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_accountLabel('SSK Account');
+
+ // send 1 ETH from snap account to account 1 and reject the transaction
+ await sendRedesignedTransactionWithSnapAccount({
+ driver,
+ recipientAddress: DEFAULT_FIXTURE_ACCOUNT,
+ amount: '1',
+ isSyncFlow: false,
+ approveTransaction: false,
+ });
+
+ // check the transaction is failed in MetaMask activity list
+ const homepage = new HomePage(driver);
+ await homepage.check_pageIsLoaded();
+ await homepage.check_failedTxNumberDisplayedInActivity();
+ },
+ );
+ });
});
});
diff --git a/test/e2e/tests/confirmations/helpers.ts b/test/e2e/tests/confirmations/helpers.ts
index 355f664ec61c..2b1078549c5b 100644
--- a/test/e2e/tests/confirmations/helpers.ts
+++ b/test/e2e/tests/confirmations/helpers.ts
@@ -14,7 +14,7 @@ export async function scrollAndConfirmAndAssertConfirm(driver: Driver) {
await driver.clickElement('[data-testid="confirm-footer-button"]');
}
-export function withRedesignConfirmationFixtures(
+export function withTransactionEnvelopeTypeFixtures(
// Default params first is discouraged because it makes it hard to call the function without the
// optional parameters. But it doesn't apply here because we're always passing in a variable for
// title. It's optional because it's sometimes unset.
@@ -35,12 +35,6 @@ export function withRedesignConfirmationFixtures(
metaMetricsId: 'fake-metrics-id',
participateInMetaMetrics: true,
})
- .withPreferencesController({
- preferences: {
- redesignedConfirmationsEnabled: true,
- isRedesignedConfirmationsDeveloperEnabled: true,
- },
- })
.build(),
ganacheOptions:
transactionEnvelopeType === TransactionEnvelopeType.legacy
diff --git a/test/e2e/tests/confirmations/navigation.spec.ts b/test/e2e/tests/confirmations/navigation.spec.ts
index 38d29ad3ad77..97985381b08b 100644
--- a/test/e2e/tests/confirmations/navigation.spec.ts
+++ b/test/e2e/tests/confirmations/navigation.spec.ts
@@ -8,11 +8,11 @@ import {
WINDOW_TITLES,
} from '../../helpers';
import { Driver } from '../../webdriver/driver';
-import { withRedesignConfirmationFixtures } from './helpers';
+import { withTransactionEnvelopeTypeFixtures } from './helpers';
describe('Navigation Signature - Different signature types', function (this: Suite) {
it('initiates and queues multiple signatures and confirms', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver }: { driver: Driver }) => {
@@ -52,7 +52,7 @@ describe('Navigation Signature - Different signature types', function (this: Sui
});
it('initiates and queues a mix of signatures and transactions and navigates', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver }: { driver: Driver }) => {
@@ -100,7 +100,7 @@ describe('Navigation Signature - Different signature types', function (this: Sui
});
it('initiates multiple signatures and rejects all', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver }: { driver: Driver }) => {
diff --git a/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts b/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts
index 328ad7811e1b..5876a4d5e17f 100644
--- a/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts
+++ b/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts
@@ -7,7 +7,7 @@ import { Driver } from '../../../webdriver/driver';
import {
mockSignatureRejected,
scrollAndConfirmAndAssertConfirm,
- withRedesignConfirmationFixtures,
+ withTransactionEnvelopeTypeFixtures,
} from '../helpers';
import { TestSuiteArguments } from '../transactions/shared';
import {
@@ -22,7 +22,7 @@ import {
describe('Malicious Confirmation Signature - Bad Domain @no-mmi', function (this: Suite) {
it('displays alert for domain binding and confirms', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver }: TestSuiteArguments) => {
@@ -45,7 +45,7 @@ describe('Malicious Confirmation Signature - Bad Domain @no-mmi', function (this
});
it('initiates and rejects from confirmation screen', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
@@ -93,7 +93,7 @@ describe('Malicious Confirmation Signature - Bad Domain @no-mmi', function (this
});
it('initiates and rejects from alert friction modal', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
diff --git a/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts b/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts
index 383a3bd6b924..de70d25b359b 100644
--- a/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts
+++ b/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts
@@ -9,7 +9,7 @@ import {
mockSignatureApproved,
mockSignatureRejected,
scrollAndConfirmAndAssertConfirm,
- withRedesignConfirmationFixtures,
+ withTransactionEnvelopeTypeFixtures,
} from '../helpers';
import { TestSuiteArguments } from '../transactions/shared';
import {
@@ -26,7 +26,7 @@ import {
describe('Confirmation Signature - NFT Permit @no-mmi', function (this: Suite) {
it('initiates and confirms and emits the correct events', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
@@ -77,7 +77,7 @@ describe('Confirmation Signature - NFT Permit @no-mmi', function (this: Suite) {
});
it('initiates and rejects and emits the correct events', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
diff --git a/test/e2e/tests/confirmations/signatures/permit.spec.ts b/test/e2e/tests/confirmations/signatures/permit.spec.ts
index bc74b9fd2f5f..f6c8fc972b5f 100644
--- a/test/e2e/tests/confirmations/signatures/permit.spec.ts
+++ b/test/e2e/tests/confirmations/signatures/permit.spec.ts
@@ -14,7 +14,7 @@ import {
mockSignatureApproved,
mockSignatureRejected,
scrollAndConfirmAndAssertConfirm,
- withRedesignConfirmationFixtures,
+ withTransactionEnvelopeTypeFixtures,
} from '../helpers';
import { TestSuiteArguments } from '../transactions/shared';
import {
@@ -31,7 +31,7 @@ import {
describe('Confirmation Signature - Permit @no-mmi', function (this: Suite) {
it('initiates and confirms and emits the correct events', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
@@ -76,7 +76,7 @@ describe('Confirmation Signature - Permit @no-mmi', function (this: Suite) {
});
it('initiates and rejects and emits the correct events', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
diff --git a/test/e2e/tests/confirmations/signatures/personal-sign.spec.ts b/test/e2e/tests/confirmations/signatures/personal-sign.spec.ts
index 8444deab7c61..5f87c2d6b6e8 100644
--- a/test/e2e/tests/confirmations/signatures/personal-sign.spec.ts
+++ b/test/e2e/tests/confirmations/signatures/personal-sign.spec.ts
@@ -8,7 +8,7 @@ import { Driver } from '../../../webdriver/driver';
import {
mockSignatureApproved,
mockSignatureRejected,
- withRedesignConfirmationFixtures,
+ withTransactionEnvelopeTypeFixtures,
} from '../helpers';
import { TestSuiteArguments } from '../transactions/shared';
import {
@@ -25,7 +25,7 @@ import {
describe('Confirmation Signature - Personal Sign @no-mmi', function (this: Suite) {
it('initiates and confirms', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
@@ -66,7 +66,7 @@ describe('Confirmation Signature - Personal Sign @no-mmi', function (this: Suite
});
it('initiates and rejects', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
diff --git a/test/e2e/tests/confirmations/signatures/sign-typed-data-v3.spec.ts b/test/e2e/tests/confirmations/signatures/sign-typed-data-v3.spec.ts
index a7f2e7b81691..7ea7f0879279 100644
--- a/test/e2e/tests/confirmations/signatures/sign-typed-data-v3.spec.ts
+++ b/test/e2e/tests/confirmations/signatures/sign-typed-data-v3.spec.ts
@@ -9,7 +9,7 @@ import {
mockSignatureApproved,
mockSignatureRejected,
scrollAndConfirmAndAssertConfirm,
- withRedesignConfirmationFixtures,
+ withTransactionEnvelopeTypeFixtures,
} from '../helpers';
import { TestSuiteArguments } from '../transactions/shared';
import {
@@ -26,7 +26,7 @@ import {
describe('Confirmation Signature - Sign Typed Data V3 @no-mmi', function (this: Suite) {
it('initiates and confirms', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
@@ -70,7 +70,7 @@ describe('Confirmation Signature - Sign Typed Data V3 @no-mmi', function (this:
});
it('initiates and rejects', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
diff --git a/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts b/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts
index 33b94be6b332..4dfe9f04972f 100644
--- a/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts
+++ b/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts
@@ -9,7 +9,7 @@ import {
mockSignatureApproved,
mockSignatureRejected,
scrollAndConfirmAndAssertConfirm,
- withRedesignConfirmationFixtures,
+ withTransactionEnvelopeTypeFixtures,
} from '../helpers';
import { TestSuiteArguments } from '../transactions/shared';
import {
@@ -26,7 +26,7 @@ import {
describe('Confirmation Signature - Sign Typed Data V4 @no-mmi', function (this: Suite) {
it('initiates and confirms', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
@@ -74,7 +74,7 @@ describe('Confirmation Signature - Sign Typed Data V4 @no-mmi', function (this:
});
it('initiates and rejects', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
diff --git a/test/e2e/tests/confirmations/signatures/sign-typed-data.spec.ts b/test/e2e/tests/confirmations/signatures/sign-typed-data.spec.ts
index 2f1c33fe4d07..e7f8e1446f5c 100644
--- a/test/e2e/tests/confirmations/signatures/sign-typed-data.spec.ts
+++ b/test/e2e/tests/confirmations/signatures/sign-typed-data.spec.ts
@@ -8,7 +8,7 @@ import { Driver } from '../../../webdriver/driver';
import {
mockSignatureApproved,
mockSignatureRejected,
- withRedesignConfirmationFixtures,
+ withTransactionEnvelopeTypeFixtures,
} from '../helpers';
import { TestSuiteArguments } from '../transactions/shared';
import {
@@ -25,7 +25,7 @@ import {
describe('Confirmation Signature - Sign Typed Data @no-mmi', function (this: Suite) {
it('initiates and confirms', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
@@ -66,7 +66,7 @@ describe('Confirmation Signature - Sign Typed Data @no-mmi', function (this: Sui
});
it('initiates and rejects', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
diff --git a/test/e2e/tests/confirmations/signatures/siwe.spec.ts b/test/e2e/tests/confirmations/signatures/siwe.spec.ts
index 9f261a28f569..4c7bec0ae121 100644
--- a/test/e2e/tests/confirmations/signatures/siwe.spec.ts
+++ b/test/e2e/tests/confirmations/signatures/siwe.spec.ts
@@ -8,7 +8,7 @@ import {
mockSignatureApproved,
mockSignatureRejected,
scrollAndConfirmAndAssertConfirm,
- withRedesignConfirmationFixtures,
+ withTransactionEnvelopeTypeFixtures,
} from '../helpers';
import { TestSuiteArguments } from '../transactions/shared';
import {
@@ -29,7 +29,7 @@ import {
describe('Confirmation Signature - SIWE @no-mmi', function (this: Suite) {
it('initiates and confirms', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
@@ -74,7 +74,7 @@ describe('Confirmation Signature - SIWE @no-mmi', function (this: Suite) {
});
it('initiates and rejects', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({
diff --git a/test/e2e/tests/confirmations/transactions/erc1155-revoke-set-approval-for-all-redesign.ts b/test/e2e/tests/confirmations/transactions/erc1155-revoke-set-approval-for-all-redesign.ts
index 33a7760a050d..f79bf6835e14 100644
--- a/test/e2e/tests/confirmations/transactions/erc1155-revoke-set-approval-for-all-redesign.ts
+++ b/test/e2e/tests/confirmations/transactions/erc1155-revoke-set-approval-for-all-redesign.ts
@@ -7,7 +7,7 @@ import SetApprovalForAllTransactionConfirmation from '../../../page-objects/page
import TestDapp from '../../../page-objects/pages/test-dapp';
import ContractAddressRegistry from '../../../seeder/contract-address-registry';
import { Driver } from '../../../webdriver/driver';
-import { withRedesignConfirmationFixtures } from '../helpers';
+import { withTransactionEnvelopeTypeFixtures } from '../helpers';
import { mocked4BytesSetApprovalForAll } from './erc721-revoke-set-approval-for-all-redesign';
import { TestSuiteArguments } from './shared';
@@ -16,7 +16,7 @@ const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');
describe('Confirmation Redesign ERC1155 Revoke setApprovalForAll', function () {
describe('Submit an revoke transaction @no-mmi', function () {
it('Sends a type 0 transaction (Legacy)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
@@ -28,7 +28,7 @@ describe('Confirmation Redesign ERC1155 Revoke setApprovalForAll', function () {
});
it('Sends a type 2 transaction (EIP1559)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.feeMarket,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
diff --git a/test/e2e/tests/confirmations/transactions/erc1155-set-approval-for-all-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/erc1155-set-approval-for-all-redesign.spec.ts
index da9da0a59873..cbb85482e6f1 100644
--- a/test/e2e/tests/confirmations/transactions/erc1155-set-approval-for-all-redesign.spec.ts
+++ b/test/e2e/tests/confirmations/transactions/erc1155-set-approval-for-all-redesign.spec.ts
@@ -6,7 +6,7 @@ import SetApprovalForAllTransactionConfirmation from '../../../page-objects/page
import TestDapp from '../../../page-objects/pages/test-dapp';
import ContractAddressRegistry from '../../../seeder/contract-address-registry';
import { Driver } from '../../../webdriver/driver';
-import { withRedesignConfirmationFixtures } from '../helpers';
+import { withTransactionEnvelopeTypeFixtures } from '../helpers';
import { TestSuiteArguments } from './shared';
const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');
@@ -14,7 +14,7 @@ const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');
describe('Confirmation Redesign ERC1155 setApprovalForAll', function () {
describe('Submit a transaction @no-mmi', function () {
it('Sends a type 0 transaction (Legacy)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
@@ -29,7 +29,7 @@ describe('Confirmation Redesign ERC1155 setApprovalForAll', function () {
});
it('Sends a type 2 transaction (EIP1559)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.feeMarket,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
diff --git a/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts
index f5293161172f..2fbe2c553a06 100644
--- a/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts
+++ b/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts
@@ -14,7 +14,7 @@ import SendTokenPage from '../../../page-objects/pages/send/send-token-page';
import TestDapp from '../../../page-objects/pages/test-dapp';
import ContractAddressRegistry from '../../../seeder/contract-address-registry';
import { Driver } from '../../../webdriver/driver';
-import { withRedesignConfirmationFixtures } from '../helpers';
+import { withTransactionEnvelopeTypeFixtures } from '../helpers';
import { TestSuiteArguments } from './shared';
const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');
@@ -22,7 +22,7 @@ const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');
describe('Confirmation Redesign ERC20 Token Send @no-mmi', function () {
describe('Wallet initiated', async function () {
it('Sends a type 0 transaction (Legacy)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
@@ -37,7 +37,7 @@ describe('Confirmation Redesign ERC20 Token Send @no-mmi', function () {
});
it('Sends a type 2 transaction (EIP1559)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.feeMarket,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
@@ -54,7 +54,7 @@ describe('Confirmation Redesign ERC20 Token Send @no-mmi', function () {
describe('dApp initiated', async function () {
it('Sends a type 0 transaction (Legacy)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
@@ -69,7 +69,7 @@ describe('Confirmation Redesign ERC20 Token Send @no-mmi', function () {
});
it('Sends a type 2 transaction (EIP1559)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.feeMarket,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
diff --git a/test/e2e/tests/confirmations/transactions/erc721-revoke-set-approval-for-all-redesign.ts b/test/e2e/tests/confirmations/transactions/erc721-revoke-set-approval-for-all-redesign.ts
index 95768c35de3f..27e2eb31a04e 100644
--- a/test/e2e/tests/confirmations/transactions/erc721-revoke-set-approval-for-all-redesign.ts
+++ b/test/e2e/tests/confirmations/transactions/erc721-revoke-set-approval-for-all-redesign.ts
@@ -7,7 +7,7 @@ import SetApprovalForAllTransactionConfirmation from '../../../page-objects/page
import TestDapp from '../../../page-objects/pages/test-dapp';
import ContractAddressRegistry from '../../../seeder/contract-address-registry';
import { Driver } from '../../../webdriver/driver';
-import { withRedesignConfirmationFixtures } from '../helpers';
+import { withTransactionEnvelopeTypeFixtures } from '../helpers';
import { TestSuiteArguments } from './shared';
const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');
@@ -15,7 +15,7 @@ const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');
describe('Confirmation Redesign ERC721 Revoke setApprovalForAll', function () {
describe('Submit an revoke transaction @no-mmi', function () {
it('Sends a type 0 transaction (Legacy)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
@@ -27,7 +27,7 @@ describe('Confirmation Redesign ERC721 Revoke setApprovalForAll', function () {
});
it('Sends a type 2 transaction (EIP1559)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.feeMarket,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
diff --git a/test/e2e/tests/confirmations/transactions/erc721-set-approval-for-all-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/erc721-set-approval-for-all-redesign.spec.ts
index ba3c877973e5..4e1f54511d40 100644
--- a/test/e2e/tests/confirmations/transactions/erc721-set-approval-for-all-redesign.spec.ts
+++ b/test/e2e/tests/confirmations/transactions/erc721-set-approval-for-all-redesign.spec.ts
@@ -6,7 +6,7 @@ import SetApprovalForAllTransactionConfirmation from '../../../page-objects/page
import TestDapp from '../../../page-objects/pages/test-dapp';
import ContractAddressRegistry from '../../../seeder/contract-address-registry';
import { Driver } from '../../../webdriver/driver';
-import { withRedesignConfirmationFixtures } from '../helpers';
+import { withTransactionEnvelopeTypeFixtures } from '../helpers';
import { TestSuiteArguments } from './shared';
const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');
@@ -14,7 +14,7 @@ const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');
describe('Confirmation Redesign ERC721 setApprovalForAll', function () {
describe('Submit a transaction @no-mmi', function () {
it('Sends a type 0 transaction (Legacy)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
@@ -29,7 +29,7 @@ describe('Confirmation Redesign ERC721 setApprovalForAll', function () {
});
it('Sends a type 2 transaction (EIP1559)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.feeMarket,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
diff --git a/test/e2e/tests/confirmations/transactions/native-send-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/native-send-redesign.spec.ts
index e8226977d019..46318e113c35 100644
--- a/test/e2e/tests/confirmations/transactions/native-send-redesign.spec.ts
+++ b/test/e2e/tests/confirmations/transactions/native-send-redesign.spec.ts
@@ -11,7 +11,7 @@ import HomePage from '../../../page-objects/pages/homepage';
import SendTokenPage from '../../../page-objects/pages/send/send-token-page';
import TestDapp from '../../../page-objects/pages/test-dapp';
import { Driver } from '../../../webdriver/driver';
-import { withRedesignConfirmationFixtures } from '../helpers';
+import { withTransactionEnvelopeTypeFixtures } from '../helpers';
import { TestSuiteArguments } from './shared';
const TOKEN_RECIPIENT_ADDRESS = '0x2f318C334780961FB129D2a6c30D0763d9a5C970';
@@ -19,7 +19,7 @@ const TOKEN_RECIPIENT_ADDRESS = '0x2f318C334780961FB129D2a6c30D0763d9a5C970';
describe('Confirmation Redesign Native Send @no-mmi', function () {
describe('Wallet initiated', async function () {
it('Sends a type 0 transaction (Legacy)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver }: TestSuiteArguments) => {
@@ -29,7 +29,7 @@ describe('Confirmation Redesign Native Send @no-mmi', function () {
});
it('Sends a type 2 transaction (EIP1559)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.feeMarket,
async ({ driver }: TestSuiteArguments) => {
@@ -41,7 +41,7 @@ describe('Confirmation Redesign Native Send @no-mmi', function () {
describe('dApp initiated', async function () {
it('Sends a type 0 transaction (Legacy)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver }: TestSuiteArguments) => {
@@ -51,7 +51,7 @@ describe('Confirmation Redesign Native Send @no-mmi', function () {
});
it('Sends a type 2 transaction (EIP1559)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.feeMarket,
async ({ driver }: TestSuiteArguments) => {
diff --git a/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts
index 8d319b62c482..6fc048c7f11b 100644
--- a/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts
+++ b/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts
@@ -16,7 +16,7 @@ import SendTokenPage from '../../../page-objects/pages/send/send-token-page';
import TestDapp from '../../../page-objects/pages/test-dapp';
import ContractAddressRegistry from '../../../seeder/contract-address-registry';
import { Driver } from '../../../webdriver/driver';
-import { withRedesignConfirmationFixtures } from '../helpers';
+import { withTransactionEnvelopeTypeFixtures } from '../helpers';
import { TestSuiteArguments } from './shared';
const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');
@@ -27,7 +27,7 @@ describe('Confirmation Redesign Token Send @no-mmi', function () {
describe('ERC721', function () {
describe('Wallet initiated', async function () {
it('Sends a type 0 transaction (Legacy)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
@@ -42,7 +42,7 @@ describe('Confirmation Redesign Token Send @no-mmi', function () {
});
it('Sends a type 2 transaction (EIP1559)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.feeMarket,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
@@ -59,7 +59,7 @@ describe('Confirmation Redesign Token Send @no-mmi', function () {
describe('dApp initiated', async function () {
it('Sends a type 0 transaction (Legacy)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
@@ -74,7 +74,7 @@ describe('Confirmation Redesign Token Send @no-mmi', function () {
});
it('Sends a type 2 transaction (EIP1559)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.feeMarket,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
@@ -93,7 +93,7 @@ describe('Confirmation Redesign Token Send @no-mmi', function () {
describe('ERC1155', function () {
describe('Wallet initiated', async function () {
it('Sends a type 0 transaction (Legacy)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.legacy,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
@@ -108,7 +108,7 @@ describe('Confirmation Redesign Token Send @no-mmi', function () {
});
it('Sends a type 2 transaction (EIP1559)', async function () {
- await withRedesignConfirmationFixtures(
+ await withTransactionEnvelopeTypeFixtures(
this.test?.fullTitle(),
TransactionEnvelopeType.feeMarket,
async ({ driver, contractRegistry }: TestSuiteArguments) => {
diff --git a/test/e2e/tests/dapp-interactions/contract-interactions.spec.js b/test/e2e/tests/dapp-interactions/contract-interactions.spec.js
index e7de105e0112..a685954b5857 100644
--- a/test/e2e/tests/dapp-interactions/contract-interactions.spec.js
+++ b/test/e2e/tests/dapp-interactions/contract-interactions.spec.js
@@ -7,8 +7,8 @@ const {
WINDOW_TITLES,
locateAccountBalanceDOM,
clickNestedButton,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
-
const { SMART_CONTRACTS } = require('../../seeder/smart-contracts');
const FixtureBuilder = require('../../fixture-builder');
@@ -32,6 +32,8 @@ describe('Deploy contract and call contract methods', function () {
);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// deploy contract
await openDapp(driver, contractAddress);
diff --git a/test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js b/test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js
index 131ebdf4ee73..ad168a2b9332 100644
--- a/test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js
+++ b/test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js
@@ -4,6 +4,7 @@ const {
openDapp,
WINDOW_TITLES,
withFixtures,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const { SMART_CONTRACTS } = require('../../seeder/smart-contracts');
const FixtureBuilder = require('../../fixture-builder');
@@ -27,6 +28,8 @@ describe('Editing confirmations of dapp initiated contract interactions', functi
);
await logInWithBalanceValidation(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// deploy contract
await openDapp(driver, contractAddress);
// wait for deployed contract, calls and confirms a contract method where ETH is sent
@@ -59,6 +62,8 @@ describe('Editing confirmations of dapp initiated contract interactions', functi
async ({ driver }) => {
await logInWithBalanceValidation(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await openDapp(driver);
await driver.clickElement('#sendButton');
diff --git a/test/e2e/tests/dapp-interactions/failing-contract.spec.js b/test/e2e/tests/dapp-interactions/failing-contract.spec.js
index 5770adb1a3b9..c05938d668e0 100644
--- a/test/e2e/tests/dapp-interactions/failing-contract.spec.js
+++ b/test/e2e/tests/dapp-interactions/failing-contract.spec.js
@@ -6,6 +6,7 @@ const {
WINDOW_TITLES,
generateGanacheOptions,
clickNestedButton,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const { SMART_CONTRACTS } = require('../../seeder/smart-contracts');
const FixtureBuilder = require('../../fixture-builder');
@@ -29,6 +30,8 @@ describe('Failing contract interaction ', function () {
);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await openDapp(driver, contractAddress);
let windowHandles = await driver.getAllWindowHandles();
const extension = windowHandles[0];
@@ -93,6 +96,8 @@ describe('Failing contract interaction on non-EIP1559 network', function () {
);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await openDapp(driver, contractAddress);
let windowHandles = await driver.getAllWindowHandles();
const extension = windowHandles[0];
diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json
index f8f965d81428..2fe21c1903a6 100644
--- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json
+++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json
@@ -67,38 +67,39 @@
"srcNetworkAllowlist": { "0": "string", "1": "string", "2": "string" },
"destNetworkAllowlist": { "0": "string", "1": "string", "2": "string" }
},
+ "srcTokens": {},
+ "srcTopAssets": {},
"destTokens": {},
"destTopAssets": {},
"quoteRequest": {
- "slippage": 0.5,
- "srcTokenAddress": "0x0000000000000000000000000000000000000000"
+ "srcTokenAddress": "0x0000000000000000000000000000000000000000",
+ "slippage": 0.5
},
"quotes": {},
- "quotesRefreshCount": 0,
- "srcTokens": {},
- "srcTopAssets": {}
+ "quotesRefreshCount": 0
}
},
+ "BridgeStatusController": { "bridgeStatusState": { "txHistory": "object" } },
"CronjobController": { "jobs": "object" },
"CurrencyController": {
+ "currentCurrency": "usd",
"currencyRates": {
"ETH": {
"conversionDate": "number",
"conversionRate": 1700,
"usdConversionRate": 1700
},
- "LineaETH": {
+ "SepoliaETH": {
"conversionDate": "number",
"conversionRate": 1700,
"usdConversionRate": 1700
},
- "SepoliaETH": {
+ "LineaETH": {
"conversionDate": "number",
"conversionRate": 1700,
"usdConversionRate": 1700
}
- },
- "currentCurrency": "usd"
+ }
},
"DecryptMessageController": {
"unapprovedDecryptMsgs": "object",
diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json
index 337b946c4b70..f278b28f2042 100644
--- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json
+++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json
@@ -35,8 +35,6 @@
"petnamesEnabled": true,
"showMultiRpcModal": "boolean",
"isRedesignedConfirmationsDeveloperEnabled": "boolean",
- "redesignedConfirmationsEnabled": true,
- "redesignedTransactionsEnabled": "boolean",
"tokenSortConfig": "object",
"tokenNetworkFilter": {
"0x1": "boolean",
@@ -45,13 +43,14 @@
"0xe705": "boolean",
"0xe708": "boolean"
},
- "shouldShowAggregatedBalancePopover": "boolean"
+ "shouldShowAggregatedBalancePopover": "boolean",
+ "redesignedConfirmationsEnabled": true,
+ "redesignedTransactionsEnabled": "boolean"
},
"firstTimeFlowType": "import",
"completedOnboarding": true,
"knownMethodData": "object",
"use4ByteResolution": true,
- "showIncomingTransactions": "object",
"participateInMetaMetrics": true,
"dataCollectionForMarketing": "boolean",
"nextNonce": null,
@@ -61,12 +60,12 @@
"conversionRate": 1700,
"usdConversionRate": 1700
},
- "LineaETH": {
+ "SepoliaETH": {
"conversionDate": "number",
"conversionRate": 1700,
"usdConversionRate": 1700
},
- "SepoliaETH": {
+ "LineaETH": {
"conversionDate": "number",
"conversionRate": 1700,
"usdConversionRate": 1700
@@ -141,7 +140,6 @@
"forgottenPassword": false,
"ipfsGateway": "string",
"isIpfsGatewayEnabled": "boolean",
- "isMultiAccountBalancesEnabled": "boolean",
"useAddressBarEnsResolution": true,
"ledgerTransportType": "webhid",
"snapRegistryList": "object",
@@ -151,6 +149,8 @@
"useTransactionSimulations": true,
"enableMV3TimestampSave": true,
"useExternalServices": "boolean",
+ "isMultiAccountBalancesEnabled": "boolean",
+ "showIncomingTransactions": "object",
"metaMetricsId": "fake-metrics-id",
"marketingCampaignCookieId": null,
"eventsBeforeMetricsOptIn": "object",
@@ -245,11 +245,11 @@
"accounts": "object",
"accountsByChainId": "object",
"marketData": "object",
- "signatureRequests": "object",
"unapprovedDecryptMsgs": "object",
"unapprovedDecryptMsgCount": 0,
"unapprovedEncryptionPublicKeyMsgs": "object",
"unapprovedEncryptionPublicKeyMsgCount": 0,
+ "signatureRequests": "object",
"unapprovedPersonalMsgs": "object",
"unapprovedTypedMessages": "object",
"unapprovedPersonalMsgCount": 0,
@@ -289,17 +289,18 @@
"srcNetworkAllowlist": { "0": "string", "1": "string", "2": "string" },
"destNetworkAllowlist": { "0": "string", "1": "string", "2": "string" }
},
+ "srcTokens": {},
+ "srcTopAssets": {},
"destTokens": {},
"destTopAssets": {},
"quoteRequest": {
- "slippage": 0.5,
- "srcTokenAddress": "0x0000000000000000000000000000000000000000"
+ "srcTokenAddress": "0x0000000000000000000000000000000000000000",
+ "slippage": 0.5
},
"quotes": {},
- "quotesRefreshCount": 0,
- "srcTokens": {},
- "srcTopAssets": {}
+ "quotesRefreshCount": 0
},
+ "bridgeStatusState": { "txHistory": "object" },
"ensEntries": "object",
"ensResolutionsByAddress": "object",
"pendingApprovals": "object",
diff --git a/test/e2e/tests/network/multi-rpc.spec.ts b/test/e2e/tests/network/multi-rpc.spec.ts
index ac693361435d..f5c40259a33b 100644
--- a/test/e2e/tests/network/multi-rpc.spec.ts
+++ b/test/e2e/tests/network/multi-rpc.spec.ts
@@ -83,7 +83,7 @@ describe('MultiRpc:', function (this: Suite) {
await completeImportSRPOnboardingFlow({ driver });
const homePage = new HomePage(driver);
await homePage.check_pageIsLoaded();
- await homePage.check_ganacheBalanceIsDisplayed(ganacheServer);
+ await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer);
await new HeaderNavbar(driver).clickSwitchNetworkDropDown();
const selectNetworkDialog = new SelectNetwork(driver);
diff --git a/test/e2e/tests/network/network-error.spec.js b/test/e2e/tests/network/network-error.spec.js
index 4d45734edf77..61842f482151 100644
--- a/test/e2e/tests/network/network-error.spec.js
+++ b/test/e2e/tests/network/network-error.spec.js
@@ -4,6 +4,7 @@ const {
logInWithBalanceValidation,
openActionMenuAndStartSendFlow,
generateGanacheOptions,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
const { GAS_API_BASE_URL } = require('../../../../shared/constants/swaps');
@@ -58,6 +59,8 @@ describe('Gas API fallback', function () {
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await openActionMenuAndStartSendFlow(driver);
await driver.fill(
'input[placeholder="Enter public address (0x) or domain name"]',
diff --git a/test/e2e/tests/network/switch-network.spec.ts b/test/e2e/tests/network/switch-network.spec.ts
index a45e634dbbec..b320e095cc69 100644
--- a/test/e2e/tests/network/switch-network.spec.ts
+++ b/test/e2e/tests/network/switch-network.spec.ts
@@ -36,19 +36,19 @@ describe('Switch network - ', function (this: Suite) {
// Validate the switch network functionality to Ethereum Mainnet
await switchToNetworkFlow(driver, 'Ethereum Mainnet');
- await homePage.check_ganacheBalanceIsDisplayed(ganacheServer);
+ await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer);
// Validate the switch network functionality to test network
await switchToNetworkFlow(driver, 'Localhost 8545', true);
- await homePage.check_ganacheBalanceIsDisplayed(ganacheServer);
+ await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer);
// Add Arbitrum network and perform the switch network functionality
await searchAndSwitchToNetworkFlow(driver, 'Arbitrum One');
- await homePage.check_ganacheBalanceIsDisplayed(ganacheServer);
+ await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer);
// Validate the switch network functionality back to Ethereum Mainnet
await switchToNetworkFlow(driver, 'Ethereum Mainnet');
- await homePage.check_ganacheBalanceIsDisplayed(ganacheServer);
+ await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer);
},
);
});
diff --git a/test/e2e/tests/notifications/account-syncing/helpers.ts b/test/e2e/tests/notifications/account-syncing/helpers.ts
index 5e2694067eed..e54a5f6f96ae 100644
--- a/test/e2e/tests/notifications/account-syncing/helpers.ts
+++ b/test/e2e/tests/notifications/account-syncing/helpers.ts
@@ -1,18 +1,3 @@
import { isManifestV3 } from '../../../../../shared/modules/mv3.utils';
-import {
- completeSRPRevealQuiz,
- openSRPRevealQuiz,
- tapAndHoldToRevealSRP,
-} from '../../../helpers';
-import { Driver } from '../../../webdriver/driver';
export const IS_ACCOUNT_SYNCING_ENABLED = isManifestV3;
-
-export const getSRP = async (driver: Driver, password: string) => {
- await openSRPRevealQuiz(driver);
- await completeSRPRevealQuiz(driver);
- await driver.fill('[data-testid="input-password"]', password);
- await driver.press('[data-testid="input-password"]', driver.Key.ENTER);
- await tapAndHoldToRevealSRP(driver);
- return (await driver.findElement('[data-testid="srp_text"]')).getText();
-};
diff --git a/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts b/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts
index 8e2908682542..ae283c47fa8b 100644
--- a/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts
+++ b/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts
@@ -12,7 +12,9 @@ import {
completeCreateNewWalletOnboardingFlow,
completeImportSRPOnboardingFlow,
} from '../../../page-objects/flows/onboarding.flow';
-import { getSRP, IS_ACCOUNT_SYNCING_ENABLED } from './helpers';
+import PrivacySettings from '../../../page-objects/pages/settings/privacy-settings';
+import SettingsPage from '../../../page-objects/pages/settings/settings-page';
+import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers';
describe('Account syncing - New User @no-mmi', function () {
if (!IS_ACCOUNT_SYNCING_ENABLED) {
@@ -65,12 +67,24 @@ describe('Account syncing - New User @no-mmi', function () {
// Add a second account
await accountListPage.openAccountOptionsMenu();
- await accountListPage.addNewAccountWithCustomLabel(
- 'My Second Account',
- );
+ await accountListPage.addNewAccount('My Second Account');
// Set SRP to use for retreival
- walletSrp = await getSRP(driver, NOTIFICATIONS_TEAM_PASSWORD);
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.openSettingsPage();
+ const settingsPage = new SettingsPage(driver);
+ await settingsPage.check_pageIsLoaded();
+ await settingsPage.goToPrivacySettings();
+
+ const privacySettings = new PrivacySettings(driver);
+ await privacySettings.check_pageIsLoaded();
+ await privacySettings.openRevealSrpQuiz();
+ await privacySettings.completeRevealSrpQuiz();
+ await privacySettings.fillPasswordToRevealSrp(
+ NOTIFICATIONS_TEAM_PASSWORD,
+ );
+ walletSrp = await privacySettings.getSrpInRevealSrpDialog();
if (!walletSrp) {
throw new Error('Wallet SRP was not set');
}
diff --git a/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts b/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts
index 209d3a51fdaf..3ec44e4fd07e 100644
--- a/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts
+++ b/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts
@@ -18,7 +18,9 @@ import {
importSRPOnboardingFlow,
completeImportSRPOnboardingFlow,
} from '../../../page-objects/flows/onboarding.flow';
-import { getSRP, IS_ACCOUNT_SYNCING_ENABLED } from './helpers';
+import PrivacySettings from '../../../page-objects/pages/settings/privacy-settings';
+import SettingsPage from '../../../page-objects/pages/settings/settings-page';
+import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers';
import { accountsSyncMockResponse } from './mockData';
describe('Account syncing - Opt-out Profile Sync @no-mmi', function () {
@@ -139,9 +141,23 @@ describe('Account syncing - Opt-out Profile Sync @no-mmi', function () {
await accountListPage.check_accountDisplayedInAccountList(
'Account 1',
);
- await accountListPage.addNewAccountWithCustomLabel('New Account');
+ await accountListPage.addNewAccount('New Account');
// Set SRP to use for retreival
- walletSrp = await getSRP(driver, NOTIFICATIONS_TEAM_PASSWORD);
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.openSettingsPage();
+ const settingsPage = new SettingsPage(driver);
+ await settingsPage.check_pageIsLoaded();
+ await settingsPage.goToPrivacySettings();
+
+ const privacySettings = new PrivacySettings(driver);
+ await privacySettings.check_pageIsLoaded();
+ await privacySettings.openRevealSrpQuiz();
+ await privacySettings.completeRevealSrpQuiz();
+ await privacySettings.fillPasswordToRevealSrp(
+ NOTIFICATIONS_TEAM_PASSWORD,
+ );
+ walletSrp = await privacySettings.getSrpInRevealSrpDialog();
if (!walletSrp) {
throw new Error('Wallet SRP was not set');
}
diff --git a/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts b/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts
index 23a5d1eaf47b..1c1f7b3119d7 100644
--- a/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts
+++ b/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts
@@ -68,9 +68,7 @@ describe('Account syncing - Add Account @no-mmi', function () {
await accountListPage.check_accountDisplayedInAccountList(
'My Second Synced Account',
);
- await accountListPage.addNewAccountWithCustomLabel(
- 'My third account',
- );
+ await accountListPage.addNewAccount('My third account');
},
);
@@ -175,7 +173,7 @@ describe('Account syncing - Add Account @no-mmi', function () {
await accountListPage.check_accountDisplayedInAccountList(
'My Second Synced Account',
);
- await accountListPage.addNewAccountWithDefaultName();
+ await accountListPage.addNewAccount();
},
);
diff --git a/test/e2e/tests/notifications/account-syncing/sync-with-account-balances.spec.ts b/test/e2e/tests/notifications/account-syncing/sync-with-account-balances.spec.ts
new file mode 100644
index 000000000000..8ecaff943721
--- /dev/null
+++ b/test/e2e/tests/notifications/account-syncing/sync-with-account-balances.spec.ts
@@ -0,0 +1,225 @@
+import { Mockttp } from 'mockttp';
+import { unlockWallet, withFixtures } from '../../../helpers';
+import FixtureBuilder from '../../../fixture-builder';
+import { mockInfuraAndAccountSync } from '../mocks';
+import {
+ NOTIFICATIONS_TEAM_PASSWORD,
+ NOTIFICATIONS_TEAM_SEED_PHRASE,
+} from '../constants';
+import { UserStorageMockttpController } from '../../../helpers/user-storage/userStorageMockttpController';
+import HeaderNavbar from '../../../page-objects/pages/header-navbar';
+import AccountListPage from '../../../page-objects/pages/account-list-page';
+import HomePage from '../../../page-objects/pages/homepage';
+import { completeImportSRPOnboardingFlow } from '../../../page-objects/flows/onboarding.flow';
+import { accountsSyncMockResponse } from './mockData';
+import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers';
+
+const INITIAL_ACCOUNTS = [
+ '0xaa4179e7f103701e904d27df223a39aa9c27405a',
+ '0xd2a4afe5c2ff0a16bf81f77ba4201a8107aa874b',
+ '0xd54ba25a07eb3da821face8478c3d965ded63018',
+ '0x2c30c098e2a560988d486c7f25798e790802f953',
+];
+
+const ADDITIONAL_ACCOUNTS = [
+ '0x6b65DA6735119E72B72fF842Bd92e9DE0C1e4Ae0',
+ '0x0f205850eaC507473AA0e47cc8eB528D875E7498',
+];
+
+const EXPECTED_ACCOUNT_NAMES = {
+ INITIAL: [
+ 'My First Synced Account',
+ 'My Second Synced Account',
+ 'Account 3',
+ 'Account 4',
+ ],
+ WITH_NEW_ACCOUNTS: [
+ 'My First Synced Account',
+ 'My Second Synced Account',
+ 'Account 3',
+ 'Account 4',
+ 'Account 5',
+ 'Account 6',
+ ],
+};
+
+describe('Account syncing - User already has balances on multple accounts @no-mmi', function () {
+ if (!IS_ACCOUNT_SYNCING_ENABLED) {
+ return;
+ }
+
+ describe('from inside MetaMask', function () {
+ /**
+ * This test verifies the complete account syncing flow in three phases:
+ * Phase 1: Initial setup, where we check that 4 accounts are shown due to balance detection even though the user storage only has 2 accounts.
+ * Phase 2: Discovery of 2 more accounts after adding balances. We still expect to only see 6 even though we had 5 accounts synced in the previous test
+ * Phase 3: Verification that any final changes to user storage are persisted and that we don't see any extra accounts created
+ */
+ it('when a user has balances on more accounts than previously synced, it should be handled gracefully', async function () {
+ const userStorageMockttpController = new UserStorageMockttpController();
+ let accountsToMock = [...INITIAL_ACCOUNTS];
+
+ // PHASE 1: Initial setup and account creation
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder({ onboarding: true })
+ .withNetworkControllerOnMainnet()
+ .build(),
+ title: this.test?.fullTitle(),
+ testSpecificMock: async (server: Mockttp) => {
+ await mockInfuraAndAccountSync(
+ server,
+ userStorageMockttpController,
+ {
+ accountsSyncResponse: accountsSyncMockResponse,
+ accountsToMock,
+ },
+ );
+ },
+ },
+ async ({ driver }) => {
+ // Complete initial setup with provided seed phrase
+ await completeImportSRPOnboardingFlow({
+ driver,
+ seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE,
+ password: NOTIFICATIONS_TEAM_PASSWORD,
+ });
+
+ // Verify initial state and balance
+ const homePage = new HomePage(driver);
+ await homePage.check_pageIsLoaded();
+ await homePage.check_expectedBalanceIsDisplayed('1');
+ await homePage.check_hasAccountSyncingSyncedAtLeastOnce();
+
+ // Open account menu and verify initial accounts
+ const header = new HeaderNavbar(driver);
+ await header.check_pageIsLoaded();
+ await header.openAccountMenu();
+
+ const accountListPage = new AccountListPage(driver);
+ await accountListPage.check_pageIsLoaded();
+ await accountListPage.check_numberOfAvailableAccounts(4);
+
+ // Verify each initial account name
+ for (const accountName of EXPECTED_ACCOUNT_NAMES.INITIAL) {
+ await accountListPage.check_accountDisplayedInAccountList(
+ accountName,
+ );
+ }
+
+ // Create new account and prepare for additional accounts
+ await accountListPage.addNewAccount();
+ accountsToMock = [...INITIAL_ACCOUNTS, ...ADDITIONAL_ACCOUNTS];
+ },
+ );
+
+ // PHASE 2: Verify discovery of new accounts with balances
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder({ onboarding: true })
+ .withNetworkControllerOnMainnet()
+ .build(),
+ title: this.test?.fullTitle(),
+ testSpecificMock: async (server: Mockttp) => {
+ await mockInfuraAndAccountSync(
+ server,
+ userStorageMockttpController,
+ { accountsToMock },
+ );
+ },
+ },
+ async ({ driver }) => {
+ // Complete setup again for new session
+ await completeImportSRPOnboardingFlow({
+ driver,
+ seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE,
+ password: NOTIFICATIONS_TEAM_PASSWORD,
+ });
+
+ const homePage = new HomePage(driver);
+ await homePage.check_pageIsLoaded();
+ await homePage.check_expectedBalanceIsDisplayed('1');
+ await homePage.check_hasAccountSyncingSyncedAtLeastOnce();
+
+ // Verify all accounts including newly discovered ones (which would have been synced / have balances)
+ const header = new HeaderNavbar(driver);
+ await header.check_pageIsLoaded();
+ await header.openAccountMenu();
+
+ const accountListPage = new AccountListPage(driver);
+ await accountListPage.check_pageIsLoaded();
+ await accountListPage.check_numberOfAvailableAccounts(6);
+
+ for (const accountName of EXPECTED_ACCOUNT_NAMES.WITH_NEW_ACCOUNTS) {
+ await accountListPage.check_accountDisplayedInAccountList(
+ accountName,
+ );
+ }
+
+ // Rename Account 6 to verify update to user storage
+ await accountListPage.switchToAccount('Account 6');
+ await header.openAccountMenu();
+ await accountListPage.openAccountDetailsModal('Account 6');
+ await accountListPage.changeLabelFromAccountDetailsModal(
+ 'My Renamed Account 6',
+ );
+ },
+ );
+
+ // PHASE 3: Verify name persistence across sessions
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder({ onboarding: true })
+ .withNetworkControllerOnMainnet()
+ .build(),
+ title: this.test?.fullTitle(),
+ testSpecificMock: async (server: Mockttp) => {
+ await mockInfuraAndAccountSync(
+ server,
+ userStorageMockttpController,
+ { accountsToMock },
+ );
+ },
+ },
+ async ({ driver }) => {
+ // Complete setup for final verification
+ await completeImportSRPOnboardingFlow({
+ driver,
+ seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE,
+ password: NOTIFICATIONS_TEAM_PASSWORD,
+ });
+
+ const homePage = new HomePage(driver);
+ await homePage.check_pageIsLoaded();
+ await homePage.check_expectedBalanceIsDisplayed('1');
+ await homePage.check_hasAccountSyncingSyncedAtLeastOnce();
+
+ // Verify renamed account persists
+ const header = new HeaderNavbar(driver);
+ await header.check_pageIsLoaded();
+ await header.openAccountMenu();
+
+ const accountListPage = new AccountListPage(driver);
+ await accountListPage.check_pageIsLoaded();
+ await accountListPage.check_numberOfAvailableAccounts(6);
+ await accountListPage.check_accountDisplayedInAccountList(
+ 'My Renamed Account 6',
+ );
+ await accountListPage.closeAccountModal();
+
+ // Lock and unlock wallet to ensure that number of preloaded accounts have not gone up
+ await homePage.headerNavbar.lockMetaMask();
+ await unlockWallet(driver, {
+ password: NOTIFICATIONS_TEAM_PASSWORD,
+ waitLoginSuccess: true,
+ navigate: true,
+ });
+
+ await header.check_pageIsLoaded();
+ await header.openAccountMenu();
+ await accountListPage.check_numberOfAvailableAccounts(6);
+ },
+ );
+ });
+ });
+});
diff --git a/test/e2e/tests/notifications/mocks.ts b/test/e2e/tests/notifications/mocks.ts
index 748084918272..b7069447fd45 100644
--- a/test/e2e/tests/notifications/mocks.ts
+++ b/test/e2e/tests/notifications/mocks.ts
@@ -6,6 +6,7 @@ import {
} from '@metamask/notification-services-controller';
import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk';
import { UserStorageMockttpController } from '../../helpers/user-storage/userStorageMockttpController';
+import { accountsSyncMockResponse } from './account-syncing/mockData';
const AuthMocks = AuthenticationController.Mocks;
const NotificationMocks = NotificationServicesController.Mocks;
@@ -105,3 +106,63 @@ function mockAPICall(server: Mockttp, response: MockResponse) {
json: response.response,
}));
}
+
+type MockInfuraAndAccountSyncOptions = {
+ accountsToMock?: string[];
+ accountsSyncResponse?: typeof accountsSyncMockResponse;
+};
+
+const MOCK_ETH_BALANCE = '0xde0b6b3a7640000';
+const INFURA_URL =
+ 'https://mainnet.infura.io/v3/00000000000000000000000000000000';
+
+/**
+ * Sets up mock responses for Infura balance checks and account syncing
+ *
+ * @param mockServer - The Mockttp server instance
+ * @param userStorageMockttpController - Controller for user storage mocks
+ * @param options - Configuration options for mocking
+ */
+export async function mockInfuraAndAccountSync(
+ mockServer: Mockttp,
+ userStorageMockttpController: UserStorageMockttpController,
+ options: MockInfuraAndAccountSyncOptions = {},
+): Promise {
+ const accounts = options.accountsToMock ?? [];
+
+ // Set up User Storage / Account Sync mock
+ userStorageMockttpController.setupPath(
+ USER_STORAGE_FEATURE_NAMES.accounts,
+ mockServer,
+ );
+
+ userStorageMockttpController.setupPath(
+ USER_STORAGE_FEATURE_NAMES.accounts,
+ mockServer,
+ {
+ getResponse: options.accountsSyncResponse ?? undefined,
+ },
+ );
+
+ // Account Balances
+ if (accounts.length > 0) {
+ accounts.forEach((account) => {
+ mockServer
+ .forPost(INFURA_URL)
+ .withJsonBodyIncluding({
+ method: 'eth_getBalance',
+ params: [account.toLowerCase()],
+ })
+ .thenCallback(() => ({
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: '1111111111111111',
+ result: MOCK_ETH_BALANCE,
+ },
+ }));
+ });
+ }
+
+ mockNotificationServices(mockServer, userStorageMockttpController);
+}
diff --git a/test/e2e/tests/onboarding/onboarding.spec.ts b/test/e2e/tests/onboarding/onboarding.spec.ts
index 9409ef7e351c..979e416f5090 100644
--- a/test/e2e/tests/onboarding/onboarding.spec.ts
+++ b/test/e2e/tests/onboarding/onboarding.spec.ts
@@ -198,7 +198,7 @@ describe('MetaMask onboarding @no-mmi', function () {
// Check the correct balance for the custom network is displayed
if (secondaryGanacheServer && Array.isArray(secondaryGanacheServer)) {
- await homePage.check_ganacheBalanceIsDisplayed(
+ await homePage.check_localBlockchainBalanceIsDisplayed(
secondaryGanacheServer[0],
);
} else {
diff --git a/test/e2e/tests/petnames/petnames-transactions.spec.js b/test/e2e/tests/petnames/petnames-transactions.spec.js
index cc19e44a55eb..55c295c51c3a 100644
--- a/test/e2e/tests/petnames/petnames-transactions.spec.js
+++ b/test/e2e/tests/petnames/petnames-transactions.spec.js
@@ -5,6 +5,7 @@ const {
unlockWallet,
defaultGanacheOptions,
openActionMenuAndStartSendFlow,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
const {
@@ -49,6 +50,9 @@ describe('Petnames - Transactions', function () {
},
async ({ driver }) => {
await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await openDapp(driver);
await createDappSendTransaction(driver);
await switchToNotificationWindow(driver, 3);
@@ -94,6 +98,8 @@ describe('Petnames - Transactions', function () {
},
async ({ driver }) => {
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await createWalletSendTransaction(driver, ADDRESS_MOCK);
await expectName(driver, ABBREVIATED_ADDRESS_MOCK, false);
diff --git a/test/e2e/tests/ppom/mocks/mock-server-json-rpc.ts b/test/e2e/tests/ppom/mocks/mock-server-json-rpc.ts
index ce197e462c80..550dc5f2acd9 100644
--- a/test/e2e/tests/ppom/mocks/mock-server-json-rpc.ts
+++ b/test/e2e/tests/ppom/mocks/mock-server-json-rpc.ts
@@ -14,6 +14,10 @@ type RequestConfig = [
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result?: any;
+ /** optional result value returned in JSON response */
+ // TODO: Replace `any` with type
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ error?: any;
},
];
@@ -48,14 +52,21 @@ export async function mockServerJsonRpc(
listOfRequestConfigs: RequestConfig[],
) {
for (const [method, options] of listOfRequestConfigs) {
- const { methodResultVariant, params, result: _result } = options || {};
+ const {
+ methodResultVariant,
+ params,
+ result: _result,
+ error: _error,
+ } = options || {};
const result =
_result ||
+ _error ||
mockJsonRpcResult[method][methodResultVariant || DEFAULT_VARIANT];
await mockServer
- .forPost()
+ .forPost(/infura/u)
+ .always()
.withJsonBodyIncluding(params ? { method, params } : { method })
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-contract-interaction.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-contract-interaction.spec.js
new file mode 100644
index 000000000000..e35c23f22c49
--- /dev/null
+++ b/test/e2e/tests/ppom/ppom-blockaid-alert-contract-interaction.spec.js
@@ -0,0 +1,238 @@
+const FixtureBuilder = require('../../fixture-builder');
+
+const {
+ WINDOW_TITLES,
+ defaultGanacheOptions,
+ openDapp,
+ unlockWallet,
+ withFixtures,
+} = require('../../helpers');
+const { mockServerJsonRpc } = require('./mocks/mock-server-json-rpc');
+
+async function mockInfura(mockServer) {
+ await mockServerJsonRpc(mockServer, [
+ ['eth_blockNumber'],
+ ['eth_estimateGas'],
+ [
+ 'eth_call',
+ {
+ params: [
+ {
+ to: '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39',
+ data: '0xf0002ea90000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000005cfe73b6021e818b776b421b1c4db2474086a7e100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000',
+ },
+ ],
+ result:
+ '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000006c8aafe1077a8',
+ },
+ ],
+ [
+ 'eth_call',
+ {
+ params: [
+ {
+ to: '0x00008f1149168c1d2fa1eba1ad3e9cd644510000',
+ data: '0x01ffc9a780ac58cd00000000000000000000000000000000000000000000000000000000',
+ },
+ ],
+ error: {
+ code: -32000,
+ message: 'execution reverted',
+ },
+ },
+ ],
+ [
+ 'eth_call',
+ {
+ params: [
+ {
+ to: '0x00008f1149168c1d2fa1eba1ad3e9cd644510000',
+ data: '0x01ffc9a7d9b67a2600000000000000000000000000000000000000000000000000000000',
+ },
+ ],
+ error: {
+ code: -32000,
+ message: 'execution reverted',
+ },
+ },
+ ],
+ [
+ 'eth_call',
+ {
+ params: [
+ {
+ to: '0x00008f1149168c1d2fa1eba1ad3e9cd644510000',
+ data: '0x95d89b41',
+ },
+ ],
+ error: {
+ code: -32000,
+ message: 'execution reverted',
+ },
+ },
+ ],
+ [
+ 'eth_call',
+ {
+ params: [
+ {
+ to: '0x00008f1149168c1d2fa1eba1ad3e9cd644510000',
+ data: '0x313ce567',
+ },
+ ],
+ error: {
+ code: -32000,
+ message: 'execution reverted',
+ },
+ },
+ ],
+ [
+ 'eth_getStorageAt',
+ {
+ params: [
+ '0x00008f1149168c1d2fa1eba1ad3e9cd644510000',
+ '0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3',
+ ],
+ result:
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
+ },
+ ],
+ [
+ 'eth_getStorageAt',
+ {
+ params: [
+ '0x00008f1149168c1d2fa1eba1ad3e9cd644510000',
+ '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc',
+ ],
+ result:
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
+ },
+ ],
+ ['eth_getBlockByNumber'],
+ ['eth_getBalance'],
+ [
+ 'eth_getCode',
+ {
+ params: ['0x00008f1149168c1d2fa1eba1ad3e9cd644510000'],
+ result:
+ '0x6080604052600436106100545760003560e01c8062f714ce1461005957806312065fe0146100825780633158952e146100ad578063715018a6146100b75780638da5cb5b146100ce578063f2fde38b146100f9575b600080fd5b34801561006557600080fd5b50610080600480360381019061007b91906104d4565b610122565b005b34801561008e57600080fd5b50610097610227565b6040516100a49190610523565b60405180910390f35b6100b561022f565b005b3480156100c357600080fd5b506100cc610231565b005b3480156100da57600080fd5b506100e3610245565b6040516100f0919061054d565b60405180910390f35b34801561010557600080fd5b50610120600480360381019061011b9190610568565b61026e565b005b61012a6102f1565b4782111561016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161016490610618565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036101dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101d3906106aa565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050158015610222573d6000803e3d6000fd5b505050565b600047905090565b565b6102396102f1565b610243600061036f565b565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6102766102f1565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036102e5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102dc9061073c565b60405180910390fd5b6102ee8161036f565b50565b6102f9610433565b73ffffffffffffffffffffffffffffffffffffffff16610317610245565b73ffffffffffffffffffffffffffffffffffffffff161461036d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610364906107a8565b60405180910390fd5b565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b600033905090565b600080fd5b6000819050919050565b61045381610440565b811461045e57600080fd5b50565b6000813590506104708161044a565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104a182610476565b9050919050565b6104b181610496565b81146104bc57600080fd5b50565b6000813590506104ce816104a8565b92915050565b600080604083850312156104eb576104ea61043b565b5b60006104f985828601610461565b925050602061050a858286016104bf565b9150509250929050565b61051d81610440565b82525050565b60006020820190506105386000830184610514565b92915050565b61054781610496565b82525050565b6000602082019050610562600083018461053e565b92915050565b60006020828403121561057e5761057d61043b565b5b600061058c848285016104bf565b91505092915050565b600082825260208201905092915050565b7f52657175657374656420616d6f756e7420657863656564732074686520636f6e60008201527f74726163742062616c616e63652e000000000000000000000000000000000000602082015250565b6000610602602e83610595565b915061060d826105a6565b604082019050919050565b60006020820190508181036000830152610631816105f5565b9050919050565b7f526563697069656e7420616464726573732063616e6e6f74206265207468652060008201527f7a65726f20616464726573732e00000000000000000000000000000000000000602082015250565b6000610694602d83610595565b915061069f82610638565b604082019050919050565b600060208201905081810360008301526106c381610687565b9050919050565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b6000610726602683610595565b9150610731826106ca565b604082019050919050565b6000602082019050818103600083015261075581610719565b9050919050565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b6000610792602083610595565b915061079d8261075c565b602082019050919050565b600060208201905081810360008301526107c181610785565b905091905056fea2646970667358221220ac74f30418aa2326105b7dea03d605de28d6069773bd4b434837ceb2008a023a64736f6c63430008130033',
+ },
+ ],
+ [
+ 'eth_getTransactionCount',
+ {
+ params: ['0x5cfe73b6021e818b776b421b1c4db2474086a7e1'],
+ result: '0x0',
+ },
+ ],
+ ]);
+
+ await mockServer
+ .forPost(/infura/u)
+ .withJsonBodyIncluding({
+ method: 'debug_traceCall',
+ params: [
+ {
+ accessList: [],
+ data: '0xef5cfb8c0000000000000000000000000b3e87a076ac4b0d1975f0f232444af6deb96c59',
+ from: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1',
+ gas: '0x1c9c380',
+ maxFeePerGas: '0x1fc3f678c',
+ maxPriorityFeePerGas: '0x0',
+ to: '0x00008f1149168c1d2fa1eba1ad3e9cd644510000',
+ type: '0x02',
+ },
+ ],
+ })
+ .thenCallback(async (req) => {
+ return {
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: (await req.body.getJson()).id,
+ error: {
+ code: -32601,
+ message:
+ 'The method debug_traceCall does not exist/is not available',
+ },
+ },
+ };
+ });
+
+ await mockServer
+ .forGet('https://www.4byte.directory/api/v1/signatures/')
+ .always()
+ .withQuery({ hex_signature: '0xef5cfb8c' })
+ .thenCallback(() => ({
+ statusCode: 200,
+ json: {
+ count: 1,
+ next: null,
+ previous: null,
+ results: [
+ {
+ id: 187294,
+ created_at: '2021-05-12T10:20:16.502438Z',
+ text_signature: 'claimRewards(address)',
+ hex_signature: '0xef5cfb8c',
+ bytes_signature: 'ï\\û',
+ },
+ ],
+ },
+ }));
+}
+
+describe('PPOM Blockaid Alert - Malicious Contract interaction @no-mmi', function () {
+ it('should show banner alert', async function () {
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerOnMainnet()
+ .withPermissionControllerConnectedToTestDapp({
+ useLocalhostHostname: true,
+ })
+ .withPreferencesController({
+ securityAlertsEnabled: true,
+ preferences: {
+ redesignedTransactionsEnabled: true,
+ redesignedConfirmationsEnabled: true,
+ isRedesignedConfirmationsDeveloperEnabled: true,
+ },
+ })
+ .build(),
+ defaultGanacheOptions,
+ testSpecificMock: mockInfura,
+ title: this.test.fullTitle(),
+ },
+
+ async ({ driver }) => {
+ await unlockWallet(driver);
+ await openDapp(driver, null, 'http://localhost:8080');
+
+ const expectedTitle = 'This is a deceptive request';
+ const expectedDescription =
+ 'If you approve this request, a third party known for scams will take all your assets.';
+
+ // Click TestDapp button to send JSON-RPC request
+ await driver.clickElement('#maliciousContractInteractionButton');
+
+ // Wait for confirmation pop-up
+ await driver.waitUntilXWindowHandles(3);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.assertElementNotPresent('.loading-indicator');
+
+ await driver.findElement({
+ css: '[data-testid="confirm-banner-alert"]',
+ text: expectedTitle,
+ });
+
+ await driver.findElement({
+ css: '[data-testid="confirm-banner-alert"]',
+ text: expectedDescription,
+ });
+ },
+ );
+ });
+});
diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js
index 9e904af6513e..5368c2617f13 100644
--- a/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js
+++ b/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js
@@ -1,12 +1,12 @@
const { strict: assert } = require('assert');
const FixtureBuilder = require('../../fixture-builder');
-
const {
defaultGanacheOptions,
withFixtures,
sendScreenToConfirmScreen,
logInWithBalanceValidation,
WINDOW_TITLES,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const {
mockMultiNetworkBalancePolling,
@@ -209,6 +209,8 @@ describe('Simple Send Security Alert - Blockaid @no-mmi', function () {
async ({ driver }) => {
await logInWithBalanceValidation(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await sendScreenToConfirmScreen(
driver,
'0xB8c77482e45F1F44dE1745F52C74426C631bDD52',
diff --git a/test/e2e/tests/privacy/polling.spec.ts b/test/e2e/tests/privacy/polling.spec.ts
new file mode 100644
index 000000000000..b63e1bce6191
--- /dev/null
+++ b/test/e2e/tests/privacy/polling.spec.ts
@@ -0,0 +1,383 @@
+import { strict as assert } from 'assert';
+import { JsonRpcRequest } from '@metamask/utils';
+import { MockedEndpoint } from 'mockttp';
+import { expect } from '@playwright/test';
+import FixtureBuilder from '../../fixture-builder';
+import { defaultGanacheOptions, withFixtures } from '../../helpers';
+import { Mockttp } from '../../mock-e2e';
+import HomePage from '../../page-objects/pages/homepage';
+import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow';
+
+const infuraMainnetUrl =
+ 'https://mainnet.infura.io/v3/00000000000000000000000000000000';
+const infuraSepoliaUrl =
+ 'https://sepolia.infura.io/v3/00000000000000000000000000000000';
+const infuraLineaMainnetUrl =
+ 'https://linea-mainnet.infura.io/v3/00000000000000000000000000000000';
+const infuraLineaSepoliaUrl =
+ 'https://linea-sepolia.infura.io/v3/00000000000000000000000000000000';
+
+const ethGetBlockByNumberResult = {
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: 367912711400466,
+ result: {
+ hash: '0x8f1697a1dfd439404fccc9ea370ab8ca4e1bb3465a6b74e5bf59891b909c5b86',
+ parentHash:
+ '0xc745f42de8dcb553511e5953b00220d2872c889261f606bbc6940600da3e24ad',
+ sha3Uncles:
+ '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
+ miner: '0x0000000000000000000000000000000000000000',
+ stateRoot:
+ '0x3e6f4a18a3d430fcb3748c89a32c98b7822c26ece58a28010c502af0247a5a05',
+ transactionsRoot:
+ '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421',
+ receiptsRoot:
+ '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421',
+ logsBloom:
+ '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+ difficulty: '0x1',
+ number: '0xd',
+ gasLimit: '0x1c9c380',
+ gasUsed: '0x0',
+ timestamp: '0x67409c7e',
+ extraData: '0x',
+ mixHash:
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
+ nonce: '0x0000000000000000',
+ totalDifficulty: '0xe',
+ size: '0x1fd',
+ transactions: [],
+ uncles: [],
+ },
+ },
+};
+
+async function mockInfura(mockServer: Mockttp): Promise {
+ const blockNumber = { value: 0 };
+ return [
+ // Mocks for mainnet
+ await mockServer
+ .forPost(infuraMainnetUrl)
+ .withJsonBodyIncluding({ method: 'net_version' })
+ .thenCallback(() => ({
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: '6327576363628226',
+ result: '0x1',
+ },
+ })),
+ await mockServer
+ .forPost(infuraMainnetUrl)
+ .withBodyIncluding('eth_blockNumber')
+ .thenCallback(() => {
+ blockNumber.value += 1;
+ return {
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: 8723760595506777,
+ result: blockNumber.value.toString(16),
+ },
+ };
+ }),
+ await mockServer
+ .forPost(infuraMainnetUrl)
+ .withBodyIncluding('eth_getBlockByNumber')
+ .thenCallback(() => {
+ return ethGetBlockByNumberResult;
+ }),
+ await mockServer
+ .forPost(infuraMainnetUrl)
+ .withBodyIncluding('eth_call')
+ .thenCallback(() => {
+ return {
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: '3aca99b4-92a1-4ad2-be3a-ae9fdd76fdaa',
+ result:
+ '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000004e2adedda15fd6',
+ },
+ };
+ }),
+ // Mocks for linea mainnet
+ await mockServer
+ .forPost(infuraLineaMainnetUrl)
+ .withJsonBodyIncluding({ method: 'net_version' })
+ .thenCallback(() => ({
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: '6327576363628226',
+ result: '0x1',
+ },
+ })),
+ await mockServer
+ .forPost(infuraLineaMainnetUrl)
+ .withBodyIncluding('eth_blockNumber')
+ .thenCallback(() => {
+ return {
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: 8794509830454968,
+ result: blockNumber.value.toString(16),
+ },
+ };
+ }),
+ await mockServer
+ .forPost(infuraLineaMainnetUrl)
+ .withBodyIncluding('eth_getBlockByNumber')
+ .thenCallback(() => {
+ return ethGetBlockByNumberResult;
+ }),
+ await mockServer
+ .forPost(infuraLineaMainnetUrl)
+ .withBodyIncluding('eth_call')
+ .thenCallback(() => {
+ return {
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: '3aca99b4-92a1-4ad2-be3a-ae9fdd76fdaa',
+ result:
+ '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000004e2adedda15fd6',
+ },
+ };
+ }),
+ // Mocks for Sepolia
+ await mockServer
+ .forPost(infuraSepoliaUrl)
+ .withJsonBodyIncluding({ method: 'net_version' })
+ .thenCallback(() => ({
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: '6327576363628226',
+ result: '0x1',
+ },
+ })),
+ await mockServer
+ .forPost(infuraSepoliaUrl)
+ .withBodyIncluding('eth_blockNumber')
+ .thenCallback(() => {
+ return {
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: 8794509830454968,
+ result: blockNumber.value.toString(16),
+ },
+ };
+ }),
+ await mockServer
+ .forPost(infuraSepoliaUrl)
+ .withBodyIncluding('eth_getBlockByNumber')
+ .thenCallback(() => {
+ return ethGetBlockByNumberResult;
+ }),
+ await mockServer
+ .forPost(infuraSepoliaUrl)
+ .withJsonBodyIncluding({ method: 'eth_getBalance' })
+ .thenCallback(() => ({
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: '367912711400467',
+ result: '0x15af1d78b58c40000',
+ },
+ })),
+ // Mocks for Linea Sepolia
+ await mockServer
+ .forPost(infuraLineaSepoliaUrl)
+ .withJsonBodyIncluding({ method: 'net_version' })
+ .thenCallback(() => ({
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: '6327576363628226',
+ result: '0x1',
+ },
+ })),
+ await mockServer
+ .forPost(infuraLineaSepoliaUrl)
+ .withBodyIncluding('eth_blockNumber')
+ .thenCallback(() => {
+ return {
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: 8794509830454968,
+ result: blockNumber.value.toString(16),
+ },
+ };
+ }),
+ await mockServer
+ .forPost(infuraLineaSepoliaUrl)
+ .withBodyIncluding('eth_getBlockByNumber')
+ .thenCallback(() => {
+ return ethGetBlockByNumberResult;
+ }),
+ await mockServer
+ .forPost(infuraLineaSepoliaUrl)
+ .withJsonBodyIncluding({ method: 'eth_getBalance' })
+ .thenCallback(() => ({
+ statusCode: 200,
+ json: {
+ jsonrpc: '2.0',
+ id: '367912711400467',
+ result: '0x15af1d78b58c40000',
+ },
+ })),
+ ];
+}
+const DELAY_UNTIL_NEXT_POLL = 20000;
+async function getAllInfuraJsonRpcRequests(
+ mockedEndpoint: MockedEndpoint[],
+): Promise {
+ const allInfuraJsonRpcRequests: JsonRpcRequest[] = [];
+ let seenRequests;
+ let seenProviderRequests;
+
+ for (const m of mockedEndpoint) {
+ seenRequests = await m.getSeenRequests();
+ seenProviderRequests = seenRequests.filter((request) =>
+ request.url.match('infura'),
+ );
+
+ for (const r of seenProviderRequests) {
+ const json = (await r.body.getJson()) as JsonRpcRequest | undefined;
+ if (json !== undefined) {
+ allInfuraJsonRpcRequests.push(json);
+ }
+ }
+ }
+
+ return allInfuraJsonRpcRequests;
+}
+describe('Account Tracker API polling', function () {
+ it('should make the expected RPC calls to infura', async function () {
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerOnMainnet()
+ .withPreferencesControllerShowNativeTokenAsMainBalanceDisabled()
+ .build(),
+ ganacheOptions: defaultGanacheOptions,
+ title: this.test?.fullTitle(),
+ testSpecificMock: mockInfura,
+ },
+ async ({ driver, mockedEndpoint }) => {
+ await loginWithoutBalanceValidation(driver);
+ const homepage = new HomePage(driver);
+ await homepage.check_pageIsLoaded();
+ // Want to wait long enough to pull requests relevant to a single loop cycle
+ await driver.delay(DELAY_UNTIL_NEXT_POLL);
+ const infuraJsonRpcRequests = await getAllInfuraJsonRpcRequests(
+ mockedEndpoint,
+ );
+
+ // TODO: expecting the length of infuraJsonRpcRequests would be more accurate
+ if (process.env.PORTFOLIO_VIEW) {
+ const ethCallInfuraRequests = infuraJsonRpcRequests.filter(
+ (obj) =>
+ obj.method === 'eth_call' &&
+ (obj.params as unknown[])?.[1] === '3',
+ );
+
+ const ethGetBalanceInfuraRequests = infuraJsonRpcRequests.filter(
+ (obj) =>
+ obj.method === 'eth_getBalance' &&
+ (obj.params as unknown[])?.[1] === '3',
+ );
+
+ // We will call eth_getBalance for Sepolia and Linea Sepolia because multicall is not available for them
+ expect(ethGetBalanceInfuraRequests.length).toEqual(2);
+ // We will call eth_call for linea mainnet and mainnet
+ expect(ethCallInfuraRequests.length).toEqual(2);
+ } else {
+ expect(
+ infuraJsonRpcRequests.some(
+ (obj) => obj.method === 'eth_blockNumber',
+ ),
+ ).toBeTruthy();
+ expect(
+ infuraJsonRpcRequests.some(
+ (obj) => obj.method === 'eth_getBlockByNumber',
+ ),
+ ).toBeTruthy();
+ expect(
+ infuraJsonRpcRequests.some((obj) => obj.method === 'eth_call'),
+ ).toBeTruthy();
+ }
+ },
+ );
+ });
+});
+
+describe('Token Detection', function () {
+ async function mockAccountApiForPortfolioView(mockServer: Mockttp) {
+ return [
+ await mockServer
+ .forGet(
+ 'https://accounts.api.cx.metamask.io/v2/accounts/0x5cfe73b6021e818b776b421b1c4db2474086a7e1/balances',
+ )
+ .withQuery({
+ networks: '1,59144',
+ })
+ .thenCallback(() => ({
+ statusCode: 200,
+ json: {
+ count: 0,
+ balances: [
+ {
+ object: 'token',
+ address: '0x0000000000000000000000000000000000000000',
+ symbol: 'ETH',
+ name: 'Ether',
+ type: 'native',
+ timestamp: '2015-07-30T03:26:13.000Z',
+ decimals: 18,
+ chainId: 1,
+ balance: '20',
+ },
+ ],
+ unprocessedNetworks: [],
+ },
+ })),
+ ];
+ }
+ it('should make calls to account api as expected', async function () {
+ if (process.env.PORTFOLIO_VIEW) {
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerOnMainnet()
+ .withPreferencesControllerShowNativeTokenAsMainBalanceDisabled()
+ .build(),
+ ganacheOptions: defaultGanacheOptions,
+ title: this.test?.fullTitle(),
+ testSpecificMock: mockAccountApiForPortfolioView,
+ },
+ async ({ driver, mockedEndpoint: mockedEndpoints }) => {
+ await loginWithoutBalanceValidation(driver);
+ const homepage = new HomePage(driver);
+ await homepage.check_pageIsLoaded();
+ await driver.delay(DELAY_UNTIL_NEXT_POLL);
+
+ for (const single of mockedEndpoints) {
+ const requests = await single.getSeenRequests();
+ assert.equal(
+ requests.length,
+ 1,
+ `${single} should make requests after onboarding`,
+ );
+ }
+ },
+ );
+ }
+ });
+});
diff --git a/test/e2e/tests/request-queuing/batch-txs-per-dapp-diff-network.spec.js b/test/e2e/tests/request-queuing/batch-txs-per-dapp-diff-network.spec.js
index deb189404fa8..958c1351d8b3 100644
--- a/test/e2e/tests/request-queuing/batch-txs-per-dapp-diff-network.spec.js
+++ b/test/e2e/tests/request-queuing/batch-txs-per-dapp-diff-network.spec.js
@@ -9,129 +9,246 @@ const {
WINDOW_TITLES,
defaultGanacheOptions,
largeDelayMs,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
-const { PAGES } = require('../../webdriver/driver');
describe('Request Queuing for Multiple Dapps and Txs on different networks', function () {
- it('should batch confirmation txs for different dapps on different networks.', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .build(),
- dappOptions: { numberOfDapps: 2 },
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ describe('Old confirmation screens', function () {
+ it('should batch confirmation txs for different dapps on different networks.', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
},
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
+ async ({ driver }) => {
+ await unlockWallet(driver);
- // Navigate to extension home screen
- await driver.navigate(PAGES.HOME);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
- // Open Dapp One
- await openDapp(driver, undefined, DAPP_URL);
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
- // Connect to dapp 1
- await driver.clickElement({ text: 'Connect', tag: 'button' });
+ // Connect to dapp 1
+ await driver.clickElement({ text: 'Connect', tag: 'button' });
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
- // Network Selector
- await driver.clickElement('[data-testid="network-display"]');
+ // Network Selector
+ await driver.clickElement('[data-testid="network-display"]');
- // Switch to second network
- await driver.clickElement({
- text: 'Localhost 8546',
- css: 'p',
- });
+ // Switch to second network
+ await driver.clickElement({
+ text: 'Localhost 8546',
+ css: 'p',
+ });
- // Wait for the first dapp's connect confirmation to disappear
- await driver.waitUntilXWindowHandles(2);
+ // Wait for the first dapp's connect confirmation to disappear
+ await driver.waitUntilXWindowHandles(2);
- // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
- // Open Dapp Two
- await openDapp(driver, undefined, DAPP_ONE_URL);
+ // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
- // Connect to dapp 2
- await driver.clickElement({ text: 'Connect', tag: 'button' });
+ // Connect to dapp 2
+ await driver.clickElement({ text: 'Connect', tag: 'button' });
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
- // Dapp one send tx
- await driver.switchToWindowWithUrl(DAPP_URL);
- await driver.delay(largeDelayMs);
- await driver.clickElement('#sendButton');
- await driver.clickElement('#sendButton');
+ // Dapp one send tx
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.delay(largeDelayMs);
+ await driver.clickElement('#sendButton');
+ await driver.clickElement('#sendButton');
- await driver.delay(largeDelayMs);
+ await driver.delay(largeDelayMs);
- // Dapp two send tx
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
- await driver.delay(largeDelayMs);
- await driver.clickElement('#sendButton');
- await driver.clickElement('#sendButton');
+ // Dapp two send tx
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ await driver.delay(largeDelayMs);
+ await driver.clickElement('#sendButton');
+ await driver.clickElement('#sendButton');
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.waitForSelector(
- By.xpath("//div[normalize-space(.)='1 of 2']"),
- );
+ await driver.waitForSelector(
+ By.xpath("//div[normalize-space(.)='1 of 2']"),
+ );
- // Reject All Transactions
- await driver.clickElement('.page-container__footer-secondary a');
+ // Reject All Transactions
+ await driver.clickElement('.page-container__footer-secondary a');
- // TODO: Do we want to confirm here?
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Reject all',
- tag: 'button',
- });
+ // TODO: Do we want to confirm here?
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Reject all',
+ tag: 'button',
+ });
- // Wait for confirmation to close
- // TODO: find a better way to handle different dialog ids
- await driver.delay(2000);
+ // Wait for confirmation to close
+ // TODO: find a better way to handle different dialog ids
+ await driver.delay(2000);
- // Wait for new confirmations queued from second dapp to open
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ // Wait for new confirmations queued from second dapp to open
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.waitForSelector(
- By.xpath("//div[normalize-space(.)='1 of 2']"),
- );
+ await driver.waitForSelector(
+ By.xpath("//div[normalize-space(.)='1 of 2']"),
+ );
- // Check correct network on confirm tx.
- await driver.findElement({
- css: '[data-testid="network-display"]',
- text: 'Localhost 8546',
- });
- },
- );
+ // Check correct network on confirm tx.
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Localhost 8546',
+ });
+ },
+ );
+ });
+ });
+
+ describe('Redesigned confirmation screens', function () {
+ it('should batch confirmation txs for different dapps on different networks.', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
+ },
+
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
+
+ // Connect to dapp 1
+ await driver.clickElement({ text: 'Connect', tag: 'button' });
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Network Selector
+ await driver.clickElement('[data-testid="network-display"]');
+
+ // Switch to second network
+ await driver.clickElement({
+ text: 'Localhost 8546',
+ css: 'p',
+ });
+
+ // Wait for the first dapp's connect confirmation to disappear
+ await driver.waitUntilXWindowHandles(2);
+
+ // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ // Connect to dapp 2
+ await driver.clickElement({ text: 'Connect', tag: 'button' });
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // Dapp one send tx
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.delay(largeDelayMs);
+ await driver.clickElement('#sendButton');
+ await driver.clickElement('#sendButton');
+
+ await driver.delay(largeDelayMs);
+
+ // Dapp two send tx
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ await driver.delay(largeDelayMs);
+ await driver.clickElement('#sendButton');
+ await driver.clickElement('#sendButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.waitForSelector(
+ By.xpath("//p[normalize-space(.)='1 of 2']"),
+ );
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Reject all',
+ tag: 'button',
+ });
+
+ // Wait for confirmation to close
+ await driver.delay(2000);
+
+ // Wait for new confirmations queued from second dapp to open
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.waitForSelector(
+ By.xpath("//p[normalize-space(.)='1 of 2']"),
+ );
+
+ // Check correct network on confirm tx.
+ await driver.findElement({
+ css: 'p',
+ text: 'Localhost 8546',
+ });
+ },
+ );
+ });
});
});
diff --git a/test/e2e/tests/request-queuing/batch-txs-per-dapp-extra-tx.spec.js b/test/e2e/tests/request-queuing/batch-txs-per-dapp-extra-tx.spec.js
index 265b28d0f56d..066acacab23a 100644
--- a/test/e2e/tests/request-queuing/batch-txs-per-dapp-extra-tx.spec.js
+++ b/test/e2e/tests/request-queuing/batch-txs-per-dapp-extra-tx.spec.js
@@ -9,169 +9,326 @@ const {
unlockWallet,
WINDOW_TITLES,
withFixtures,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
-const { PAGES } = require('../../webdriver/driver');
describe('Request Queuing for Multiple Dapps and Txs on different networks', function () {
- it('should batch confirmation txs for different dapps on different networks adds extra tx after.', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .build(),
- dappOptions: { numberOfDapps: 2 },
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ describe('Old confirmation flows', function () {
+ it('should batch confirmation txs for different dapps on different networks adds extra tx after.', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
},
- title: this.test.fullTitle(),
- },
-
- async ({ driver }) => {
- await unlockWallet(driver);
-
- // Navigate to extension home screen
- await driver.navigate(PAGES.HOME);
-
- // Open Dapp One
- await openDapp(driver, undefined, DAPP_URL);
-
- // Connect to dapp 1
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
-
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.clickElement({
- text: 'Connect',
- tag: 'button',
- });
-
- await driver.switchToWindowWithUrl(DAPP_URL);
-
- const switchEthereumChainRequest = JSON.stringify({
- jsonrpc: '2.0',
- method: 'wallet_switchEthereumChain',
- params: [{ chainId: '0x53a' }],
- });
-
- // Ensure Dapp One is on Localhost 8546
- await driver.executeScript(
- `window.ethereum.request(${switchEthereumChainRequest})`,
- );
-
- // Should auto switch without prompt since already approved via connect
-
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
-
- // Wait for the first dapp's connect confirmation to disappear
- await driver.waitUntilXWindowHandles(2);
-
- // Open Dapp Two
- await openDapp(driver, undefined, DAPP_ONE_URL);
-
- // Connect to dapp 2
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
-
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.clickElement({
- text: 'Connect',
- tag: 'button',
- });
-
- // Dapp 1 send 2 tx
- await driver.switchToWindowWithUrl(DAPP_URL);
- await driver.findElement({
- css: '[id="chainId"]',
- text: '0x53a',
- });
- await driver.clickElement('#sendButton');
- await driver.clickElement('#sendButton');
-
- await driver.waitUntilXWindowHandles(4);
-
- // Dapp 2 send 2 tx
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
- await driver.findElement({
- css: '[id="chainId"]',
- text: '0x53a',
- });
- await driver.clickElement('#sendButton');
- await driver.clickElement('#sendButton');
- // We cannot wait for the dialog, since it is already opened from before
- await driver.delay(largeDelayMs);
-
- // Dapp 1 send 1 tx
- await driver.switchToWindowWithUrl(DAPP_URL);
- await driver.findElement({
- css: '[id="chainId"]',
- text: '0x53a',
- });
- await driver.clickElement('#sendButton');
- // We cannot switch directly, as the dialog is sometimes closed and re-opened
- await driver.delay(largeDelayMs);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.waitForSelector(
- By.xpath("//div[normalize-space(.)='1 of 2']"),
- );
-
- // Reject All Transactions
- await driver.clickElement('.page-container__footer-secondary a');
-
- // TODO: Do we want to confirm here?
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Reject all',
- tag: 'button',
- });
-
- await driver.switchToWindowWithUrl(DAPP_URL);
-
- // Wait for new confirmations queued from second dapp to open
- // We need a big delay to make sure dialog is not invalidated
- // TODO: find a better way to handle different dialog ids
- await driver.delay(2000);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.waitForSelector(
- By.xpath("//div[normalize-space(.)='1 of 2']"),
- );
-
- // Check correct network on confirm tx.
- await driver.findElement({
- css: '[data-testid="network-display"]',
- text: 'Localhost 8546',
- });
-
- // Reject All Transactions
- await driver.clickElement('.page-container__footer-secondary a');
-
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Reject all',
- tag: 'button',
- });
-
- // Wait for new confirmations queued from second dapp to open
- // We need a big delay to make sure dialog is not invalidated
- // TODO: find a better way to handle different dialog ids
- await driver.delay(2000);
- await driver.switchToWindowWithUrl(DAPP_URL);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- },
- );
+
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
+
+ // Connect to dapp 1
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ const switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x53a' }],
+ });
+
+ // Ensure Dapp One is on Localhost 8546
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+
+ // Should auto switch without prompt since already approved via connect
+
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Wait for the first dapp's connect confirmation to disappear
+ await driver.waitUntilXWindowHandles(2);
+
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ // Connect to dapp 2
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // Dapp 1 send 2 tx
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.findElement({
+ css: '[id="chainId"]',
+ text: '0x53a',
+ });
+ await driver.clickElement('#sendButton');
+ await driver.clickElement('#sendButton');
+
+ await driver.waitUntilXWindowHandles(4);
+
+ // Dapp 2 send 2 tx
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ await driver.findElement({
+ css: '[id="chainId"]',
+ text: '0x53a',
+ });
+ await driver.clickElement('#sendButton');
+ await driver.clickElement('#sendButton');
+ // We cannot wait for the dialog, since it is already opened from before
+ await driver.delay(largeDelayMs);
+
+ // Dapp 1 send 1 tx
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.findElement({
+ css: '[id="chainId"]',
+ text: '0x53a',
+ });
+ await driver.clickElement('#sendButton');
+ // We cannot switch directly, as the dialog is sometimes closed and re-opened
+ await driver.delay(largeDelayMs);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.waitForSelector(
+ By.xpath("//div[normalize-space(.)='1 of 2']"),
+ );
+
+ // Reject All Transactions
+ await driver.clickElement('.page-container__footer-secondary a');
+
+ // TODO: Do we want to confirm here?
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Reject all',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ // Wait for new confirmations queued from second dapp to open
+ // We need a big delay to make sure dialog is not invalidated
+ // TODO: find a better way to handle different dialog ids
+ await driver.delay(2000);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.waitForSelector(
+ By.xpath("//div[normalize-space(.)='1 of 2']"),
+ );
+
+ // Check correct network on confirm tx.
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Localhost 8546',
+ });
+
+ // Reject All Transactions
+ await driver.clickElement('.page-container__footer-secondary a');
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Reject all',
+ tag: 'button',
+ });
+
+ // Wait for new confirmations queued from second dapp to open
+ // We need a big delay to make sure dialog is not invalidated
+ // TODO: find a better way to handle different dialog ids
+ await driver.delay(2000);
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ },
+ );
+ });
+ });
+
+ describe('Redesigned confirmation flows', function () {
+ it('should batch confirmation txs for different dapps on different networks adds extra tx after.', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
+ },
+
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
+
+ // Connect to dapp 1
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ const switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x53a' }],
+ });
+
+ // Ensure Dapp One is on Localhost 8546
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+
+ // Should auto switch without prompt since already approved via connect
+
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Wait for the first dapp's connect confirmation to disappear
+ await driver.waitUntilXWindowHandles(2);
+
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ // Connect to dapp 2
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // Dapp 1 send 2 tx
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.findElement({
+ css: '[id="chainId"]',
+ text: '0x53a',
+ });
+ await driver.clickElement('#sendButton');
+ await driver.clickElement('#sendButton');
+
+ await driver.waitUntilXWindowHandles(4);
+
+ // Dapp 2 send 2 tx
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ await driver.findElement({
+ css: '[id="chainId"]',
+ text: '0x53a',
+ });
+ await driver.clickElement('#sendButton');
+ await driver.clickElement('#sendButton');
+ // We cannot wait for the dialog, since it is already opened from before
+ await driver.delay(largeDelayMs);
+
+ // Dapp 1 send 1 tx
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.findElement({
+ css: '[id="chainId"]',
+ text: '0x53a',
+ });
+ await driver.clickElement('#sendButton');
+ // We cannot switch directly, as the dialog is sometimes closed and re-opened
+ await driver.delay(largeDelayMs);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.waitForSelector(
+ By.xpath("//p[normalize-space(.)='1 of 2']"),
+ );
+
+ // Reject All Transactions
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Reject all',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ // Wait for new confirmations queued from second dapp to open
+ // We need a big delay to make sure dialog is not invalidated
+ // TODO: find a better way to handle different dialog ids
+ await driver.delay(2000);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.waitForSelector(
+ By.xpath("//p[normalize-space(.)='1 of 2']"),
+ );
+
+ // Check correct network on confirm tx.
+ await driver.findElement({
+ css: 'p',
+ text: 'Localhost 8546',
+ });
+
+ // Reject All Transactions
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Reject all',
+ tag: 'button',
+ });
+
+ // Wait for new confirmations queued from second dapp to open
+ // We need a big delay to make sure dialog is not invalidated
+ // TODO: find a better way to handle different dialog ids
+ await driver.delay(2000);
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ },
+ );
+ });
});
});
diff --git a/test/e2e/tests/request-queuing/batch-txs-per-dapp-same-network.spec.js b/test/e2e/tests/request-queuing/batch-txs-per-dapp-same-network.spec.js
index c30d6a73c063..d3241c95c9d5 100644
--- a/test/e2e/tests/request-queuing/batch-txs-per-dapp-same-network.spec.js
+++ b/test/e2e/tests/request-queuing/batch-txs-per-dapp-same-network.spec.js
@@ -10,163 +10,317 @@ const {
WINDOW_TITLES,
defaultGanacheOptions,
largeDelayMs,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
-const { PAGES } = require('../../webdriver/driver');
describe('Request Queuing for Multiple Dapps and Txs on same networks', function () {
- it('should batch confirmation txs for different dapps on same networks ', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerTripleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .build(),
- dappOptions: { numberOfDapps: 3 },
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- {
- port: 7777,
- chainId: 1000,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ describe('Old confirmation screens', function () {
+ it('should batch confirmation txs for different dapps on same networks ', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerTripleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ dappOptions: { numberOfDapps: 3 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ {
+ port: 7777,
+ chainId: 1000,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
},
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
+ async ({ driver }) => {
+ await unlockWallet(driver);
- // Navigate to extension home screen
- await driver.navigate(PAGES.HOME);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
- // Open Dapp One
- await openDapp(driver, undefined, DAPP_URL);
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
- // Connect to dapp 1
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
+ // Connect to dapp 1
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
- await driver.delay(regularDelayMs);
+ await driver.delay(regularDelayMs);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.clickElement({
- text: 'Connect',
- tag: 'button',
- });
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
- await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.switchToWindowWithUrl(DAPP_URL);
- let switchEthereumChainRequest = JSON.stringify({
- jsonrpc: '2.0',
- method: 'wallet_switchEthereumChain',
- params: [{ chainId: '0x3e8' }],
- });
+ let switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x3e8' }],
+ });
- // Ensure Dapp One is on Localhost 7777
- await driver.executeScript(
- `window.ethereum.request(${switchEthereumChainRequest})`,
- );
+ // Ensure Dapp One is on Localhost 7777
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
- // Should auto switch without prompt since already approved via connect
+ // Should auto switch without prompt since already approved via connect
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
- // Wait for the first dapp's connect confirmation to disappear
- await driver.waitUntilXWindowHandles(2);
+ // Wait for the first dapp's connect confirmation to disappear
+ await driver.waitUntilXWindowHandles(2);
- // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
- // Open Dapp Two
- await openDapp(driver, undefined, DAPP_ONE_URL);
+ // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
- // Connect to dapp 2
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
+ // Connect to dapp 2
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
- await driver.delay(regularDelayMs);
+ await driver.delay(regularDelayMs);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.clickElement({
- text: 'Connect',
- tag: 'button',
- });
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
- switchEthereumChainRequest = JSON.stringify({
- jsonrpc: '2.0',
- method: 'wallet_switchEthereumChain',
- params: [{ chainId: '0x53a' }],
- });
+ switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x53a' }],
+ });
- // Ensure Dapp Two is on Localhost 8545
- await driver.executeScript(
- `window.ethereum.request(${switchEthereumChainRequest})`,
- );
+ // Ensure Dapp Two is on Localhost 8545
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
- // Should auto switch without prompt since already approved via connect
+ // Should auto switch without prompt since already approved via connect
- // Dapp one send two tx
- await driver.switchToWindowWithUrl(DAPP_URL);
- await driver.delay(largeDelayMs);
- await driver.clickElement('#sendButton');
- await driver.clickElement('#sendButton');
+ // Dapp one send two tx
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.delay(largeDelayMs);
+ await driver.clickElement('#sendButton');
+ await driver.clickElement('#sendButton');
- await driver.delay(largeDelayMs);
+ await driver.delay(largeDelayMs);
- // Dapp two send two tx
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
- await driver.delay(largeDelayMs);
- await driver.clickElement('#sendButton');
- await driver.clickElement('#sendButton');
+ // Dapp two send two tx
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ await driver.delay(largeDelayMs);
+ await driver.clickElement('#sendButton');
+ await driver.clickElement('#sendButton');
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.waitForSelector(
- By.xpath("//div[normalize-space(.)='1 of 2']"),
- );
+ await driver.waitForSelector(
+ By.xpath("//div[normalize-space(.)='1 of 2']"),
+ );
- // Check correct network on confirm tx.
- await driver.findElement({
- css: '[data-testid="network-display"]',
- text: 'Localhost 7777',
- });
+ // Check correct network on confirm tx.
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Localhost 7777',
+ });
- // Reject All Transactions
- await driver.clickElement('.page-container__footer-secondary a');
+ // Reject All Transactions
+ await driver.clickElement('.page-container__footer-secondary a');
- await driver.clickElement({ text: 'Reject all', tag: 'button' }); // TODO: Do we want to confirm here?
+ await driver.clickElement({ text: 'Reject all', tag: 'button' }); // TODO: Do we want to confirm here?
- // Wait for confirmation to close
- await driver.waitUntilXWindowHandles(4);
+ // Wait for confirmation to close
+ await driver.waitUntilXWindowHandles(4);
- // Wait for new confirmations queued from second dapp to open
- await driver.delay(largeDelayMs);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ // Wait for new confirmations queued from second dapp to open
+ await driver.delay(largeDelayMs);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.waitForSelector(
- By.xpath("//div[normalize-space(.)='1 of 2']"),
- );
+ await driver.waitForSelector(
+ By.xpath("//div[normalize-space(.)='1 of 2']"),
+ );
- // Check correct network on confirm tx.
- await driver.findElement({
- css: '[data-testid="network-display"]',
- text: 'Localhost 8546',
- });
- },
- );
+ // Check correct network on confirm tx.
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Localhost 8546',
+ });
+ },
+ );
+ });
+ });
+
+ describe('Redesigned confirmation screens', function () {
+ it('should batch confirmation txs for different dapps on same networks ', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerTripleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ dappOptions: { numberOfDapps: 3 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ {
+ port: 7777,
+ chainId: 1000,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
+ },
+
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
+
+ // Connect to dapp 1
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.delay(regularDelayMs);
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ let switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x3e8' }],
+ });
+
+ // Ensure Dapp One is on Localhost 7777
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+
+ // Should auto switch without prompt since already approved via connect
+
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Wait for the first dapp's connect confirmation to disappear
+ await driver.waitUntilXWindowHandles(2);
+
+ // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ // Connect to dapp 2
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.delay(regularDelayMs);
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+
+ switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x53a' }],
+ });
+
+ // Ensure Dapp Two is on Localhost 8545
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+
+ // Should auto switch without prompt since already approved via connect
+
+ // Dapp one send two tx
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.delay(largeDelayMs);
+ await driver.clickElement('#sendButton');
+ await driver.clickElement('#sendButton');
+
+ await driver.delay(largeDelayMs);
+
+ // Dapp two send two tx
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ await driver.delay(largeDelayMs);
+ await driver.clickElement('#sendButton');
+ await driver.clickElement('#sendButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.waitForSelector(
+ By.xpath("//p[normalize-space(.)='1 of 2']"),
+ );
+
+ // Check correct network on confirm tx.
+ await driver.findElement({
+ css: 'p',
+ text: 'Localhost 7777',
+ });
+
+ // Reject All Transactions
+ await driver.clickElement({ text: 'Reject all', tag: 'button' });
+
+ // Wait for confirmation to close
+ await driver.waitUntilXWindowHandles(4);
+
+ // Wait for new confirmations queued from second dapp to open
+ await driver.delay(largeDelayMs);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.waitForSelector(
+ By.xpath("//p[normalize-space(.)='1 of 2']"),
+ );
+
+ // Check correct network on confirm tx.
+ await driver.findElement({
+ css: 'p',
+ text: 'Localhost 8546',
+ });
+ },
+ );
+ });
});
});
diff --git a/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js b/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js
index 5814d8a60a2b..28b19efbeb04 100644
--- a/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js
+++ b/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js
@@ -10,148 +10,300 @@ const {
tempToggleSettingRedesignedConfirmations,
WINDOW_TITLES,
largeDelayMs,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
+const { PAGES } = require('../../webdriver/driver');
describe('Request Queuing Dapp 1, Switch Tx -> Dapp 2 Send Tx', function () {
- it('should queue signTypedData tx after eth_sendTransaction confirmation and signTypedData confirmation should target the correct network after eth_sendTransaction is confirmed @no-mmi', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerTripleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .withSelectedNetworkControllerPerDomain()
- .build(),
- dappOptions: { numberOfDapps: 2 },
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- {
- port: 7777,
- chainId: 1000,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ describe('Old confirmation screens', function () {
+ it('should queue signTypedData tx after eth_sendTransaction confirmation and signTypedData confirmation should target the correct network after eth_sendTransaction is confirmed @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerTripleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .withSelectedNetworkControllerPerDomain()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ {
+ port: 7777,
+ chainId: 1000,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
},
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
- await tempToggleSettingRedesignedConfirmations(driver);
-
- // Open and connect Dapp One
- await openDapp(driver, undefined, DAPP_URL);
-
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
-
- await driver.delay(regularDelayMs);
-
- await driver.waitUntilXWindowHandles(3);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
- await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
- // Open and connect to Dapp Two
- await openDapp(driver, undefined, DAPP_ONE_URL);
-
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
-
- await driver.delay(regularDelayMs);
-
- await driver.waitUntilXWindowHandles(4);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.clickElement({
- text: 'Connect',
- tag: 'button',
- });
-
- // Switch Dapp Two to Localhost 8546
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
- let switchEthereumChainRequest = JSON.stringify({
- jsonrpc: '2.0',
- method: 'wallet_switchEthereumChain',
- params: [{ chainId: '0x53a' }],
- });
-
- // Initiate switchEthereumChain on Dapp one
- await driver.executeScript(
- `window.ethereum.request(${switchEthereumChainRequest})`,
- );
-
- await driver.waitForSelector({
- css: '[id="chainId"]',
- text: '0x53a',
- });
-
- // Should auto switch without prompt since already approved via connect
-
- // Switch back to Dapp One
- await driver.switchToWindowWithUrl(DAPP_URL);
-
- // switch chain for Dapp One
- switchEthereumChainRequest = JSON.stringify({
- jsonrpc: '2.0',
- method: 'wallet_switchEthereumChain',
- params: [{ chainId: '0x3e8' }],
- });
-
- // Initiate switchEthereumChain on Dapp one
- await driver.executeScript(
- `window.ethereum.request(${switchEthereumChainRequest})`,
- );
- await driver.waitForSelector({
- css: '[id="chainId"]',
- text: '0x3e8',
- });
- // Should auto switch without prompt since already approved via connect
-
- await driver.switchToWindowWithUrl(DAPP_URL);
-
- // eth_sendTransaction request
- await driver.clickElement('#sendButton');
- await driver.waitUntilXWindowHandles(3);
-
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
-
- // signTypedData request
- await driver.clickElement('#signTypedData');
-
- await driver.waitUntilXWindowHandles(4);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- // Check correct network on the send confirmation.
- await driver.waitForSelector({
- css: '[data-testid="network-display"]',
- text: 'Localhost 7777',
- });
-
- await driver.clickElement({ text: 'Confirm', tag: 'button' });
-
- await driver.delay(largeDelayMs);
- await driver.waitUntilXWindowHandles(4);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- // Check correct network on the signTypedData confirmation.
- await driver.waitForSelector({
- css: '[data-testid="signature-request-network-display"]',
- text: 'Localhost 8546',
- });
-
- await driver.clickElement({ text: 'Reject', tag: 'button' });
- },
- );
+ async ({ driver }) => {
+ await unlockWallet(driver);
+ await tempToggleSettingRedesignedConfirmations(driver);
+
+ // Navigate to extension home screen
+ await driver.navigate(PAGES.HOME);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ // Open and connect Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
+
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.delay(regularDelayMs);
+
+ await driver.waitUntilXWindowHandles(3);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
+ // Open and connect to Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.delay(regularDelayMs);
+
+ await driver.waitUntilXWindowHandles(4);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // Switch Dapp Two to Localhost 8546
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ let switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x53a' }],
+ });
+
+ // Initiate switchEthereumChain on Dapp one
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+
+ await driver.waitForSelector({
+ css: '[id="chainId"]',
+ text: '0x53a',
+ });
+
+ // Should auto switch without prompt since already approved via connect
+
+ // Switch back to Dapp One
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ // switch chain for Dapp One
+ switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x3e8' }],
+ });
+
+ // Initiate switchEthereumChain on Dapp one
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+ await driver.waitForSelector({
+ css: '[id="chainId"]',
+ text: '0x3e8',
+ });
+ // Should auto switch without prompt since already approved via connect
+
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ // eth_sendTransaction request
+ await driver.clickElement('#sendButton');
+ await driver.waitUntilXWindowHandles(3);
+
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+
+ // signTypedData request
+ await driver.clickElement('#signTypedData');
+
+ await driver.waitUntilXWindowHandles(4);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // Check correct network on the send confirmation.
+ await driver.waitForSelector({
+ css: '[data-testid="network-display"]',
+ text: 'Localhost 7777',
+ });
+
+ await driver.clickElement({ text: 'Confirm', tag: 'button' });
+
+ await driver.delay(largeDelayMs);
+ await driver.waitUntilXWindowHandles(4);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // Check correct network on the signTypedData confirmation.
+ await driver.waitForSelector({
+ css: '[data-testid="signature-request-network-display"]',
+ text: 'Localhost 8546',
+ });
+
+ await driver.clickElement({ text: 'Reject', tag: 'button' });
+ },
+ );
+ });
+ });
+
+ describe('Redesigned confirmation screens', function () {
+ it('should queue signTypedData tx after eth_sendTransaction confirmation and signTypedData confirmation should target the correct network after eth_sendTransaction is confirmed @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerTripleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .withSelectedNetworkControllerPerDomain()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ {
+ port: 7777,
+ chainId: 1000,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open and connect Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
+
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.delay(regularDelayMs);
+
+ await driver.waitUntilXWindowHandles(3);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
+ // Open and connect to Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.delay(regularDelayMs);
+
+ await driver.waitUntilXWindowHandles(4);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // Switch Dapp Two to Localhost 8546
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ let switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x53a' }],
+ });
+
+ // Initiate switchEthereumChain on Dapp one
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+
+ await driver.waitForSelector({
+ css: '[id="chainId"]',
+ text: '0x53a',
+ });
+
+ // Should auto switch without prompt since already approved via connect
+
+ // Switch back to Dapp One
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ // switch chain for Dapp One
+ switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x3e8' }],
+ });
+
+ // Initiate switchEthereumChain on Dapp one
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+ await driver.waitForSelector({
+ css: '[id="chainId"]',
+ text: '0x3e8',
+ });
+ // Should auto switch without prompt since already approved via connect
+
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ // eth_sendTransaction request
+ await driver.clickElement('#sendButton');
+ await driver.waitUntilXWindowHandles(3);
+
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+
+ // signTypedData request
+ await driver.clickElement('#signTypedData');
+
+ await driver.waitUntilXWindowHandles(4);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // Check correct network on the send confirmation.
+ await driver.waitForSelector({
+ css: 'p',
+ text: 'Localhost 7777',
+ });
+
+ await driver.clickElement({ text: 'Confirm', tag: 'button' });
+
+ await driver.delay(largeDelayMs);
+ await driver.waitUntilXWindowHandles(4);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // Check correct network on the signTypedData confirmation.
+ await driver.waitForSelector({
+ css: 'p',
+ text: 'Localhost 8546',
+ });
+
+ await driver.clickElement({ text: 'Cancel', tag: 'button' });
+ },
+ );
+ });
});
});
diff --git a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js
index 7a212533de4b..67efbfe6fee9 100644
--- a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js
+++ b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js
@@ -1,5 +1,4 @@
const { strict: assert } = require('assert');
-
const FixtureBuilder = require('../../fixture-builder');
const {
withFixtures,
@@ -10,149 +9,247 @@ const {
regularDelayMs,
WINDOW_TITLES,
defaultGanacheOptions,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
describe('Request Queuing Dapp 1 Send Tx -> Dapp 2 Request Accounts Tx', function () {
- it('should queue `eth_requestAccounts` requests when the requesting dapp does not already have connected accounts', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .withPermissionControllerConnectedToTestDapp()
- .build(),
- dappOptions: { numberOfDapps: 2 },
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ describe('Old confirmation screens', function () {
+ it('should queue `eth_requestAccounts` requests when the requesting dapp does not already have connected accounts', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .withPermissionControllerConnectedToTestDapp()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
},
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
-
- // Open Dapp One
- await openDapp(driver, undefined, DAPP_URL);
-
- // Dapp Send Button
- await driver.clickElement('#sendButton');
- await driver.delay(regularDelayMs);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.waitForSelector({
- text: 'Reject',
- tag: 'button',
- });
-
- await driver.delay(regularDelayMs);
-
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
-
- // Leave the confirmation pending
- await openDapp(driver, undefined, DAPP_ONE_URL);
-
- const accountsOnload = await (
- await driver.findElement('#accounts')
- ).getText();
- assert.deepStrictEqual(accountsOnload, '');
-
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
-
- await driver.delay(regularDelayMs);
-
- const accountsBeforeConnect = await (
- await driver.findElement('#accounts')
- ).getText();
- assert.deepStrictEqual(accountsBeforeConnect, '');
-
- // Reject the pending confirmation from the first dapp
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Reject',
- tag: 'button',
- });
-
- // Wait for switch confirmation to close then request accounts confirmation to show for the second dapp
- await driver.delay(regularDelayMs);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.clickElement({
- text: 'Connect',
- tag: 'button',
- });
-
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
-
- await driver.waitForSelector({
- text: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1',
- css: '#accounts',
- });
- },
- );
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
+
+ // Dapp Send Button
+ await driver.clickElement('#sendButton');
+ await driver.delay(regularDelayMs);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.waitForSelector({
+ text: 'Reject',
+ tag: 'button',
+ });
+
+ await driver.delay(regularDelayMs);
+
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Leave the confirmation pending
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ const accountsOnload = await (
+ await driver.findElement('#accounts')
+ ).getText();
+ assert.deepStrictEqual(accountsOnload, '');
+
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.delay(regularDelayMs);
+
+ const accountsBeforeConnect = await (
+ await driver.findElement('#accounts')
+ ).getText();
+ assert.deepStrictEqual(accountsBeforeConnect, '');
+
+ // Reject the pending confirmation from the first dapp
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Reject',
+ tag: 'button',
+ });
+
+ // Wait for switch confirmation to close then request accounts confirmation to show for the second dapp
+ await driver.delay(regularDelayMs);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+
+ await driver.waitForSelector({
+ text: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1',
+ css: '#accounts',
+ });
+ },
+ );
+ });
});
- it('should not queue the `eth_requestAccounts` requests when the requesting dapp already has connected accounts', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .withPermissionControllerConnectedToTwoTestDapps()
- .build(),
- dappOptions: { numberOfDapps: 2 },
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ describe('Redesigned confirmation screens', function () {
+ it('should queue `eth_requestAccounts` requests when the requesting dapp does not already have connected accounts', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .withPermissionControllerConnectedToTestDapp()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
},
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
- // Open Dapp One
- await openDapp(driver, undefined, DAPP_URL);
+ // Dapp Send Button
+ await driver.clickElement('#sendButton');
+ await driver.delay(regularDelayMs);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- // Dapp Send Button
- await driver.clickElement('#sendButton');
+ await driver.waitForSelector({
+ text: 'Cancel',
+ tag: 'button',
+ });
- // Leave the confirmation pending
+ await driver.delay(regularDelayMs);
- await openDapp(driver, undefined, DAPP_ONE_URL);
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
- const ethRequestAccounts = JSON.stringify({
- jsonrpc: '2.0',
- method: 'eth_requestAccounts',
- });
+ // Leave the confirmation pending
+ await openDapp(driver, undefined, DAPP_ONE_URL);
- const accounts = await driver.executeScript(
- `return window.ethereum.request(${ethRequestAccounts})`,
- );
+ const accountsOnload = await (
+ await driver.findElement('#accounts')
+ ).getText();
+ assert.deepStrictEqual(accountsOnload, '');
- assert.deepStrictEqual(accounts, [
- '0x5cfe73b6021e818b776b421b1c4db2474086a7e1',
- ]);
- },
- );
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.delay(regularDelayMs);
+
+ const accountsBeforeConnect = await (
+ await driver.findElement('#accounts')
+ ).getText();
+ assert.deepStrictEqual(accountsBeforeConnect, '');
+
+ // Reject the pending confirmation from the first dapp
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({
+ text: 'Cancel',
+ tag: 'button',
+ });
+
+ // Wait for switch confirmation to close then request accounts confirmation to show for the second dapp
+ await driver.delay(regularDelayMs);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+
+ await driver.waitForSelector({
+ text: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1',
+ css: '#accounts',
+ });
+ },
+ );
+ });
+
+ it('should not queue the `eth_requestAccounts` requests when the requesting dapp already has connected accounts', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .withPermissionControllerConnectedToTwoTestDapps()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
+
+ // Dapp Send Button
+ await driver.clickElement('#sendButton');
+
+ // Leave the confirmation pending
+
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ const ethRequestAccounts = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'eth_requestAccounts',
+ });
+
+ const accounts = await driver.executeScript(
+ `return window.ethereum.request(${ethRequestAccounts})`,
+ );
+
+ assert.deepStrictEqual(accounts, [
+ '0x5cfe73b6021e818b776b421b1c4db2474086a7e1',
+ ]);
+ },
+ );
+ });
});
});
diff --git a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js
index c98e0eb229c6..24c09ee18d09 100644
--- a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js
+++ b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js
@@ -7,304 +7,619 @@ const {
unlockWallet,
WINDOW_TITLES,
withFixtures,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
describe('Request Queuing Dapp 1, Switch Tx -> Dapp 2 Send Tx', function () {
- it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is confirmed', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerTripleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .withSelectedNetworkControllerPerDomain()
- .build(),
- dappOptions: { numberOfDapps: 2 },
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- {
- port: 7777,
- chainId: 1000,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ describe('Old confirmation screens', function () {
+ it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is confirmed', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerTripleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .withSelectedNetworkControllerPerDomain()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ {
+ port: 7777,
+ chainId: 1000,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
},
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
+ async ({ driver }) => {
+ await unlockWallet(driver);
- // Open Dapp One
- await openDapp(driver, undefined, DAPP_URL);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
- // Connect to dapp
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ // Connect to dapp
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
- const editButtons = await driver.findElements('[data-testid="edit"]');
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await editButtons[1].click();
+ const editButtons = await driver.findElements('[data-testid="edit"]');
- // Disconnect Localhost 8545
- await driver.clickElement({
- text: 'Localhost 8545',
- tag: 'p',
- });
+ await editButtons[1].click();
- await driver.clickElement('[data-testid="connect-more-chains-button"]');
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
+ // Disconnect Localhost 8545
+ await driver.clickElement({
+ text: 'Localhost 8545',
+ tag: 'p',
+ });
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
+ await driver.clickElement(
+ '[data-testid="connect-more-chains-button"]',
+ );
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Network Selector
+ await driver.clickElement('[data-testid="network-display"]');
+
+ // Switch to second network
+ await driver.clickElement({
+ text: 'Localhost 8546',
+ css: 'p',
+ });
+
+ // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ // Connect to dapp 2
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ // switchEthereumChain request
+ const switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x539' }],
+ });
+
+ // Initiate switchEthereumChain on Dapp One
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.findElement({
+ text: 'Use your enabled networks',
+ tag: 'p',
+ });
+
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+
+ await driver.clickElement('#sendButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({ text: 'Confirm', tag: 'button' });
+
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+
+ // Wait for switch confirmation to close then tx confirmation to show.
+ // There is an extra window appearing and disappearing
+ // so we leave this delay until the issue is fixed (#27360)
+ await driver.delay(5000);
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- // Network Selector
- await driver.clickElement('[data-testid="network-display"]');
+ // Check correct network on the send confirmation.
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Localhost 8546',
+ });
- // Switch to second network
- await driver.clickElement({
- text: 'Localhost 8546',
- css: 'p',
- });
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Confirm',
+ tag: 'button',
+ });
- // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
- // Open Dapp Two
- await openDapp(driver, undefined, DAPP_ONE_URL);
+ // Switch back to the extension
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
- // Connect to dapp 2
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
+ await driver.clickElement(
+ '[data-testid="account-overview__activity-tab"]',
+ );
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ // Check for transaction
+ await driver.wait(async () => {
+ const confirmedTxes = await driver.findElements(
+ '.transaction-list__completed-transactions .activity-list-item',
+ );
+ return confirmedTxes.length === 1;
+ }, 10000);
+ },
+ );
+ });
+
+ it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is cancelled.', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerTripleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .withSelectedNetworkControllerPerDomain()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ {
+ port: 7777,
+ chainId: 1000,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
- await driver.switchToWindowWithUrl(DAPP_URL);
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
- // switchEthereumChain request
- const switchEthereumChainRequest = JSON.stringify({
- jsonrpc: '2.0',
- method: 'wallet_switchEthereumChain',
- params: [{ chainId: '0x539' }],
- });
+ // Connect to dapp
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
- // Initiate switchEthereumChain on Dapp One
- await driver.executeScript(
- `window.ethereum.request(${switchEthereumChainRequest})`,
- );
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ const editButtons = await driver.findElements('[data-testid="edit"]');
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.findElement({
- text: 'Use your enabled networks',
- tag: 'p',
- });
+ await editButtons[1].click();
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ // Disconnect Localhost 8545
+ await driver.clickElement({
+ text: 'Localhost 8545',
+ tag: 'p',
+ });
- await driver.clickElement('#sendButton');
+ await driver.clickElement(
+ '[data-testid="connect-more-chains-button"]',
+ );
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
- await driver.clickElement({ text: 'Confirm', tag: 'button' });
+ // Network Selector
+ await driver.clickElement('[data-testid="network-display"]');
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ // Switch to second network
+ await driver.clickElement({
+ text: 'Localhost 8546',
+ css: 'p',
+ });
- // Wait for switch confirmation to close then tx confirmation to show.
- // There is an extra window appearing and disappearing
- // so we leave this delay until the issue is fixed (#27360)
- await driver.delay(5000);
+ // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ // Connect to dapp 2
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
- // Check correct network on the send confirmation.
- await driver.findElement({
- css: '[data-testid="network-display"]',
- text: 'Localhost 8546',
- });
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Confirm',
- tag: 'button',
- });
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
- // Switch back to the extension
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
+ await driver.switchToWindowWithUrl(DAPP_URL);
- await driver.clickElement(
- '[data-testid="account-overview__activity-tab"]',
- );
+ // switchEthereumChain request
+ const switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x539' }],
+ });
- // Check for transaction
- await driver.wait(async () => {
- const confirmedTxes = await driver.findElements(
- '.transaction-list__completed-transactions .activity-list-item',
+ // Initiate switchEthereumChain on Dapp One
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
);
- return confirmedTxes.length === 1;
- }, 10000);
- },
- );
+
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+
+ await driver.clickElement('#sendButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({ text: 'Cancel', tag: 'button' });
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+
+ // Wait for switch confirmation to close then tx confirmation to show.
+ // There is an extra window appearing and disappearing
+ // so we leave this delay until the issue is fixed (#27360)
+ await driver.delay(5000);
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // Check correct network on the send confirmation.
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Localhost 8546',
+ });
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Confirm',
+ tag: 'button',
+ });
+
+ // Switch back to the extension
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ await driver.clickElement(
+ '[data-testid="account-overview__activity-tab"]',
+ );
+
+ // Check for transaction
+ await driver.wait(async () => {
+ const confirmedTxes = await driver.findElements(
+ '.transaction-list__completed-transactions .activity-list-item',
+ );
+ return confirmedTxes.length === 1;
+ }, 10000);
+ },
+ );
+ });
});
- it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is cancelled.', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerTripleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .withSelectedNetworkControllerPerDomain()
- .build(),
- dappOptions: { numberOfDapps: 2 },
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- {
- port: 7777,
- chainId: 1000,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ describe('Redesigned confirmation screens', function () {
+ it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is confirmed', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerTripleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .withSelectedNetworkControllerPerDomain()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ {
+ port: 7777,
+ chainId: 1000,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
},
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
+ async ({ driver }) => {
+ await unlockWallet(driver);
- // Open Dapp One
- await openDapp(driver, undefined, DAPP_URL);
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
- // Connect to dapp
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
+ // Connect to dapp
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- const editButtons = await driver.findElements('[data-testid="edit"]');
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await editButtons[1].click();
+ const editButtons = await driver.findElements('[data-testid="edit"]');
- // Disconnect Localhost 8545
- await driver.clickElement({
- text: 'Localhost 8545',
- tag: 'p',
- });
+ await editButtons[1].click();
- await driver.clickElement('[data-testid="connect-more-chains-button"]');
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
+ // Disconnect Localhost 8545
+ await driver.clickElement({
+ text: 'Localhost 8545',
+ tag: 'p',
+ });
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
+ await driver.clickElement(
+ '[data-testid="connect-more-chains-button"]',
+ );
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
- // Network Selector
- await driver.clickElement('[data-testid="network-display"]');
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Network Selector
+ await driver.clickElement('[data-testid="network-display"]');
+
+ // Switch to second network
+ await driver.clickElement({
+ text: 'Localhost 8546',
+ css: 'p',
+ });
- // Switch to second network
- await driver.clickElement({
- text: 'Localhost 8546',
- css: 'p',
- });
+ // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
- // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
- // Open Dapp Two
- await openDapp(driver, undefined, DAPP_ONE_URL);
+ // Connect to dapp 2
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
- // Connect to dapp 2
- await driver.findClickableElement({ text: 'Connect', tag: 'button' });
- await driver.clickElement('#connectButton');
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
+ await driver.switchToWindowWithUrl(DAPP_URL);
- await driver.switchToWindowWithUrl(DAPP_URL);
+ // switchEthereumChain request
+ const switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x539' }],
+ });
- // switchEthereumChain request
- const switchEthereumChainRequest = JSON.stringify({
- jsonrpc: '2.0',
- method: 'wallet_switchEthereumChain',
- params: [{ chainId: '0x539' }],
- });
+ // Initiate switchEthereumChain on Dapp One
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
- // Initiate switchEthereumChain on Dapp One
- await driver.executeScript(
- `window.ethereum.request(${switchEthereumChainRequest})`,
- );
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.findElement({
+ text: 'Use your enabled networks',
+ tag: 'p',
+ });
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
- await driver.clickElement('#sendButton');
+ await driver.clickElement('#sendButton');
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.clickElement({ text: 'Cancel', tag: 'button' });
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ await driver.clickElement({ text: 'Confirm', tag: 'button' });
- // Wait for switch confirmation to close then tx confirmation to show.
- // There is an extra window appearing and disappearing
- // so we leave this delay until the issue is fixed (#27360)
- await driver.delay(5000);
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ // Wait for switch confirmation to close then tx confirmation to show.
+ // There is an extra window appearing and disappearing
+ // so we leave this delay until the issue is fixed (#27360)
+ await driver.delay(5000);
- // Check correct network on the send confirmation.
- await driver.findElement({
- css: '[data-testid="network-display"]',
- text: 'Localhost 8546',
- });
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Confirm',
- tag: 'button',
- });
+ // Check correct network on the send confirmation.
+ await driver.findElement({
+ css: 'p',
+ text: 'Localhost 8546',
+ });
- // Switch back to the extension
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Confirm',
+ tag: 'button',
+ });
- await driver.clickElement(
- '[data-testid="account-overview__activity-tab"]',
- );
+ // Switch back to the extension
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
- // Check for transaction
- await driver.wait(async () => {
- const confirmedTxes = await driver.findElements(
- '.transaction-list__completed-transactions .activity-list-item',
+ await driver.clickElement(
+ '[data-testid="account-overview__activity-tab"]',
);
- return confirmedTxes.length === 1;
- }, 10000);
- },
- );
+
+ // Check for transaction
+ await driver.wait(async () => {
+ const confirmedTxes = await driver.findElements(
+ '.transaction-list__completed-transactions .activity-list-item',
+ );
+ return confirmedTxes.length === 1;
+ }, 10000);
+ },
+ );
+ });
+
+ it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is cancelled.', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerTripleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .withSelectedNetworkControllerPerDomain()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ {
+ port: 7777,
+ chainId: 1000,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
+
+ // Connect to dapp
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ const editButtons = await driver.findElements('[data-testid="edit"]');
+
+ await editButtons[1].click();
+
+ // Disconnect Localhost 8545
+ await driver.clickElement({
+ text: 'Localhost 8545',
+ tag: 'p',
+ });
+
+ await driver.clickElement(
+ '[data-testid="connect-more-chains-button"]',
+ );
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Network Selector
+ await driver.clickElement('[data-testid="network-display"]');
+
+ // Switch to second network
+ await driver.clickElement({
+ text: 'Localhost 8546',
+ css: 'p',
+ });
+
+ // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ // Connect to dapp 2
+ await driver.findClickableElement({ text: 'Connect', tag: 'button' });
+ await driver.clickElement('#connectButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ // switchEthereumChain request
+ const switchEthereumChainRequest = JSON.stringify({
+ jsonrpc: '2.0',
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x539' }],
+ });
+
+ // Initiate switchEthereumChain on Dapp One
+ await driver.executeScript(
+ `window.ethereum.request(${switchEthereumChainRequest})`,
+ );
+
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+
+ await driver.clickElement('#sendButton');
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({ text: 'Cancel', tag: 'button' });
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+
+ // Wait for switch confirmation to close then tx confirmation to show.
+ // There is an extra window appearing and disappearing
+ // so we leave this delay until the issue is fixed (#27360)
+ await driver.delay(5000);
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // Check correct network on the send confirmation.
+ await driver.findElement({
+ css: 'p',
+ text: 'Localhost 8546',
+ });
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Confirm',
+ tag: 'button',
+ });
+
+ // Switch back to the extension
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ await driver.clickElement(
+ '[data-testid="account-overview__activity-tab"]',
+ );
+
+ // Check for transaction
+ await driver.wait(async () => {
+ const confirmedTxes = await driver.findElements(
+ '.transaction-list__completed-transactions .activity-list-item',
+ );
+ return confirmedTxes.length === 1;
+ }, 10000);
+ },
+ );
+ });
});
});
diff --git a/test/e2e/tests/request-queuing/multi-dapp-sendTx-revokePermission.spec.js b/test/e2e/tests/request-queuing/multi-dapp-sendTx-revokePermission.spec.js
index 06d232635131..3a413f147e06 100644
--- a/test/e2e/tests/request-queuing/multi-dapp-sendTx-revokePermission.spec.js
+++ b/test/e2e/tests/request-queuing/multi-dapp-sendTx-revokePermission.spec.js
@@ -7,127 +7,247 @@ const {
DAPP_ONE_URL,
WINDOW_TITLES,
defaultGanacheOptions,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
-const { PAGES } = require('../../webdriver/driver');
describe('Request Queuing for Multiple Dapps and Txs on different networks revokePermissions', function () {
- it('should close transaction for revoked permission of eth_accounts but show queued tx from second dapp on a different network.', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .build(),
- dappOptions: { numberOfDapps: 2 },
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ describe('Old confirmation screens', function () {
+ it('should close transaction for revoked permission of eth_accounts but show queued tx from second dapp on a different network.', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
},
- title: this.test.fullTitle(),
- },
-
- async ({ driver }) => {
- await unlockWallet(driver);
-
- // Navigate to extension home screen
- await driver.navigate(PAGES.HOME);
-
- // Open Dapp One
- await openDapp(driver, undefined, DAPP_URL);
-
- // Connect to dapp 1
- await driver.clickElement({ text: 'Connect', tag: 'button' });
-
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
-
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
-
- // Network Selector
- await driver.clickElement('[data-testid="network-display"]');
-
- // Switch to second network
- await driver.clickElement({
- text: 'Localhost 8546',
- css: 'p',
- });
-
- // Wait for the first dapp's connect confirmation to disappear
- await driver.waitUntilXWindowHandles(2);
-
- // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
- // Open Dapp Two
- await openDapp(driver, undefined, DAPP_ONE_URL);
-
- // Connect to dapp 2
- await driver.clickElement({ text: 'Connect', tag: 'button' });
-
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
-
- // Dapp 1 send tx
- await driver.switchToWindowWithUrl(DAPP_URL);
- await driver.findElement({
- css: '[id="chainId"]',
- text: '0x539',
- });
- await driver.clickElement('#sendButton');
-
- await driver.waitUntilXWindowHandles(4);
- await driver.delay(3000);
-
- // Dapp 2 send tx
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
- await driver.findElement({
- css: '[id="chainId"]',
- text: '0x53a',
- });
- await driver.clickElement('#sendButton');
- await driver.waitUntilXWindowHandles(4);
-
- // Dapp 1 revokePermissions
- await driver.switchToWindowWithUrl(DAPP_URL);
- await driver.findElement({
- css: '[id="chainId"]',
- text: '0x539',
- });
- await driver.assertElementNotPresent({
- css: '[id="chainId"]',
- text: '0x53a',
- });
-
- // Confirmation will close then reopen
- await driver.clickElement('#revokeAccountsPermission');
- // TODO: find a better way to handle different dialog ids
- await driver.delay(3000);
-
- // Check correct network on confirm tx.
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
-
- await driver.findElement({
- css: '[data-testid="network-display"]',
- text: 'Localhost 8546',
- });
- },
- );
+
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
+
+ // Connect to dapp 1
+ await driver.clickElement({ text: 'Connect', tag: 'button' });
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Network Selector
+ await driver.clickElement('[data-testid="network-display"]');
+
+ // Switch to second network
+ await driver.clickElement({
+ text: 'Localhost 8546',
+ css: 'p',
+ });
+
+ // Wait for the first dapp's connect confirmation to disappear
+ await driver.waitUntilXWindowHandles(2);
+
+ // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ // Connect to dapp 2
+ await driver.clickElement({ text: 'Connect', tag: 'button' });
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // Dapp 1 send tx
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.findElement({
+ css: '[id="chainId"]',
+ text: '0x539',
+ });
+ await driver.clickElement('#sendButton');
+
+ await driver.waitUntilXWindowHandles(4);
+ await driver.delay(3000);
+
+ // Dapp 2 send tx
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ await driver.findElement({
+ css: '[id="chainId"]',
+ text: '0x53a',
+ });
+ await driver.clickElement('#sendButton');
+ await driver.waitUntilXWindowHandles(4);
+
+ // Dapp 1 revokePermissions
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.findElement({
+ css: '[id="chainId"]',
+ text: '0x539',
+ });
+ await driver.assertElementNotPresent({
+ css: '[id="chainId"]',
+ text: '0x53a',
+ });
+
+ // Confirmation will close then reopen
+ await driver.clickElement('#revokeAccountsPermission');
+ // TODO: find a better way to handle different dialog ids
+ await driver.delay(3000);
+
+ // Check correct network on confirm tx.
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Localhost 8546',
+ });
+ },
+ );
+ });
+ });
+
+ describe('New confirmation screens', function () {
+ it('should close transaction for revoked permission of eth_accounts but show queued tx from second dapp on a different network.', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
+ },
+
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
+
+ // Connect to dapp 1
+ await driver.clickElement({ text: 'Connect', tag: 'button' });
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Network Selector
+ await driver.clickElement('[data-testid="network-display"]');
+
+ // Switch to second network
+ await driver.clickElement({
+ text: 'Localhost 8546',
+ css: 'p',
+ });
+
+ // Wait for the first dapp's connect confirmation to disappear
+ await driver.waitUntilXWindowHandles(2);
+
+ // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ // Connect to dapp 2
+ await driver.clickElement({ text: 'Connect', tag: 'button' });
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // Dapp 1 send tx
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.findElement({
+ css: '[id="chainId"]',
+ text: '0x539',
+ });
+ await driver.clickElement('#sendButton');
+
+ await driver.waitUntilXWindowHandles(4);
+ await driver.delay(3000);
+
+ // Dapp 2 send tx
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ await driver.findElement({
+ css: '[id="chainId"]',
+ text: '0x53a',
+ });
+ await driver.clickElement('#sendButton');
+ await driver.waitUntilXWindowHandles(4);
+
+ // Dapp 1 revokePermissions
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.findElement({
+ css: '[id="chainId"]',
+ text: '0x539',
+ });
+ await driver.assertElementNotPresent({
+ css: '[id="chainId"]',
+ text: '0x53a',
+ });
+
+ // Confirmation will close then reopen
+ await driver.clickElement('#revokeAccountsPermission');
+ // TODO: find a better way to handle different dialog ids
+ await driver.delay(3000);
+
+ // Check correct network on confirm tx.
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.findElement({
+ css: 'p',
+ text: 'Localhost 8546',
+ });
+ },
+ );
+ });
});
});
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 38fe1d7204d2..f831ff1ff38d 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
@@ -8,142 +8,277 @@ const {
WINDOW_TITLES,
defaultGanacheOptions,
largeDelayMs,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
-const { PAGES } = require('../../webdriver/driver');
describe('Request Queuing for Multiple Dapps and Txs on different networks.', function () {
- it('should switch to the dapps network automatically when handling sendTransaction calls @no-mmi', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .withSelectedNetworkControllerPerDomain()
- .build(),
- dappOptions: { numberOfDapps: 2 },
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ describe('Old confirmation screens', function () {
+ it('should switch to the dapps network automatically when handling sendTransaction calls @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .withSelectedNetworkControllerPerDomain()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
},
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
+ async ({ driver }) => {
+ await unlockWallet(driver);
- // Navigate to extension home screen
- await driver.navigate(PAGES.HOME);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
- // Open Dapp One
- await openDapp(driver, undefined, DAPP_URL);
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
- // Connect to dapp 1
- await driver.clickElement({ text: 'Connect', tag: 'button' });
+ // Connect to dapp 1
+ await driver.clickElement({ text: 'Connect', tag: 'button' });
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Network Selector
+ await driver.clickElement('[data-testid="network-display"]');
- // Network Selector
- await driver.clickElement('[data-testid="network-display"]');
+ // Switch to second network
+ await driver.clickElement({
+ text: 'Localhost 8546',
+ css: 'p',
+ });
- // Switch to second network
- await driver.clickElement({
- text: 'Localhost 8546',
- css: 'p',
- });
+ // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
- // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
- // Open Dapp Two
- await openDapp(driver, undefined, DAPP_ONE_URL);
+ // Connect to dapp 2
+ await driver.clickElement({ text: 'Connect', tag: 'button' });
- // Connect to dapp 2
- await driver.clickElement({ text: 'Connect', tag: 'button' });
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
- await driver.clickElementAndWaitForWindowToClose({
- text: 'Connect',
- tag: 'button',
- });
+ // Dapp one send tx
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.delay(largeDelayMs);
+ await driver.clickElement('#sendButton');
- // Dapp one send tx
- await driver.switchToWindowWithUrl(DAPP_URL);
- await driver.delay(largeDelayMs);
- await driver.clickElement('#sendButton');
+ await driver.waitUntilXWindowHandles(4);
- await driver.waitUntilXWindowHandles(4);
+ // Dapp two send tx
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ await driver.delay(largeDelayMs);
+ await driver.clickElement('#sendButton');
- // Dapp two send tx
- await driver.switchToWindowWithUrl(DAPP_ONE_URL);
- await driver.delay(largeDelayMs);
- await driver.clickElement('#sendButton');
+ // First switch network
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- // First switch network
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ // Wait for confirm tx after switch network confirmation.
+ await driver.delay(largeDelayMs);
- // Wait for confirm tx after switch network confirmation.
- await driver.delay(largeDelayMs);
+ await driver.waitUntilXWindowHandles(4);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- await driver.waitUntilXWindowHandles(4);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ // Reject Transaction
+ await driver.findClickableElement({ text: 'Reject', tag: 'button' });
+ await driver.clickElement(
+ '[data-testid="page-container-footer-cancel"]',
+ );
- // Reject Transaction
- await driver.findClickableElement({ text: 'Reject', tag: 'button' });
- await driver.clickElement(
- '[data-testid="page-container-footer-cancel"]',
- );
+ // TODO: No second confirmation from dapp two will show, have to go back to the extension to see the switch chain & dapp two's tx.
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ await driver.clickElement(
+ '[data-testid="account-overview__activity-tab"]',
+ );
- // TODO: No second confirmation from dapp two will show, have to go back to the extension to see the switch chain & dapp two's tx.
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
- await driver.clickElement(
- '[data-testid="account-overview__activity-tab"]',
- );
+ // Check for unconfirmed transaction in tx list
+ await driver.wait(async () => {
+ const unconfirmedTxes = await driver.findElements(
+ '.transaction-list-item--unconfirmed',
+ );
+ return unconfirmedTxes.length === 1;
+ }, 10000);
- // Check for unconfirmed transaction in tx list
- await driver.wait(async () => {
- const unconfirmedTxes = await driver.findElements(
- '.transaction-list-item--unconfirmed',
+ // Click Unconfirmed Tx
+ await driver.clickElement('.transaction-list-item--unconfirmed');
+
+ await driver.assertElementNotPresent({
+ tag: 'p',
+ text: 'Network switched to Localhost 8546',
+ });
+
+ // Confirm Tx
+ await driver.clickElement(
+ '[data-testid="page-container-footer-next"]',
);
- return unconfirmedTxes.length === 1;
- }, 10000);
- // Click Unconfirmed Tx
- await driver.clickElement('.transaction-list-item--unconfirmed');
+ // Check for Confirmed Transaction
+ await driver.wait(async () => {
+ const confirmedTxes = await driver.findElements(
+ '.transaction-list__completed-transactions .activity-list-item',
+ );
+ return confirmedTxes.length === 1;
+ }, 10000);
+ },
+ );
+ });
+ });
+
+ describe('Redesigned confirmation screens', function () {
+ it('should switch to the dapps network automatically when handling sendTransaction calls @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .withSelectedNetworkControllerPerDomain()
+ .build(),
+ dappOptions: { numberOfDapps: 2 },
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open Dapp One
+ await openDapp(driver, undefined, DAPP_URL);
- await driver.assertElementNotPresent({
- tag: 'p',
- text: 'Network switched to Localhost 8546',
- });
+ // Connect to dapp 1
+ await driver.clickElement({ text: 'Connect', tag: 'button' });
- // Confirm Tx
- await driver.clickElement('[data-testid="page-container-footer-next"]');
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
- // Check for Confirmed Transaction
- await driver.wait(async () => {
- const confirmedTxes = await driver.findElements(
- '.transaction-list__completed-transactions .activity-list-item',
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
);
- return confirmedTxes.length === 1;
- }, 10000);
- },
- );
+
+ // Network Selector
+ await driver.clickElement('[data-testid="network-display"]');
+
+ // Switch to second network
+ await driver.clickElement({
+ text: 'Localhost 8546',
+ css: 'p',
+ });
+
+ // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them.
+ // Open Dapp Two
+ await openDapp(driver, undefined, DAPP_ONE_URL);
+
+ // Connect to dapp 2
+ await driver.clickElement({ text: 'Connect', tag: 'button' });
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElementAndWaitForWindowToClose({
+ text: 'Connect',
+ tag: 'button',
+ });
+
+ // Dapp one send tx
+ await driver.switchToWindowWithUrl(DAPP_URL);
+ await driver.delay(largeDelayMs);
+ await driver.clickElement('#sendButton');
+
+ await driver.waitUntilXWindowHandles(4);
+
+ // Dapp two send tx
+ await driver.switchToWindowWithUrl(DAPP_ONE_URL);
+ await driver.delay(largeDelayMs);
+ await driver.clickElement('#sendButton');
+
+ // First switch network
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // Wait for confirm tx after switch network confirmation.
+ await driver.delay(largeDelayMs);
+
+ await driver.waitUntilXWindowHandles(4);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // Reject Transaction
+ await driver.findClickableElement({ text: 'Cancel', tag: 'button' });
+ await driver.clickElement({ text: 'Cancel', tag: 'button' });
+
+ // TODO: No second confirmation from dapp two will show, have to go back to the extension to see the switch chain & dapp two's tx.
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ await driver.clickElement(
+ '[data-testid="account-overview__activity-tab"]',
+ );
+
+ // Check for unconfirmed transaction in tx list
+ await driver.wait(async () => {
+ const unconfirmedTxes = await driver.findElements(
+ '.transaction-list-item--unconfirmed',
+ );
+ return unconfirmedTxes.length === 1;
+ }, 10000);
+
+ // Click Unconfirmed Tx
+ await driver.clickElement('.transaction-list-item--unconfirmed');
+
+ await driver.assertElementNotPresent({
+ tag: 'p',
+ text: 'Network switched to Localhost 8546',
+ });
+
+ // Confirm Tx
+ await driver.clickElement({ text: 'Confirm', tag: 'button' });
+
+ // Check for Confirmed Transaction
+ await driver.wait(async () => {
+ const confirmedTxes = await driver.findElements(
+ '.transaction-list__completed-transactions .activity-list-item',
+ );
+ return confirmedTxes.length === 1;
+ }, 10000);
+ },
+ );
+ });
});
});
diff --git a/test/e2e/tests/request-queuing/switch-network.spec.js b/test/e2e/tests/request-queuing/switch-network.spec.js
index 5949800f9840..913bdf459a26 100644
--- a/test/e2e/tests/request-queuing/switch-network.spec.js
+++ b/test/e2e/tests/request-queuing/switch-network.spec.js
@@ -7,88 +7,178 @@ const {
regularDelayMs,
WINDOW_TITLES,
defaultGanacheOptions,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const { PAGES } = require('../../webdriver/driver');
describe('Request Queuing Switch Network on Dapp Send Tx while on different networks.', function () {
- it('should switch to the dapps network automatically when mm network differs, dapp tx is on correct network', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .withPermissionControllerConnectedToTestDapp()
- .withPreferencesControllerUseRequestQueueEnabled()
- .build(),
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ describe('Old confirmation screens', function () {
+ it('should switch to the dapps network automatically when mm network differs, dapp tx is on correct network', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPermissionControllerConnectedToTestDapp()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
},
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
+ async ({ driver }) => {
+ await unlockWallet(driver);
- // Open dapp
- await openDapp(driver, undefined, DAPP_URL);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
+ // Open dapp
+ await openDapp(driver, undefined, DAPP_URL);
- // Network Selector
- await driver.clickElement('[data-testid="network-display"]');
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Network Selector
+ await driver.clickElement('[data-testid="network-display"]');
+
+ // Switch to second network
+ await driver.clickElement({
+ text: 'Localhost 8546',
+ css: 'p',
+ });
- // Switch to second network
- await driver.clickElement({
- text: 'Localhost 8546',
- css: 'p',
- });
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
+ // Queue confirm tx should first auto switch network
+ await driver.clickElement('#sendButton');
- // Queue confirm tx should first auto switch network
- await driver.clickElement('#sendButton');
+ await driver.delay(regularDelayMs);
+
+ await driver.waitUntilXWindowHandles(3);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // Confirm Transaction
+ await driver.findClickableElement({ text: 'Confirm', tag: 'button' });
+ await driver.clickElement(
+ '[data-testid="page-container-footer-next"]',
+ );
- await driver.delay(regularDelayMs);
+ await driver.delay(regularDelayMs);
- await driver.waitUntilXWindowHandles(3);
- await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ // Switch back to the extension
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ await driver.navigate(PAGES.HOME);
- // Confirm Transaction
- await driver.findClickableElement({ text: 'Confirm', tag: 'button' });
- await driver.clickElement('[data-testid="page-container-footer-next"]');
+ // Check correct network switched and on the correct network
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Localhost 8545',
+ });
- await driver.delay(regularDelayMs);
+ // Check for transaction
+ await driver.wait(async () => {
+ const confirmedTxes = await driver.findElements(
+ '.transaction-list__completed-transactions .activity-list-item',
+ );
+ return confirmedTxes.length === 1;
+ }, 10000);
+ },
+ );
+ });
+ });
- // Switch back to the extension
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
- await driver.navigate(PAGES.HOME);
+ describe('Redesigned confirmation screens', function () {
+ it('should switch to the dapps network automatically when mm network differs, dapp tx is on correct network', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPermissionControllerConnectedToTestDapp()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
- // Check correct network switched and on the correct network
- await driver.findElement({
- css: '[data-testid="network-display"]',
- text: 'Localhost 8545',
- });
+ // Open dapp
+ await openDapp(driver, undefined, DAPP_URL);
- // Check for transaction
- await driver.wait(async () => {
- const confirmedTxes = await driver.findElements(
- '.transaction-list__completed-transactions .activity-list-item',
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
);
- return confirmedTxes.length === 1;
- }, 10000);
- },
- );
+
+ // Network Selector
+ await driver.clickElement('[data-testid="network-display"]');
+
+ // Switch to second network
+ await driver.clickElement({
+ text: 'Localhost 8546',
+ css: 'p',
+ });
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
+
+ // Queue confirm tx should first auto switch network
+ await driver.clickElement('#sendButton');
+
+ await driver.delay(regularDelayMs);
+
+ await driver.waitUntilXWindowHandles(3);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // Confirm Transaction
+ await driver.findClickableElement({ text: 'Confirm', tag: 'button' });
+ await driver.clickElement({ text: 'Confirm', tag: 'button' });
+
+ await driver.delay(regularDelayMs);
+
+ // Switch back to the extension
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ await driver.navigate(PAGES.HOME);
+
+ // Check correct network switched and on the correct network
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Localhost 8545',
+ });
+
+ // Check for transaction
+ await driver.wait(async () => {
+ const confirmedTxes = await driver.findElements(
+ '.transaction-list__completed-transactions .activity-list-item',
+ );
+ return confirmedTxes.length === 1;
+ }, 10000);
+ },
+ );
+ });
});
});
diff --git a/test/e2e/tests/request-queuing/ui.spec.js b/test/e2e/tests/request-queuing/ui.spec.js
index b857d4307d5b..707c252396b7 100644
--- a/test/e2e/tests/request-queuing/ui.spec.js
+++ b/test/e2e/tests/request-queuing/ui.spec.js
@@ -14,6 +14,7 @@ const {
tempToggleSettingRedesignedConfirmations,
veryLargeDelayMs,
DAPP_TWO_URL,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const { PAGES } = require('../../webdriver/driver');
const {
@@ -136,6 +137,38 @@ async function switchToDialogPopoverValidateDetails(driver, expectedDetails) {
assert.equal(chainId, expectedDetails.chainId);
}
+async function switchToDialogPopoverValidateDetailsRedesign(
+ driver,
+ expectedDetails,
+) {
+ // Switches to the MetaMask Dialog window for confirmation
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.findElement({
+ css: 'p',
+ text: expectedDetails.networkText,
+ });
+
+ // Get state details
+ await driver.waitForControllersLoaded();
+ const notificationWindowState = await driver.executeScript(() =>
+ window.stateHooks?.getCleanAppState?.(),
+ );
+
+ const {
+ metamask: { selectedNetworkClientId, networkConfigurationsByChainId },
+ } = notificationWindowState;
+
+ const { chainId } = Object.values(networkConfigurationsByChainId).find(
+ ({ rpcEndpoints }) =>
+ rpcEndpoints.some(
+ ({ networkClientId }) => networkClientId === selectedNetworkClientId,
+ ),
+ );
+
+ assert.equal(chainId, expectedDetails.chainId);
+}
+
async function rejectTransaction(driver) {
await driver.clickElementAndWaitForWindowToClose({
tag: 'button',
@@ -143,6 +176,13 @@ async function rejectTransaction(driver) {
});
}
+async function rejectTransactionRedesign(driver) {
+ await driver.clickElementAndWaitForWindowToClose({
+ tag: 'button',
+ text: 'Cancel',
+ });
+}
+
async function confirmTransaction(driver) {
await driver.clickElement({ tag: 'button', text: 'Confirm' });
}
@@ -190,573 +230,1036 @@ async function validateBalanceAndActivity(
}
describe('Request-queue UI changes', function () {
- it('should show network specific to domain @no-mmi', async function () {
- const port = 8546;
- const chainId = 1338; // 0x53a
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .build(),
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ describe('Old confirmation screens', function () {
+ it('should show network specific to domain @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338; // 0x53a
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ dappOptions: { numberOfDapps: 2 },
+ title: this.test.fullTitle(),
},
- dappOptions: { numberOfDapps: 2 },
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
-
- // Navigate to extension home screen
- await driver.navigate(PAGES.HOME);
-
- // Open the first dapp
- await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
-
- // Open the second dapp and switch chains
- await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a');
-
- // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
- await driver.findElement({
- css: '[data-testid="network-display"]',
- text: 'Localhost 8546',
- });
-
- // Go to the first dapp, ensure it uses localhost
- await selectDappClickSend(driver, DAPP_URL);
- await switchToDialogPopoverValidateDetails(driver, {
- chainId: '0x539',
- networkText: 'Localhost 8545',
- originText: DAPP_URL,
- });
- await rejectTransaction(driver);
-
- // Go to the second dapp, ensure it uses Ethereum Mainnet
- await selectDappClickSend(driver, DAPP_ONE_URL);
- await switchToDialogPopoverValidateDetails(driver, {
- chainId: '0x53a',
- networkText: 'Localhost 8546',
- originText: DAPP_ONE_URL,
- });
- await rejectTransaction(driver);
- },
- );
- });
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ // Open the first dapp
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
+
+ // Open the second dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a');
+
+ // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Localhost 8546',
+ });
+
+ // Go to the first dapp, ensure it uses localhost
+ await selectDappClickSend(driver, DAPP_URL);
+ await switchToDialogPopoverValidateDetails(driver, {
+ chainId: '0x539',
+ networkText: 'Localhost 8545',
+ originText: DAPP_URL,
+ });
+ await rejectTransaction(driver);
+
+ // Go to the second dapp, ensure it uses Ethereum Mainnet
+ await selectDappClickSend(driver, DAPP_ONE_URL);
+ await switchToDialogPopoverValidateDetails(driver, {
+ chainId: '0x53a',
+ networkText: 'Localhost 8546',
+ originText: DAPP_ONE_URL,
+ });
+ await rejectTransaction(driver);
+ },
+ );
+ });
- it('handles three confirmations on three confirmations concurrently @no-mmi', async function () {
- const port = 8546;
- const chainId = 1338; // 0x53a
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerTripleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .build(),
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- // Ganache for network 1
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- // Ganache for network 3
- {
- port: 7777,
- chainId: 1000,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ it('handles three confirmations on three confirmations concurrently @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338; // 0x53a
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerTripleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ // Ganache for network 1
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ // Ganache for network 3
+ {
+ port: 7777,
+ chainId: 1000,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ dappOptions: { numberOfDapps: 3 },
+ title: this.test.fullTitle(),
},
- dappOptions: { numberOfDapps: 3 },
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
-
- // Navigate to extension home screen
- await driver.navigate(PAGES.HOME);
-
- // Open the first dapp
- await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
-
- // Open the second dapp and switch chains
- await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a');
-
- if (!IS_FIREFOX) {
- // Open the third dapp and switch chains
- await openDappAndSwitchChain(driver, DAPP_TWO_URL, '0x3e8');
- }
-
- // Trigger a send confirmation on the first dapp, do not confirm or reject
- await selectDappClickSend(driver, DAPP_URL);
-
- // Trigger a send confirmation on the second dapp, do not confirm or reject
- await selectDappClickSend(driver, DAPP_ONE_URL);
-
- if (!IS_FIREFOX) {
- // Trigger a send confirmation on the third dapp, do not confirm or reject
- await selectDappClickSend(driver, DAPP_TWO_URL);
- }
-
- // Switch to the Notification window, ensure first transaction still showing
- await switchToDialogPopoverValidateDetails(driver, {
- chainId: '0x539',
- networkText: 'Localhost 8545',
- originText: DAPP_URL,
- });
-
- // Confirm transaction, wait for first confirmation window to close, second to display
- await confirmTransaction(driver);
- await driver.delay(veryLargeDelayMs);
-
- // Switch to the new Notification window, ensure second transaction showing
- await switchToDialogPopoverValidateDetails(driver, {
- chainId: '0x53a',
- networkText: 'Localhost 8546',
- originText: DAPP_ONE_URL,
- });
-
- // Reject this transaction, wait for second confirmation window to close, third to display
- await rejectTransaction(driver);
- await driver.delay(veryLargeDelayMs);
-
- if (!IS_FIREFOX) {
- // Switch to the new Notification window, ensure third transaction showing
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ // Open the first dapp
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
+
+ // Open the second dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a');
+
+ if (!IS_FIREFOX) {
+ // Open the third dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_TWO_URL, '0x3e8');
+ }
+
+ // Trigger a send confirmation on the first dapp, do not confirm or reject
+ await selectDappClickSend(driver, DAPP_URL);
+
+ // Trigger a send confirmation on the second dapp, do not confirm or reject
+ await selectDappClickSend(driver, DAPP_ONE_URL);
+
+ if (!IS_FIREFOX) {
+ // Trigger a send confirmation on the third dapp, do not confirm or reject
+ await selectDappClickSend(driver, DAPP_TWO_URL);
+ }
+
+ // Switch to the Notification window, ensure first transaction still showing
await switchToDialogPopoverValidateDetails(driver, {
- chainId: '0x3e8',
- networkText: 'Localhost 7777',
- originText: DAPP_TWO_URL,
+ chainId: '0x539',
+ networkText: 'Localhost 8545',
+ originText: DAPP_URL,
});
- // Confirm transaction
+ // Confirm transaction, wait for first confirmation window to close, second to display
await confirmTransaction(driver);
- }
-
- // With first and last confirmations confirmed, and second rejected,
- // Ensure only first and last network balances were affected
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
+ await driver.delay(veryLargeDelayMs);
- // Wait for transaction to be completed on final confirmation
- await driver.delay(veryLargeDelayMs);
+ // Switch to the new Notification window, ensure second transaction showing
+ await switchToDialogPopoverValidateDetails(driver, {
+ chainId: '0x53a',
+ networkText: 'Localhost 8546',
+ originText: DAPP_ONE_URL,
+ });
- if (!IS_FIREFOX) {
- // Start on the last joined network, whose send transaction was just confirmed
+ // Reject this transaction, wait for second confirmation window to close, third to display
+ await rejectTransaction(driver);
+ await driver.delay(veryLargeDelayMs);
+
+ if (!IS_FIREFOX) {
+ // Switch to the new Notification window, ensure third transaction showing
+ await switchToDialogPopoverValidateDetails(driver, {
+ chainId: '0x3e8',
+ networkText: 'Localhost 7777',
+ originText: DAPP_TWO_URL,
+ });
+
+ // Confirm transaction
+ await confirmTransaction(driver);
+ }
+
+ // With first and last confirmations confirmed, and second rejected,
+ // Ensure only first and last network balances were affected
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Wait for transaction to be completed on final confirmation
+ await driver.delay(veryLargeDelayMs);
+
+ if (!IS_FIREFOX) {
+ // Start on the last joined network, whose send transaction was just confirmed
+ await validateBalanceAndActivity(driver, '24.9998');
+ }
+
+ // Switch to second network, ensure full balance
+ await switchToNetworkByName(driver, 'Localhost 8546');
+ await validateBalanceAndActivity(driver, '25', 0);
+
+ // Turn on test networks in Networks menu so Localhost 8545 is available
+ await driver.clickElement('[data-testid="network-display"]');
+ await driver.clickElement('.mm-modal-content__dialog .toggle-button');
+ await driver.clickElement(
+ '.mm-modal-content__dialog button[aria-label="Close"]',
+ );
+
+ // Switch to first network, whose send transaction was just confirmed
+ await switchToNetworkByName(driver, 'Localhost 8545');
await validateBalanceAndActivity(driver, '24.9998');
- }
-
- // Switch to second network, ensure full balance
- await switchToNetworkByName(driver, 'Localhost 8546');
- await validateBalanceAndActivity(driver, '25', 0);
-
- // Turn on test networks in Networks menu so Localhost 8545 is available
- await driver.clickElement('[data-testid="network-display"]');
- await driver.clickElement('.mm-modal-content__dialog .toggle-button');
- await driver.clickElement(
- '.mm-modal-content__dialog button[aria-label="Close"]',
- );
-
- // Switch to first network, whose send transaction was just confirmed
- await switchToNetworkByName(driver, 'Localhost 8545');
- await validateBalanceAndActivity(driver, '24.9998');
- },
- );
- });
+ },
+ );
+ });
- it('should gracefully handle deleted network @no-mmi', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .withPreferencesController({
- preferences: { showTestNetworks: true },
- })
- .withPreferencesControllerUseRequestQueueEnabled()
- .build(),
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ it('should gracefully handle deleted network @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesController({
+ preferences: { showTestNetworks: true },
+ })
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ dappOptions: { numberOfDapps: 2 },
+ title: this.test.fullTitle(),
},
- dappOptions: { numberOfDapps: 2 },
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
-
- // Navigate to extension home screen
- await driver.navigate(PAGES.HOME);
-
- // Open the first dapp
- await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
-
- // Open the second dapp and switch chains
- await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
-
- // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
- await driver.findElement({
- css: '[data-testid="network-display"]',
- text: 'Ethereum Mainnet',
- });
-
- await driver.clickElement('[data-testid="network-display"]');
-
- const networkRow = await driver.findElement({
- css: '.multichain-network-list-item',
- text: 'Localhost 8545',
- });
-
- const networkMenu = await driver.findNestedElement(
- networkRow,
- `[data-testid="network-list-item-options-button-${CHAIN_IDS.LOCALHOST}"]`,
- );
-
- await networkMenu.click();
- await driver.clickElement(
- '[data-testid="network-list-item-options-delete"]',
- );
-
- await driver.clickElement({ tag: 'button', text: 'Delete' });
-
- // Go back to first dapp, try an action, ensure deleted network doesn't block UI
- // The current globally selected network, Ethereum Mainnet, should be used
- await selectDappClickSend(driver, DAPP_URL);
- await driver.delay(veryLargeDelayMs);
- await switchToDialogPopoverValidateDetails(driver, {
- chainId: '0x1',
- networkText: 'Ethereum Mainnet',
- originText: DAPP_URL,
- });
- },
- );
- });
+ async ({ driver }) => {
+ await unlockWallet(driver);
- it('should signal from UI to dapp the network change @no-mmi', async function () {
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withPreferencesControllerUseRequestQueueEnabled()
- .build(),
- ganacheOptions: defaultGanacheOptions,
- title: this.test.fullTitle(),
- driverOptions: { constrainWindowSize: true },
- },
- async ({ driver }) => {
- // Navigate to extension home screen
- await unlockWallet(driver);
-
- // Open the first dapp which starts on chain '0x539
- await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
-
- // Ensure the dapp starts on the correct network
- await driver.waitForSelector({
- css: '[id="chainId"]',
- text: '0x539',
- });
-
- // Open the popup with shimmed activeTabOrigin
- await openPopupWithActiveTabOrigin(driver, DAPP_URL);
-
- // Switch to mainnet
- await switchToNetworkByName(driver, 'Ethereum Mainnet');
-
- // Switch back to the Dapp tab
- await driver.switchToWindowWithUrl(DAPP_URL);
-
- // Check to make sure the dapp network changed
- await driver.waitForSelector({
- css: '[id="chainId"]',
- text: '0x1',
- });
- },
- );
- });
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ // Open the first dapp
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
+
+ // Open the second dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
+
+ // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Ethereum Mainnet',
+ });
+
+ await driver.clickElement('[data-testid="network-display"]');
- it('should autoswitch networks to the last used network for domain', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .build(),
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ const networkRow = await driver.findElement({
+ css: '.multichain-network-list-item',
+ text: 'Localhost 8545',
+ });
+
+ const networkMenu = await driver.findNestedElement(
+ networkRow,
+ `[data-testid="network-list-item-options-button-${CHAIN_IDS.LOCALHOST}"]`,
+ );
+
+ await networkMenu.click();
+ await driver.clickElement(
+ '[data-testid="network-list-item-options-delete"]',
+ );
+
+ await driver.clickElement({ tag: 'button', text: 'Delete' });
+
+ // Go back to first dapp, try an action, ensure deleted network doesn't block UI
+ // The current globally selected network, Ethereum Mainnet, should be used
+ await selectDappClickSend(driver, DAPP_URL);
+ await driver.delay(veryLargeDelayMs);
+ await switchToDialogPopoverValidateDetails(driver, {
+ chainId: '0x1',
+ networkText: 'Ethereum Mainnet',
+ originText: DAPP_URL,
+ });
},
- dappOptions: { numberOfDapps: 2 },
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- // Open fullscreen
- await unlockWallet(driver);
-
- // Open the first dapp which starts on chain '0x539
- await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
-
- // Open tab 2, switch to Ethereum Mainnet
- await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
-
- // Open the popup with shimmed activeTabOrigin
- await openPopupWithActiveTabOrigin(driver, DAPP_URL);
-
- // Ensure network was reset to original
- await driver.findElement({
- css: '.multichain-app-header__contents--avatar-network .mm-text',
- text: 'Localhost 8545',
- });
-
- // Ensure toast is shown to the user
- await driver.findElement({
- css: '.toast-text',
- text: 'Localhost 8545 is now active on 127.0.0.1:8080',
- });
- },
- );
- });
+ );
+ });
- it('should autoswitch networks when last confirmation from another network is rejected', async function () {
- const port = 8546;
- const chainId = 1338;
-
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .build(),
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ it('should autoswitch networks when last confirmation from another network is rejected', async function () {
+ const port = 8546;
+ const chainId = 1338;
+
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ dappOptions: { numberOfDapps: 2 },
+ title: this.test.fullTitle(),
+ driverOptions: { constrainWindowSize: true },
},
- dappOptions: { numberOfDapps: 2 },
- title: this.test.fullTitle(),
- driverOptions: { constrainWindowSize: true },
- },
- async ({ driver }) => {
- await unlockWallet(driver);
-
- // Open the first dapp which starts on chain '0x539
- await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
-
- // Open tab 2, switch to Ethereum Mainnet
- await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
- await driver.waitForSelector({
- css: '.error-message-text',
- text: 'You are on the Ethereum Mainnet.',
- });
- await driver.delay(veryLargeDelayMs);
-
- // Start a Send on Ethereum Mainnet
- await driver.clickElement('#sendButton');
- await driver.delay(regularDelayMs);
-
- // Open the popup with shimmed activeTabOrigin
- await openPopupWithActiveTabOrigin(driver, DAPP_URL);
-
- // Ensure the confirmation pill shows Ethereum Mainnet
- await driver.waitForSelector({
- css: '[data-testid="network-display"]',
- text: 'Ethereum Mainnet',
- });
-
- // Reject the confirmation
- await driver.clickElement(
- '[data-testid="page-container-footer-cancel"]',
- );
-
- // Wait for network to automatically change to localhost
- await driver.waitForSelector({
- css: '.multichain-app-header__contents--avatar-network .mm-text',
- text: 'Localhost 8545',
- });
-
- // Ensure toast is shown to the user
- await driver.waitForSelector({
- css: '.toast-text',
- text: 'Localhost 8545 is now active on 127.0.0.1:8080',
- });
- },
- );
- });
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ // Open the first dapp which starts on chain '0x539
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
- it('should gracefully handle network connectivity failure for signatures @no-mmi', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .build(),
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ // Open tab 2, switch to Ethereum Mainnet
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
+ await driver.waitForSelector({
+ css: '.error-message-text',
+ text: 'You are on the Ethereum Mainnet.',
+ });
+ await driver.delay(veryLargeDelayMs);
+
+ // Start a Send on Ethereum Mainnet
+ await driver.clickElement('#sendButton');
+ await driver.delay(regularDelayMs);
+
+ // Open the popup with shimmed activeTabOrigin
+ await openPopupWithActiveTabOrigin(driver, DAPP_URL);
+
+ // Ensure the confirmation pill shows Ethereum Mainnet
+ await driver.waitForSelector({
+ css: '[data-testid="network-display"]',
+ text: 'Ethereum Mainnet',
+ });
+
+ // Reject the confirmation
+ await driver.clickElement(
+ '[data-testid="page-container-footer-cancel"]',
+ );
+
+ // Wait for network to automatically change to localhost
+ await driver.waitForSelector({
+ css: '.multichain-app-header__contents--avatar-network .mm-text',
+ text: 'Localhost 8545',
+ });
+
+ // Ensure toast is shown to the user
+ await driver.waitForSelector({
+ css: '.toast-text',
+ text: 'Localhost 8545 is now active on 127.0.0.1:8080',
+ });
},
- // This test intentionally quits Ganache while the extension is using it, causing
- // PollingBlockTracker errors and others. These are expected.
- ignoredConsoleErrors: ['ignore-all'],
- dappOptions: { numberOfDapps: 2 },
- title: this.test.fullTitle(),
- },
- async ({ driver, ganacheServer, secondaryGanacheServer }) => {
- await unlockWallet(driver);
- await tempToggleSettingRedesignedConfirmations(driver);
-
- // Navigate to extension home screen
- await driver.navigate(PAGES.HOME);
-
- // Open the first dapp
- await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
-
- // Open the second dapp and switch chains
- await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
-
- // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
- await driver.waitForSelector({
- css: '[data-testid="network-display"]',
- text: 'Ethereum Mainnet',
- });
-
- // Kill ganache servers
- await ganacheServer.quit();
- await secondaryGanacheServer[0].quit();
-
- // Go back to first dapp, try an action, ensure network connection failure doesn't block UI
- await selectDappClickPersonalSign(driver, DAPP_URL);
-
- // When the network is down, there is a performance degradation that causes the
- // popup to take a few seconds to open in MV3 (issue #25690)
- await driver.waitUntilXWindowHandles(4, 1000, 15000);
-
- await switchToDialogPopoverValidateDetails(driver, {
- chainId: '0x539',
- networkText: 'Localhost 8545',
- originText: DAPP_URL,
- });
- },
- );
+ );
+ });
+
+ it('should gracefully handle network connectivity failure for signatures @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ // This test intentionally quits Ganache while the extension is using it, causing
+ // PollingBlockTracker errors and others. These are expected.
+ ignoredConsoleErrors: ['ignore-all'],
+ dappOptions: { numberOfDapps: 2 },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver, ganacheServer, secondaryGanacheServer }) => {
+ await unlockWallet(driver);
+ await tempToggleSettingRedesignedConfirmations(driver);
+
+ // Navigate to extension home screen
+ await driver.navigate(PAGES.HOME);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ // Open the first dapp
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
+
+ // Open the second dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
+
+ // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ await driver.waitForSelector({
+ css: '[data-testid="network-display"]',
+ text: 'Ethereum Mainnet',
+ });
+
+ // Kill ganache servers
+ await ganacheServer.quit();
+ await secondaryGanacheServer[0].quit();
+
+ // Go back to first dapp, try an action, ensure network connection failure doesn't block UI
+ await selectDappClickPersonalSign(driver, DAPP_URL);
+
+ // When the network is down, there is a performance degradation that causes the
+ // popup to take a few seconds to open in MV3 (issue #25690)
+ await driver.waitUntilXWindowHandles(4, 1000, 15000);
+
+ await switchToDialogPopoverValidateDetails(driver, {
+ chainId: '0x539',
+ networkText: 'Localhost 8545',
+ originText: DAPP_URL,
+ });
+ },
+ );
+ });
+
+ it('should gracefully handle network connectivity failure for confirmations @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ // Presently confirmations take up to 10 seconds to display on a dead network
+ driverOptions: { timeOut: 30000 },
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ // This test intentionally quits Ganache while the extension is using it, causing
+ // PollingBlockTracker errors and others. These are expected.
+ ignoredConsoleErrors: ['ignore-all'],
+ dappOptions: { numberOfDapps: 2 },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver, ganacheServer, secondaryGanacheServer }) => {
+ await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
+ // Open the first dapp
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
+
+ // Open the second dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
+
+ // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Ethereum Mainnet',
+ });
+
+ // Kill ganache servers
+ await ganacheServer.quit();
+ await secondaryGanacheServer[0].quit();
+
+ // Go back to first dapp, try an action, ensure network connection failure doesn't block UI
+ await selectDappClickSend(driver, DAPP_URL);
+
+ // When the network is down, there is a performance degradation that causes the
+ // popup to take a few seconds to open in MV3 (issue #25690)
+ await driver.waitUntilXWindowHandles(4, 1000, 15000);
+
+ await switchToDialogPopoverValidateDetails(driver, {
+ chainId: '0x539',
+ networkText: 'Localhost 8545',
+ originText: DAPP_URL,
+ });
+ },
+ );
+ });
});
- it('should gracefully handle network connectivity failure for confirmations @no-mmi', async function () {
- const port = 8546;
- const chainId = 1338;
- await withFixtures(
- {
- dapp: true,
- // Presently confirmations take up to 10 seconds to display on a dead network
- driverOptions: { timeOut: 30000 },
- fixtures: new FixtureBuilder()
- .withNetworkControllerDoubleGanache()
- .withPreferencesControllerUseRequestQueueEnabled()
- .build(),
- ganacheOptions: {
- ...defaultGanacheOptions,
- concurrent: [
- {
- port,
- chainId,
- ganacheOptions2: defaultGanacheOptions,
- },
- ],
+ describe('Redesigned confirmation screens', function () {
+ it('should show network specific to domain @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338; // 0x53a
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ dappOptions: { numberOfDapps: 2 },
+ title: this.test.fullTitle(),
},
- // This test intentionally quits Ganache while the extension is using it, causing
- // PollingBlockTracker errors and others. These are expected.
- ignoredConsoleErrors: ['ignore-all'],
- dappOptions: { numberOfDapps: 2 },
- title: this.test.fullTitle(),
- },
- async ({ driver, ganacheServer, secondaryGanacheServer }) => {
- await unlockWallet(driver);
-
- // Navigate to extension home screen
- await driver.navigate(PAGES.HOME);
-
- // Open the first dapp
- await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
-
- // Open the second dapp and switch chains
- await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
-
- // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
- await driver.findElement({
- css: '[data-testid="network-display"]',
- text: 'Ethereum Mainnet',
- });
-
- // Kill ganache servers
- await ganacheServer.quit();
- await secondaryGanacheServer[0].quit();
-
- // Go back to first dapp, try an action, ensure network connection failure doesn't block UI
- await selectDappClickSend(driver, DAPP_URL);
-
- // When the network is down, there is a performance degradation that causes the
- // popup to take a few seconds to open in MV3 (issue #25690)
- await driver.waitUntilXWindowHandles(4, 1000, 15000);
-
- await switchToDialogPopoverValidateDetails(driver, {
- chainId: '0x539',
- networkText: 'Localhost 8545',
- originText: DAPP_URL,
- });
- },
- );
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open the first dapp
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
+
+ // Open the second dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a');
+
+ // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Localhost 8546',
+ });
+
+ // Go to the first dapp, ensure it uses localhost
+ await selectDappClickSend(driver, DAPP_URL);
+ await switchToDialogPopoverValidateDetailsRedesign(driver, {
+ chainId: '0x539',
+ networkText: 'Localhost 8545',
+ originText: DAPP_URL,
+ });
+ await rejectTransactionRedesign(driver);
+
+ // Go to the second dapp, ensure it uses Ethereum Mainnet
+ await selectDappClickSend(driver, DAPP_ONE_URL);
+ await switchToDialogPopoverValidateDetailsRedesign(driver, {
+ chainId: '0x53a',
+ networkText: 'Localhost 8546',
+ originText: DAPP_ONE_URL,
+ });
+ await rejectTransactionRedesign(driver);
+ },
+ );
+ });
+
+ it('handles three confirmations on three confirmations concurrently @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338; // 0x53a
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerTripleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ // Ganache for network 1
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ // Ganache for network 3
+ {
+ port: 7777,
+ chainId: 1000,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ dappOptions: { numberOfDapps: 3 },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open the first dapp
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
+
+ // Open the second dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a');
+
+ if (!IS_FIREFOX) {
+ // Open the third dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_TWO_URL, '0x3e8');
+ }
+
+ // Trigger a send confirmation on the first dapp, do not confirm or reject
+ await selectDappClickSend(driver, DAPP_URL);
+
+ // Trigger a send confirmation on the second dapp, do not confirm or reject
+ await selectDappClickSend(driver, DAPP_ONE_URL);
+
+ if (!IS_FIREFOX) {
+ // Trigger a send confirmation on the third dapp, do not confirm or reject
+ await selectDappClickSend(driver, DAPP_TWO_URL);
+ }
+
+ // Switch to the Notification window, ensure first transaction still showing
+ await switchToDialogPopoverValidateDetailsRedesign(driver, {
+ chainId: '0x539',
+ networkText: 'Localhost 8545',
+ originText: DAPP_URL,
+ });
+
+ // Confirm transaction, wait for first confirmation window to close, second to display
+ await confirmTransaction(driver);
+ await driver.delay(veryLargeDelayMs);
+
+ // Switch to the new Notification window, ensure second transaction showing
+ await switchToDialogPopoverValidateDetailsRedesign(driver, {
+ chainId: '0x53a',
+ networkText: 'Localhost 8546',
+ originText: DAPP_ONE_URL,
+ });
+
+ // Reject this transaction, wait for second confirmation window to close, third to display
+ await rejectTransactionRedesign(driver);
+ await driver.delay(veryLargeDelayMs);
+
+ if (!IS_FIREFOX) {
+ // Switch to the new Notification window, ensure third transaction showing
+ await switchToDialogPopoverValidateDetailsRedesign(driver, {
+ chainId: '0x3e8',
+ networkText: 'Localhost 7777',
+ originText: DAPP_TWO_URL,
+ });
+
+ // Confirm transaction
+ await confirmTransaction(driver);
+ }
+
+ // With first and last confirmations confirmed, and second rejected,
+ // Ensure only first and last network balances were affected
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Wait for transaction to be completed on final confirmation
+ await driver.delay(veryLargeDelayMs);
+
+ if (!IS_FIREFOX) {
+ // Start on the last joined network, whose send transaction was just confirmed
+ await validateBalanceAndActivity(driver, '24.9998');
+ }
+
+ // Switch to second network, ensure full balance
+ await switchToNetworkByName(driver, 'Localhost 8546');
+ await validateBalanceAndActivity(driver, '25', 0);
+
+ // Turn on test networks in Networks menu so Localhost 8545 is available
+ await driver.clickElement('[data-testid="network-display"]');
+ await driver.clickElement('.mm-modal-content__dialog .toggle-button');
+ await driver.clickElement(
+ '.mm-modal-content__dialog button[aria-label="Close"]',
+ );
+
+ // Switch to first network, whose send transaction was just confirmed
+ await switchToNetworkByName(driver, 'Localhost 8545');
+ await validateBalanceAndActivity(driver, '24.9998');
+ },
+ );
+ });
+
+ it('should gracefully handle deleted network @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesController({
+ preferences: { showTestNetworks: true },
+ })
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ dappOptions: { numberOfDapps: 2 },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open the first dapp
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
+
+ // Open the second dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
+
+ // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Ethereum Mainnet',
+ });
+
+ await driver.clickElement('[data-testid="network-display"]');
+
+ const networkRow = await driver.findElement({
+ css: '.multichain-network-list-item',
+ text: 'Localhost 8545',
+ });
+
+ const networkMenu = await driver.findNestedElement(
+ networkRow,
+ `[data-testid="network-list-item-options-button-${CHAIN_IDS.LOCALHOST}"]`,
+ );
+
+ await networkMenu.click();
+ await driver.clickElement(
+ '[data-testid="network-list-item-options-delete"]',
+ );
+
+ await driver.clickElement({ tag: 'button', text: 'Delete' });
+
+ // Go back to first dapp, try an action, ensure deleted network doesn't block UI
+ // The current globally selected network, Ethereum Mainnet, should be used
+ await selectDappClickSend(driver, DAPP_URL);
+ await driver.delay(veryLargeDelayMs);
+ await switchToDialogPopoverValidateDetailsRedesign(driver, {
+ chainId: '0x1',
+ networkText: 'Ethereum Mainnet',
+ originText: DAPP_URL,
+ });
+ },
+ );
+ });
+
+ it('should signal from UI to dapp the network change @no-mmi', async function () {
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: defaultGanacheOptions,
+ title: this.test.fullTitle(),
+ driverOptions: { constrainWindowSize: true },
+ },
+ async ({ driver }) => {
+ // Navigate to extension home screen
+ await unlockWallet(driver);
+
+ // Open the first dapp which starts on chain '0x539
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
+
+ // Ensure the dapp starts on the correct network
+ await driver.waitForSelector({
+ css: '[id="chainId"]',
+ text: '0x539',
+ });
+
+ // Open the popup with shimmed activeTabOrigin
+ await openPopupWithActiveTabOrigin(driver, DAPP_URL);
+
+ // Switch to mainnet
+ await switchToNetworkByName(driver, 'Ethereum Mainnet');
+
+ // Switch back to the Dapp tab
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ // Check to make sure the dapp network changed
+ await driver.waitForSelector({
+ css: '[id="chainId"]',
+ text: '0x1',
+ });
+ },
+ );
+ });
+
+ it('should autoswitch networks to the last used network for domain', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ dappOptions: { numberOfDapps: 2 },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver }) => {
+ // Open fullscreen
+ await unlockWallet(driver);
+
+ // Open the first dapp which starts on chain '0x539
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
+
+ // Open tab 2, switch to Ethereum Mainnet
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
+
+ // Open the popup with shimmed activeTabOrigin
+ await openPopupWithActiveTabOrigin(driver, DAPP_URL);
+
+ // Ensure network was reset to original
+ await driver.findElement({
+ css: '.multichain-app-header__contents--avatar-network .mm-text',
+ text: 'Localhost 8545',
+ });
+
+ // Ensure toast is shown to the user
+ await driver.findElement({
+ css: '.toast-text',
+ text: 'Localhost 8545 is now active on 127.0.0.1:8080',
+ });
+ },
+ );
+ });
+
+ it('should autoswitch networks when last confirmation from another network is rejected', async function () {
+ const port = 8546;
+ const chainId = 1338;
+
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ dappOptions: { numberOfDapps: 2 },
+ title: this.test.fullTitle(),
+ driverOptions: { constrainWindowSize: true },
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+
+ // Open the first dapp which starts on chain '0x539
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
+
+ // Open tab 2, switch to Ethereum Mainnet
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
+ await driver.waitForSelector({
+ css: '.error-message-text',
+ text: 'You are on the Ethereum Mainnet.',
+ });
+ await driver.delay(veryLargeDelayMs);
+
+ // Start a Send on Ethereum Mainnet
+ await driver.clickElement('#sendButton');
+ await driver.delay(regularDelayMs);
+
+ // Open the popup with shimmed activeTabOrigin
+ await openPopupWithActiveTabOrigin(driver, DAPP_URL);
+
+ // Ensure the confirmation pill shows Ethereum Mainnet
+ await driver.waitForSelector({
+ css: 'p',
+ text: 'Ethereum Mainnet',
+ });
+
+ // Reject the confirmation
+ await driver.clickElement({ css: 'button', text: 'Cancel' });
+
+ // Wait for network to automatically change to localhost
+ await driver.waitForSelector({
+ css: '.multichain-app-header__contents--avatar-network .mm-text',
+ text: 'Localhost 8545',
+ });
+
+ // Ensure toast is shown to the user
+ await driver.waitForSelector({
+ css: '.toast-text',
+ text: 'Localhost 8545 is now active on 127.0.0.1:8080',
+ });
+ },
+ );
+ });
+
+ it('should gracefully handle network connectivity failure for signatures @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ // This test intentionally quits Ganache while the extension is using it, causing
+ // PollingBlockTracker errors and others. These are expected.
+ ignoredConsoleErrors: ['ignore-all'],
+ dappOptions: { numberOfDapps: 2 },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver, ganacheServer, secondaryGanacheServer }) => {
+ await unlockWallet(driver);
+
+ // Open the first dapp
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
+
+ // Open the second dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
+
+ // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ await driver.waitForSelector({
+ css: '[data-testid="network-display"]',
+ text: 'Ethereum Mainnet',
+ });
+
+ // Kill ganache servers
+ await ganacheServer.quit();
+ await secondaryGanacheServer[0].quit();
+
+ // Go back to first dapp, try an action, ensure network connection failure doesn't block UI
+ await selectDappClickPersonalSign(driver, DAPP_URL);
+
+ // When the network is down, there is a performance degradation that causes the
+ // popup to take a few seconds to open in MV3 (issue #25690)
+ await driver.waitUntilXWindowHandles(4, 1000, 15000);
+
+ await switchToDialogPopoverValidateDetailsRedesign(driver, {
+ chainId: '0x539',
+ networkText: 'Localhost 8545',
+ originText: DAPP_URL,
+ });
+ },
+ );
+ });
+
+ it('should gracefully handle network connectivity failure for confirmations @no-mmi', async function () {
+ const port = 8546;
+ const chainId = 1338;
+ await withFixtures(
+ {
+ dapp: true,
+ // Presently confirmations take up to 10 seconds to display on a dead network
+ driverOptions: { timeOut: 30000 },
+ fixtures: new FixtureBuilder()
+ .withNetworkControllerDoubleGanache()
+ .withPreferencesControllerUseRequestQueueEnabled()
+ .build(),
+ ganacheOptions: {
+ ...defaultGanacheOptions,
+ concurrent: [
+ {
+ port,
+ chainId,
+ ganacheOptions2: defaultGanacheOptions,
+ },
+ ],
+ },
+ // This test intentionally quits Ganache while the extension is using it, causing
+ // PollingBlockTracker errors and others. These are expected.
+ ignoredConsoleErrors: ['ignore-all'],
+ dappOptions: { numberOfDapps: 2 },
+ title: this.test.fullTitle(),
+ },
+ async ({ driver, ganacheServer, secondaryGanacheServer }) => {
+ await unlockWallet(driver);
+
+ // Open the first dapp
+ await openDappAndSwitchChain(driver, DAPP_URL, '0x539');
+
+ // Open the second dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
+
+ // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ await driver.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Ethereum Mainnet',
+ });
+
+ // Kill ganache servers
+ await ganacheServer.quit();
+ await secondaryGanacheServer[0].quit();
+
+ // Go back to first dapp, try an action, ensure network connection failure doesn't block UI
+ await selectDappClickSend(driver, DAPP_URL);
+
+ // When the network is down, there is a performance degradation that causes the
+ // popup to take a few seconds to open in MV3 (issue #25690)
+ await driver.waitUntilXWindowHandles(4, 1000, 15000);
+
+ await switchToDialogPopoverValidateDetailsRedesign(driver, {
+ chainId: '0x539',
+ networkText: 'Localhost 8545',
+ originText: DAPP_URL,
+ });
+ },
+ );
+ });
});
});
diff --git a/test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js b/test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js
index 958854a5252c..c1ff9f3477ab 100644
--- a/test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js
+++ b/test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js
@@ -6,6 +6,7 @@ const {
logInWithBalanceValidation,
openActionMenuAndStartSendFlow,
withFixtures,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
@@ -21,6 +22,7 @@ describe('MetaMask Responsive UI', function () {
},
async ({ driver }) => {
await driver.navigate();
+
// agree to terms of use
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
@@ -129,6 +131,8 @@ describe('MetaMask Responsive UI', function () {
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Send ETH from inside MetaMask
// starts to send a transaction
await openActionMenuAndStartSendFlow(driver);
diff --git a/test/e2e/tests/settings/4byte-directory.spec.js b/test/e2e/tests/settings/4byte-directory.spec.js
index 483ff1e0149a..b36f72f0575c 100644
--- a/test/e2e/tests/settings/4byte-directory.spec.js
+++ b/test/e2e/tests/settings/4byte-directory.spec.js
@@ -6,6 +6,7 @@ const {
unlockWallet,
withFixtures,
WINDOW_TITLES,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const { SMART_CONTRACTS } = require('../../seeder/smart-contracts');
@@ -27,6 +28,8 @@ describe('4byte setting', function () {
);
await logInWithBalanceValidation(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// deploy contract
await openDapp(driver, contractAddress);
@@ -63,6 +66,8 @@ describe('4byte setting', function () {
);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// goes to the settings screen
await openMenuSafe(driver);
await driver.clickElement({ text: 'Settings', tag: 'div' });
diff --git a/test/e2e/tests/settings/show-hex-data.spec.js b/test/e2e/tests/settings/show-hex-data.spec.js
index 4bef79ca0a3b..353847a544b4 100644
--- a/test/e2e/tests/settings/show-hex-data.spec.js
+++ b/test/e2e/tests/settings/show-hex-data.spec.js
@@ -2,6 +2,7 @@ const {
defaultGanacheOptions,
withFixtures,
logInWithBalanceValidation,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
@@ -78,6 +79,9 @@ describe('Check the toggle for hex data', function () {
},
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await toggleHexData(driver);
await clickOnLogo(driver);
await sendTransactionAndVerifyHexData(driver);
diff --git a/test/e2e/tests/smart-transactions/mock-requests-for-swap-test.ts b/test/e2e/tests/smart-transactions/mock-requests-for-swap-test.ts
index 78d1497dc9cf..bf9740c257ad 100644
--- a/test/e2e/tests/smart-transactions/mock-requests-for-swap-test.ts
+++ b/test/e2e/tests/smart-transactions/mock-requests-for-swap-test.ts
@@ -60,7 +60,6 @@ const GET_BATCH_STATUS_RESPONSE_PENDING = {
minedTx: 'not_mined',
wouldRevertMessage: null,
minedHash: '',
- duplicated: false,
timedOut: false,
proxied: false,
type: 'sentinel',
@@ -77,7 +76,6 @@ const GET_BATCH_STATUS_RESPONSE_SUCCESS = {
wouldRevertMessage: null,
minedHash:
'0xec9d6214684d6dc191133ae4a7ec97db3e521fff9cfe5c4f48a84cb6c93a5fa5',
- duplicated: true,
timedOut: true,
proxied: false,
type: 'sentinel',
diff --git a/test/e2e/tests/tokens/custom-token-add-approve.spec.js b/test/e2e/tests/tokens/custom-token-add-approve.spec.js
index 4e85aae76fd6..9226ad1a36f1 100644
--- a/test/e2e/tests/tokens/custom-token-add-approve.spec.js
+++ b/test/e2e/tests/tokens/custom-token-add-approve.spec.js
@@ -1,5 +1,4 @@
const { strict: assert } = require('assert');
-
const {
clickNestedButton,
defaultGanacheOptions,
@@ -8,6 +7,7 @@ const {
openDapp,
WINDOW_TITLES,
withFixtures,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
const { SMART_CONTRACTS } = require('../../seeder/smart-contracts');
@@ -84,6 +84,8 @@ describe('Create token, approve token and approve token without gas', function (
);
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// create token
await openDapp(driver, contractAddress);
@@ -182,6 +184,8 @@ describe('Create token, approve token and approve token without gas', function (
);
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// create token
await openDapp(driver, contractAddress);
@@ -317,6 +321,8 @@ describe('Create token, approve token and approve token without gas', function (
);
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// create token
await openDapp(driver, contractAddress);
const windowHandles = await driver.getAllWindowHandles();
@@ -398,6 +404,8 @@ describe('Create token, approve token and approve token without gas', function (
);
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await openDapp(driver, contractAddress);
const windowHandles = await driver.getAllWindowHandles();
const extension = windowHandles[0];
diff --git a/test/e2e/tests/tokens/custom-token-send-transfer.spec.js b/test/e2e/tests/tokens/custom-token-send-transfer.spec.js
index 40b1872011bd..0c5498a82cca 100644
--- a/test/e2e/tests/tokens/custom-token-send-transfer.spec.js
+++ b/test/e2e/tests/tokens/custom-token-send-transfer.spec.js
@@ -8,6 +8,8 @@ const {
editGasFeeForm,
WINDOW_TITLES,
clickNestedButton,
+ tempToggleSettingRedesignedTransactionConfirmations,
+ veryLargeDelayMs,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
const { SMART_CONTRACTS } = require('../../seeder/smart-contracts');
@@ -28,6 +30,8 @@ describe('Transfer custom tokens @no-mmi', function () {
async ({ driver }) => {
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// go to custom tokens view on extension, perform send tokens
await driver.clickElement({
css: '[data-testid="multichain-token-list-item-value"]',
@@ -115,10 +119,15 @@ describe('Transfer custom tokens @no-mmi', function () {
);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// transfer token from dapp
await openDapp(driver, contractAddress);
+ await driver.delay(veryLargeDelayMs);
+
await driver.clickElement({ text: 'Transfer Tokens', tag: 'button' });
- await switchToNotificationWindow(driver);
+
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
await driver.waitForSelector({ text: '1.5 TST', tag: 'h1' });
// edit gas fee
@@ -174,8 +183,11 @@ describe('Transfer custom tokens @no-mmi', function () {
);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// transfer token from dapp
await openDapp(driver, contractAddress);
+ await driver.delay(veryLargeDelayMs);
await driver.clickElement({
text: 'Transfer Tokens Without Gas',
tag: 'button',
diff --git a/test/e2e/tests/tokens/increase-token-allowance.spec.js b/test/e2e/tests/tokens/increase-token-allowance.spec.js
index 7df956e9df43..9ce8db2cc065 100644
--- a/test/e2e/tests/tokens/increase-token-allowance.spec.js
+++ b/test/e2e/tests/tokens/increase-token-allowance.spec.js
@@ -10,6 +10,7 @@ const {
ACCOUNT_2,
WINDOW_TITLES,
clickNestedButton,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const { SMART_CONTRACTS } = require('../../seeder/smart-contracts');
@@ -38,6 +39,8 @@ describe('Increase Token Allowance', function () {
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
const contractAddress = await contractRegistry.getContractAddress(
smartContract,
);
diff --git a/test/e2e/tests/tokens/nft/erc1155-interaction.spec.js b/test/e2e/tests/tokens/nft/erc1155-interaction.spec.js
index c635d465353a..388247bb3fcd 100644
--- a/test/e2e/tests/tokens/nft/erc1155-interaction.spec.js
+++ b/test/e2e/tests/tokens/nft/erc1155-interaction.spec.js
@@ -7,6 +7,8 @@ const {
unlockWallet,
WINDOW_TITLES,
defaultGanacheOptions,
+ tempToggleSettingRedesignedTransactionConfirmations,
+ veryLargeDelayMs,
} = require('../../../helpers');
const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');
const FixtureBuilder = require('../../../fixture-builder');
@@ -38,6 +40,8 @@ describe('ERC1155 NFTs testdapp interaction', function () {
const contract = contractRegistry.getContractAddress(smartContract);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Open Dapp and wait for deployed contract
await openDapp(driver, contract);
await driver.findClickableElement('#deployButton');
@@ -118,6 +122,8 @@ describe('ERC1155 NFTs testdapp interaction', function () {
const contract = contractRegistry.getContractAddress(smartContract);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await openDapp(driver, contract);
await driver.fill('#batchTransferTokenIds', '1, 2, 3');
@@ -170,6 +176,8 @@ describe('ERC1155 NFTs testdapp interaction', function () {
const contract = contractRegistry.getContractAddress(smartContract);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Create a set approval for all erc1155 token request in test dapp
await openDapp(driver, contract);
await driver.clickElement('#setApprovalForAllERC1155Button');
@@ -254,8 +262,13 @@ describe('ERC1155 NFTs testdapp interaction', function () {
const contract = contractRegistry.getContractAddress(smartContract);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Create a revoke approval for all erc1155 token request in test dapp
await openDapp(driver, contract);
+
+ await driver.delay(veryLargeDelayMs);
+
await driver.clickElement('#revokeERC1155Button');
// Wait for notification popup and check the displayed message
diff --git a/test/e2e/tests/tokens/nft/erc721-interaction.spec.js b/test/e2e/tests/tokens/nft/erc721-interaction.spec.js
index 35750bae6d2c..8b634ffc3ce3 100644
--- a/test/e2e/tests/tokens/nft/erc721-interaction.spec.js
+++ b/test/e2e/tests/tokens/nft/erc721-interaction.spec.js
@@ -6,6 +6,7 @@ const {
WINDOW_TITLES,
defaultGanacheOptions,
clickNestedButton,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../../helpers');
const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');
const FixtureBuilder = require('../../../fixture-builder');
@@ -28,6 +29,8 @@ describe('ERC721 NFTs testdapp interaction', function () {
const contract = contractRegistry.getContractAddress(smartContract);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Open Dapp and wait for deployed contract
await openDapp(driver, contract);
await driver.findClickableElement('#deployButton');
@@ -91,6 +94,8 @@ describe('ERC721 NFTs testdapp interaction', function () {
const contract = contractRegistry.getContractAddress(smartContract);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Open Dapp and wait for deployed contract
await openDapp(driver, contract);
await driver.findClickableElement('#deployButton');
@@ -212,6 +217,8 @@ describe('ERC721 NFTs testdapp interaction', function () {
const contract = contractRegistry.getContractAddress(smartContract);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Open Dapp and wait for deployed contract
await openDapp(driver, contract);
await driver.findClickableElement('#deployButton');
@@ -310,6 +317,8 @@ describe('ERC721 NFTs testdapp interaction', function () {
const contract = contractRegistry.getContractAddress(smartContract);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Open Dapp and wait for deployed contract
await openDapp(driver, contract);
await driver.findClickableElement('#deployButton');
@@ -357,6 +366,8 @@ describe('ERC721 NFTs testdapp interaction', function () {
const contract = contractRegistry.getContractAddress(smartContract);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Open Dapp and wait for deployed contract
await openDapp(driver, contract);
await driver.findClickableElement('#deployButton');
@@ -424,6 +435,8 @@ describe('ERC721 NFTs testdapp interaction', function () {
const contract = contractRegistry.getContractAddress(smartContract);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Open Dapp and wait for deployed contract
await openDapp(driver, contract);
await driver.findClickableElement('#deployButton');
@@ -490,6 +503,8 @@ describe('ERC721 NFTs testdapp interaction', function () {
const contract = contractRegistry.getContractAddress(smartContract);
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Open Dapp and wait for deployed contract
await openDapp(driver, contract);
await driver.findClickableElement('#deployButton');
diff --git a/test/e2e/tests/tokens/nft/import-nft.spec.ts b/test/e2e/tests/tokens/nft/import-nft.spec.ts
index 73049a8c9d0d..bc709ba463c0 100644
--- a/test/e2e/tests/tokens/nft/import-nft.spec.ts
+++ b/test/e2e/tests/tokens/nft/import-nft.spec.ts
@@ -65,7 +65,7 @@ describe('Import NFT', function () {
await headerNavbar.openAccountMenu();
const accountListPage = new AccountListPage(driver);
await accountListPage.check_pageIsLoaded();
- await accountListPage.addNewAccountWithDefaultName();
+ await accountListPage.addNewAccount();
await headerNavbar.check_accountLabel('Account 2');
await homepage.check_expectedBalanceIsDisplayed();
@@ -75,7 +75,7 @@ describe('Import NFT', function () {
await accountListPage.check_accountDisplayedInAccountList('Account 1');
await accountListPage.switchToAccount('Account 1');
await headerNavbar.check_accountLabel('Account 1');
- await homepage.check_ganacheBalanceIsDisplayed(ganacheServer);
+ await homepage.check_localBlockchainBalanceIsDisplayed(ganacheServer);
await homepage.check_nftNameIsDisplayed('TestDappNFTs');
await homepage.check_nftImageIsDisplayed();
},
diff --git a/test/e2e/tests/tokens/nft/send-nft.spec.js b/test/e2e/tests/tokens/nft/send-nft.spec.js
index 35585bbaf2ea..13cb310f1416 100644
--- a/test/e2e/tests/tokens/nft/send-nft.spec.js
+++ b/test/e2e/tests/tokens/nft/send-nft.spec.js
@@ -4,6 +4,7 @@ const {
logInWithBalanceValidation,
unlockWallet,
withFixtures,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../../helpers');
const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');
const FixtureBuilder = require('../../../fixture-builder');
@@ -24,6 +25,8 @@ describe('Send NFT', function () {
async ({ driver }) => {
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Fill the send NFT form and confirm the transaction
await driver.clickElement('[data-testid="account-overview__nfts-tab"]');
await driver.clickElement('.nft-item__container');
diff --git a/test/e2e/tests/transaction/change-assets.spec.js b/test/e2e/tests/transaction/change-assets.spec.js
index 7ce971fd8d80..f6a997c164e9 100644
--- a/test/e2e/tests/transaction/change-assets.spec.js
+++ b/test/e2e/tests/transaction/change-assets.spec.js
@@ -3,6 +3,7 @@ const {
defaultGanacheOptions,
withFixtures,
logInWithBalanceValidation,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
const { SMART_CONTRACTS } = require('../../seeder/smart-contracts');
@@ -22,6 +23,8 @@ describe('Change assets', function () {
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Wait for balance to load
await driver.delay(500);
@@ -100,6 +103,8 @@ describe('Change assets', function () {
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Click the Send button
await driver.clickElement({
css: '[data-testid="multichain-token-list-button"] span',
@@ -179,6 +184,8 @@ describe('Change assets', function () {
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Choose the nft
await driver.clickElement('[data-testid="account-overview__nfts-tab"]');
await driver.clickElement('[data-testid="nft-default-image"]');
@@ -266,6 +273,8 @@ describe('Change assets', function () {
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Create second account
await driver.clickElement('[data-testid="account-menu-icon"]');
await driver.clickElement(
diff --git a/test/e2e/tests/transaction/edit-gas-fee.spec.js b/test/e2e/tests/transaction/edit-gas-fee.spec.js
index 918831f8f3ad..3e7655750594 100644
--- a/test/e2e/tests/transaction/edit-gas-fee.spec.js
+++ b/test/e2e/tests/transaction/edit-gas-fee.spec.js
@@ -9,6 +9,7 @@ const {
unlockWallet,
generateGanacheOptions,
WINDOW_TITLES,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
@@ -22,6 +23,9 @@ describe('Editing Confirm Transaction', function () {
},
async ({ driver }) => {
await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await createInternalTransaction(driver);
await driver.findElement({
@@ -95,6 +99,9 @@ describe('Editing Confirm Transaction', function () {
},
async ({ driver }) => {
await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await createInternalTransaction(driver);
await driver.findElement({
@@ -172,6 +179,8 @@ describe('Editing Confirm Transaction', function () {
// login to extension
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await createDappTransaction(driver, {
maxFeePerGas: '0x2000000000',
maxPriorityFeePerGas: '0x1000000000',
diff --git a/test/e2e/tests/transaction/gas-estimates.spec.js b/test/e2e/tests/transaction/gas-estimates.spec.js
index 263dfc85d904..f12275ad4d9f 100644
--- a/test/e2e/tests/transaction/gas-estimates.spec.js
+++ b/test/e2e/tests/transaction/gas-estimates.spec.js
@@ -3,6 +3,7 @@ const {
logInWithBalanceValidation,
openActionMenuAndStartSendFlow,
generateGanacheOptions,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
const { CHAIN_IDS } = require('../../../../shared/constants/network');
@@ -27,6 +28,8 @@ describe('Gas estimates generated by MetaMask', function () {
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await openActionMenuAndStartSendFlow(driver);
await driver.fill(
@@ -69,6 +72,8 @@ describe('Gas estimates generated by MetaMask', function () {
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await openActionMenuAndStartSendFlow(driver);
await driver.fill(
@@ -108,6 +113,8 @@ describe('Gas estimates generated by MetaMask', function () {
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await openActionMenuAndStartSendFlow(driver);
await driver.fill(
@@ -143,6 +150,8 @@ describe('Gas estimates generated by MetaMask', function () {
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await openActionMenuAndStartSendFlow(driver);
await driver.fill(
'input[placeholder="Enter public address (0x) or domain name"]',
@@ -189,6 +198,8 @@ describe('Gas estimates generated by MetaMask', function () {
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await openActionMenuAndStartSendFlow(driver);
await driver.fill(
'input[placeholder="Enter public address (0x) or domain name"]',
@@ -218,6 +229,8 @@ describe('Gas estimates generated by MetaMask', function () {
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await openActionMenuAndStartSendFlow(driver);
await driver.fill(
'input[placeholder="Enter public address (0x) or domain name"]',
diff --git a/test/e2e/tests/transaction/multiple-transactions.spec.js b/test/e2e/tests/transaction/multiple-transactions.spec.js
index 4d913cb07edb..b7ad05b3a93d 100644
--- a/test/e2e/tests/transaction/multiple-transactions.spec.js
+++ b/test/e2e/tests/transaction/multiple-transactions.spec.js
@@ -6,6 +6,7 @@ const {
unlockWallet,
generateGanacheOptions,
WINDOW_TITLES,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
@@ -23,6 +24,8 @@ describe('Multiple transactions', function () {
async ({ driver }) => {
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// initiates a transaction from the dapp
await openDapp(driver);
// creates first transaction
@@ -85,6 +88,8 @@ describe('Multiple transactions', function () {
async ({ driver }) => {
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// initiates a transaction from the dapp
await openDapp(driver);
// creates first transaction
diff --git a/test/e2e/tests/transaction/navigate-transactions.spec.js b/test/e2e/tests/transaction/navigate-transactions.spec.js
index 63170d027874..16e8f9374cb5 100644
--- a/test/e2e/tests/transaction/navigate-transactions.spec.js
+++ b/test/e2e/tests/transaction/navigate-transactions.spec.js
@@ -1,7 +1,6 @@
const {
createDappTransaction,
} = require('../../page-objects/flows/transaction');
-
const {
default: ConfirmationNavigation,
} = require('../../page-objects/pages/confirmations/legacy/navigation');
@@ -13,6 +12,7 @@ const {
unlockWallet,
generateGanacheOptions,
WINDOW_TITLES,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
@@ -32,6 +32,9 @@ describe('Navigate transactions', function () {
},
async ({ driver }) => {
await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await createMultipleTransactions(driver, TRANSACTION_COUNT);
const navigation = new ConfirmationNavigation(driver);
@@ -73,6 +76,9 @@ describe('Navigate transactions', function () {
},
async ({ driver }) => {
await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await createMultipleTransactions(driver, TRANSACTION_COUNT);
const navigation = new ConfirmationNavigation(driver);
@@ -107,6 +113,9 @@ describe('Navigate transactions', function () {
},
async ({ driver }) => {
await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await createMultipleTransactions(driver, TRANSACTION_COUNT);
// reject transaction
@@ -131,6 +140,9 @@ describe('Navigate transactions', function () {
},
async ({ driver }) => {
await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await createMultipleTransactions(driver, TRANSACTION_COUNT);
// confirm transaction
@@ -155,6 +167,9 @@ describe('Navigate transactions', function () {
},
async ({ driver, ganacheServer }) => {
await unlockWallet(driver);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await createMultipleTransactions(driver, TRANSACTION_COUNT);
// reject transactions
diff --git a/test/e2e/tests/transaction/send-edit.spec.js b/test/e2e/tests/transaction/send-edit.spec.js
index 953f2ebf3569..8d19d6d071b1 100644
--- a/test/e2e/tests/transaction/send-edit.spec.js
+++ b/test/e2e/tests/transaction/send-edit.spec.js
@@ -8,6 +8,7 @@ const {
withFixtures,
unlockWallet,
generateGanacheOptions,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
@@ -21,6 +22,7 @@ describe('Editing Confirm Transaction', function () {
},
async ({ driver }) => {
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
await createInternalTransaction(driver);
await driver.findElement({
@@ -96,6 +98,8 @@ describe('Editing Confirm Transaction', function () {
},
async ({ driver }) => {
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await createInternalTransaction(driver);
await driver.findElement({
diff --git a/test/e2e/tests/transaction/send-eth.spec.js b/test/e2e/tests/transaction/send-eth.spec.js
index 5cbcb8309a18..9ee1b58dc170 100644
--- a/test/e2e/tests/transaction/send-eth.spec.js
+++ b/test/e2e/tests/transaction/send-eth.spec.js
@@ -9,6 +9,7 @@ const {
editGasFeeForm,
WINDOW_TITLES,
defaultGanacheOptions,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const FixtureBuilder = require('../../fixture-builder');
@@ -106,6 +107,8 @@ describe('Send ETH', function () {
async ({ driver }) => {
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await driver.delay(1000);
await openActionMenuAndStartSendFlow(driver);
@@ -256,6 +259,8 @@ describe('Send ETH', function () {
async ({ driver }) => {
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// initiates a send from the dapp
await openDapp(driver);
await driver.clickElement({ text: 'Send', tag: 'button' });
@@ -332,6 +337,8 @@ describe('Send ETH', function () {
async ({ driver }) => {
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// initiates a transaction from the dapp
await openDapp(driver);
await driver.clickElement({ text: 'Create Token', tag: 'button' });
@@ -435,6 +442,8 @@ describe('Send ETH', function () {
async ({ driver }) => {
await unlockWallet(driver);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await driver.assertElementNotPresent('.loading-overlay__spinner');
const balance = await driver.findElement(
'[data-testid="eth-overview__primary-currency"]',
diff --git a/test/e2e/tests/transaction/send-hex-address.spec.js b/test/e2e/tests/transaction/send-hex-address.spec.js
index d93f1a0d5484..b6ad969c6735 100644
--- a/test/e2e/tests/transaction/send-hex-address.spec.js
+++ b/test/e2e/tests/transaction/send-hex-address.spec.js
@@ -3,6 +3,7 @@ const {
withFixtures,
logInWithBalanceValidation,
openActionMenuAndStartSendFlow,
+ tempToggleSettingRedesignedTransactionConfirmations,
} = require('../../helpers');
const { SMART_CONTRACTS } = require('../../seeder/smart-contracts');
const FixtureBuilder = require('../../fixture-builder');
@@ -120,6 +121,8 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () {
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Send TST
await driver.clickElement(
'[data-testid="account-overview__asset-tab"]',
@@ -181,6 +184,9 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () {
},
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
// Send TST
await driver.clickElement(
'[data-testid="account-overview__asset-tab"]',
diff --git a/test/e2e/tests/transaction/simple-send.spec.ts b/test/e2e/tests/transaction/simple-send.spec.ts
index 25f2368a9cfc..7d2f4835cdca 100644
--- a/test/e2e/tests/transaction/simple-send.spec.ts
+++ b/test/e2e/tests/transaction/simple-send.spec.ts
@@ -1,7 +1,11 @@
import { Suite } from 'mocha';
import { Driver } from '../../webdriver/driver';
import { Ganache } from '../../seeder/ganache';
-import { withFixtures, defaultGanacheOptions } from '../../helpers';
+import {
+ withFixtures,
+ defaultGanacheOptions,
+ tempToggleSettingRedesignedTransactionConfirmations,
+} from '../../helpers';
import FixtureBuilder from '../../fixture-builder';
import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow';
import { sendTransactionToAddress } from '../../page-objects/flows/send-transaction.flow';
@@ -23,6 +27,9 @@ describe('Simple send eth', function (this: Suite) {
ganacheServer?: Ganache;
}) => {
await loginWithBalanceValidation(driver, ganacheServer);
+
+ await tempToggleSettingRedesignedTransactionConfirmations(driver);
+
await sendTransactionToAddress({
driver,
recipientAddress: '0x985c30949c92df7a0bd42e0f3e3d539ece98db24',
diff --git a/test/integration/confirmations/signatures/permit.test.tsx b/test/integration/confirmations/signatures/permit.test.tsx
index 5ff87bf7c533..7af3be743f5f 100644
--- a/test/integration/confirmations/signatures/permit.test.tsx
+++ b/test/integration/confirmations/signatures/permit.test.tsx
@@ -103,25 +103,29 @@ describe('Permit Confirmation', () => {
});
});
- expect(screen.getByTestId('header-account-name')).toHaveTextContent(
+ expect(await screen.findByTestId('header-account-name')).toHaveTextContent(
accountName,
);
- expect(screen.getByTestId('header-network-display-name')).toHaveTextContent(
- 'Sepolia',
- );
+ expect(
+ await screen.findByTestId('header-network-display-name'),
+ ).toHaveTextContent('Sepolia');
- fireEvent.click(screen.getByTestId('header-info__account-details-button'));
+ fireEvent.click(
+ await screen.findByTestId('header-info__account-details-button'),
+ );
expect(
await screen.findByTestId(
'confirmation-account-details-modal__account-name',
),
).toHaveTextContent(accountName);
- expect(screen.getByTestId('address-copy-button-text')).toHaveTextContent(
- '0x0DCD5...3E7bc',
- );
expect(
- screen.getByTestId('confirmation-account-details-modal__account-balance'),
+ await screen.findByTestId('address-copy-button-text'),
+ ).toHaveTextContent('0x0DCD5...3E7bc');
+ expect(
+ await screen.findByTestId(
+ 'confirmation-account-details-modal__account-balance',
+ ),
).toHaveTextContent('1.582717SepoliaETH');
let confirmAccountDetailsModalMetricsEvent;
@@ -154,7 +158,9 @@ describe('Permit Confirmation', () => {
);
fireEvent.click(
- screen.getByTestId('confirmation-account-details-modal__close-button'),
+ await screen.findByTestId(
+ 'confirmation-account-details-modal__close-button',
+ ),
);
await waitFor(() => {
@@ -252,7 +258,7 @@ describe('Permit Confirmation', () => {
});
});
- const simulationSection = screen.getByTestId(
+ const simulationSection = await screen.findByTestId(
'confirmation__simulation_section',
);
expect(simulationSection).toBeInTheDocument();
@@ -262,9 +268,9 @@ describe('Permit Confirmation', () => {
);
expect(simulationSection).toHaveTextContent('Spending cap');
expect(simulationSection).toHaveTextContent('0xCcCCc...ccccC');
- expect(screen.getByTestId('simulation-token-value')).toHaveTextContent(
- '30',
- );
+ expect(
+ await screen.findByTestId('simulation-token-value'),
+ ).toHaveTextContent('30');
const individualFiatDisplay = await screen.findByTestId(
'individual-fiat-display',
@@ -303,6 +309,6 @@ describe('Permit Confirmation', () => {
account.address,
)})`;
- expect(screen.getByText(mismatchAccountText)).toBeInTheDocument();
+ expect(await screen.findByText(mismatchAccountText)).toBeInTheDocument();
});
});
diff --git a/test/integration/confirmations/signatures/personalSign.test.tsx b/test/integration/confirmations/signatures/personalSign.test.tsx
index 5a9c311c9abd..03685f46ab7b 100644
--- a/test/integration/confirmations/signatures/personalSign.test.tsx
+++ b/test/integration/confirmations/signatures/personalSign.test.tsx
@@ -82,17 +82,20 @@ describe('PersonalSign Confirmation', () => {
account.address,
);
- const { getByTestId, queryByTestId } = await integrationTestRender({
- preloadedState: mockedMetaMaskState,
- backgroundConnection: backgroundConnectionMocked,
- });
+ const { findByTestId, getByTestId, queryByTestId } =
+ await integrationTestRender({
+ preloadedState: mockedMetaMaskState,
+ backgroundConnection: backgroundConnectionMocked,
+ });
- expect(getByTestId('header-account-name')).toHaveTextContent(accountName);
- expect(getByTestId('header-network-display-name')).toHaveTextContent(
+ expect(await findByTestId('header-account-name')).toHaveTextContent(
+ accountName,
+ );
+ expect(await findByTestId('header-network-display-name')).toHaveTextContent(
'Sepolia',
);
- fireEvent.click(getByTestId('header-info__account-details-button'));
+ fireEvent.click(await findByTestId('header-info__account-details-button'));
await waitFor(() => {
expect(
@@ -101,13 +104,13 @@ describe('PersonalSign Confirmation', () => {
});
expect(
- getByTestId('confirmation-account-details-modal__account-name'),
+ await findByTestId('confirmation-account-details-modal__account-name'),
).toHaveTextContent(accountName);
- expect(getByTestId('address-copy-button-text')).toHaveTextContent(
+ expect(await findByTestId('address-copy-button-text')).toHaveTextContent(
'0x0DCD5...3E7bc',
);
expect(
- getByTestId('confirmation-account-details-modal__account-balance'),
+ await findByTestId('confirmation-account-details-modal__account-balance'),
).toHaveTextContent('1.582717SepoliaETH');
let confirmAccountDetailsModalMetricsEvent;
@@ -137,7 +140,7 @@ describe('PersonalSign Confirmation', () => {
);
fireEvent.click(
- getByTestId('confirmation-account-details-modal__close-button'),
+ await findByTestId('confirmation-account-details-modal__close-button'),
);
await waitFor(() => {
@@ -165,9 +168,9 @@ describe('PersonalSign Confirmation', () => {
});
});
- expect(screen.getByText('Signature request')).toBeInTheDocument();
+ expect(await screen.findByText('Signature request')).toBeInTheDocument();
expect(
- screen.getByText('Review request details before you confirm.'),
+ await screen.findByText('Review request details before you confirm.'),
).toBeInTheDocument();
});
@@ -186,7 +189,7 @@ describe('PersonalSign Confirmation', () => {
account.address,
);
- const { getByText } = await integrationTestRender({
+ const { findByText } = await integrationTestRender({
preloadedState: mockedMetaMaskState,
backgroundConnection: backgroundConnectionMocked,
});
@@ -197,6 +200,6 @@ describe('PersonalSign Confirmation', () => {
account.address,
)})`;
- expect(getByText(mismatchAccountText)).toBeInTheDocument();
+ expect(await findByText(mismatchAccountText)).toBeInTheDocument();
});
});
diff --git a/test/integration/confirmations/transactions/alerts.test.tsx b/test/integration/confirmations/transactions/alerts.test.tsx
index 74d37c858b0f..1bbf9d1fd2c5 100644
--- a/test/integration/confirmations/transactions/alerts.test.tsx
+++ b/test/integration/confirmations/transactions/alerts.test.tsx
@@ -129,7 +129,7 @@ describe('Contract Interaction Confirmation Alerts', () => {
});
});
- fireEvent.click(screen.getByTestId('inline-alert'));
+ fireEvent.click(await screen.findByTestId('inline-alert'));
expect(await screen.findByTestId('alert-modal')).toBeInTheDocument();
@@ -182,7 +182,7 @@ describe('Contract Interaction Confirmation Alerts', () => {
});
});
- fireEvent.click(screen.getByTestId('inline-alert'));
+ fireEvent.click(await screen.findByTestId('inline-alert'));
expect(await screen.findByTestId('alert-modal')).toBeInTheDocument();
@@ -228,7 +228,7 @@ describe('Contract Interaction Confirmation Alerts', () => {
});
});
- fireEvent.click(screen.getByTestId('inline-alert'));
+ fireEvent.click(await screen.findByTestId('inline-alert'));
expect(await screen.findByTestId('alert-modal')).toBeInTheDocument();
@@ -274,7 +274,7 @@ describe('Contract Interaction Confirmation Alerts', () => {
});
});
- fireEvent.click(screen.getByTestId('inline-alert'));
+ fireEvent.click(await screen.findByTestId('inline-alert'));
expect(await screen.findByTestId('alert-modal')).toBeInTheDocument();
@@ -349,9 +349,9 @@ describe('Contract Interaction Confirmation Alerts', () => {
});
});
- expect(await screen.getByTestId('inline-alert')).toBeInTheDocument();
+ expect(await screen.findByTestId('inline-alert')).toBeInTheDocument();
- fireEvent.click(screen.getByTestId('inline-alert'));
+ fireEvent.click(await screen.findByTestId('inline-alert'));
expect(await screen.findByTestId('alert-modal')).toBeInTheDocument();
@@ -390,7 +390,7 @@ describe('Contract Interaction Confirmation Alerts', () => {
});
});
- fireEvent.click(screen.getByTestId('inline-alert'));
+ fireEvent.click(await screen.findByTestId('inline-alert'));
expect(await screen.findByTestId('alert-modal')).toBeInTheDocument();
diff --git a/test/integration/confirmations/transactions/contract-deployment.test.tsx b/test/integration/confirmations/transactions/contract-deployment.test.tsx
index 67862e6b9550..7698ce3ef8b2 100644
--- a/test/integration/confirmations/transactions/contract-deployment.test.tsx
+++ b/test/integration/confirmations/transactions/contract-deployment.test.tsx
@@ -162,25 +162,29 @@ describe('Contract Deployment Confirmation', () => {
});
});
- expect(screen.getByTestId('header-account-name')).toHaveTextContent(
+ expect(await screen.findByTestId('header-account-name')).toHaveTextContent(
accountName,
);
- expect(screen.getByTestId('header-network-display-name')).toHaveTextContent(
- 'Sepolia',
- );
+ expect(
+ await screen.findByTestId('header-network-display-name'),
+ ).toHaveTextContent('Sepolia');
- fireEvent.click(screen.getByTestId('header-info__account-details-button'));
+ fireEvent.click(
+ await screen.findByTestId('header-info__account-details-button'),
+ );
expect(
await screen.findByTestId(
'confirmation-account-details-modal__account-name',
),
).toHaveTextContent(accountName);
- expect(screen.getByTestId('address-copy-button-text')).toHaveTextContent(
- '0x0DCD5...3E7bc',
- );
expect(
- screen.getByTestId('confirmation-account-details-modal__account-balance'),
+ await screen.findByTestId('address-copy-button-text'),
+ ).toHaveTextContent('0x0DCD5...3E7bc');
+ expect(
+ await screen.findByTestId(
+ 'confirmation-account-details-modal__account-balance',
+ ),
).toHaveTextContent('1.582717SepoliaETH');
let confirmAccountDetailsModalMetricsEvent;
@@ -213,7 +217,9 @@ describe('Contract Deployment Confirmation', () => {
);
fireEvent.click(
- screen.getByTestId('confirmation-account-details-modal__close-button'),
+ await screen.findByTestId(
+ 'confirmation-account-details-modal__close-button',
+ ),
);
await waitFor(() => {
@@ -245,10 +251,12 @@ describe('Contract Deployment Confirmation', () => {
});
expect(
- screen.getByText(tEn('confirmTitleDeployContract') as string),
+ await screen.findByText(tEn('confirmTitleDeployContract') as string),
).toBeInTheDocument();
- const simulationSection = screen.getByTestId('simulation-details-layout');
+ const simulationSection = await screen.findByTestId(
+ 'simulation-details-layout',
+ );
expect(simulationSection).toBeInTheDocument();
expect(simulationSection).toHaveTextContent(
tEn('simulationDetailsTitle') as string,
@@ -261,10 +269,10 @@ describe('Contract Deployment Confirmation', () => {
tEn('simulationDetailsIncomingHeading') as string,
);
expect(simulationDetailsRow).toContainElement(
- screen.getByTestId('simulation-details-amount-pill'),
+ await screen.findByTestId('simulation-details-amount-pill'),
);
- const transactionDetailsSection = screen.getByTestId(
+ const transactionDetailsSection = await screen.findByTestId(
'transaction-details-section',
);
expect(transactionDetailsSection).toBeInTheDocument();
@@ -275,25 +283,30 @@ describe('Contract Deployment Confirmation', () => {
tEn('interactingWith') as string,
);
- const gasFeesSection = screen.getByTestId('gas-fee-section');
+ const gasFeesSection = await screen.findByTestId('gas-fee-section');
expect(gasFeesSection).toBeInTheDocument();
- const editGasFeesRow =
- within(gasFeesSection).getByTestId('edit-gas-fees-row');
+ const editGasFeesRow = await within(gasFeesSection).findByTestId(
+ 'edit-gas-fees-row',
+ );
expect(editGasFeesRow).toHaveTextContent(tEn('networkFee') as string);
- const firstGasField = within(editGasFeesRow).getByTestId('first-gas-field');
+ const firstGasField = await within(editGasFeesRow).findByTestId(
+ 'first-gas-field',
+ );
expect(firstGasField).toHaveTextContent('0.0001 SepoliaETH');
expect(editGasFeesRow).toContainElement(
- screen.getByTestId('edit-gas-fee-icon'),
+ await screen.findByTestId('edit-gas-fee-icon'),
);
- const gasFeeSpeed = within(gasFeesSection).getByTestId(
+ const gasFeeSpeed = await within(gasFeesSection).findByTestId(
'gas-fee-details-speed',
);
expect(gasFeeSpeed).toHaveTextContent(tEn('speed') as string);
- const gasTimingTime = within(gasFeeSpeed).getByTestId('gas-timing-time');
+ const gasTimingTime = await within(gasFeeSpeed).findByTestId(
+ 'gas-timing-time',
+ );
expect(gasTimingTime).toHaveTextContent('~0 sec');
});
@@ -321,7 +334,9 @@ describe('Contract Deployment Confirmation', () => {
});
});
- fireEvent.click(screen.getByTestId('header-advanced-details-button'));
+ fireEvent.click(
+ await screen.findByTestId('header-advanced-details-button'),
+ );
await waitFor(() => {
expect(
@@ -364,28 +379,32 @@ describe('Contract Deployment Confirmation', () => {
).toHaveBeenCalledWith('getNextNonce', expect.anything());
});
- const gasFeesSection = screen.getByTestId('gas-fee-section');
- const maxFee = screen.getByTestId('gas-fee-details-max-fee');
+ const gasFeesSection = await screen.findByTestId('gas-fee-section');
+ const maxFee = await screen.findByTestId('gas-fee-details-max-fee');
expect(gasFeesSection).toContainElement(maxFee);
expect(maxFee).toHaveTextContent(tEn('maxFee') as string);
expect(maxFee).toHaveTextContent('0.0023 SepoliaETH');
- const nonceSection = screen.getByTestId('advanced-details-nonce-section');
+ const nonceSection = await screen.findByTestId(
+ 'advanced-details-nonce-section',
+ );
expect(nonceSection).toBeInTheDocument();
expect(nonceSection).toHaveTextContent(
tEn('advancedDetailsNonceDesc') as string,
);
expect(nonceSection).toContainElement(
- screen.getByTestId('advanced-details-displayed-nonce'),
+ await screen.findByTestId('advanced-details-displayed-nonce'),
);
expect(
- screen.getByTestId('advanced-details-displayed-nonce'),
+ await screen.findByTestId('advanced-details-displayed-nonce'),
).toHaveTextContent('9');
- const dataSection = screen.getByTestId('advanced-details-data-section');
+ const dataSection = await screen.findByTestId(
+ 'advanced-details-data-section',
+ );
expect(dataSection).toBeInTheDocument();
- const dataSectionFunction = screen.getByTestId(
+ const dataSectionFunction = await screen.findByTestId(
'advanced-details-data-function',
);
expect(dataSection).toContainElement(dataSectionFunction);
@@ -394,7 +413,7 @@ describe('Contract Deployment Confirmation', () => {
);
expect(dataSectionFunction).toHaveTextContent('Deposit');
- const transactionDataParams = screen.getByTestId(
+ const transactionDataParams = await screen.findByTestId(
'advanced-details-data-param-0',
);
expect(dataSection).toContainElement(transactionDataParams);
diff --git a/test/integration/confirmations/transactions/contract-interaction.test.tsx b/test/integration/confirmations/transactions/contract-interaction.test.tsx
index 9a955e1a45fb..5db121bc9fda 100644
--- a/test/integration/confirmations/transactions/contract-interaction.test.tsx
+++ b/test/integration/confirmations/transactions/contract-interaction.test.tsx
@@ -23,6 +23,8 @@ import {
getUnapprovedContractInteractionTransaction,
} from './transactionDataHelpers';
+jest.setTimeout(20_000);
+
jest.mock('../../../../ui/store/background-connection', () => ({
...jest.requireActual('../../../../ui/store/background-connection'),
submitRequestToBackground: jest.fn(),
@@ -180,20 +182,25 @@ describe('Contract Interaction Confirmation', () => {
});
});
- expect(screen.getByTestId('header-account-name')).toHaveTextContent(
+ await screen.findByText(accountName);
+ expect(await screen.findByTestId('header-account-name')).toHaveTextContent(
accountName,
);
- expect(screen.getByTestId('header-network-display-name')).toHaveTextContent(
- 'Sepolia',
- );
+ expect(
+ await screen.findByTestId('header-network-display-name'),
+ ).toHaveTextContent('Sepolia');
- fireEvent.click(screen.getByTestId('header-info__account-details-button'));
+ await act(async () => {
+ fireEvent.click(
+ await screen.findByTestId('header-info__account-details-button'),
+ );
+ });
- expect(
- await screen.findByTestId(
- 'confirmation-account-details-modal__account-name',
- ),
- ).toHaveTextContent(accountName);
+ await waitFor(() => {
+ expect(
+ screen.getByTestId('confirmation-account-details-modal__account-name'),
+ ).toHaveTextContent(accountName);
+ });
expect(screen.getByTestId('address-copy-button-text')).toHaveTextContent(
'0x0DCD5...3E7bc',
);
@@ -214,21 +221,21 @@ describe('Contract Interaction Confirmation', () => {
expect(confirmAccountDetailsModalMetricsEvent?.[0]).toBe(
'trackMetaMetricsEvent',
);
- });
- expect(confirmAccountDetailsModalMetricsEvent?.[1]).toEqual(
- expect.arrayContaining([
- expect.objectContaining({
- category: MetaMetricsEventCategory.Confirmations,
- event: MetaMetricsEventName.AccountDetailsOpened,
- properties: {
- action: 'Confirm Screen',
- location: MetaMetricsEventLocation.Transaction,
- transaction_type: TransactionType.contractInteraction,
- },
- }),
- ]),
- );
+ expect(confirmAccountDetailsModalMetricsEvent?.[1]).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ category: MetaMetricsEventCategory.Confirmations,
+ event: MetaMetricsEventName.AccountDetailsOpened,
+ properties: {
+ action: 'Confirm Screen',
+ location: MetaMetricsEventLocation.Transaction,
+ transaction_type: TransactionType.contractInteraction,
+ },
+ }),
+ ]),
+ );
+ });
fireEvent.click(
screen.getByTestId('confirmation-account-details-modal__close-button'),
@@ -263,10 +270,12 @@ describe('Contract Interaction Confirmation', () => {
});
expect(
- screen.getByText(tEn('confirmTitleTransaction') as string),
+ await screen.findByText(tEn('confirmTitleTransaction') as string),
).toBeInTheDocument();
- const simulationSection = screen.getByTestId('simulation-details-layout');
+ const simulationSection = await screen.findByTestId(
+ 'simulation-details-layout',
+ );
expect(simulationSection).toBeInTheDocument();
expect(simulationSection).toHaveTextContent(
tEn('simulationDetailsTitle') as string,
@@ -279,10 +288,10 @@ describe('Contract Interaction Confirmation', () => {
tEn('simulationDetailsIncomingHeading') as string,
);
expect(simulationDetailsRow).toContainElement(
- screen.getByTestId('simulation-details-amount-pill'),
+ await screen.findByTestId('simulation-details-amount-pill'),
);
- const transactionDetailsSection = screen.getByTestId(
+ const transactionDetailsSection = await screen.findByTestId(
'transaction-details-section',
);
expect(transactionDetailsSection).toBeInTheDocument();
@@ -293,25 +302,30 @@ describe('Contract Interaction Confirmation', () => {
tEn('interactingWith') as string,
);
- const gasFeesSection = screen.getByTestId('gas-fee-section');
+ const gasFeesSection = await screen.findByTestId('gas-fee-section');
expect(gasFeesSection).toBeInTheDocument();
- const editGasFeesRow =
- within(gasFeesSection).getByTestId('edit-gas-fees-row');
+ const editGasFeesRow = await within(gasFeesSection).findByTestId(
+ 'edit-gas-fees-row',
+ );
expect(editGasFeesRow).toHaveTextContent(tEn('networkFee') as string);
- const firstGasField = within(editGasFeesRow).getByTestId('first-gas-field');
+ const firstGasField = await within(editGasFeesRow).findByTestId(
+ 'first-gas-field',
+ );
expect(firstGasField).toHaveTextContent('0.0001 SepoliaETH');
expect(editGasFeesRow).toContainElement(
- screen.getByTestId('edit-gas-fee-icon'),
+ await screen.findByTestId('edit-gas-fee-icon'),
);
- const gasFeeSpeed = within(gasFeesSection).getByTestId(
+ const gasFeeSpeed = await within(gasFeesSection).findByTestId(
'gas-fee-details-speed',
);
expect(gasFeeSpeed).toHaveTextContent(tEn('speed') as string);
- const gasTimingTime = within(gasFeeSpeed).getByTestId('gas-timing-time');
+ const gasTimingTime = await within(gasFeeSpeed).findByTestId(
+ 'gas-timing-time',
+ );
expect(gasTimingTime).toHaveTextContent('~0 sec');
});
@@ -339,7 +353,9 @@ describe('Contract Interaction Confirmation', () => {
});
});
- fireEvent.click(screen.getByTestId('header-advanced-details-button'));
+ fireEvent.click(
+ await screen.findByTestId('header-advanced-details-button'),
+ );
await waitFor(() => {
expect(
@@ -395,28 +411,32 @@ describe('Contract Interaction Confirmation', () => {
]);
});
- const gasFeesSection = screen.getByTestId('gas-fee-section');
- const maxFee = screen.getByTestId('gas-fee-details-max-fee');
+ const gasFeesSection = await screen.findByTestId('gas-fee-section');
+ const maxFee = await screen.findByTestId('gas-fee-details-max-fee');
expect(gasFeesSection).toContainElement(maxFee);
expect(maxFee).toHaveTextContent(tEn('maxFee') as string);
expect(maxFee).toHaveTextContent('0.0023 SepoliaETH');
- const nonceSection = screen.getByTestId('advanced-details-nonce-section');
+ const nonceSection = await screen.findByTestId(
+ 'advanced-details-nonce-section',
+ );
expect(nonceSection).toBeInTheDocument();
expect(nonceSection).toHaveTextContent(
tEn('advancedDetailsNonceDesc') as string,
);
expect(nonceSection).toContainElement(
- screen.getByTestId('advanced-details-displayed-nonce'),
+ await screen.findByTestId('advanced-details-displayed-nonce'),
);
expect(
- screen.getByTestId('advanced-details-displayed-nonce'),
+ await screen.findByTestId('advanced-details-displayed-nonce'),
).toHaveTextContent('9');
- const dataSection = screen.getByTestId('advanced-details-data-section');
+ const dataSection = await screen.findByTestId(
+ 'advanced-details-data-section',
+ );
expect(dataSection).toBeInTheDocument();
- const dataSectionFunction = screen.getByTestId(
+ const dataSectionFunction = await screen.findByTestId(
'advanced-details-data-function',
);
expect(dataSection).toContainElement(dataSectionFunction);
@@ -425,7 +445,7 @@ describe('Contract Interaction Confirmation', () => {
);
expect(dataSectionFunction).toHaveTextContent('mintNFTs');
- const transactionDataParams = screen.getByTestId(
+ const transactionDataParams = await screen.findByTestId(
'advanced-details-data-param-0',
);
expect(dataSection).toContainElement(transactionDataParams);
@@ -454,7 +474,7 @@ describe('Contract Interaction Confirmation', () => {
const headingText = tEn('blockaidTitleDeceptive') as string;
const bodyText = tEn('blockaidDescriptionTransferFarming') as string;
- expect(screen.getByText(headingText)).toBeInTheDocument();
- expect(screen.getByText(bodyText)).toBeInTheDocument();
+ expect(await screen.findByText(headingText)).toBeInTheDocument();
+ expect(await screen.findByText(bodyText)).toBeInTheDocument();
});
});
diff --git a/test/integration/confirmations/transactions/erc20-approve.test.tsx b/test/integration/confirmations/transactions/erc20-approve.test.tsx
index a2404ba75b09..c25b2ee3627d 100644
--- a/test/integration/confirmations/transactions/erc20-approve.test.tsx
+++ b/test/integration/confirmations/transactions/erc20-approve.test.tsx
@@ -163,10 +163,10 @@ describe('ERC20 Approve Confirmation', () => {
});
expect(
- screen.getByText(tEn('confirmTitlePermitTokens') as string),
+ await screen.findByText(tEn('confirmTitlePermitTokens') as string),
).toBeInTheDocument();
expect(
- screen.getByText(
+ await screen.findByText(
tEn('confirmTitleDescERC20ApproveTransaction') as string,
),
).toBeInTheDocument();
@@ -183,7 +183,7 @@ describe('ERC20 Approve Confirmation', () => {
});
});
- const simulationSection = screen.getByTestId(
+ const simulationSection = await screen.findByTestId(
'confirmation__simulation_section',
);
expect(simulationSection).toBeInTheDocument();
@@ -192,7 +192,9 @@ describe('ERC20 Approve Confirmation', () => {
tEn('simulationDetailsERC20ApproveDesc') as string,
);
expect(simulationSection).toHaveTextContent(tEn('spendingCap') as string);
- const spendingCapValue = screen.getByTestId('simulation-token-value');
+ const spendingCapValue = await screen.findByTestId(
+ 'simulation-token-value',
+ );
expect(simulationSection).toContainElement(spendingCapValue);
expect(spendingCapValue).toHaveTextContent('1');
expect(simulationSection).toHaveTextContent('0x07614...3ad68');
@@ -211,16 +213,18 @@ describe('ERC20 Approve Confirmation', () => {
});
});
- const approveDetails = screen.getByTestId('confirmation__approve-details');
+ const approveDetails = await screen.findByTestId(
+ 'confirmation__approve-details',
+ );
expect(approveDetails).toBeInTheDocument();
- const approveDetailsSpender = screen.getByTestId(
+ const approveDetailsSpender = await screen.findByTestId(
'confirmation__approve-spender',
);
expect(approveDetails).toContainElement(approveDetailsSpender);
expect(approveDetailsSpender).toHaveTextContent(tEn('spender') as string);
expect(approveDetailsSpender).toHaveTextContent('0x2e0D7...5d09B');
- const spenderTooltip = screen.getByTestId(
+ const spenderTooltip = await screen.findByTestId(
'confirmation__approve-spender-tooltip',
);
expect(approveDetailsSpender).toContainElement(spenderTooltip);
@@ -231,7 +235,7 @@ describe('ERC20 Approve Confirmation', () => {
);
expect(spenderTooltipContent).toBeInTheDocument();
- const approveDetailsRequestFrom = screen.getByTestId(
+ const approveDetailsRequestFrom = await screen.findByTestId(
'transaction-details-origin-row',
);
expect(approveDetails).toContainElement(approveDetailsRequestFrom);
@@ -240,7 +244,7 @@ describe('ERC20 Approve Confirmation', () => {
'http://localhost:8086/',
);
- const approveDetailsRequestFromTooltip = screen.getByTestId(
+ const approveDetailsRequestFromTooltip = await screen.findByTestId(
'transaction-details-origin-row-tooltip',
);
expect(approveDetailsRequestFrom).toContainElement(
@@ -266,7 +270,7 @@ describe('ERC20 Approve Confirmation', () => {
});
});
- const spendingCapSection = screen.getByTestId(
+ const spendingCapSection = await screen.findByTestId(
'confirmation__approve-spending-cap-section',
);
expect(spendingCapSection).toBeInTheDocument();
@@ -275,14 +279,14 @@ describe('ERC20 Approve Confirmation', () => {
tEn('accountBalance') as string,
);
expect(spendingCapSection).toHaveTextContent('0');
- const spendingCapGroup = screen.getByTestId(
+ const spendingCapGroup = await screen.findByTestId(
'confirmation__approve-spending-cap-group',
);
expect(spendingCapSection).toContainElement(spendingCapGroup);
expect(spendingCapGroup).toHaveTextContent(tEn('spendingCap') as string);
expect(spendingCapGroup).toHaveTextContent('1');
- const spendingCapGroupTooltip = screen.getByTestId(
+ const spendingCapGroupTooltip = await screen.findByTestId(
'confirmation__approve-spending-cap-group-tooltip',
);
expect(spendingCapGroup).toContainElement(spendingCapGroupTooltip);
@@ -308,10 +312,12 @@ describe('ERC20 Approve Confirmation', () => {
});
});
- const approveDetails = screen.getByTestId('confirmation__approve-details');
+ const approveDetails = await screen.findByTestId(
+ 'confirmation__approve-details',
+ );
expect(approveDetails).toBeInTheDocument();
- const approveDetailsRecipient = screen.getByTestId(
+ const approveDetailsRecipient = await screen.findByTestId(
'transaction-details-recipient-row',
);
expect(approveDetails).toContainElement(approveDetailsRecipient);
@@ -320,7 +326,7 @@ describe('ERC20 Approve Confirmation', () => {
);
expect(approveDetailsRecipient).toHaveTextContent('0x07614...3ad68');
- const approveDetailsRecipientTooltip = screen.getByTestId(
+ const approveDetailsRecipientTooltip = await screen.findByTestId(
'transaction-details-recipient-row-tooltip',
);
expect(approveDetailsRecipient).toContainElement(
@@ -338,7 +344,7 @@ describe('ERC20 Approve Confirmation', () => {
expect(approveDetails).toContainElement(approveMethodData);
expect(approveMethodData).toHaveTextContent(tEn('methodData') as string);
expect(approveMethodData).toHaveTextContent('Approve');
- const approveMethodDataTooltip = screen.getByTestId(
+ const approveMethodDataTooltip = await screen.findByTestId(
'transaction-details-method-data-row-tooltip',
);
expect(approveMethodData).toContainElement(approveMethodDataTooltip);
@@ -348,15 +354,17 @@ describe('ERC20 Approve Confirmation', () => {
);
expect(approveMethodDataTooltipContent).toBeInTheDocument();
- const approveDetailsNonce = screen.getByTestId(
+ const approveDetailsNonce = await screen.findByTestId(
'advanced-details-nonce-section',
);
expect(approveDetailsNonce).toBeInTheDocument();
- const dataSection = screen.getByTestId('advanced-details-data-section');
+ const dataSection = await screen.findByTestId(
+ 'advanced-details-data-section',
+ );
expect(dataSection).toBeInTheDocument();
- const dataSectionFunction = screen.getByTestId(
+ const dataSectionFunction = await screen.findByTestId(
'advanced-details-data-function',
);
expect(dataSection).toContainElement(dataSectionFunction);
@@ -365,14 +373,14 @@ describe('ERC20 Approve Confirmation', () => {
);
expect(dataSectionFunction).toHaveTextContent('Approve');
- const approveDataParams1 = screen.getByTestId(
+ const approveDataParams1 = await screen.findByTestId(
'advanced-details-data-param-0',
);
expect(dataSection).toContainElement(approveDataParams1);
expect(approveDataParams1).toHaveTextContent('Param #1');
expect(approveDataParams1).toHaveTextContent('0x2e0D7...5d09B');
- const approveDataParams2 = screen.getByTestId(
+ const approveDataParams2 = await screen.findByTestId(
'advanced-details-data-param-1',
);
expect(dataSection).toContainElement(approveDataParams2);
diff --git a/test/integration/confirmations/transactions/erc721-approve.test.tsx b/test/integration/confirmations/transactions/erc721-approve.test.tsx
index c3948d150b1d..c158717cc9c9 100644
--- a/test/integration/confirmations/transactions/erc721-approve.test.tsx
+++ b/test/integration/confirmations/transactions/erc721-approve.test.tsx
@@ -163,10 +163,12 @@ describe('ERC721 Approve Confirmation', () => {
});
expect(
- screen.getByText(tEn('confirmTitleApproveTransaction') as string),
+ await screen.findByText(tEn('confirmTitleApproveTransaction') as string),
).toBeInTheDocument();
expect(
- screen.getByText(tEn('confirmTitleDescApproveTransaction') as string),
+ await screen.findByText(
+ tEn('confirmTitleDescApproveTransaction') as string,
+ ),
).toBeInTheDocument();
});
@@ -181,7 +183,7 @@ describe('ERC721 Approve Confirmation', () => {
});
});
- const simulationSection = screen.getByTestId(
+ const simulationSection = await screen.findByTestId(
'confirmation__simulation_section',
);
expect(simulationSection).toBeInTheDocument();
@@ -192,7 +194,9 @@ describe('ERC721 Approve Confirmation', () => {
expect(simulationSection).toHaveTextContent(
tEn('simulationApproveHeading') as string,
);
- const spendingCapValue = screen.getByTestId('simulation-token-value');
+ const spendingCapValue = await screen.findByTestId(
+ 'simulation-token-value',
+ );
expect(simulationSection).toContainElement(spendingCapValue);
expect(spendingCapValue).toHaveTextContent('1');
expect(simulationSection).toHaveTextContent('0x07614...3ad68');
@@ -211,16 +215,18 @@ describe('ERC721 Approve Confirmation', () => {
});
});
- const approveDetails = screen.getByTestId('confirmation__approve-details');
+ const approveDetails = await screen.findByTestId(
+ 'confirmation__approve-details',
+ );
expect(approveDetails).toBeInTheDocument();
- const approveDetailsSpender = screen.getByTestId(
+ const approveDetailsSpender = await screen.findByTestId(
'confirmation__approve-spender',
);
expect(approveDetails).toContainElement(approveDetailsSpender);
expect(approveDetailsSpender).toHaveTextContent(tEn('spender') as string);
expect(approveDetailsSpender).toHaveTextContent('0x2e0D7...5d09B');
- const spenderTooltip = screen.getByTestId(
+ const spenderTooltip = await screen.findByTestId(
'confirmation__approve-spender-tooltip',
);
expect(approveDetailsSpender).toContainElement(spenderTooltip);
@@ -230,7 +236,7 @@ describe('ERC721 Approve Confirmation', () => {
);
expect(spenderTooltipContent).toBeInTheDocument();
- const approveDetailsRequestFrom = screen.getByTestId(
+ const approveDetailsRequestFrom = await screen.findByTestId(
'transaction-details-origin-row',
);
expect(approveDetails).toContainElement(approveDetailsRequestFrom);
@@ -241,7 +247,7 @@ describe('ERC721 Approve Confirmation', () => {
'http://localhost:8086/',
);
- const approveDetailsRequestFromTooltip = screen.getByTestId(
+ const approveDetailsRequestFromTooltip = await screen.findByTestId(
'transaction-details-origin-row-tooltip',
);
expect(approveDetailsRequestFrom).toContainElement(
@@ -269,10 +275,12 @@ describe('ERC721 Approve Confirmation', () => {
});
});
- const approveDetails = screen.getByTestId('confirmation__approve-details');
+ const approveDetails = await screen.findByTestId(
+ 'confirmation__approve-details',
+ );
expect(approveDetails).toBeInTheDocument();
- const approveDetailsRecipient = screen.getByTestId(
+ const approveDetailsRecipient = await screen.findByTestId(
'transaction-details-recipient-row',
);
expect(approveDetails).toContainElement(approveDetailsRecipient);
@@ -281,7 +289,7 @@ describe('ERC721 Approve Confirmation', () => {
);
expect(approveDetailsRecipient).toHaveTextContent('0x07614...3ad68');
- const approveDetailsRecipientTooltip = screen.getByTestId(
+ const approveDetailsRecipientTooltip = await screen.findByTestId(
'transaction-details-recipient-row-tooltip',
);
expect(approveDetailsRecipient).toContainElement(
@@ -299,7 +307,7 @@ describe('ERC721 Approve Confirmation', () => {
expect(approveDetails).toContainElement(approveMethodData);
expect(approveMethodData).toHaveTextContent(tEn('methodData') as string);
expect(approveMethodData).toHaveTextContent('Approve');
- const approveMethodDataTooltip = screen.getByTestId(
+ const approveMethodDataTooltip = await screen.findByTestId(
'transaction-details-method-data-row-tooltip',
);
expect(approveMethodData).toContainElement(approveMethodDataTooltip);
@@ -309,15 +317,17 @@ describe('ERC721 Approve Confirmation', () => {
);
expect(approveMethodDataTooltipContent).toBeInTheDocument();
- const approveDetailsNonce = screen.getByTestId(
+ const approveDetailsNonce = await screen.findByTestId(
'advanced-details-nonce-section',
);
expect(approveDetailsNonce).toBeInTheDocument();
- const dataSection = screen.getByTestId('advanced-details-data-section');
+ const dataSection = await screen.findByTestId(
+ 'advanced-details-data-section',
+ );
expect(dataSection).toBeInTheDocument();
- const dataSectionFunction = screen.getByTestId(
+ const dataSectionFunction = await screen.findByTestId(
'advanced-details-data-function',
);
expect(dataSection).toContainElement(dataSectionFunction);
@@ -326,14 +336,14 @@ describe('ERC721 Approve Confirmation', () => {
);
expect(dataSectionFunction).toHaveTextContent('Approve');
- const approveDataParams1 = screen.getByTestId(
+ const approveDataParams1 = await screen.findByTestId(
'advanced-details-data-param-0',
);
expect(dataSection).toContainElement(approveDataParams1);
expect(approveDataParams1).toHaveTextContent('Param #1');
expect(approveDataParams1).toHaveTextContent('0x2e0D7...5d09B');
- const approveDataParams2 = screen.getByTestId(
+ const approveDataParams2 = await screen.findByTestId(
'advanced-details-data-param-1',
);
expect(dataSection).toContainElement(approveDataParams2);
diff --git a/test/integration/confirmations/transactions/increase-allowance.test.tsx b/test/integration/confirmations/transactions/increase-allowance.test.tsx
index c288a5cc4e6d..810477d3a3a5 100644
--- a/test/integration/confirmations/transactions/increase-allowance.test.tsx
+++ b/test/integration/confirmations/transactions/increase-allowance.test.tsx
@@ -167,10 +167,10 @@ describe('ERC20 increaseAllowance Confirmation', () => {
});
expect(
- screen.getByText(tEn('confirmTitlePermitTokens') as string),
+ await screen.findByText(tEn('confirmTitlePermitTokens') as string),
).toBeInTheDocument();
expect(
- screen.getByText(tEn('confirmTitleDescPermitSignature') as string),
+ await screen.findByText(tEn('confirmTitleDescPermitSignature') as string),
).toBeInTheDocument();
});
@@ -185,7 +185,7 @@ describe('ERC20 increaseAllowance Confirmation', () => {
});
});
- const simulationSection = screen.getByTestId(
+ const simulationSection = await screen.findByTestId(
'confirmation__simulation_section',
);
expect(simulationSection).toBeInTheDocument();
@@ -194,7 +194,9 @@ describe('ERC20 increaseAllowance Confirmation', () => {
tEn('simulationDetailsERC20ApproveDesc') as string,
);
expect(simulationSection).toHaveTextContent(tEn('spendingCap') as string);
- const spendingCapValue = screen.getByTestId('simulation-token-value');
+ const spendingCapValue = await screen.findByTestId(
+ 'simulation-token-value',
+ );
expect(simulationSection).toContainElement(spendingCapValue);
expect(spendingCapValue).toHaveTextContent('1');
expect(simulationSection).toHaveTextContent('0x07614...3ad68');
@@ -213,16 +215,18 @@ describe('ERC20 increaseAllowance Confirmation', () => {
});
});
- const approveDetails = screen.getByTestId('confirmation__approve-details');
+ const approveDetails = await screen.findByTestId(
+ 'confirmation__approve-details',
+ );
expect(approveDetails).toBeInTheDocument();
- const approveDetailsSpender = screen.getByTestId(
+ const approveDetailsSpender = await screen.findByTestId(
'confirmation__approve-spender',
);
expect(approveDetails).toContainElement(approveDetailsSpender);
expect(approveDetailsSpender).toHaveTextContent(tEn('spender') as string);
expect(approveDetailsSpender).toHaveTextContent('0x2e0D7...5d09B');
- const spenderTooltip = screen.getByTestId(
+ const spenderTooltip = await screen.findByTestId(
'confirmation__approve-spender-tooltip',
);
expect(approveDetailsSpender).toContainElement(spenderTooltip);
@@ -233,7 +237,7 @@ describe('ERC20 increaseAllowance Confirmation', () => {
);
expect(spenderTooltipContent).toBeInTheDocument();
- const approveDetailsRequestFrom = screen.getByTestId(
+ const approveDetailsRequestFrom = await screen.findByTestId(
'transaction-details-origin-row',
);
expect(approveDetails).toContainElement(approveDetailsRequestFrom);
@@ -242,7 +246,7 @@ describe('ERC20 increaseAllowance Confirmation', () => {
'http://localhost:8086/',
);
- const approveDetailsRequestFromTooltip = screen.getByTestId(
+ const approveDetailsRequestFromTooltip = await screen.findByTestId(
'transaction-details-origin-row-tooltip',
);
expect(approveDetailsRequestFrom).toContainElement(
@@ -268,7 +272,7 @@ describe('ERC20 increaseAllowance Confirmation', () => {
});
});
- const spendingCapSection = screen.getByTestId(
+ const spendingCapSection = await screen.findByTestId(
'confirmation__approve-spending-cap-section',
);
expect(spendingCapSection).toBeInTheDocument();
@@ -277,14 +281,14 @@ describe('ERC20 increaseAllowance Confirmation', () => {
tEn('accountBalance') as string,
);
expect(spendingCapSection).toHaveTextContent('0');
- const spendingCapGroup = screen.getByTestId(
+ const spendingCapGroup = await screen.findByTestId(
'confirmation__approve-spending-cap-group',
);
expect(spendingCapSection).toContainElement(spendingCapGroup);
expect(spendingCapGroup).toHaveTextContent(tEn('spendingCap') as string);
expect(spendingCapGroup).toHaveTextContent('1');
- const spendingCapGroupTooltip = screen.getByTestId(
+ const spendingCapGroupTooltip = await screen.findByTestId(
'confirmation__approve-spending-cap-group-tooltip',
);
expect(spendingCapGroup).toContainElement(spendingCapGroupTooltip);
@@ -310,10 +314,12 @@ describe('ERC20 increaseAllowance Confirmation', () => {
});
});
- const approveDetails = screen.getByTestId('confirmation__approve-details');
+ const approveDetails = await screen.findByTestId(
+ 'confirmation__approve-details',
+ );
expect(approveDetails).toBeInTheDocument();
- const approveDetailsRecipient = screen.getByTestId(
+ const approveDetailsRecipient = await screen.findByTestId(
'transaction-details-recipient-row',
);
expect(approveDetails).toContainElement(approveDetailsRecipient);
@@ -322,7 +328,7 @@ describe('ERC20 increaseAllowance Confirmation', () => {
);
expect(approveDetailsRecipient).toHaveTextContent('0x07614...3ad68');
- const approveDetailsRecipientTooltip = screen.getByTestId(
+ const approveDetailsRecipientTooltip = await screen.findByTestId(
'transaction-details-recipient-row-tooltip',
);
expect(approveDetailsRecipient).toContainElement(
@@ -340,7 +346,7 @@ describe('ERC20 increaseAllowance Confirmation', () => {
expect(approveDetails).toContainElement(approveMethodData);
expect(approveMethodData).toHaveTextContent(tEn('methodData') as string);
expect(approveMethodData).toHaveTextContent('increaseAllowance');
- const approveMethodDataTooltip = screen.getByTestId(
+ const approveMethodDataTooltip = await screen.findByTestId(
'transaction-details-method-data-row-tooltip',
);
expect(approveMethodData).toContainElement(approveMethodDataTooltip);
@@ -350,15 +356,17 @@ describe('ERC20 increaseAllowance Confirmation', () => {
);
expect(approveMethodDataTooltipContent).toBeInTheDocument();
- const approveDetailsNonce = screen.getByTestId(
+ const approveDetailsNonce = await screen.findByTestId(
'advanced-details-nonce-section',
);
expect(approveDetailsNonce).toBeInTheDocument();
- const dataSection = screen.getByTestId('advanced-details-data-section');
+ const dataSection = await screen.findByTestId(
+ 'advanced-details-data-section',
+ );
expect(dataSection).toBeInTheDocument();
- const dataSectionFunction = screen.getByTestId(
+ const dataSectionFunction = await screen.findByTestId(
'advanced-details-data-function',
);
expect(dataSection).toContainElement(dataSectionFunction);
@@ -367,14 +375,14 @@ describe('ERC20 increaseAllowance Confirmation', () => {
);
expect(dataSectionFunction).toHaveTextContent('increaseAllowance');
- const approveDataParams1 = screen.getByTestId(
+ const approveDataParams1 = await screen.findByTestId(
'advanced-details-data-param-0',
);
expect(dataSection).toContainElement(approveDataParams1);
expect(approveDataParams1).toHaveTextContent('Param #1');
expect(approveDataParams1).toHaveTextContent('0x2e0D7...5d09B');
- const approveDataParams2 = screen.getByTestId(
+ const approveDataParams2 = await screen.findByTestId(
'advanced-details-data-param-1',
);
expect(dataSection).toContainElement(approveDataParams2);
diff --git a/test/integration/confirmations/transactions/set-approval-for-all.test.tsx b/test/integration/confirmations/transactions/set-approval-for-all.test.tsx
index a65688030e90..ebe680983a6c 100644
--- a/test/integration/confirmations/transactions/set-approval-for-all.test.tsx
+++ b/test/integration/confirmations/transactions/set-approval-for-all.test.tsx
@@ -167,10 +167,14 @@ describe('ERC721 setApprovalForAll Confirmation', () => {
});
expect(
- screen.getByText(tEn('setApprovalForAllRedesignedTitle') as string),
+ await screen.findByText(
+ tEn('setApprovalForAllRedesignedTitle') as string,
+ ),
).toBeInTheDocument();
expect(
- screen.getByText(tEn('confirmTitleDescApproveTransaction') as string),
+ await screen.findByText(
+ tEn('confirmTitleDescApproveTransaction') as string,
+ ),
).toBeInTheDocument();
});
@@ -185,7 +189,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => {
});
});
- const simulationSection = screen.getByTestId(
+ const simulationSection = await screen.findByTestId(
'confirmation__simulation_section',
);
expect(simulationSection).toBeInTheDocument();
@@ -194,7 +198,9 @@ describe('ERC721 setApprovalForAll Confirmation', () => {
tEn('simulationDetailsSetApprovalForAllDesc') as string,
);
expect(simulationSection).toHaveTextContent(tEn('withdrawing') as string);
- const spendingCapValue = screen.getByTestId('simulation-token-value');
+ const spendingCapValue = await screen.findByTestId(
+ 'simulation-token-value',
+ );
expect(simulationSection).toContainElement(spendingCapValue);
expect(spendingCapValue).toHaveTextContent(tEn('all') as string);
expect(simulationSection).toHaveTextContent('0x07614...3ad68');
@@ -213,9 +219,11 @@ describe('ERC721 setApprovalForAll Confirmation', () => {
});
});
- const approveDetails = screen.getByTestId('confirmation__approve-details');
+ const approveDetails = await screen.findByTestId(
+ 'confirmation__approve-details',
+ );
expect(approveDetails).toBeInTheDocument();
- const approveDetailsSpender = screen.getByTestId(
+ const approveDetailsSpender = await screen.findByTestId(
'confirmation__approve-spender',
);
@@ -224,7 +232,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => {
tEn('permissionFor') as string,
);
expect(approveDetailsSpender).toHaveTextContent('0x2e0D7...5d09B');
- const spenderTooltip = screen.getByTestId(
+ const spenderTooltip = await screen.findByTestId(
'confirmation__approve-spender-tooltip',
);
expect(approveDetailsSpender).toContainElement(spenderTooltip);
@@ -235,7 +243,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => {
);
expect(spenderTooltipContent).toBeInTheDocument();
- const approveDetailsRequestFrom = screen.getByTestId(
+ const approveDetailsRequestFrom = await screen.findByTestId(
'transaction-details-origin-row',
);
expect(approveDetails).toContainElement(approveDetailsRequestFrom);
@@ -246,7 +254,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => {
'http://localhost:8086/',
);
- const approveDetailsRequestFromTooltip = screen.getByTestId(
+ const approveDetailsRequestFromTooltip = await screen.findByTestId(
'transaction-details-origin-row-tooltip',
);
expect(approveDetailsRequestFrom).toContainElement(
@@ -274,10 +282,12 @@ describe('ERC721 setApprovalForAll Confirmation', () => {
});
});
- const approveDetails = screen.getByTestId('confirmation__approve-details');
+ const approveDetails = await screen.findByTestId(
+ 'confirmation__approve-details',
+ );
expect(approveDetails).toBeInTheDocument();
- const approveDetailsRecipient = screen.getByTestId(
+ const approveDetailsRecipient = await screen.findByTestId(
'transaction-details-recipient-row',
);
expect(approveDetails).toContainElement(approveDetailsRecipient);
@@ -286,7 +296,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => {
);
expect(approveDetailsRecipient).toHaveTextContent('0x07614...3ad68');
- const approveDetailsRecipientTooltip = screen.getByTestId(
+ const approveDetailsRecipientTooltip = await screen.findByTestId(
'transaction-details-recipient-row-tooltip',
);
expect(approveDetailsRecipient).toContainElement(
@@ -304,7 +314,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => {
expect(approveDetails).toContainElement(approveMethodData);
expect(approveMethodData).toHaveTextContent(tEn('methodData') as string);
expect(approveMethodData).toHaveTextContent('setApprovalForAll');
- const approveMethodDataTooltip = screen.getByTestId(
+ const approveMethodDataTooltip = await screen.findByTestId(
'transaction-details-method-data-row-tooltip',
);
expect(approveMethodData).toContainElement(approveMethodDataTooltip);
@@ -314,15 +324,17 @@ describe('ERC721 setApprovalForAll Confirmation', () => {
);
expect(approveMethodDataTooltipContent).toBeInTheDocument();
- const approveDetailsNonce = screen.getByTestId(
+ const approveDetailsNonce = await screen.findByTestId(
'advanced-details-nonce-section',
);
expect(approveDetailsNonce).toBeInTheDocument();
- const dataSection = screen.getByTestId('advanced-details-data-section');
+ const dataSection = await screen.findByTestId(
+ 'advanced-details-data-section',
+ );
expect(dataSection).toBeInTheDocument();
- const dataSectionFunction = screen.getByTestId(
+ const dataSectionFunction = await screen.findByTestId(
'advanced-details-data-function',
);
expect(dataSection).toContainElement(dataSectionFunction);
@@ -331,14 +343,14 @@ describe('ERC721 setApprovalForAll Confirmation', () => {
);
expect(dataSectionFunction).toHaveTextContent('setApprovalForAll');
- const approveDataParams1 = screen.getByTestId(
+ const approveDataParams1 = await screen.findByTestId(
'advanced-details-data-param-0',
);
expect(dataSection).toContainElement(approveDataParams1);
expect(approveDataParams1).toHaveTextContent('Param #1');
expect(approveDataParams1).toHaveTextContent('0x2e0D7...5d09B');
- const approveDataParams2 = screen.getByTestId(
+ const approveDataParams2 = await screen.findByTestId(
'advanced-details-data-param-1',
);
expect(dataSection).toContainElement(approveDataParams2);
diff --git a/test/integration/notifications&auth/notifications-activation.test.tsx b/test/integration/notifications&auth/notifications-activation.test.tsx
index e11e58dad320..d52921c386e8 100644
--- a/test/integration/notifications&auth/notifications-activation.test.tsx
+++ b/test/integration/notifications&auth/notifications-activation.test.tsx
@@ -70,7 +70,7 @@ describe('Notifications Activation', () => {
const clickElement = async (testId: string) => {
await act(async () => {
- fireEvent.click(screen.getByTestId(testId));
+ fireEvent.click(await screen.findByTestId(testId));
});
};
@@ -105,7 +105,7 @@ describe('Notifications Activation', () => {
});
await act(async () => {
- fireEvent.click(screen.getByText('Turn on'));
+ fireEvent.click(await screen.findByText('Turn on'));
});
await waitFor(() => {
@@ -148,7 +148,7 @@ describe('Notifications Activation', () => {
await act(async () => {
fireEvent.click(
- within(screen.getByRole('dialog')).getByRole('button', {
+ await within(screen.getByRole('dialog')).findByRole('button', {
name: 'Close',
}),
);
diff --git a/test/integration/notifications&auth/notifications-list.test.tsx b/test/integration/notifications&auth/notifications-list.test.tsx
index 4e17a53db107..e4c1d6f20107 100644
--- a/test/integration/notifications&auth/notifications-list.test.tsx
+++ b/test/integration/notifications&auth/notifications-list.test.tsx
@@ -77,8 +77,8 @@ describe('Notifications List', () => {
});
});
- await waitFor(() => {
- const unreadCount = screen.getByTestId(
+ await waitFor(async () => {
+ const unreadCount = await screen.findByTestId(
'notifications-tag-counter__unread-dot',
);
expect(unreadCount).toBeInTheDocument();
@@ -96,30 +96,36 @@ describe('Notifications List', () => {
});
});
- fireEvent.click(screen.getByTestId('account-options-menu-button'));
+ fireEvent.click(await screen.findByTestId('account-options-menu-button'));
- await waitFor(() => {
- expect(screen.getByTestId('notifications-menu-item')).toBeInTheDocument();
- fireEvent.click(screen.getByTestId('notifications-menu-item'));
+ await waitFor(async () => {
+ expect(
+ await screen.findByTestId('notifications-menu-item'),
+ ).toBeInTheDocument();
+ fireEvent.click(await screen.findByTestId('notifications-menu-item'));
});
- await waitFor(() => {
- const notificationsList = screen.getByTestId('notifications-list');
+ await waitFor(async () => {
+ const notificationsList = await screen.findByTestId('notifications-list');
expect(notificationsList).toBeInTheDocument();
expect(notificationsList.childElementCount).toBe(3);
// Feature notification details
expect(
- within(notificationsList).getByText(featureNotification.data.title),
+ await within(notificationsList).findByText(
+ featureNotification.data.title,
+ ),
).toBeInTheDocument();
expect(
- within(notificationsList).getByText(
+ await within(notificationsList).findByText(
featureNotification.data.shortDescription,
),
).toBeInTheDocument();
// Eth sent notification details
- const sentToElement = within(notificationsList).getByText('Sent to');
+ const sentToElement = await within(notificationsList).findByText(
+ 'Sent to',
+ );
expect(sentToElement).toBeInTheDocument();
const addressElement = sentToElement.nextElementSibling;
@@ -127,12 +133,12 @@ describe('Notifications List', () => {
// Read all button
expect(
- within(notificationsList).getByTestId(
+ await within(notificationsList).findByTestId(
'notifications-list-read-all-button',
),
).toBeInTheDocument();
- const unreadDot = screen.getAllByTestId('unread-dot');
+ const unreadDot = await screen.findAllByTestId('unread-dot');
expect(unreadDot).toHaveLength(2);
});
@@ -178,17 +184,19 @@ describe('Notifications List', () => {
backgroundConnection: backgroundConnectionMocked,
});
- fireEvent.click(screen.getByTestId('account-options-menu-button'));
+ fireEvent.click(await screen.findByTestId('account-options-menu-button'));
- await waitFor(() => {
+ await waitFor(async () => {
expect(
- screen.getByTestId('notifications-menu-item'),
+ await screen.findByTestId('notifications-menu-item'),
).toBeInTheDocument();
- fireEvent.click(screen.getByTestId('notifications-menu-item'));
+ fireEvent.click(await screen.findByTestId('notifications-menu-item'));
});
- await waitFor(() => {
- const notificationsList = screen.getByTestId('notifications-list');
+ await waitFor(async () => {
+ const notificationsList = await screen.findByTestId(
+ 'notifications-list',
+ );
expect(notificationsList).toBeInTheDocument();
expect(notificationsList.childElementCount).toBe(2);
@@ -211,14 +219,18 @@ describe('Notifications List', () => {
});
});
- fireEvent.click(screen.getByTestId('account-options-menu-button'));
+ fireEvent.click(await screen.findByTestId('account-options-menu-button'));
- await waitFor(() => {
- expect(screen.getByTestId('notifications-menu-item')).toBeInTheDocument();
- fireEvent.click(screen.getByTestId('notifications-menu-item'));
+ await waitFor(async () => {
+ expect(
+ await screen.findByTestId('notifications-menu-item'),
+ ).toBeInTheDocument();
+ fireEvent.click(await screen.findByTestId('notifications-menu-item'));
});
- fireEvent.click(screen.getByTestId('notifications-list-read-all-button'));
+ fireEvent.click(
+ await screen.findByTestId('notifications-list-read-all-button'),
+ );
await waitFor(() => {
const markAllAsReadEvent =
diff --git a/test/integration/notifications&auth/notifications-toggle.test.tsx b/test/integration/notifications&auth/notifications-toggle.test.tsx
index 8133e4c4bc3d..fd4c11ec4494 100644
--- a/test/integration/notifications&auth/notifications-toggle.test.tsx
+++ b/test/integration/notifications&auth/notifications-toggle.test.tsx
@@ -48,13 +48,13 @@ describe('Notifications Toggle', () => {
const clickElement = async (testId: string) => {
await act(async () => {
- fireEvent.click(screen.getByTestId(testId));
+ fireEvent.click(await screen.findByTestId(testId));
});
};
const waitForElement = async (testId: string) => {
- await waitFor(() => {
- expect(screen.getByTestId(testId)).toBeInTheDocument();
+ await waitFor(async () => {
+ expect(await screen.findByTestId(testId)).toBeInTheDocument();
});
};
@@ -73,12 +73,12 @@ describe('Notifications Toggle', () => {
await clickElement('notifications-settings-button');
await waitForElement('notifications-settings-allow-notifications');
- const toggleSection = screen.getByTestId(
+ const toggleSection = await screen.findByTestId(
'notifications-settings-allow-notifications',
);
await act(async () => {
- fireEvent.click(within(toggleSection).getByRole('checkbox'));
+ fireEvent.click(await within(toggleSection).findByRole('checkbox'));
});
await waitFor(() => {
@@ -159,7 +159,7 @@ describe('Notifications Toggle', () => {
await clickElement('notifications-settings-button');
await waitForElement('notifications-settings-allow-notifications');
- const allToggles = screen.getAllByTestId('test-toggle');
+ const allToggles = await screen.findAllByTestId('test-toggle');
await act(async () => {
fireEvent.click(allToggles[1]);
diff --git a/test/integration/onboarding/wallet-created.test.tsx b/test/integration/onboarding/wallet-created.test.tsx
index 55be476839fe..c1ddb1f1886a 100644
--- a/test/integration/onboarding/wallet-created.test.tsx
+++ b/test/integration/onboarding/wallet-created.test.tsx
@@ -31,14 +31,15 @@ describe('Wallet Created Events', () => {
});
it('are sent when onboarding user who chooses to opt in metrics', async () => {
- const { getByTestId, getByText } = await integrationTestRender({
- preloadedState: mockMetaMaskState,
- backgroundConnection: backgroundConnectionMocked,
- });
+ const { getByTestId, findByTestId, getByText, findByText } =
+ await integrationTestRender({
+ preloadedState: mockMetaMaskState,
+ backgroundConnection: backgroundConnectionMocked,
+ });
- expect(getByText('Congratulations!')).toBeInTheDocument();
+ expect(await findByText('Congratulations!')).toBeInTheDocument();
- fireEvent.click(getByTestId('onboarding-complete-done'));
+ fireEvent.click(await findByTestId('onboarding-complete-done'));
await waitFor(() => {
expect(getByTestId('onboarding-pin-extension')).toBeInTheDocument();
@@ -69,7 +70,7 @@ describe('Wallet Created Events', () => {
]),
);
- fireEvent.click(getByTestId('pin-extension-next'));
+ fireEvent.click(await findByTestId('pin-extension-next'));
let onboardingPinExtensionMetricsEvent;
@@ -91,7 +92,7 @@ describe('Wallet Created Events', () => {
).toBeInTheDocument();
});
- fireEvent.click(getByTestId('pin-extension-done'));
+ fireEvent.click(await findByTestId('pin-extension-done'));
await waitFor(() => {
const completeOnboardingBackgroundRequest =
diff --git a/test/jest/mock-store.js b/test/jest/mock-store.js
index 4720bf427372..a3543e485bb7 100644
--- a/test/jest/mock-store.js
+++ b/test/jest/mock-store.js
@@ -4,6 +4,7 @@ import { KeyringType } from '../../shared/constants/keyring';
import { ETH_EOA_METHODS } from '../../shared/constants/eth-methods';
import { mockNetworkState } from '../stub/networks';
import { DEFAULT_BRIDGE_CONTROLLER_STATE } from '../../app/scripts/controllers/bridge/constants';
+import { BRIDGE_PREFERRED_GAS_ESTIMATE } from '../../shared/constants/bridge';
export const createGetSmartTransactionFeesApiResponse = () => {
return {
@@ -711,6 +712,7 @@ export const createBridgeMockStore = (
...swapsStore,
bridge: {
toChainId: null,
+ sortOrder: 0,
...bridgeSliceOverrides,
},
metamask: {
@@ -719,6 +721,16 @@ export const createBridgeMockStore = (
{ chainId: CHAIN_IDS.MAINNET },
{ chainId: CHAIN_IDS.LINEA_MAINNET },
),
+ gasFeeEstimates: {
+ estimatedBaseFee: '0.00010456',
+ [BRIDGE_PREFERRED_GAS_ESTIMATE]: {
+ suggestedMaxFeePerGas: '0.00018456',
+ suggestedMaxPriorityFeePerGas: '0.0001',
+ },
+ },
+ currencyRates: {
+ ETH: { conversionRate: 2524.25 },
+ },
...metamaskStateOverrides,
bridgeState: {
...(swapsStore.metamask.bridgeState ?? {}),
diff --git a/test/jest/mocks.ts b/test/jest/mocks.ts
index bc7127fb2383..8822b96315b6 100644
--- a/test/jest/mocks.ts
+++ b/test/jest/mocks.ts
@@ -4,16 +4,21 @@ import {
BtcMethod,
BtcAccountType,
InternalAccount,
+ isEvmAccountType,
} from '@metamask/keyring-api';
import { KeyringTypes } from '@metamask/keyring-controller';
import { v4 as uuidv4 } from 'uuid';
import { keyringTypeToName } from '@metamask/accounts-controller';
+import { Json } from '@metamask/utils';
import {
DraftTransaction,
draftTransactionInitialState,
initialState,
} from '../../ui/ducks/send';
import { MetaMaskReduxState } from '../../ui/store/store';
+import mockState from '../data/mock-state.json';
+
+export type MockState = typeof mockState;
export const MOCK_DEFAULT_ADDRESS =
'0xd5e099c71b797516c10ed0f0d895f429c2781111';
@@ -182,6 +187,7 @@ export function createMockInternalAccount({
keyringType = KeyringTypes.hd,
lastSelected = 0,
snapOptions = undefined,
+ options = undefined,
}: {
name?: string;
address?: string;
@@ -193,6 +199,7 @@ export function createMockInternalAccount({
name: string;
id: string;
};
+ options?: Record;
} = {}) {
let methods;
@@ -232,7 +239,7 @@ export function createMockInternalAccount({
snap: snapOptions,
lastSelected,
},
- options: {},
+ options: options ?? {},
methods,
type,
};
@@ -245,3 +252,63 @@ export const getSelectedInternalAccountFromMockState = (
state.metamask.internalAccounts.selectedAccount
];
};
+
+export function overrideAccountsFromMockState<
+ MockMetaMaskState extends MockState['metamask'],
+>(
+ state: { metamask: MockMetaMaskState },
+ accounts: InternalAccount[],
+ selectedAccountId?: string,
+): { metamask: MockMetaMaskState } {
+ // First, re-create the accounts mapping and the currently selected account.
+ const [{ id: newFirstAccountId }] = accounts;
+ const newSelectedAccount = selectedAccountId ?? newFirstAccountId ?? '';
+ const newInternalAccounts = accounts.reduce(
+ (
+ acc: MetaMaskReduxState['metamask']['internalAccounts']['accounts'],
+ account,
+ ) => {
+ acc[account.id] = account;
+ return acc;
+ },
+ {},
+ );
+
+ // Re-create the keyring mapping too, since some selectors are using their internal
+ // account list.
+ const newKeyrings: MetaMaskReduxState['metamask']['keyrings'] = [];
+ for (const keyring of state.metamask.keyrings) {
+ const newAccountsForKeyring = [];
+ for (const account of accounts) {
+ if (account.metadata.keyring.type === keyring.type) {
+ newAccountsForKeyring.push(account.address);
+ }
+ }
+ newKeyrings.push({
+ type: keyring.type,
+ accounts: newAccountsForKeyring,
+ });
+ }
+
+ // Compute balances for EVM addresses:
+ // FIXME: Looks like there's no `balances` type in `MetaMaskReduxState`.
+ const newBalances: Record = {};
+ for (const account of accounts) {
+ if (isEvmAccountType(account.type)) {
+ newBalances[account.address] = '0x0';
+ }
+ }
+
+ return {
+ ...state,
+ metamask: {
+ ...state.metamask,
+ internalAccounts: {
+ accounts: newInternalAccounts,
+ selectedAccount: newSelectedAccount,
+ },
+ keyrings: newKeyrings,
+ balances: newBalances,
+ },
+ };
+}
diff --git a/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx b/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx
index dede9004d29e..8e6abb940d1c 100644
--- a/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx
+++ b/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx
@@ -6,7 +6,9 @@ import {
Box,
ButtonBase,
ButtonBaseSize,
+ Icon,
IconName,
+ IconSize,
Popover,
PopoverPosition,
} from '../../../../component-library';
@@ -198,7 +200,8 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => {
className="asset-list-control-bar__button"
onClick={toggleTokenSortPopover}
size={ButtonBaseSize.Sm}
- endIconName={IconName.SwapVertical}
+ startIconName={IconName.Filter}
+ startIconProps={{ marginInlineEnd: 0 }}
backgroundColor={
isTokenSortPopoverOpen
? BackgroundColor.backgroundPressed
@@ -221,13 +224,13 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => {
isOpen={isNetworkFilterPopoverOpen}
position={PopoverPosition.BottomStart}
referenceElement={popoverRef.current}
- matchWidth={!isFullScreen}
+ matchWidth={false}
style={{
zIndex: 10,
display: 'flex',
flexDirection: 'column',
padding: 0,
- minWidth: isFullScreen ? '325px' : '',
+ minWidth: isFullScreen ? '250px' : '',
}}
>
@@ -237,13 +240,13 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => {
isOpen={isTokenSortPopoverOpen}
position={PopoverPosition.BottomEnd}
referenceElement={popoverRef.current}
- matchWidth={!isFullScreen}
+ matchWidth={false}
style={{
zIndex: 10,
display: 'flex',
flexDirection: 'column',
padding: 0,
- minWidth: isFullScreen ? '325px' : '',
+ minWidth: isFullScreen ? '250px' : '',
}}
>
@@ -254,19 +257,25 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => {
isOpen={isImportTokensPopoverOpen}
position={PopoverPosition.BottomEnd}
referenceElement={popoverRef.current}
- matchWidth={!isFullScreen}
+ matchWidth={false}
style={{
zIndex: 10,
display: 'flex',
flexDirection: 'column',
padding: 0,
- minWidth: isFullScreen ? '325px' : '',
+ minWidth: isFullScreen ? '158px' : '',
}}
>
+
{t('importTokensCamelCase')}
+
{t('refreshList')}
diff --git a/ui/components/app/assets/asset-list/asset-list-control-bar/index.scss b/ui/components/app/assets/asset-list/asset-list-control-bar/index.scss
index b133586371c3..21cdbe2e83e1 100644
--- a/ui/components/app/assets/asset-list/asset-list-control-bar/index.scss
+++ b/ui/components/app/assets/asset-list/asset-list-control-bar/index.scss
@@ -8,12 +8,7 @@
min-width: auto;
border-radius: 8px;
padding: 0 8px !important;
- gap: 5px;
- text-transform: lowercase;
-
- span::first-letter {
- text-transform: uppercase;
- }
+ gap: 4px;
}
&__buttons {
diff --git a/ui/components/app/assets/asset-list/import-control/import-control.tsx b/ui/components/app/assets/asset-list/import-control/import-control.tsx
index d3a9bfd9ccb7..6ac4a6b1fce1 100644
--- a/ui/components/app/assets/asset-list/import-control/import-control.tsx
+++ b/ui/components/app/assets/asset-list/import-control/import-control.tsx
@@ -33,6 +33,7 @@ const AssetListControlBar = ({
disabled={!shouldShowTokensLinks}
size={ButtonBaseSize.Sm}
startIconName={IconName.MoreVertical}
+ startIconProps={{ marginInlineEnd: 0 }}
backgroundColor={BackgroundColor.backgroundDefault}
color={TextColor.textDefault}
onClick={onClick}
diff --git a/ui/components/app/assets/asset-list/network-filter/index.scss b/ui/components/app/assets/asset-list/network-filter/index.scss
deleted file mode 100644
index 76e61c1025ae..000000000000
--- a/ui/components/app/assets/asset-list/network-filter/index.scss
+++ /dev/null
@@ -1,27 +0,0 @@
-.selectable-list-item-wrapper {
- position: relative;
-}
-
-.selectable-list-item {
- cursor: pointer;
- padding: 16px;
-
- &--selected {
- background: var(--color-primary-muted);
- }
-
- &:not(.selectable-list-item--selected) {
- &:hover,
- &:focus-within {
- background: var(--color-background-default-hover);
- }
- }
-
- &__selected-indicator {
- width: 4px;
- height: calc(100% - 8px);
- position: absolute;
- top: 4px;
- left: 4px;
- }
-}
diff --git a/ui/components/app/assets/asset-list/network-filter/network-filter.tsx b/ui/components/app/assets/asset-list/network-filter/network-filter.tsx
index 4e9aa14eea25..de68d8d6e13e 100644
--- a/ui/components/app/assets/asset-list/network-filter/network-filter.tsx
+++ b/ui/components/app/assets/asset-list/network-filter/network-filter.tsx
@@ -5,9 +5,9 @@ import {
getCurrentChainId,
getCurrentNetwork,
getPreferences,
- getChainIdsToPoll,
getShouldHideZeroBalanceTokens,
getSelectedAccount,
+ getAllChainsToPoll,
} from '../../../../../selectors';
import { getNetworkConfigurationsByChainId } from '../../../../../../shared/modules/selectors/networks';
import { useI18nContext } from '../../../../../hooks/useI18nContext';
@@ -15,6 +15,7 @@ import { SelectableListItem } from '../sort-control/sort-control';
import { Text } from '../../../../component-library/text/text';
import {
AlignItems,
+ BlockSize,
Display,
JustifyContent,
TextColor,
@@ -49,7 +50,7 @@ const NetworkFilter = ({ handleClose }: SortControlProps) => {
const shouldHideZeroBalanceTokens = useSelector(
getShouldHideZeroBalanceTokens,
);
- const allChainIDs = useSelector(getChainIdsToPoll);
+ const allChainIDs = useSelector(getAllChainsToPoll);
const { formattedTokensWithBalancesPerChain } = useGetFormattedTokensPerChain(
selectedAccount,
shouldHideZeroBalanceTokens,
@@ -108,6 +109,8 @@ const NetworkFilter = ({ handleClose }: SortControlProps) => {
{
color={TextColor.textAlternative}
data-testid="network-filter-all__total"
>
- {/* TODO: Should query cross chain account balance */}
-
{
{
diff --git a/ui/components/app/assets/asset-list/sort-control/index.scss b/ui/components/app/assets/asset-list/sort-control/index.scss
index 74a8e142824d..b5f0b20f2d40 100644
--- a/ui/components/app/assets/asset-list/sort-control/index.scss
+++ b/ui/components/app/assets/asset-list/sort-control/index.scss
@@ -4,7 +4,7 @@
.selectable-list-item {
cursor: pointer;
- padding: 12px 16px;
+ padding: 16px;
&--selected {
background: var(--color-primary-muted);
@@ -24,4 +24,9 @@
top: 4px;
left: 4px;
}
+
+ .currency-display-component__text,
+ .currency-display-component__suffix {
+ color: var(--color-text-alternative);
+ }
}
diff --git a/ui/components/app/assets/asset-list/sort-control/sort-control.tsx b/ui/components/app/assets/asset-list/sort-control/sort-control.tsx
index 7e2bd48e7c4b..cf7285c9115b 100644
--- a/ui/components/app/assets/asset-list/sort-control/sort-control.tsx
+++ b/ui/components/app/assets/asset-list/sort-control/sort-control.tsx
@@ -1,11 +1,15 @@
import React, { ReactNode, useContext } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import classnames from 'classnames';
-import { Box } from '../../../../component-library';
+import { Box, Text } from '../../../../component-library';
import { SortOrder, SortingCallbacksT } from '../../util/sort';
import {
+ AlignItems,
BackgroundColor,
+ BlockSize,
BorderRadius,
+ Display,
+ TextVariant,
} from '../../../../../helpers/constants/design-system';
import { setTokenSortConfig } from '../../../../../store/actions';
import { MetaMetricsContext } from '../../../../../contexts/metametrics';
@@ -36,15 +40,21 @@ export const SelectableListItem = ({
}: SelectableListItemProps) => {
return (
-
{children}
-
+
{isSelected && (
= ({
{expanded &&
+ children &&
(typeof children === 'string' ? (
{children}
diff --git a/ui/components/app/modals/customize-nonce/__snapshots__/customize-nonce.test.js.snap b/ui/components/app/modals/customize-nonce/__snapshots__/customize-nonce.test.js.snap
index 020adaa0c952..4698963a26ec 100644
--- a/ui/components/app/modals/customize-nonce/__snapshots__/customize-nonce.test.js.snap
+++ b/ui/components/app/modals/customize-nonce/__snapshots__/customize-nonce.test.js.snap
@@ -86,7 +86,7 @@ exports[`Customize Nonce should match snapshot 1`] = `
min="0"
placeholder="1"
type="number"
- value=""
+ value="1"
/>
diff --git a/ui/components/app/modals/customize-nonce/customize-nonce.component.js b/ui/components/app/modals/customize-nonce/customize-nonce.component.js
index 1d87cbd549b1..b6b2f82a096e 100644
--- a/ui/components/app/modals/customize-nonce/customize-nonce.component.js
+++ b/ui/components/app/modals/customize-nonce/customize-nonce.component.js
@@ -27,7 +27,9 @@ const CustomizeNonce = ({
updateCustomNonce,
getNextNonce,
}) => {
- const [customNonce, setCustomNonce] = useState('');
+ const defaultNonce =
+ customNonceValue || (typeof nextNonce === 'number' && nextNonce.toString());
+ const [customNonce, setCustomNonce] = useState(defaultNonce);
const t = useI18nContext();
return (
@@ -107,10 +109,7 @@ const CustomizeNonce = ({
type="number"
data-testid="custom-nonce-input"
min="0"
- placeholder={
- customNonceValue ||
- (typeof nextNonce === 'number' && nextNonce.toString())
- }
+ placeholder={defaultNonce}
onChange={(e) => {
setCustomNonce(e.target.value);
}}
diff --git a/ui/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.test.js b/ui/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.test.js
index 982a6a272943..04ba342e9c48 100644
--- a/ui/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.test.js
+++ b/ui/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.test.js
@@ -5,6 +5,7 @@ import thunk from 'redux-thunk';
import * as actions from '../../../../store/actions';
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
import mockState from '../../../../../test/data/mock-state.json';
+import { mockNetworkState } from '../../../../../test/stub/networks';
import HideTokenConfirmationModal from '.';
const mockHistoryPush = jest.fn();
@@ -25,6 +26,13 @@ describe('Hide Token Confirmation Modal', () => {
image: '',
};
+ const tokenState2 = {
+ address: '0xTokenAddress2',
+ symbol: 'TKN2',
+ image: '',
+ chainId: '0x89',
+ };
+
const tokenModalState = {
...mockState,
appState: {
@@ -82,4 +90,47 @@ describe('Hide Token Confirmation Modal', () => {
networkClientId: 'goerli',
});
});
+
+ it('should hide token from another chain', () => {
+ const tokenModalStateWithDifferentChain = {
+ ...mockState,
+ metamask: {
+ ...mockState.metamask,
+ selectedNetworkClientId: 'bsc',
+ ...mockNetworkState({ chainId: '0x89', id: 'bsc' }),
+ },
+ appState: {
+ ...mockState.appState,
+ modal: {
+ modalState: {
+ props: {
+ history: {
+ push: mockHistoryPush,
+ },
+ token: tokenState2,
+ },
+ },
+ },
+ },
+ };
+
+ const mockStoreDifferentChain = configureMockStore([thunk])(
+ tokenModalStateWithDifferentChain,
+ );
+
+ const { queryByTestId } = renderWithProvider(
+ ,
+ mockStoreDifferentChain,
+ );
+
+ const hideButton = queryByTestId('hide-token-confirmation__hide');
+
+ fireEvent.click(hideButton);
+
+ expect(mockHideModal).toHaveBeenCalled();
+ expect(actions.ignoreTokens).toHaveBeenCalledWith({
+ tokensToIgnore: tokenState2.address,
+ networkClientId: 'bsc',
+ });
+ });
});
diff --git a/ui/components/app/name/__snapshots__/name.test.tsx.snap b/ui/components/app/name/__snapshots__/name.test.tsx.snap
index 7e3f98c8576e..286429760c1e 100644
--- a/ui/components/app/name/__snapshots__/name.test.tsx.snap
+++ b/ui/components/app/name/__snapshots__/name.test.tsx.snap
@@ -79,7 +79,7 @@ exports[`Name renders address with long saved name 1`] = `
- Very long and l...
+ Very long an...
diff --git a/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap b/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap
index da6f598dfa5e..3520a1b64a13 100644
--- a/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap
+++ b/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap
@@ -753,7 +753,7 @@ exports[`NameDetails renders with recognized name 1`] = `
- iZUMi Bond USD
+ iZUMi Bond U...
diff --git a/ui/components/app/name/name.tsx b/ui/components/app/name/name.tsx
index 75f2a2f79c15..4871cc481f0c 100644
--- a/ui/components/app/name/name.tsx
+++ b/ui/components/app/name/name.tsx
@@ -103,9 +103,10 @@ const Name = memo(
}, [setModalOpen]);
const formattedValue = formatValue(value, type);
+ const MAX_PET_NAME_LENGTH = 12;
const formattedName = shortenString(name || undefined, {
- truncatedCharLimit: 15,
- truncatedStartChars: 15,
+ truncatedCharLimit: MAX_PET_NAME_LENGTH,
+ truncatedStartChars: MAX_PET_NAME_LENGTH,
truncatedEndChars: 0,
skipCharacterInEnd: true,
});
diff --git a/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx b/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx
index fe3698e2fc2f..6f7de0c95c7c 100644
--- a/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx
+++ b/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx
@@ -1,7 +1,9 @@
import React, { useMemo } from 'react';
import { useSelector } from 'react-redux';
-import { zeroAddress, toChecksumAddress } from 'ethereumjs-util';
+import { toChecksumAddress } from 'ethereumjs-util';
+import { getNativeTokenAddress } from '@metamask/assets-controllers';
+import { Hex } from '@metamask/utils';
import {
getCurrentCurrency,
getSelectedAccount,
@@ -89,8 +91,9 @@ export const AggregatedPercentageOverviewCrossChains = () => {
item.tokensWithBalances,
);
const nativePricePercentChange1d =
- crossChainMarketData?.[item.chainId]?.[zeroAddress()]
- ?.pricePercentChange1d;
+ crossChainMarketData?.[item.chainId]?.[
+ getNativeTokenAddress(item.chainId as Hex)
+ ]?.pricePercentChange1d;
const nativeFiat1dAgo = getCalculatedTokenAmount1dAgo(
item.nativeFiatValue,
diff --git a/ui/components/app/wallet-overview/aggregated-percentage-overview.test.tsx b/ui/components/app/wallet-overview/aggregated-percentage-overview.test.tsx
index 8da096151908..7610890d48da 100644
--- a/ui/components/app/wallet-overview/aggregated-percentage-overview.test.tsx
+++ b/ui/components/app/wallet-overview/aggregated-percentage-overview.test.tsx
@@ -8,6 +8,7 @@ import {
getShouldHideZeroBalanceTokens,
getTokensMarketData,
getPreferences,
+ getCurrentChainId,
} from '../../../selectors';
import { useAccountTotalFiatBalance } from '../../../hooks/useAccountTotalFiatBalance';
import { AggregatedPercentageOverview } from './aggregated-percentage-overview';
@@ -26,20 +27,22 @@ jest.mock('../../../selectors', () => ({
getPreferences: jest.fn(),
getShouldHideZeroBalanceTokens: jest.fn(),
getTokensMarketData: jest.fn(),
+ getCurrentChainId: jest.fn(),
}));
jest.mock('../../../hooks/useAccountTotalFiatBalance', () => ({
useAccountTotalFiatBalance: jest.fn(),
}));
-const mockGetIntlLocale = getIntlLocale as unknown as jest.Mock;
-const mockGetCurrentCurrency = getCurrentCurrency as jest.Mock;
-const mockGetPreferences = getPreferences as jest.Mock;
-const mockGetSelectedAccount = getSelectedAccount as unknown as jest.Mock;
-const mockGetShouldHideZeroBalanceTokens =
- getShouldHideZeroBalanceTokens as jest.Mock;
-
+const mockGetIntlLocale = jest.mocked(getIntlLocale);
+const mockGetCurrentCurrency = jest.mocked(getCurrentCurrency);
+const mockGetPreferences = jest.mocked(getPreferences);
+const mockGetSelectedAccount = jest.mocked(getSelectedAccount);
+const mockGetShouldHideZeroBalanceTokens = jest.mocked(
+ getShouldHideZeroBalanceTokens,
+);
const mockGetTokensMarketData = getTokensMarketData as jest.Mock;
+const mockGetCurrentChainId = jest.mocked(getCurrentChainId);
const selectedAccountMock = {
id: 'd51c0116-de36-4e77-b35b-408d4ea82d01',
@@ -166,7 +169,7 @@ describe('AggregatedPercentageOverview', () => {
mockGetSelectedAccount.mockReturnValue(selectedAccountMock);
mockGetShouldHideZeroBalanceTokens.mockReturnValue(false);
mockGetTokensMarketData.mockReturnValue(marketDataMock);
-
+ mockGetCurrentChainId.mockReturnValue('0x1');
jest.clearAllMocks();
});
diff --git a/ui/components/app/wallet-overview/aggregated-percentage-overview.tsx b/ui/components/app/wallet-overview/aggregated-percentage-overview.tsx
index 8c609610daa1..89bc94dab774 100644
--- a/ui/components/app/wallet-overview/aggregated-percentage-overview.tsx
+++ b/ui/components/app/wallet-overview/aggregated-percentage-overview.tsx
@@ -1,13 +1,15 @@
import React, { useMemo } from 'react';
import { useSelector } from 'react-redux';
-import { zeroAddress, toChecksumAddress } from 'ethereumjs-util';
+import { toChecksumAddress } from 'ethereumjs-util';
+import { getNativeTokenAddress } from '@metamask/assets-controllers';
import {
getCurrentCurrency,
getSelectedAccount,
getShouldHideZeroBalanceTokens,
getTokensMarketData,
getPreferences,
+ getCurrentChainId,
} from '../../../selectors';
import { useAccountTotalFiatBalance } from '../../../hooks/useAccountTotalFiatBalance';
@@ -37,6 +39,7 @@ export const AggregatedPercentageOverview = () => {
const fiatCurrency = useSelector(getCurrentCurrency);
const { privacyMode } = useSelector(getPreferences);
const selectedAccount = useSelector(getSelectedAccount);
+ const currentChainId = useSelector(getCurrentChainId);
const shouldHideZeroBalanceTokens = useSelector(
getShouldHideZeroBalanceTokens,
);
@@ -63,7 +66,8 @@ export const AggregatedPercentageOverview = () => {
}
// native token
const nativePricePercentChange1d =
- tokensMarketData?.[zeroAddress()]?.pricePercentChange1d;
+ tokensMarketData?.[getNativeTokenAddress(currentChainId)]
+ ?.pricePercentChange1d;
const nativeFiat1dAgo = getCalculatedTokenAmount1dAgo(
item.fiatBalance,
nativePricePercentChange1d,
diff --git a/ui/components/app/wallet-overview/coin-overview.tsx b/ui/components/app/wallet-overview/coin-overview.tsx
index bf054d993e74..3be53776581d 100644
--- a/ui/components/app/wallet-overview/coin-overview.tsx
+++ b/ui/components/app/wallet-overview/coin-overview.tsx
@@ -7,11 +7,11 @@ import React, {
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import classnames from 'classnames';
-import { zeroAddress } from 'ethereumjs-util';
import { CaipChainId } from '@metamask/utils';
import type { Hex } from '@metamask/utils';
import { InternalAccount } from '@metamask/keyring-api';
+import { getNativeTokenAddress } from '@metamask/assets-controllers';
import {
Box,
ButtonIcon,
@@ -231,7 +231,10 @@ export const CoinOverview = ({
return (
{
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
diff --git a/ui/components/app/wallet-overview/index.js b/ui/components/app/wallet-overview/index.js
index 54536007bc41..82003b364199 100644
--- a/ui/components/app/wallet-overview/index.js
+++ b/ui/components/app/wallet-overview/index.js
@@ -1,2 +1,2 @@
export { default as EthOverview } from './eth-overview';
-export { default as BtcOverview } from './btc-overview';
+export { default as NonEvmOverview } from './non-evm-overview';
diff --git a/ui/components/app/wallet-overview/btc-overview.stories.tsx b/ui/components/app/wallet-overview/non-evm-overview.stories.tsx
similarity index 70%
rename from ui/components/app/wallet-overview/btc-overview.stories.tsx
rename to ui/components/app/wallet-overview/non-evm-overview.stories.tsx
index 43dff2554bef..2e8ae16045ce 100644
--- a/ui/components/app/wallet-overview/btc-overview.stories.tsx
+++ b/ui/components/app/wallet-overview/non-evm-overview.stories.tsx
@@ -1,9 +1,9 @@
import React from 'react';
-import BtcOverview from './btc-overview';
+import NonEvmOverview from './non-evm-overview';
export default {
title: 'Components/App/WalletOverview/BtcOverview',
- component: BtcOverview,
+ component: NonEvmOverview,
parameters: {
docs: {
description: {
@@ -14,6 +14,6 @@ export default {
},
};
-const Template = (args) => ;
+const Template = (args) => ;
export const Default = Template.bind({});
diff --git a/ui/components/app/wallet-overview/btc-overview.test.tsx b/ui/components/app/wallet-overview/non-evm-overview.test.tsx
similarity index 92%
rename from ui/components/app/wallet-overview/btc-overview.test.tsx
rename to ui/components/app/wallet-overview/non-evm-overview.test.tsx
index 3c5697cb5853..aa49eb77e79d 100644
--- a/ui/components/app/wallet-overview/btc-overview.test.tsx
+++ b/ui/components/app/wallet-overview/non-evm-overview.test.tsx
@@ -17,7 +17,7 @@ import {
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
import useMultiPolling from '../../../hooks/useMultiPolling';
-import BtcOverview from './btc-overview';
+import NonEvmOverview from './non-evm-overview';
// We need to mock `dispatch` since we use it for `setDefaultHomeActiveTabName`.
const mockDispatch = jest.fn().mockReturnValue(() => jest.fn());
@@ -134,7 +134,7 @@ function makePortfolioUrl(path: string, getParams: Record) {
return `${PORTOFOLIO_URL}/${path}?${params.toString()}`;
}
-describe('BtcOverview', () => {
+describe('NonEvmOverview', () => {
beforeEach(() => {
setBackgroundConnection({ setBridgeFeatureFlags: jest.fn() } as never);
// Clear previous mock implementations
@@ -156,8 +156,11 @@ describe('BtcOverview', () => {
});
});
- it('shows the primary balance as BTC when showNativeTokenAsMainBalance if true', async () => {
- const { queryByTestId } = renderWithProvider(, getStore());
+ it('shows the primary balance using the native token when showNativeTokenAsMainBalance if true', async () => {
+ const { queryByTestId } = renderWithProvider(
+ ,
+ getStore(),
+ );
const primaryBalance = queryByTestId(BTC_OVERVIEW_PRIMARY_CURRENCY);
expect(primaryBalance).toBeInTheDocument();
@@ -166,7 +169,7 @@ describe('BtcOverview', () => {
it('shows the primary balance as fiat when showNativeTokenAsMainBalance if false', async () => {
const { queryByTestId } = renderWithProvider(
- ,
+ ,
getStore({
metamask: {
...mockMetamaskStore,
@@ -186,7 +189,7 @@ describe('BtcOverview', () => {
it('shows a spinner if balance is not available', async () => {
const { container } = renderWithProvider(
- ,
+ ,
getStore({
metamask: {
...mockMetamaskStore,
@@ -203,7 +206,10 @@ describe('BtcOverview', () => {
});
it('buttons Swap/Bridge are disabled', () => {
- const { queryByTestId } = renderWithProvider(, getStore());
+ const { queryByTestId } = renderWithProvider(
+ ,
+ getStore(),
+ );
for (const buttonTestId of [BTC_OVERVIEW_SWAP, BTC_OVERVIEW_BRIDGE]) {
const button = queryByTestId(buttonTestId);
@@ -213,13 +219,19 @@ describe('BtcOverview', () => {
});
it('shows the "Buy & Sell" button', () => {
- const { queryByTestId } = renderWithProvider(, getStore());
+ const { queryByTestId } = renderWithProvider(
+ ,
+ getStore(),
+ );
const buyButton = queryByTestId(BTC_OVERVIEW_BUY);
expect(buyButton).toBeInTheDocument();
});
it('"Buy & Sell" button is disabled if BTC is not buyable', () => {
- const { queryByTestId } = renderWithProvider(, getStore());
+ const { queryByTestId } = renderWithProvider(
+ ,
+ getStore(),
+ );
const buyButton = queryByTestId(BTC_OVERVIEW_BUY);
expect(buyButton).toBeInTheDocument();
@@ -234,7 +246,7 @@ describe('BtcOverview', () => {
});
const { queryByTestId } = renderWithProvider(
- ,
+ ,
storeWithBtcBuyable,
);
@@ -252,7 +264,7 @@ describe('BtcOverview', () => {
});
const { queryByTestId } = renderWithProvider(
- ,
+ ,
storeWithBtcBuyable,
);
@@ -283,7 +295,7 @@ describe('BtcOverview', () => {
const mockTrackEvent = jest.fn();
const { queryByTestId } = renderWithProvider(
-
+
,
storeWithBtcBuyable,
);
@@ -307,7 +319,10 @@ describe('BtcOverview', () => {
});
it('always show the Receive button', () => {
- const { queryByTestId } = renderWithProvider(, getStore());
+ const { queryByTestId } = renderWithProvider(
+ ,
+ getStore(),
+ );
const receiveButton = queryByTestId(BTC_OVERVIEW_RECEIVE);
expect(receiveButton).toBeInTheDocument();
});
@@ -332,7 +347,7 @@ describe('BtcOverview', () => {
});
const { queryByTestId } = renderWithProvider(
- ,
+ ,
storeWithBtcBuyable,
);
@@ -343,7 +358,10 @@ describe('BtcOverview', () => {
});
it('always show the Send button', () => {
- const { queryByTestId } = renderWithProvider(, getStore());
+ const { queryByTestId } = renderWithProvider(
+ ,
+ getStore(),
+ );
const sendButton = queryByTestId(BTC_OVERVIEW_SEND);
expect(sendButton).toBeInTheDocument();
expect(sendButton).not.toBeDisabled();
@@ -353,7 +371,7 @@ describe('BtcOverview', () => {
const mockTrackEvent = jest.fn();
const { queryByTestId } = renderWithProvider(
-
+
,
getStore(),
);
diff --git a/ui/components/app/wallet-overview/btc-overview.tsx b/ui/components/app/wallet-overview/non-evm-overview.tsx
similarity index 73%
rename from ui/components/app/wallet-overview/btc-overview.tsx
rename to ui/components/app/wallet-overview/non-evm-overview.tsx
index fb315d3ab3b0..8905a3a938f1 100644
--- a/ui/components/app/wallet-overview/btc-overview.tsx
+++ b/ui/components/app/wallet-overview/non-evm-overview.tsx
@@ -1,5 +1,8 @@
import React from 'react';
import { useSelector } from 'react-redux';
+///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
+import { BtcAccountType } from '@metamask/keyring-api';
+///: END:ONLY_INCLUDE_IF
import {
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
getMultichainIsMainnet,
@@ -14,11 +17,11 @@ import { useMultichainSelector } from '../../../hooks/useMultichainSelector';
import { getSelectedInternalAccount } from '../../../selectors';
import { CoinOverview } from './coin-overview';
-type BtcOverviewProps = {
+type NonEvmOverviewProps = {
className?: string;
};
-const BtcOverview = ({ className }: BtcOverviewProps) => {
+const NonEvmOverview = ({ className }: NonEvmOverviewProps) => {
const { chainId } = useSelector(getMultichainProviderConfig);
const balance = useSelector(getMultichainSelectedAccountCachedBalance);
const account = useSelector(getSelectedInternalAccount);
@@ -28,6 +31,11 @@ const BtcOverview = ({ className }: BtcOverviewProps) => {
account,
);
const isBtcBuyable = useSelector(getIsBitcoinBuyable);
+
+ // TODO: Update this to add support to check if Solana is buyable when the Send flow starts
+ const accountType = account.type;
+ const isBtc = accountType === BtcAccountType.P2wpkh;
+ const isBuyableChain = isBtc ? isBtcBuyable && isBtcMainnetAccount : false;
///: END:ONLY_INCLUDE_IF
return (
@@ -42,10 +50,10 @@ const BtcOverview = ({ className }: BtcOverviewProps) => {
isSwapsChain={false}
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
isBridgeChain={false}
- isBuyableChain={isBtcBuyable && isBtcMainnetAccount}
+ isBuyableChain={isBuyableChain}
///: END:ONLY_INCLUDE_IF
/>
);
};
-export default BtcOverview;
+export default NonEvmOverview;
diff --git a/ui/components/multichain/account-list-menu/account-list-menu.tsx b/ui/components/multichain/account-list-menu/account-list-menu.tsx
index 29d79e8537b1..030b57ebe242 100644
--- a/ui/components/multichain/account-list-menu/account-list-menu.tsx
+++ b/ui/components/multichain/account-list-menu/account-list-menu.tsx
@@ -17,6 +17,7 @@ import { useDispatch, useSelector } from 'react-redux';
import {
BtcAccountType,
EthAccountType,
+ SolAccountType,
///: BEGIN:ONLY_INCLUDE_IF(build-flask)
InternalAccount,
KeyringAccountType,
@@ -232,6 +233,7 @@ export const AccountListMenu = ({
EthAccountType.Eoa,
EthAccountType.Erc4337,
BtcAccountType.P2wpkh,
+ SolAccountType.DataAccount,
],
}: AccountListMenuProps) => {
const t = useI18nContext();
diff --git a/ui/components/multichain/account-overview/account-overview-btc.stories.tsx b/ui/components/multichain/account-overview/account-overview-btc.stories.tsx
deleted file mode 100644
index 2afc54e22b23..000000000000
--- a/ui/components/multichain/account-overview/account-overview-btc.stories.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import { AccountOverviewBtc } from './account-overview-btc'
-import { AccountOverviewCommonProps } from './common';
-
-export default {
- title: 'Components/Multichain/AccountOverviewBtc',
- component: AccountOverviewBtc,
-};
-
-export const DefaultStory = (
- args: JSX.IntrinsicAttributes & AccountOverviewCommonProps
-) => ;
diff --git a/ui/components/multichain/account-overview/account-overview-non-evm.stories.tsx b/ui/components/multichain/account-overview/account-overview-non-evm.stories.tsx
new file mode 100644
index 000000000000..de3ac5484baf
--- /dev/null
+++ b/ui/components/multichain/account-overview/account-overview-non-evm.stories.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { AccountOverviewNonEvm } from './account-overview-non-evm';
+import { AccountOverviewCommonProps } from './common';
+import { BtcAccountType, SolAccountType } from '@metamask/keyring-api';
+
+export default {
+ title: 'Components/Multichain/AccountOverviewNonEvm',
+ component: AccountOverviewNonEvm,
+ args: {
+ accountType: BtcAccountType.P2wpkh,
+ },
+};
+
+export const DefaultStory = (
+ args: JSX.IntrinsicAttributes &
+ AccountOverviewCommonProps & {
+ accountType: BtcAccountType.P2wpkh | SolAccountType.DataAccount;
+ },
+) => ;
diff --git a/ui/components/multichain/account-overview/account-overview-btc.test.tsx b/ui/components/multichain/account-overview/account-overview-non-evm.test.tsx
similarity index 88%
rename from ui/components/multichain/account-overview/account-overview-btc.test.tsx
rename to ui/components/multichain/account-overview/account-overview-non-evm.test.tsx
index b171840a540e..17989cbf31a6 100644
--- a/ui/components/multichain/account-overview/account-overview-btc.test.tsx
+++ b/ui/components/multichain/account-overview/account-overview-non-evm.test.tsx
@@ -5,9 +5,9 @@ import { renderWithProvider } from '../../../../test/jest/rendering';
import { setBackgroundConnection } from '../../../store/background-connection';
import { CHAIN_IDS } from '../../../../shared/constants/network';
import {
- AccountOverviewBtc,
- AccountOverviewBtcProps,
-} from './account-overview-btc';
+ AccountOverviewNonEvm,
+ AccountOverviewNonEvmProps,
+} from './account-overview-non-evm';
jest.mock('../../../store/actions', () => ({
tokenBalancesStartPolling: jest.fn().mockResolvedValue('pollingToken'),
@@ -26,14 +26,14 @@ jest.mock('react-redux', () => {
};
});
-const defaultProps: AccountOverviewBtcProps = {
+const defaultProps: AccountOverviewNonEvmProps = {
defaultHomeActiveTabName: null,
onTabClick: jest.fn(),
setBasicFunctionalityModalOpen: jest.fn(),
onSupportLinkClick: jest.fn(),
};
-const render = (props: AccountOverviewBtcProps = defaultProps) => {
+const render = (props: AccountOverviewNonEvmProps = defaultProps) => {
const store = configureStore({
metamask: {
...mockState.metamask,
@@ -47,7 +47,7 @@ const render = (props: AccountOverviewBtcProps = defaultProps) => {
},
});
- return renderWithProvider(, store);
+ return renderWithProvider(, store);
};
describe('AccountOverviewBtc', () => {
diff --git a/ui/components/multichain/account-overview/account-overview-btc.tsx b/ui/components/multichain/account-overview/account-overview-non-evm.tsx
similarity index 66%
rename from ui/components/multichain/account-overview/account-overview-btc.tsx
rename to ui/components/multichain/account-overview/account-overview-non-evm.tsx
index dd58b2eef414..dd7db4484306 100644
--- a/ui/components/multichain/account-overview/account-overview-btc.tsx
+++ b/ui/components/multichain/account-overview/account-overview-non-evm.tsx
@@ -1,11 +1,13 @@
import React from 'react';
-import { BtcOverview } from '../../app/wallet-overview';
+import { NonEvmOverview } from '../../app/wallet-overview';
import { AccountOverviewLayout } from './account-overview-layout';
import { AccountOverviewCommonProps } from './common';
-export type AccountOverviewBtcProps = AccountOverviewCommonProps;
+export type AccountOverviewNonEvmProps = AccountOverviewCommonProps;
-export const AccountOverviewBtc = (props: AccountOverviewBtcProps) => {
+export const AccountOverviewNonEvm = ({
+ ...props
+}: AccountOverviewNonEvmProps) => {
return (
{
>
{
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask,build-mmi)
-
+
///: END:ONLY_INCLUDE_IF
}
diff --git a/ui/components/multichain/account-overview/account-overview.tsx b/ui/components/multichain/account-overview/account-overview.tsx
index 3d6121e41471..f3f3e427a688 100644
--- a/ui/components/multichain/account-overview/account-overview.tsx
+++ b/ui/components/multichain/account-overview/account-overview.tsx
@@ -1,13 +1,17 @@
import React from 'react';
import { useSelector } from 'react-redux';
-import { BtcAccountType, EthAccountType } from '@metamask/keyring-api';
+import {
+ BtcAccountType,
+ EthAccountType,
+ SolAccountType,
+} from '@metamask/keyring-api';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { BannerAlert, BannerAlertSeverity } from '../../component-library';
import { getSelectedInternalAccount } from '../../../selectors';
import { AccountOverviewEth } from './account-overview-eth';
-import { AccountOverviewBtc } from './account-overview-btc';
import { AccountOverviewUnknown } from './account-overview-unknown';
import { AccountOverviewCommonProps } from './common';
+import { AccountOverviewNonEvm } from './account-overview-non-evm';
export type AccountOverviewProps = AccountOverviewCommonProps & {
useExternalServices: boolean;
@@ -25,7 +29,8 @@ export function AccountOverview(props: AccountOverviewProps) {
case EthAccountType.Erc4337:
return ;
case BtcAccountType.P2wpkh:
- return ;
+ case SolAccountType.DataAccount:
+ return ;
default:
return ;
}
diff --git a/ui/components/multichain/pages/index.js b/ui/components/multichain/pages/index.js
deleted file mode 100644
index a19f23039138..000000000000
--- a/ui/components/multichain/pages/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export { Connections } from './connections';
-export { PermissionsPage } from './permissions-page/permissions-page';
-export { ReviewPermissions, SiteCell } from './review-permissions-page';
diff --git a/ui/components/multichain/pages/review-permissions-page/index.js b/ui/components/multichain/pages/review-permissions-page/index.js
deleted file mode 100644
index e2da178368f1..000000000000
--- a/ui/components/multichain/pages/review-permissions-page/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { ReviewPermissions } from './review-permissions-page';
-export { SiteCell } from './site-cell/site-cell';
diff --git a/ui/components/multichain/pages/review-permissions-page/review-permissions-page.stories.tsx b/ui/components/multichain/pages/review-permissions-page/review-permissions-page.stories.tsx
index b2da4553ce50..a886d26e77e6 100644
--- a/ui/components/multichain/pages/review-permissions-page/review-permissions-page.stories.tsx
+++ b/ui/components/multichain/pages/review-permissions-page/review-permissions-page.stories.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { ReviewPermissions } from '.';
+import { ReviewPermissions } from './review-permissions-page';
export default {
title: 'Components/Multichain/ReviewPermissions',
diff --git a/ui/components/multichain/pages/review-permissions-page/review-permissions-page.test.tsx b/ui/components/multichain/pages/review-permissions-page/review-permissions-page.test.tsx
index b644c16b6440..55f7ab9bf332 100644
--- a/ui/components/multichain/pages/review-permissions-page/review-permissions-page.test.tsx
+++ b/ui/components/multichain/pages/review-permissions-page/review-permissions-page.test.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { renderWithProvider } from '../../../../../test/jest/rendering';
import mockState from '../../../../../test/data/mock-state.json';
import configureStore from '../../../../store/store';
-import { ReviewPermissions } from '.';
+import { ReviewPermissions } from './review-permissions-page';
const render = (state = {}) => {
const store = configureStore({
diff --git a/ui/components/multichain/pages/review-permissions-page/review-permissions-page.tsx b/ui/components/multichain/pages/review-permissions-page/review-permissions-page.tsx
index f65dd7a662cf..95a8ea394000 100644
--- a/ui/components/multichain/pages/review-permissions-page/review-permissions-page.tsx
+++ b/ui/components/multichain/pages/review-permissions-page/review-permissions-page.tsx
@@ -54,7 +54,7 @@ import { PermissionsHeader } from '../../permissions-header/permissions-header';
import { mergeAccounts } from '../../account-list-menu/account-list-menu';
import { MergedInternalAccount } from '../../../../selectors/selectors.types';
import { TEST_CHAINS } from '../../../../../shared/constants/network';
-import { SiteCell } from '.';
+import { SiteCell } from './site-cell/site-cell';
export const ReviewPermissions = () => {
const t = useI18nContext();
diff --git a/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell-tooltip.js b/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell-tooltip.js
index 2e4eef35d594..84ede0d4dd4a 100644
--- a/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell-tooltip.js
+++ b/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell-tooltip.js
@@ -79,7 +79,7 @@ export const SiteCellTooltip = ({ accounts, networks }) => {
data-testid="accounts-list-item-connected-account-name"
ellipsis
>
- {acc.label || acc.metadata.name}
+ {acc.metadata.name || acc.label}
);
diff --git a/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell.tsx b/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell.tsx
index d5ca0b816d48..fcb104937e28 100644
--- a/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell.tsx
+++ b/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell.tsx
@@ -72,13 +72,13 @@ export const SiteCell: React.FC = ({
const accountMessageConnectedState =
selectedAccounts.length === 1
? t('connectedWithAccountName', [
- selectedAccounts[0].label || selectedAccounts[0].metadata.name,
+ selectedAccounts[0].metadata.name || selectedAccounts[0].label,
])
- : t('connectedWithAccount', [accounts.length]);
+ : t('connectedWithAccount', [selectedAccounts.length]);
const accountMessageNotConnectedState =
selectedAccounts.length === 1
? t('requestingForAccount', [
- selectedAccounts[0].label || selectedAccounts[0].metadata.name,
+ selectedAccounts[0].metadata.name || selectedAccounts[0].label,
])
: t('requestingFor');
diff --git a/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.test.tsx b/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.test.tsx
index abff9f40da8d..439c030a59cd 100644
--- a/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.test.tsx
+++ b/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.test.tsx
@@ -2,11 +2,13 @@ import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { zeroAddress } from 'ethereumjs-util';
+import { MarketDataDetails } from '@metamask/assets-controllers';
import { getIntlLocale } from '../../../../../ducks/locale/locale';
import {
getCurrentCurrency,
getSelectedAccountCachedBalance,
getTokensMarketData,
+ getCurrentChainId,
} from '../../../../../selectors';
import {
getConversionRate,
@@ -26,6 +28,7 @@ jest.mock('../../../../../selectors', () => ({
getCurrentCurrency: jest.fn(),
getSelectedAccountCachedBalance: jest.fn(),
getTokensMarketData: jest.fn(),
+ getCurrentChainId: jest.fn(),
}));
jest.mock('../../../../../ducks/metamask/metamask', () => ({
@@ -33,13 +36,15 @@ jest.mock('../../../../../ducks/metamask/metamask', () => ({
getNativeCurrency: jest.fn(),
}));
-const mockGetIntlLocale = getIntlLocale as unknown as jest.Mock;
-const mockGetCurrentCurrency = getCurrentCurrency as jest.Mock;
-const mockGetSelectedAccountCachedBalance =
- getSelectedAccountCachedBalance as jest.Mock;
-const mockGetConversionRate = getConversionRate as jest.Mock;
-const mockGetNativeCurrency = getNativeCurrency as jest.Mock;
-const mockGetTokensMarketData = getTokensMarketData as jest.Mock;
+const mockGetIntlLocale = jest.mocked(getIntlLocale);
+const mockGetCurrentCurrency = jest.mocked(getCurrentCurrency);
+const mockGetSelectedAccountCachedBalance = jest.mocked(
+ getSelectedAccountCachedBalance,
+);
+const mockGetConversionRate = jest.mocked(getConversionRate);
+const mockGetNativeCurrency = jest.mocked(getNativeCurrency);
+const mockGetTokensMarketData = jest.mocked(getTokensMarketData);
+const mockGetCurrentChainId = jest.mocked(getCurrentChainId);
describe('PercentageChange Component', () => {
beforeEach(() => {
@@ -51,9 +56,9 @@ describe('PercentageChange Component', () => {
mockGetTokensMarketData.mockReturnValue({
[zeroAddress()]: {
pricePercentChange1d: 2,
- },
+ } as MarketDataDetails,
});
-
+ mockGetCurrentChainId.mockReturnValue('0x1');
jest.clearAllMocks();
});
@@ -108,4 +113,19 @@ describe('PercentageChange Component', () => {
expect(percentageElement).toBeInTheDocument();
expect(numberElement).toBeInTheDocument();
});
+
+ it('should display percentage for non-zero native tokens (MATIC)', () => {
+ mockGetTokensMarketData.mockReturnValue({
+ '0x0000000000000000000000000000000000001010': {
+ pricePercentChange1d: 2,
+ } as MarketDataDetails,
+ });
+ mockGetCurrentCurrency.mockReturnValue('POL');
+ mockGetCurrentChainId.mockReturnValue('0x89');
+ render();
+ const percentageElement = screen.getByText('(+1.00%)');
+ const numberElement = screen.getByText('+POL 12.21');
+ expect(percentageElement).toBeInTheDocument();
+ expect(numberElement).toBeInTheDocument();
+ });
});
diff --git a/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.tsx b/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.tsx
index be9921e88793..f1ba436ef47f 100644
--- a/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.tsx
+++ b/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.tsx
@@ -1,7 +1,8 @@
import React, { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { BigNumber } from 'bignumber.js';
-import { isHexString, zeroAddress } from 'ethereumjs-util';
+import { isHexString } from 'ethereumjs-util';
+import { getNativeTokenAddress } from '@metamask/assets-controllers';
import { Text, Box } from '../../../../component-library';
import {
Display,
@@ -9,6 +10,7 @@ import {
TextVariant,
} from '../../../../../helpers/constants/design-system';
import {
+ getCurrentChainId,
getCurrentCurrency,
getSelectedAccountCachedBalance,
getTokensMarketData,
@@ -66,10 +68,12 @@ export const PercentageAndAmountChange = ({
const conversionRate = useSelector(getConversionRate);
const nativeCurrency = useSelector(getNativeCurrency);
const marketData = useSelector(getTokensMarketData);
+ const currentChainId = useSelector(getCurrentChainId);
const balanceChange = useMemo(() => {
// Extracts the 1-day percentage change in price from marketData using the zero address as a key.
- const percentage1d = marketData?.[zeroAddress()]?.pricePercentChange1d;
+ const percentage1d =
+ marketData?.[getNativeTokenAddress(currentChainId)]?.pricePercentChange1d;
// Checks if the balanceValue is in hex format. This is important for cryptocurrency balances which are often represented in hex.
if (isHexString(balanceValue)) {
diff --git a/ui/components/multichain/token-list-item/token-list-item.tsx b/ui/components/multichain/token-list-item/token-list-item.tsx
index 540d2d8be98a..5ee4c19c8c52 100644
--- a/ui/components/multichain/token-list-item/token-list-item.tsx
+++ b/ui/components/multichain/token-list-item/token-list-item.tsx
@@ -2,7 +2,8 @@ import React, { useContext, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import classnames from 'classnames';
-import { zeroAddress } from 'ethereumjs-util';
+import { getNativeTokenAddress } from '@metamask/assets-controllers';
+import { Hex } from '@metamask/utils';
import {
AlignItems,
BackgroundColor,
@@ -55,7 +56,10 @@ import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
-import { CURRENCY_SYMBOLS } from '../../../../shared/constants/network';
+import {
+ CURRENCY_SYMBOLS,
+ NON_EVM_CURRENCY_SYMBOLS,
+} from '../../../../shared/constants/network';
import { hexToDecimal } from '../../../../shared/modules/conversion.utils';
import { NETWORKS_ROUTE } from '../../../helpers/constants/routes';
@@ -140,8 +144,10 @@ export const TokenListItem = ({
switch (title) {
case CURRENCY_SYMBOLS.ETH:
return t('networkNameEthereum');
- case CURRENCY_SYMBOLS.BTC:
+ case NON_EVM_CURRENCY_SYMBOLS.BTC:
return t('networkNameBitcoin');
+ case NON_EVM_CURRENCY_SYMBOLS.SOL:
+ return t('networkNameSolana');
default:
return title;
}
@@ -336,13 +342,14 @@ export const TokenListItem = ({
diff --git a/ui/ducks/bridge/actions.ts b/ui/ducks/bridge/actions.ts
index a61d2fdcd8fd..766689cb8cda 100644
--- a/ui/ducks/bridge/actions.ts
+++ b/ui/ducks/bridge/actions.ts
@@ -11,7 +11,11 @@ import { forceUpdateMetamaskState } from '../../store/actions';
import { submitRequestToBackground } from '../../store/background-connection';
import { QuoteRequest } from '../../pages/bridge/types';
import { MetaMaskReduxDispatch } from '../../store/store';
-import { bridgeSlice } from './bridge';
+import {
+ bridgeSlice,
+ setDestTokenExchangeRates,
+ setSrcTokenExchangeRates,
+} from './bridge';
const {
setToChainId,
@@ -19,6 +23,8 @@ const {
setToToken,
setFromTokenInputValue,
resetInputFields,
+ setSortOrder,
+ setSelectedQuote,
} = bridgeSlice.actions;
export {
@@ -27,6 +33,10 @@ export {
setToToken,
setFromToken,
setFromTokenInputValue,
+ setDestTokenExchangeRates,
+ setSrcTokenExchangeRates,
+ setSortOrder,
+ setSelectedQuote,
};
const callBridgeControllerMethod = (
diff --git a/ui/ducks/bridge/bridge.test.ts b/ui/ducks/bridge/bridge.test.ts
index dc9596fcafba..5a395fa23036 100644
--- a/ui/ducks/bridge/bridge.test.ts
+++ b/ui/ducks/bridge/bridge.test.ts
@@ -10,6 +10,7 @@ import {
// TODO: Remove restricted import
// eslint-disable-next-line import/no-restricted-paths
} from '../../../app/scripts/controllers/bridge/types';
+import * as util from '../../helpers/utils/util';
import bridgeReducer from './bridge';
import {
setBridgeFeatureFlags,
@@ -22,6 +23,7 @@ import {
setToChainId,
updateQuoteRequestParams,
resetBridgeState,
+ setDestTokenExchangeRates,
} from './actions';
const middleware = [thunk];
@@ -143,10 +145,14 @@ describe('Ducks - Bridge', () => {
expect(actions[0].type).toStrictEqual('bridge/resetInputFields');
const newState = bridgeReducer(state, actions[0]);
expect(newState).toStrictEqual({
+ selectedQuote: null,
toChainId: null,
fromToken: null,
toToken: null,
fromTokenInputValue: null,
+ sortOrder: 0,
+ toTokenExchangeRate: null,
+ fromTokenExchangeRate: null,
});
});
});
@@ -201,10 +207,103 @@ describe('Ducks - Bridge', () => {
expect(actions[0].type).toStrictEqual('bridge/resetInputFields');
const newState = bridgeReducer(state, actions[0]);
expect(newState).toStrictEqual({
- toChainId: null,
fromToken: null,
- toToken: null,
+ fromTokenExchangeRate: null,
fromTokenInputValue: null,
+ selectedQuote: null,
+ sortOrder: 0,
+ toChainId: null,
+ toToken: null,
+ toTokenExchangeRate: null,
+ });
+ });
+ });
+ describe('setDestTokenExchangeRates', () => {
+ it('fetches token prices and updates dest exchange rates in state, native dest token', async () => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const mockStore = configureMockStore(middleware)(
+ createBridgeMockStore(),
+ );
+ const state = mockStore.getState().bridge;
+ const fetchTokenExchangeRatesSpy = jest
+ .spyOn(util, 'fetchTokenExchangeRates')
+ .mockResolvedValue({
+ '0x0000000000000000000000000000000000000000': 0.356628,
+ });
+
+ await mockStore.dispatch(
+ setDestTokenExchangeRates({
+ chainId: CHAIN_IDS.LINEA_MAINNET,
+ tokenAddress: zeroAddress(),
+ currency: 'usd',
+ }) as never,
+ );
+
+ expect(fetchTokenExchangeRatesSpy).toHaveBeenCalledTimes(1);
+ expect(fetchTokenExchangeRatesSpy).toHaveBeenCalledWith(
+ 'usd',
+ ['0x0000000000000000000000000000000000000000'],
+ CHAIN_IDS.LINEA_MAINNET,
+ );
+
+ const actions = mockStore.getActions();
+ expect(actions).toHaveLength(2);
+ expect(actions[0].type).toStrictEqual(
+ 'bridge/setDestTokenExchangeRates/pending',
+ );
+ expect(actions[1].type).toStrictEqual(
+ 'bridge/setDestTokenExchangeRates/fulfilled',
+ );
+ const newState = bridgeReducer(state, actions[1]);
+ expect(newState).toStrictEqual({
+ toChainId: null,
+ toTokenExchangeRate: 0.356628,
+ sortOrder: 0,
+ });
+ });
+
+ it('fetches token prices and updates dest exchange rates in state, erc20 dest token', async () => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const mockStore = configureMockStore(middleware)(
+ createBridgeMockStore(),
+ );
+ const state = mockStore.getState().bridge;
+ const fetchTokenExchangeRatesSpy = jest
+ .spyOn(util, 'fetchTokenExchangeRates')
+ .mockResolvedValue({
+ '0x0000000000000000000000000000000000000000': 0.356628,
+ '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359': 0.999881,
+ });
+
+ await mockStore.dispatch(
+ setDestTokenExchangeRates({
+ chainId: CHAIN_IDS.LINEA_MAINNET,
+ tokenAddress:
+ '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359'.toLowerCase(),
+ currency: 'usd',
+ }) as never,
+ );
+
+ expect(fetchTokenExchangeRatesSpy).toHaveBeenCalledTimes(1);
+ expect(fetchTokenExchangeRatesSpy).toHaveBeenCalledWith(
+ 'usd',
+ ['0x3c499c542cef5e3811e1192ce70d8cc03d5c3359'],
+ CHAIN_IDS.LINEA_MAINNET,
+ );
+
+ const actions = mockStore.getActions();
+ expect(actions).toHaveLength(2);
+ expect(actions[0].type).toStrictEqual(
+ 'bridge/setDestTokenExchangeRates/pending',
+ );
+ expect(actions[1].type).toStrictEqual(
+ 'bridge/setDestTokenExchangeRates/fulfilled',
+ );
+ const newState = bridgeReducer(state, actions[1]);
+ expect(newState).toStrictEqual({
+ toChainId: null,
+ toTokenExchangeRate: 0.999881,
+ sortOrder: 0,
});
});
});
diff --git a/ui/ducks/bridge/bridge.ts b/ui/ducks/bridge/bridge.ts
index c75030c7591d..edb0c9ca0d13 100644
--- a/ui/ducks/bridge/bridge.ts
+++ b/ui/ducks/bridge/bridge.ts
@@ -1,15 +1,24 @@
-import { createSlice } from '@reduxjs/toolkit';
-
+import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { Hex } from '@metamask/utils';
import { swapsSlice } from '../swaps/swaps';
import { SwapsTokenObject } from '../../../shared/constants/swaps';
import { SwapsEthToken } from '../../selectors';
+import {
+ QuoteMetadata,
+ QuoteResponse,
+ SortOrder,
+} from '../../pages/bridge/types';
+import { getTokenExchangeRate } from './utils';
export type BridgeState = {
toChainId: Hex | null;
fromToken: SwapsTokenObject | SwapsEthToken | null;
toToken: SwapsTokenObject | SwapsEthToken | null;
fromTokenInputValue: string | null;
+ fromTokenExchangeRate: number | null;
+ toTokenExchangeRate: number | null;
+ sortOrder: SortOrder;
+ selectedQuote: (QuoteResponse & QuoteMetadata) | null; // Alternate quote selected by user. When quotes refresh, the best match will be activated.
};
const initialState: BridgeState = {
@@ -17,8 +26,22 @@ const initialState: BridgeState = {
fromToken: null,
toToken: null,
fromTokenInputValue: null,
+ fromTokenExchangeRate: null,
+ toTokenExchangeRate: null,
+ sortOrder: SortOrder.COST_ASC,
+ selectedQuote: null,
};
+export const setSrcTokenExchangeRates = createAsyncThunk(
+ 'bridge/setSrcTokenExchangeRates',
+ getTokenExchangeRate,
+);
+
+export const setDestTokenExchangeRates = createAsyncThunk(
+ 'bridge/setDestTokenExchangeRates',
+ getTokenExchangeRate,
+);
+
const bridgeSlice = createSlice({
name: 'bridge',
initialState: { ...initialState },
@@ -39,6 +62,20 @@ const bridgeSlice = createSlice({
resetInputFields: () => ({
...initialState,
}),
+ setSortOrder: (state, action) => {
+ state.sortOrder = action.payload;
+ },
+ setSelectedQuote: (state, action) => {
+ state.selectedQuote = action.payload;
+ },
+ },
+ extraReducers: (builder) => {
+ builder.addCase(setDestTokenExchangeRates.fulfilled, (state, action) => {
+ state.toTokenExchangeRate = action.payload ?? null;
+ });
+ builder.addCase(setSrcTokenExchangeRates.fulfilled, (state, action) => {
+ state.fromTokenExchangeRate = action.payload ?? null;
+ });
},
});
diff --git a/ui/ducks/bridge/selectors.test.ts b/ui/ducks/bridge/selectors.test.ts
index e39f73f2fa15..b92c8e60e4f0 100644
--- a/ui/ducks/bridge/selectors.test.ts
+++ b/ui/ducks/bridge/selectors.test.ts
@@ -1,12 +1,18 @@
+import { BigNumber } from 'bignumber.js';
import { createBridgeMockStore } from '../../../test/jest/mock-store';
import {
BUILT_IN_NETWORKS,
CHAIN_IDS,
FEATURED_RPCS,
} from '../../../shared/constants/network';
-import { ALLOWED_BRIDGE_CHAIN_IDS } from '../../../shared/constants/bridge';
+import {
+ ALLOWED_BRIDGE_CHAIN_IDS,
+ BRIDGE_QUOTE_MAX_ETA_SECONDS,
+} from '../../../shared/constants/bridge';
import { mockNetworkState } from '../../../test/stub/networks';
import mockErc20Erc20Quotes from '../../../test/data/bridge/mock-quotes-erc20-erc20.json';
+import mockBridgeQuotesNativeErc20 from '../../../test/data/bridge/mock-quotes-native-erc20.json';
+import { SortOrder } from '../../pages/bridge/types';
import {
getAllBridgeableNetworks,
getBridgeQuotes,
@@ -17,7 +23,6 @@ import {
getFromTokens,
getFromTopAssets,
getIsBridgeTx,
- getToAmount,
getToChain,
getToChains,
getToToken,
@@ -392,15 +397,6 @@ describe('Bridge selectors', () => {
});
});
- describe('getToAmount', () => {
- it('returns hardcoded 0', () => {
- const state = createBridgeMockStore();
- const result = getToAmount(state as never);
-
- expect(result).toStrictEqual(undefined);
- });
- });
-
describe('getToTokens', () => {
it('returns dest tokens from controller state when toChainId is defined', () => {
const state = createBridgeMockStore(
@@ -498,7 +494,12 @@ describe('Bridge selectors', () => {
it('returns quote list and fetch data, insufficientBal=false,quotesRefreshCount=5', () => {
const state = createBridgeMockStore(
{ extensionConfig: { maxRefreshCount: 5 } },
- { toChainId: '0x1' },
+ {
+ toChainId: '0x1',
+ fromTokenExchangeRate: 1,
+ toTokenExchangeRate: 0.99,
+ toNativeExchangeRate: 0.354073,
+ },
{
quoteRequest: { insufficientBal: false },
quotes: mockErc20Erc20Quotes,
@@ -508,11 +509,51 @@ describe('Bridge selectors', () => {
srcTokens: { '0x00': { address: '0x00', symbol: 'TEST' } },
srcTopAssets: [{ address: '0x00', symbol: 'TEST' }],
},
+ {
+ currencyRates: {
+ ETH: {
+ conversionRate: 1,
+ },
+ },
+ },
);
- const result = getBridgeQuotes(state as never);
+ const recommendedQuoteMetadata = {
+ adjustedReturn: {
+ fiat: expect.any(Object),
+ },
+ cost: { fiat: new BigNumber('0.15656287141025952') },
+ sentAmount: {
+ fiat: new BigNumber('14'),
+ amount: new BigNumber('14'),
+ },
+ swapRate: new BigNumber('0.998877142857142857142857142857142857'),
+ toTokenAmount: {
+ fiat: new BigNumber('13.8444372'),
+ amount: new BigNumber('13.98428'),
+ },
+ gasFee: {
+ amount: new BigNumber('7.141025952e-8'),
+ fiat: new BigNumber('7.141025952e-8'),
+ },
+ totalNetworkFee: {
+ fiat: new BigNumber('0.00100007141025952'),
+ amount: new BigNumber('0.00100007141025952'),
+ },
+ };
+
+ const result = getBridgeQuotes(state as never);
+ expect(result.sortedQuotes).toHaveLength(2);
expect(result).toStrictEqual({
- quotes: mockErc20Erc20Quotes,
+ sortedQuotes: expect.any(Array),
+ recommendedQuote: {
+ ...mockErc20Erc20Quotes[0],
+ ...recommendedQuoteMetadata,
+ },
+ activeQuote: {
+ ...mockErc20Erc20Quotes[0],
+ ...recommendedQuoteMetadata,
+ },
quotesLastFetchedMs: 100,
isLoading: false,
quotesRefreshCount: 5,
@@ -523,7 +564,12 @@ describe('Bridge selectors', () => {
it('returns quote list and fetch data, insufficientBal=false,quotesRefreshCount=2', () => {
const state = createBridgeMockStore(
{ extensionConfig: { maxRefreshCount: 5 } },
- { toChainId: '0x1' },
+ {
+ toChainId: '0x1',
+ fromTokenExchangeRate: 1,
+ toTokenExchangeRate: 0.99,
+ toNativeExchangeRate: 0.354073,
+ },
{
quoteRequest: { insufficientBal: false },
quotes: mockErc20Erc20Quotes,
@@ -533,11 +579,57 @@ describe('Bridge selectors', () => {
srcTokens: { '0x00': { address: '0x00', symbol: 'TEST' } },
srcTopAssets: [{ address: '0x00', symbol: 'TEST' }],
},
+ {
+ currencyRates: {
+ ETH: {
+ conversionRate: 1,
+ },
+ },
+ },
);
const result = getBridgeQuotes(state as never);
+ const recommendedQuoteMetadata = {
+ adjustedReturn: {
+ fiat: new BigNumber('13.84343712858974048'),
+ },
+ cost: { fiat: new BigNumber('0.15656287141025952') },
+ sentAmount: {
+ fiat: new BigNumber('14'),
+ amount: new BigNumber('14'),
+ },
+ swapRate: new BigNumber('0.998877142857142857142857142857142857'),
+ toTokenAmount: {
+ fiat: new BigNumber('13.8444372'),
+ amount: new BigNumber('13.98428'),
+ },
+ gasFee: {
+ amount: new BigNumber('7.141025952e-8'),
+ fiat: new BigNumber('7.141025952e-8'),
+ },
+ totalNetworkFee: {
+ fiat: new BigNumber('0.00100007141025952'),
+ amount: new BigNumber('0.00100007141025952'),
+ },
+ };
+ expect(result.sortedQuotes).toHaveLength(2);
+ const EXPECTED_SORTED_COSTS = [
+ { fiat: new BigNumber('0.15656287141025952') },
+ { fiat: new BigNumber('0.33900008283534464') },
+ ];
+ result.sortedQuotes.forEach((quote, idx) => {
+ expect(quote.cost).toStrictEqual(EXPECTED_SORTED_COSTS[idx]);
+ });
expect(result).toStrictEqual({
- quotes: mockErc20Erc20Quotes,
+ sortedQuotes: expect.any(Array),
+ recommendedQuote: {
+ ...mockErc20Erc20Quotes[0],
+ ...recommendedQuoteMetadata,
+ },
+ activeQuote: {
+ ...mockErc20Erc20Quotes[0],
+ ...recommendedQuoteMetadata,
+ },
quotesLastFetchedMs: 100,
isLoading: false,
quotesRefreshCount: 2,
@@ -548,7 +640,12 @@ describe('Bridge selectors', () => {
it('returns quote list and fetch data, insufficientBal=true', () => {
const state = createBridgeMockStore(
{ extensionConfig: { maxRefreshCount: 5 } },
- { toChainId: '0x1' },
+ {
+ toChainId: '0x1',
+ fromTokenExchangeRate: 1,
+ toTokenExchangeRate: 0.99,
+ toNativeExchangeRate: 0.354073,
+ },
{
quoteRequest: { insufficientBal: true },
quotes: mockErc20Erc20Quotes,
@@ -558,11 +655,58 @@ describe('Bridge selectors', () => {
srcTokens: { '0x00': { address: '0x00', symbol: 'TEST' } },
srcTopAssets: [{ address: '0x00', symbol: 'TEST' }],
},
+ {
+ currencyRates: {
+ ETH: {
+ conversionRate: 1,
+ },
+ },
+ },
);
const result = getBridgeQuotes(state as never);
+ const recommendedQuoteMetadata = {
+ adjustedReturn: {
+ fiat: new BigNumber('13.84343712858974048'),
+ },
+ cost: { fiat: new BigNumber('0.15656287141025952') },
+ sentAmount: {
+ fiat: new BigNumber('14'),
+ amount: new BigNumber('14'),
+ },
+ swapRate: new BigNumber('0.998877142857142857142857142857142857'),
+ toTokenAmount: {
+ fiat: new BigNumber('13.8444372'),
+ amount: new BigNumber('13.98428'),
+ },
+ gasFee: {
+ amount: new BigNumber('7.141025952e-8'),
+ fiat: new BigNumber('7.141025952e-8'),
+ },
+ totalNetworkFee: {
+ fiat: new BigNumber('0.00100007141025952'),
+ amount: new BigNumber('0.00100007141025952'),
+ },
+ };
+ expect(result.sortedQuotes).toHaveLength(2);
+ const EXPECTED_SORTED_COSTS = [
+ { fiat: new BigNumber('0.15656287141025952') },
+ { fiat: new BigNumber('0.33900008283534464') },
+ ];
+ result.sortedQuotes.forEach((quote, idx) => {
+ expect(quote.cost).toStrictEqual(EXPECTED_SORTED_COSTS[idx]);
+ });
+
expect(result).toStrictEqual({
- quotes: mockErc20Erc20Quotes,
+ sortedQuotes: expect.any(Array),
+ recommendedQuote: {
+ ...mockErc20Erc20Quotes[0],
+ ...recommendedQuoteMetadata,
+ },
+ activeQuote: {
+ ...mockErc20Erc20Quotes[0],
+ ...recommendedQuoteMetadata,
+ },
quotesLastFetchedMs: 100,
isLoading: false,
quotesRefreshCount: 1,
@@ -570,4 +714,203 @@ describe('Bridge selectors', () => {
});
});
});
+
+ describe('getBridgeQuotes', () => {
+ it('should return empty values when quotes are not present', () => {
+ const state = createBridgeMockStore();
+
+ const result = getBridgeQuotes(state as never);
+
+ expect(result).toStrictEqual({
+ activeQuote: undefined,
+ isLoading: false,
+ isQuoteGoingToRefresh: false,
+ quotesLastFetchedMs: undefined,
+ quotesRefreshCount: undefined,
+ recommendedQuote: undefined,
+ sortedQuotes: [],
+ });
+ });
+
+ it('should sort quotes by adjustedReturn', () => {
+ const state = createBridgeMockStore(
+ {},
+ {},
+ { quotes: mockBridgeQuotesNativeErc20 },
+ );
+
+ const { activeQuote, recommendedQuote, sortedQuotes } = getBridgeQuotes(
+ state as never,
+ );
+
+ const quoteMetadataKeys = [
+ 'adjustedReturn',
+ 'toTokenAmount',
+ 'sentAmount',
+ 'totalNetworkFee',
+ 'swapRate',
+ ];
+ expect(
+ quoteMetadataKeys.every((k) =>
+ Object.keys(activeQuote ?? {}).includes(k),
+ ),
+ ).toBe(true);
+ expect(activeQuote?.quote.requestId).toStrictEqual(
+ '381c23bc-e3e4-48fe-bc53-257471e388ad',
+ );
+ expect(recommendedQuote?.quote.requestId).toStrictEqual(
+ '381c23bc-e3e4-48fe-bc53-257471e388ad',
+ );
+ expect(sortedQuotes).toHaveLength(2);
+ sortedQuotes.forEach((quote, idx) => {
+ expect(
+ quoteMetadataKeys.every((k) => Object.keys(quote ?? {}).includes(k)),
+ ).toBe(true);
+ expect(quote?.quote.requestId).toStrictEqual(
+ mockBridgeQuotesNativeErc20[idx]?.quote.requestId,
+ );
+ });
+ });
+
+ it('should sort quotes by ETA', () => {
+ const state = createBridgeMockStore(
+ {},
+ { sortOrder: SortOrder.ETA_ASC },
+ {
+ quotes: [
+ ...mockBridgeQuotesNativeErc20,
+ {
+ ...mockBridgeQuotesNativeErc20[0],
+ estimatedProcessingTimeInSeconds: 1,
+ quote: {
+ ...mockBridgeQuotesNativeErc20[0].quote,
+ requestId: 'fastestQuote',
+ },
+ },
+ ],
+ },
+ );
+
+ const { activeQuote, recommendedQuote, sortedQuotes } = getBridgeQuotes(
+ state as never,
+ );
+
+ expect(activeQuote?.quote.requestId).toStrictEqual('fastestQuote');
+ expect(recommendedQuote?.quote.requestId).toStrictEqual('fastestQuote');
+ expect(sortedQuotes).toHaveLength(3);
+ expect(sortedQuotes[0]?.quote.requestId).toStrictEqual('fastestQuote');
+ expect(sortedQuotes[1]?.quote.requestId).toStrictEqual(
+ mockBridgeQuotesNativeErc20[1]?.quote.requestId,
+ );
+ expect(sortedQuotes[2]?.quote.requestId).toStrictEqual(
+ mockBridgeQuotesNativeErc20[0]?.quote.requestId,
+ );
+ });
+
+ it('should recommend 2nd cheapest quote if ETA exceeds 1 hour', () => {
+ const state = createBridgeMockStore(
+ {},
+ { sortOrder: SortOrder.COST_ASC },
+ {
+ quotes: [
+ mockBridgeQuotesNativeErc20[1],
+ {
+ ...mockBridgeQuotesNativeErc20[0],
+ estimatedProcessingTimeInSeconds:
+ BRIDGE_QUOTE_MAX_ETA_SECONDS + 1,
+ quote: {
+ ...mockBridgeQuotesNativeErc20[0].quote,
+ requestId: 'cheapestQuoteWithLongETA',
+ },
+ },
+ ],
+ },
+ );
+
+ const { activeQuote, recommendedQuote, sortedQuotes } = getBridgeQuotes(
+ state as never,
+ );
+
+ expect(activeQuote?.quote.requestId).toStrictEqual(
+ '4277a368-40d7-4e82-aa67-74f29dc5f98a',
+ );
+ expect(recommendedQuote?.quote.requestId).toStrictEqual(
+ '4277a368-40d7-4e82-aa67-74f29dc5f98a',
+ );
+ expect(sortedQuotes).toHaveLength(2);
+ expect(sortedQuotes[0]?.quote.requestId).toStrictEqual(
+ '4277a368-40d7-4e82-aa67-74f29dc5f98a',
+ );
+ expect(sortedQuotes[1]?.quote.requestId).toStrictEqual(
+ 'cheapestQuoteWithLongETA',
+ );
+ });
+
+ it('should recommend 2nd fastest quote if adjustedReturn is less than 80% of cheapest quote', () => {
+ const state = createBridgeMockStore(
+ {},
+ {
+ sortOrder: SortOrder.ETA_ASC,
+ toTokenExchangeRate: 0.998781,
+ toNativeExchangeRate: 0.354073,
+ },
+ {
+ quotes: [
+ ...mockBridgeQuotesNativeErc20,
+ {
+ ...mockBridgeQuotesNativeErc20[0],
+ estimatedProcessingTimeInSeconds: 1,
+ quote: {
+ ...mockBridgeQuotesNativeErc20[0].quote,
+ requestId: 'fastestQuote',
+ destTokenAmount: '1',
+ },
+ },
+ ],
+ },
+ {
+ currencyRates: {
+ ETH: {
+ conversionRate: 2524.25,
+ },
+ },
+ },
+ );
+
+ const { activeQuote, recommendedQuote, sortedQuotes } = getBridgeQuotes(
+ state as never,
+ );
+ const {
+ sentAmount,
+ totalNetworkFee,
+ toTokenAmount,
+ adjustedReturn,
+ cost,
+ } = activeQuote ?? {};
+
+ expect(activeQuote?.quote.requestId).toStrictEqual(
+ '4277a368-40d7-4e82-aa67-74f29dc5f98a',
+ );
+ expect(recommendedQuote?.quote.requestId).toStrictEqual(
+ '4277a368-40d7-4e82-aa67-74f29dc5f98a',
+ );
+ expect(sentAmount?.fiat?.toString()).toStrictEqual('25.2425');
+ expect(totalNetworkFee?.fiat?.toString()).toStrictEqual(
+ '2.52459306428938562',
+ );
+ expect(toTokenAmount?.fiat?.toString()).toStrictEqual('24.226654664163');
+ expect(adjustedReturn?.fiat?.toString()).toStrictEqual(
+ '21.70206159987361438',
+ );
+ expect(cost?.fiat?.toString()).toStrictEqual('3.54043840012638562');
+ expect(sortedQuotes).toHaveLength(3);
+ expect(sortedQuotes[0]?.quote.requestId).toStrictEqual('fastestQuote');
+ expect(sortedQuotes[1]?.quote.requestId).toStrictEqual(
+ '4277a368-40d7-4e82-aa67-74f29dc5f98a',
+ );
+ expect(sortedQuotes[2]?.quote.requestId).toStrictEqual(
+ '381c23bc-e3e4-48fe-bc53-257471e388ad',
+ );
+ });
+ });
});
diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts
index 86f4c8155b17..b78a0d09de51 100644
--- a/ui/ducks/bridge/selectors.ts
+++ b/ui/ducks/bridge/selectors.ts
@@ -1,12 +1,22 @@
-import { NetworkConfiguration } from '@metamask/network-controller';
-import { uniqBy } from 'lodash';
+import {
+ NetworkConfiguration,
+ NetworkState,
+} from '@metamask/network-controller';
+import { orderBy, uniqBy } from 'lodash';
import { createSelector } from 'reselect';
+import { GasFeeEstimates } from '@metamask/gas-fee-controller';
+import { BigNumber } from 'bignumber.js';
import {
getIsBridgeEnabled,
getSwapsDefaultToken,
SwapsEthToken,
} from '../../selectors/selectors';
-import { ALLOWED_BRIDGE_CHAIN_IDS } from '../../../shared/constants/bridge';
+import {
+ ALLOWED_BRIDGE_CHAIN_IDS,
+ BRIDGE_PREFERRED_GAS_ESTIMATE,
+ BRIDGE_QUOTE_MAX_ETA_SECONDS,
+ BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE,
+} from '../../../shared/constants/bridge';
import {
BridgeControllerState,
BridgeFeatureFlagsKey,
@@ -15,21 +25,38 @@ import {
} from '../../../app/scripts/controllers/bridge/types';
import { createDeepEqualSelector } from '../../../shared/modules/selectors/util';
import {
- NetworkState,
getProviderConfig,
getNetworkConfigurationsByChainId,
} from '../../../shared/modules/selectors/networks';
import { SwapsTokenObject } from '../../../shared/constants/swaps';
-import { calcTokenAmount } from '../../../shared/lib/transactions-controller-utils';
+import { getConversionRate, getGasFeeEstimates } from '../metamask/metamask';
// TODO: Remove restricted import
// eslint-disable-next-line import/no-restricted-paths
import { RequestStatus } from '../../../app/scripts/controllers/bridge/constants';
+import {
+ L1GasFees,
+ QuoteMetadata,
+ QuoteResponse,
+ SortOrder,
+} from '../../pages/bridge/types';
+import {
+ calcAdjustedReturn,
+ calcCost,
+ calcRelayerFee,
+ calcSentAmount,
+ calcSwapRate,
+ calcToAmount,
+ calcTotalGasFee,
+ isNativeAddress,
+} from '../../pages/bridge/utils/quote';
+import { decGWEIToHexWEI } from '../../../shared/modules/conversion.utils';
import { BridgeState } from './bridge';
-type BridgeAppState = NetworkState & {
- metamask: { bridgeState: BridgeControllerState } & {
- useExternalServices: boolean;
- };
+type BridgeAppState = {
+ metamask: { bridgeState: BridgeControllerState } & NetworkState & {
+ useExternalServices: boolean;
+ currencyRates: { [currency: string]: { conversionRate: number } };
+ };
bridge: BridgeState;
};
@@ -140,48 +167,203 @@ export const getBridgeQuotesConfig = (state: BridgeAppState) =>
BridgeFeatureFlagsKey.EXTENSION_CONFIG
] ?? {};
+const _getBridgeFeesPerGas = createSelector(
+ getGasFeeEstimates,
+ (gasFeeEstimates) => ({
+ estimatedBaseFeeInDecGwei: (gasFeeEstimates as GasFeeEstimates)
+ ?.estimatedBaseFee,
+ maxPriorityFeePerGasInDecGwei: (gasFeeEstimates as GasFeeEstimates)?.[
+ BRIDGE_PREFERRED_GAS_ESTIMATE
+ ]?.suggestedMaxPriorityFeePerGas,
+ maxFeePerGas: decGWEIToHexWEI(
+ (gasFeeEstimates as GasFeeEstimates)?.high?.suggestedMaxFeePerGas,
+ ),
+ maxPriorityFeePerGas: decGWEIToHexWEI(
+ (gasFeeEstimates as GasFeeEstimates)?.high?.suggestedMaxPriorityFeePerGas,
+ ),
+ }),
+);
+
+export const getBridgeSortOrder = (state: BridgeAppState) =>
+ state.bridge.sortOrder;
+
+// A dest network can be selected before it's imported
+// The cached exchange rate won't be available so the rate from the bridge state is used
+const _getToTokenExchangeRate = createSelector(
+ (state) => state.metamask.currencyRates,
+ (state: BridgeAppState) => state.bridge.toTokenExchangeRate,
+ getToChain,
+ getToToken,
+ (cachedCurrencyRates, toTokenExchangeRate, toChain, toToken) => {
+ return (
+ toTokenExchangeRate ??
+ (isNativeAddress(toToken?.address) && toChain?.nativeCurrency
+ ? cachedCurrencyRates[toChain.nativeCurrency]?.conversionRate
+ : null)
+ );
+ },
+);
+
+const _getQuotesWithMetadata = createDeepEqualSelector(
+ (state) => state.metamask.bridgeState.quotes,
+ _getToTokenExchangeRate,
+ (state: BridgeAppState) => state.bridge.fromTokenExchangeRate,
+ getConversionRate,
+ _getBridgeFeesPerGas,
+ (
+ quotes,
+ toTokenExchangeRate,
+ fromTokenExchangeRate,
+ nativeExchangeRate,
+ { estimatedBaseFeeInDecGwei, maxPriorityFeePerGasInDecGwei },
+ ): (QuoteResponse & QuoteMetadata)[] => {
+ const newQuotes = quotes.map((quote: QuoteResponse) => {
+ const toTokenAmount = calcToAmount(quote.quote, toTokenExchangeRate);
+ const gasFee = calcTotalGasFee(
+ quote,
+ estimatedBaseFeeInDecGwei,
+ maxPriorityFeePerGasInDecGwei,
+ nativeExchangeRate,
+ );
+ const relayerFee = calcRelayerFee(quote, nativeExchangeRate);
+ const totalNetworkFee = {
+ amount: gasFee.amount.plus(relayerFee.amount),
+ fiat: gasFee.fiat?.plus(relayerFee.fiat || '0') ?? null,
+ };
+ const sentAmount = calcSentAmount(
+ quote.quote,
+ isNativeAddress(quote.quote.srcAsset.address)
+ ? nativeExchangeRate
+ : fromTokenExchangeRate,
+ );
+ const adjustedReturn = calcAdjustedReturn(
+ toTokenAmount.fiat,
+ totalNetworkFee.fiat,
+ );
+
+ return {
+ ...quote,
+ toTokenAmount,
+ sentAmount,
+ totalNetworkFee,
+ adjustedReturn,
+ gasFee,
+ swapRate: calcSwapRate(sentAmount.amount, toTokenAmount.amount),
+ cost: calcCost(adjustedReturn.fiat, sentAmount.fiat),
+ };
+ });
+
+ return newQuotes;
+ },
+);
+
+const _getSortedQuotesWithMetadata = createDeepEqualSelector(
+ _getQuotesWithMetadata,
+ getBridgeSortOrder,
+ (quotesWithMetadata, sortOrder) => {
+ switch (sortOrder) {
+ case SortOrder.ETA_ASC:
+ return orderBy(
+ quotesWithMetadata,
+ (quote) => quote.estimatedProcessingTimeInSeconds,
+ 'asc',
+ );
+ case SortOrder.COST_ASC:
+ default:
+ return orderBy(quotesWithMetadata, ({ cost }) => cost.fiat, 'asc');
+ }
+ },
+);
+
+const _getRecommendedQuote = createDeepEqualSelector(
+ _getSortedQuotesWithMetadata,
+ getBridgeSortOrder,
+ (sortedQuotesWithMetadata, sortOrder) => {
+ if (!sortedQuotesWithMetadata.length) {
+ return undefined;
+ }
+
+ const bestReturnValue = BigNumber.max(
+ sortedQuotesWithMetadata.map(
+ ({ adjustedReturn }) => adjustedReturn.fiat ?? 0,
+ ),
+ );
+
+ const isFastestQuoteValueReasonable = (
+ adjustedReturnInFiat: BigNumber | null,
+ ) =>
+ adjustedReturnInFiat
+ ? adjustedReturnInFiat
+ .div(bestReturnValue)
+ .gte(BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE)
+ : true;
+
+ const isBestPricedQuoteETAReasonable = (
+ estimatedProcessingTimeInSeconds: number,
+ ) => estimatedProcessingTimeInSeconds < BRIDGE_QUOTE_MAX_ETA_SECONDS;
+
+ return (
+ sortedQuotesWithMetadata.find((quote) => {
+ return sortOrder === SortOrder.ETA_ASC
+ ? isFastestQuoteValueReasonable(quote.adjustedReturn.fiat)
+ : isBestPricedQuoteETAReasonable(
+ quote.estimatedProcessingTimeInSeconds,
+ );
+ }) ?? sortedQuotesWithMetadata[0]
+ );
+ },
+);
+
+// Generates a pseudo-unique string that identifies each quote
+// by aggregator, bridge, steps and value
+const _getQuoteIdentifier = ({ quote }: QuoteResponse & L1GasFees) =>
+ `${quote.bridgeId}-${quote.bridges[0]}-${quote.steps.length}`;
+
+const _getSelectedQuote = createSelector(
+ (state: BridgeAppState) => state.metamask.bridgeState.quotesRefreshCount,
+ (state: BridgeAppState) => state.bridge.selectedQuote,
+ _getSortedQuotesWithMetadata,
+ (quotesRefreshCount, selectedQuote, sortedQuotesWithMetadata) =>
+ quotesRefreshCount <= 1
+ ? selectedQuote
+ : // Find match for selectedQuote in new quotes
+ sortedQuotesWithMetadata.find((quote) =>
+ selectedQuote
+ ? _getQuoteIdentifier(quote) === _getQuoteIdentifier(selectedQuote)
+ : false,
+ ),
+);
+
export const getBridgeQuotes = createSelector(
- (state: BridgeAppState) => state.metamask.bridgeState.quotes,
- (state: BridgeAppState) => state.metamask.bridgeState.quotesLastFetched,
- (state: BridgeAppState) =>
+ _getSortedQuotesWithMetadata,
+ _getRecommendedQuote,
+ _getSelectedQuote,
+ (state) => state.metamask.bridgeState.quotesLastFetched,
+ (state) =>
state.metamask.bridgeState.quotesLoadingStatus === RequestStatus.LOADING,
(state: BridgeAppState) => state.metamask.bridgeState.quotesRefreshCount,
getBridgeQuotesConfig,
getQuoteRequest,
(
- quotes,
+ sortedQuotesWithMetadata,
+ recommendedQuote,
+ selectedQuote,
quotesLastFetchedMs,
isLoading,
quotesRefreshCount,
{ maxRefreshCount },
{ insufficientBal },
- ) => {
- return {
- quotes,
- quotesLastFetchedMs,
- isLoading,
- quotesRefreshCount,
- isQuoteGoingToRefresh: insufficientBal
- ? false
- : quotesRefreshCount < maxRefreshCount,
- };
- },
-);
-
-export const getRecommendedQuote = createSelector(
- getBridgeQuotes,
- ({ quotes }) => {
- return quotes[0];
- },
-);
-
-export const getToAmount = createSelector(getRecommendedQuote, (quote) =>
- quote
- ? calcTokenAmount(
- quote.quote.destTokenAmount,
- quote.quote.destAsset.decimals,
- )
- : undefined,
+ ) => ({
+ sortedQuotes: sortedQuotesWithMetadata,
+ recommendedQuote,
+ activeQuote: selectedQuote ?? recommendedQuote,
+ quotesLastFetchedMs,
+ isLoading,
+ quotesRefreshCount,
+ isQuoteGoingToRefresh: insufficientBal
+ ? false
+ : quotesRefreshCount < maxRefreshCount,
+ }),
);
export const getIsBridgeTx = createDeepEqualSelector(
diff --git a/ui/ducks/bridge/utils.ts b/ui/ducks/bridge/utils.ts
index 853c344310fe..de45111cc10b 100644
--- a/ui/ducks/bridge/utils.ts
+++ b/ui/ducks/bridge/utils.ts
@@ -1,9 +1,11 @@
import { Hex } from '@metamask/utils';
import { BigNumber } from 'bignumber.js';
+import { getAddress } from 'ethers/lib/utils';
import { decGWEIToHexWEI } from '../../../shared/modules/conversion.utils';
import { Numeric } from '../../../shared/modules/Numeric';
import { TxData } from '../../pages/bridge/types';
import { getTransaction1559GasFeeEstimates } from '../../pages/swaps/swaps.util';
+import { fetchTokenExchangeRates } from '../../helpers/utils/util';
// We don't need to use gas multipliers here because the gasLimit from Bridge API already included it
export const getHexMaxGasLimit = (gasLimit: number) => {
@@ -45,3 +47,20 @@ export const getTxGasEstimates = async ({
maxPriorityFeePerGas: undefined,
};
};
+
+export const getTokenExchangeRate = async (request: {
+ chainId: Hex;
+ tokenAddress: string;
+ currency: string;
+}) => {
+ const { chainId, tokenAddress, currency } = request;
+ const exchangeRates = await fetchTokenExchangeRates(
+ currency,
+ [tokenAddress],
+ chainId,
+ );
+ return (
+ exchangeRates?.[tokenAddress.toLowerCase()] ??
+ exchangeRates?.[getAddress(tokenAddress)]
+ );
+};
diff --git a/ui/helpers/utils/mm-lazy.ts b/ui/helpers/utils/mm-lazy.ts
new file mode 100644
index 000000000000..e31c22dfc99a
--- /dev/null
+++ b/ui/helpers/utils/mm-lazy.ts
@@ -0,0 +1,86 @@
+import React from 'react';
+// eslint-disable-next-line import/no-restricted-paths
+import { getManifestFlags } from '../../../app/scripts/lib/manifestFlags';
+import { endTrace, trace, TraceName } from '../../../shared/lib/trace';
+
+type DynamicImportType = () => Promise<{ default: React.ComponentType }>;
+type ModuleWithDefaultType = {
+ default: React.ComponentType;
+};
+
+// This only has to happen once per app load, so do it outside a function
+const lazyLoadSubSampleRate = getManifestFlags().sentry?.lazyLoadSubSampleRate;
+
+/**
+ * A wrapper around React.lazy that adds two things:
+ * 1. Sentry tracing for how long it takes to load the component (not render, just load)
+ * 2. React.lazy can only deal with default exports, but the wrapper can handle named exports too
+ *
+ * @param fn - an import of the form `() => import('AAA')`
+ */
+export function mmLazy(fn: DynamicImportType) {
+ return React.lazy(async () => {
+ // We can't start the trace here because we don't have the componentName yet, so we just hold the startTime
+ const startTime = Date.now();
+
+ const importedModule = await fn();
+ const { componentName, component } = parseImportedComponent(importedModule);
+
+ // Only trace load time of lazy-loaded components if the manifestFlag is set, and then do it by Math.random probability
+ if (lazyLoadSubSampleRate && Math.random() < lazyLoadSubSampleRate) {
+ trace({
+ name: TraceName.LazyLoadComponent,
+ data: { componentName },
+ startTime,
+ });
+
+ endTrace({ name: TraceName.LazyLoadComponent });
+ }
+
+ return component;
+ });
+}
+
+// There can be a lot of different types here, and we're basically doing type-checking in the code,
+// so I don't think TypeScript safety on `importedModule` is worth it in this function
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function parseImportedComponent(importedModule: any): {
+ componentName: string; // TODO: in many circumstances, the componentName gets minified
+ component: ModuleWithDefaultType;
+} {
+ let componentName: string;
+
+ // If there's no default export
+ if (!importedModule.default) {
+ const keys = Object.keys(importedModule);
+
+ // If there's only one named export
+ if (keys.length === 1) {
+ componentName = keys[0];
+
+ return {
+ componentName,
+ // Force the component to be the default export
+ component: { default: importedModule[componentName] },
+ };
+ }
+
+ // If there are multiple named exports, this isn't good for tree-shaking, so throw an error
+ throw new Error(
+ 'mmLazy: You cannot lazy-load a component when there are multiple exported components in one file',
+ );
+ }
+
+ if (importedModule.default.WrappedComponent) {
+ // If there's a wrapped component, we don't want to see the name reported as `withRouter(Connect(AAA))` we want just `AAA`
+ componentName = importedModule.default.WrappedComponent.name;
+ } else {
+ componentName =
+ importedModule.default.name || importedModule.default.displayName;
+ }
+
+ return {
+ componentName,
+ component: importedModule,
+ };
+}
diff --git a/ui/helpers/utils/util.js b/ui/helpers/utils/util.js
index eafc8e31bfe5..d687d9b82338 100644
--- a/ui/helpers/utils/util.js
+++ b/ui/helpers/utils/util.js
@@ -1,7 +1,7 @@
import punycode from 'punycode/punycode';
import abi from 'human-standard-token-abi';
import BigNumber from 'bignumber.js';
-import * as ethUtil from 'ethereumjs-util';
+import BN from 'bn.js';
import { DateTime } from 'luxon';
import {
getFormattedIpfsUrl,
@@ -168,10 +168,10 @@ export function isOriginContractAddress(to, sendTokenAddress) {
// Takes wei Hex, returns wei BN, even if input is null
export function numericBalance(balance) {
if (!balance) {
- return new ethUtil.BN(0, 16);
+ return new BN(0, 16);
}
const stripped = stripHexPrefix(balance);
- return new ethUtil.BN(stripped, 16);
+ return new BN(stripped, 16);
}
// Takes hex, returns [beforeDecimal, afterDecimal]
diff --git a/ui/helpers/utils/util.test.js b/ui/helpers/utils/util.test.js
index d12a57675343..bdf5c9dd9b98 100644
--- a/ui/helpers/utils/util.test.js
+++ b/ui/helpers/utils/util.test.js
@@ -1,5 +1,6 @@
import Bowser from 'bowser';
-import { BN, toChecksumAddress } from 'ethereumjs-util';
+import BN from 'bn.js';
+import { toChecksumAddress } from 'ethereumjs-util';
import { CHAIN_IDS } from '../../../shared/constants/network';
import { addHexPrefixToObjectValues } from '../../../shared/lib/swaps-utils';
import { toPrecisionWithoutTrailingZeros } from '../../../shared/lib/transactions-controller-utils';
diff --git a/ui/hooks/bridge/useBridging.test.ts b/ui/hooks/bridge/useBridging.test.ts
index 6e3f3b534e35..9fe02c439048 100644
--- a/ui/hooks/bridge/useBridging.test.ts
+++ b/ui/hooks/bridge/useBridging.test.ts
@@ -123,19 +123,19 @@ describe('useBridging', () => {
// @ts-expect-error This is missing from the Mocha type definitions
it.each([
[
- '/cross-chain/swaps/prepare-swap-page',
+ '/cross-chain/swaps/prepare-swap-page?token=0x0000000000000000000000000000000000000000',
ETH_SWAPS_TOKEN_OBJECT,
'Home',
undefined,
],
[
- '/cross-chain/swaps/prepare-swap-page',
+ '/cross-chain/swaps/prepare-swap-page?token=0x0000000000000000000000000000000000000000',
ETH_SWAPS_TOKEN_OBJECT,
MetaMetricsSwapsEventSource.TokenView,
'&token=native',
],
[
- '/cross-chain/swaps/prepare-swap-page',
+ '/cross-chain/swaps/prepare-swap-page?token=0x00232f2jksdauo',
{
iconUrl: 'https://icon.url',
symbol: 'TEST',
@@ -174,7 +174,7 @@ describe('useBridging', () => {
result.current.openBridgeExperience(location, token, urlSuffix);
- expect(mockDispatch.mock.calls).toHaveLength(2);
+ expect(mockDispatch.mock.calls).toHaveLength(1);
expect(mockHistoryPush.mock.calls).toHaveLength(1);
expect(mockHistoryPush).toHaveBeenCalledWith(expectedUrl);
expect(openTabSpy).not.toHaveBeenCalled();
diff --git a/ui/hooks/bridge/useBridging.ts b/ui/hooks/bridge/useBridging.ts
index c4ae1cca57a3..62945e3e7a92 100644
--- a/ui/hooks/bridge/useBridging.ts
+++ b/ui/hooks/bridge/useBridging.ts
@@ -28,7 +28,6 @@ import {
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
import { isHardwareKeyring } from '../../helpers/utils/hardware';
import { getPortfolioUrl } from '../../helpers/utils/portfolio';
-import { setSwapsFromToken } from '../../ducks/swaps/swaps';
import { SwapsTokenObject } from '../../../shared/constants/swaps';
import { getProviderConfig } from '../../../shared/modules/selectors/networks';
///: END:ONLY_INCLUDE_IF
@@ -74,9 +73,6 @@ const useBridging = () => {
chain_id: providerConfig.chainId,
},
});
- dispatch(
- setSwapsFromToken({ ...token, address: token.address.toLowerCase() }),
- );
if (usingHardwareWallet && global.platform.openExtensionInBrowser) {
global.platform.openExtensionInBrowser(
PREPARE_SWAP_ROUTE,
@@ -84,7 +80,9 @@ const useBridging = () => {
false,
);
} else {
- history.push(CROSS_CHAIN_SWAP_ROUTE + PREPARE_SWAP_ROUTE);
+ history.push(
+ `${CROSS_CHAIN_SWAP_ROUTE}${PREPARE_SWAP_ROUTE}?token=${token.address.toLowerCase()}`,
+ );
}
} else {
const portfolioUrl = getPortfolioUrl(
@@ -115,7 +113,6 @@ const useBridging = () => {
[
isBridgeSupported,
isBridgeChain,
- setSwapsFromToken,
dispatch,
usingHardwareWallet,
history,
diff --git a/ui/hooks/bridge/useCountdownTimer.test.ts b/ui/hooks/bridge/useCountdownTimer.test.ts
index f2cd1190b1ba..293fe1ac679b 100644
--- a/ui/hooks/bridge/useCountdownTimer.test.ts
+++ b/ui/hooks/bridge/useCountdownTimer.test.ts
@@ -17,14 +17,11 @@ describe('useCountdownTimer', () => {
const quotesLastFetched = Date.now();
const { result } = renderUseCountdownTimer(
createBridgeMockStore(
- {},
+ { extensionConfig: { maxRefreshCount: 5, refreshRate: 40000 } },
{},
{
quotesLastFetched,
quotesRefreshCount: 0,
- bridgeFeatureFlags: {
- extensionConfig: { maxRefreshCount: 5, refreshRate: 40000 },
- },
},
),
);
diff --git a/ui/hooks/useAccountTrackerPolling.test.ts b/ui/hooks/useAccountTrackerPolling.test.ts
new file mode 100644
index 000000000000..010085ddce61
--- /dev/null
+++ b/ui/hooks/useAccountTrackerPolling.test.ts
@@ -0,0 +1,143 @@
+import { renderHookWithProvider } from '../../test/lib/render-helpers';
+import {
+ accountTrackerStartPolling,
+ accountTrackerStopPollingByPollingToken,
+} from '../store/actions';
+import useAccountTrackerPolling from './useAccountTrackerPolling';
+
+let mockPromises: Promise[];
+
+jest.mock('../store/actions', () => ({
+ accountTrackerStartPolling: jest.fn().mockImplementation((input) => {
+ const promise = Promise.resolve(`${input}_tracking`);
+ mockPromises.push(promise);
+ return promise;
+ }),
+ accountTrackerStopPollingByPollingToken: jest.fn(),
+}));
+
+let originalPortfolioView: string | undefined;
+
+describe('useAccountTrackerPolling', () => {
+ beforeEach(() => {
+ // Mock process.env.PORTFOLIO_VIEW
+ originalPortfolioView = process.env.PORTFOLIO_VIEW;
+ process.env.PORTFOLIO_VIEW = 'true'; // Set your desired mock value here
+
+ mockPromises = [];
+ jest.clearAllMocks();
+ });
+
+ afterEach(() => {
+ // Restore the original value
+ process.env.PORTFOLIO_VIEW = originalPortfolioView;
+ });
+
+ it('should poll account trackers for network client IDs when enabled and stop on dismount', async () => {
+ process.env.PORTFOLIO_VIEW = 'true';
+
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: true,
+ selectedNetworkClientId: 'selectedNetworkClientId',
+ networkConfigurationsByChainId: {
+ '0x1': {
+ chainId: '0x1',
+ defaultRpcEndpointIndex: 0,
+ rpcEndpoints: [
+ {
+ networkClientId: 'selectedNetworkClientId',
+ },
+ ],
+ },
+ '0x89': {
+ chainId: '0x89',
+ defaultRpcEndpointIndex: 0,
+ rpcEndpoints: [
+ {
+ networkClientId: 'selectedNetworkClientId2',
+ },
+ ],
+ },
+ },
+ },
+ };
+
+ const { unmount } = renderHookWithProvider(
+ () => useAccountTrackerPolling(),
+ state,
+ );
+
+ // Should poll each client ID
+ await Promise.all(mockPromises);
+ expect(accountTrackerStartPolling).toHaveBeenCalledTimes(2);
+ expect(accountTrackerStartPolling).toHaveBeenCalledWith(
+ 'selectedNetworkClientId',
+ );
+ expect(accountTrackerStartPolling).toHaveBeenCalledWith(
+ 'selectedNetworkClientId2',
+ );
+
+ // Stop polling on dismount
+ unmount();
+ expect(accountTrackerStopPollingByPollingToken).toHaveBeenCalledTimes(2);
+ expect(accountTrackerStopPollingByPollingToken).toHaveBeenCalledWith(
+ 'selectedNetworkClientId_tracking',
+ );
+ });
+
+ it('should not poll if onboarding is not completed', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: false,
+ networkConfigurationsByChainId: {
+ '0x1': {},
+ },
+ },
+ };
+
+ renderHookWithProvider(() => useAccountTrackerPolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(accountTrackerStartPolling).toHaveBeenCalledTimes(0);
+ expect(accountTrackerStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+
+ it('should not poll when locked', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: false,
+ completedOnboarding: true,
+ networkConfigurationsByChainId: {
+ '0x1': {},
+ },
+ },
+ };
+
+ renderHookWithProvider(() => useAccountTrackerPolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(accountTrackerStartPolling).toHaveBeenCalledTimes(0);
+ expect(accountTrackerStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+
+ it('should not poll when no network client IDs are provided', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: true,
+ networkConfigurationsByChainId: {
+ '0x1': {},
+ },
+ },
+ };
+
+ renderHookWithProvider(() => useAccountTrackerPolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(accountTrackerStartPolling).toHaveBeenCalledTimes(0);
+ expect(accountTrackerStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+});
diff --git a/ui/hooks/useCurrencyDisplay.js b/ui/hooks/useCurrencyDisplay.js
index 12b2cfc06ec3..03561153ed53 100644
--- a/ui/hooks/useCurrencyDisplay.js
+++ b/ui/hooks/useCurrencyDisplay.js
@@ -62,7 +62,8 @@ function formatEthCurrencyDisplay({
return null;
}
-function formatBtcCurrencyDisplay({
+function formatNonEvmAssetCurrencyDisplay({
+ tokenSymbol,
isNativeCurrency,
isUserPreferredCurrency,
currency,
@@ -77,7 +78,7 @@ function formatBtcCurrencyDisplay({
// We use `Numeric` here, so we handle those amount the same way than for EVMs (it's worth
// noting that if `inputValue` is not properly defined, the amount will be set to '0', see
// `Numeric` constructor for that)
- return new Numeric(inputValue, 10).toString(); // BTC usually uses 10 digits
+ return new Numeric(inputValue, 10).toString();
} else if (isUserPreferredCurrency && conversionRate) {
const amount =
getTokenFiatAmount(
@@ -85,7 +86,7 @@ function formatBtcCurrencyDisplay({
Number(conversionRate), // native to fiat conversion rate
currentCurrency,
inputValue,
- 'BTC',
+ tokenSymbol,
false,
false,
) ?? '0'; // if the conversion fails, return 0
@@ -162,8 +163,8 @@ export function useCurrencyDisplay(
}
if (!isEvm) {
- // TODO: We would need to update this for other non-EVM coins
- return formatBtcCurrencyDisplay({
+ return formatNonEvmAssetCurrencyDisplay({
+ tokenSymbol: nativeCurrency,
isNativeCurrency,
isUserPreferredCurrency,
currency,
diff --git a/ui/hooks/useCurrencyRatePolling.test.ts b/ui/hooks/useCurrencyRatePolling.test.ts
new file mode 100644
index 000000000000..9ad405a31c46
--- /dev/null
+++ b/ui/hooks/useCurrencyRatePolling.test.ts
@@ -0,0 +1,128 @@
+import { renderHookWithProvider } from '../../test/lib/render-helpers';
+import {
+ currencyRateStartPolling,
+ currencyRateStopPollingByPollingToken,
+} from '../store/actions';
+import useCurrencyRatePolling from './useCurrencyRatePolling';
+
+let mockPromises: Promise[];
+
+jest.mock('../store/actions', () => ({
+ currencyRateStartPolling: jest.fn().mockImplementation((input) => {
+ const promise = Promise.resolve(`${input}_rates`);
+ mockPromises.push(promise);
+ return promise;
+ }),
+ currencyRateStopPollingByPollingToken: jest.fn(),
+}));
+
+describe('useCurrencyRatePolling', () => {
+ beforeEach(() => {
+ mockPromises = [];
+ jest.clearAllMocks();
+ });
+
+ it('should poll currency rates for native currencies when enabled and stop on dismount', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: true,
+ useCurrencyRateCheck: true,
+ selectedNetworkClientId: 'selectedNetworkClientId',
+ networkConfigurationsByChainId: {
+ '0x1': {
+ nativeCurrency: 'ETH',
+ defaultRpcEndpointIndex: 0,
+ rpcEndpoints: [
+ {
+ networkClientId: 'selectedNetworkClientId',
+ },
+ ],
+ },
+ '0x89': {
+ nativeCurrency: 'BNB',
+ defaultRpcEndpointIndex: 0,
+ rpcEndpoints: [
+ {
+ networkClientId: 'selectedNetworkClientId2',
+ },
+ ],
+ },
+ },
+ },
+ };
+
+ const { unmount } = renderHookWithProvider(
+ () => useCurrencyRatePolling(),
+ state,
+ );
+
+ await Promise.all(mockPromises);
+ expect(currencyRateStartPolling).toHaveBeenCalledTimes(1);
+ expect(currencyRateStartPolling).toHaveBeenCalledWith(['ETH', 'BNB']);
+
+ // Stop polling on dismount
+ unmount();
+ expect(currencyRateStopPollingByPollingToken).toHaveBeenCalledTimes(1);
+ expect(currencyRateStopPollingByPollingToken).toHaveBeenCalledWith(
+ 'ETH,BNB_rates',
+ );
+ });
+
+ it('should not poll if onboarding is not completed', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: false,
+ useCurrencyRateCheck: true,
+ networkConfigurationsByChainId: {
+ '0x1': { nativeCurrency: 'ETH' },
+ },
+ },
+ };
+
+ renderHookWithProvider(() => useCurrencyRatePolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(currencyRateStartPolling).toHaveBeenCalledTimes(0);
+ expect(currencyRateStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+
+ it('should not poll when locked', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: false,
+ completedOnboarding: true,
+ useCurrencyRateCheck: true,
+ networkConfigurationsByChainId: {
+ '0x1': { nativeCurrency: 'ETH' },
+ },
+ },
+ };
+
+ renderHookWithProvider(() => useCurrencyRatePolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(currencyRateStartPolling).toHaveBeenCalledTimes(0);
+ expect(currencyRateStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+
+ it('should not poll when currency rate checking is disabled', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: true,
+ useCurrencyRateCheck: false,
+ networkConfigurationsByChainId: {
+ '0x1': { nativeCurrency: 'ETH' },
+ },
+ },
+ };
+
+ renderHookWithProvider(() => useCurrencyRatePolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(currencyRateStartPolling).toHaveBeenCalledTimes(0);
+ expect(currencyRateStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+});
diff --git a/ui/hooks/useTokenDetectionPolling.test.ts b/ui/hooks/useTokenDetectionPolling.test.ts
new file mode 100644
index 000000000000..bae369ffd525
--- /dev/null
+++ b/ui/hooks/useTokenDetectionPolling.test.ts
@@ -0,0 +1,157 @@
+import { renderHookWithProvider } from '../../test/lib/render-helpers';
+import {
+ tokenDetectionStartPolling,
+ tokenDetectionStopPollingByPollingToken,
+} from '../store/actions';
+import useTokenDetectionPolling from './useTokenDetectionPolling';
+
+let mockPromises: Promise[];
+
+jest.mock('../store/actions', () => ({
+ tokenDetectionStartPolling: jest.fn().mockImplementation((input) => {
+ const promise = Promise.resolve(`${input}_detection`);
+ mockPromises.push(promise);
+ return promise;
+ }),
+ tokenDetectionStopPollingByPollingToken: jest.fn(),
+}));
+let originalPortfolioView: string | undefined;
+
+describe('useTokenDetectionPolling', () => {
+ beforeEach(() => {
+ // Mock process.env.PORTFOLIO_VIEW
+ originalPortfolioView = process.env.PORTFOLIO_VIEW;
+ process.env.PORTFOLIO_VIEW = 'true'; // Set your desired mock value here
+
+ mockPromises = [];
+ jest.clearAllMocks();
+ });
+
+ afterEach(() => {
+ // Restore the original value
+ process.env.PORTFOLIO_VIEW = originalPortfolioView;
+ });
+
+ it('should poll token detection for chain IDs when enabled and stop on dismount', async () => {
+ process.env.PORTFOLIO_VIEW = 'true';
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: true,
+ useTokenDetection: true,
+ selectedNetworkClientId: 'selectedNetworkClientId',
+ networkConfigurationsByChainId: {
+ '0x1': {
+ chainId: '0x1',
+ rpcEndpoints: [
+ {
+ networkClientId: 'selectedNetworkClientId',
+ },
+ ],
+ },
+ '0x89': {
+ chainId: '0x89',
+ rpcEndpoints: [
+ {
+ networkClientId: 'selectedNetworkClientId2',
+ },
+ ],
+ },
+ },
+ },
+ };
+
+ const { unmount } = renderHookWithProvider(
+ () => useTokenDetectionPolling(),
+ state,
+ );
+
+ // Should poll each chain
+ await Promise.all(mockPromises);
+ expect(tokenDetectionStartPolling).toHaveBeenCalledTimes(1);
+ expect(tokenDetectionStartPolling).toHaveBeenCalledWith(['0x1', '0x89']);
+
+ // Stop polling on dismount
+ unmount();
+ expect(tokenDetectionStopPollingByPollingToken).toHaveBeenCalledTimes(1);
+ expect(tokenDetectionStopPollingByPollingToken).toHaveBeenCalledWith(
+ '0x1,0x89_detection',
+ );
+ });
+
+ it('should not poll if onboarding is not completed', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: false,
+ useTokenDetection: true,
+ networkConfigurationsByChainId: {
+ '0x1': {},
+ },
+ },
+ };
+
+ renderHookWithProvider(() => useTokenDetectionPolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(tokenDetectionStartPolling).toHaveBeenCalledTimes(0);
+ expect(tokenDetectionStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+
+ it('should not poll when locked', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: false,
+ completedOnboarding: true,
+ useTokenDetection: true,
+ networkConfigurationsByChainId: {
+ '0x1': {},
+ },
+ },
+ };
+
+ renderHookWithProvider(() => useTokenDetectionPolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(tokenDetectionStartPolling).toHaveBeenCalledTimes(0);
+ expect(tokenDetectionStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+
+ it('should not poll when token detection is disabled', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: true,
+ useTokenDetection: false,
+ networkConfigurationsByChainId: {
+ '0x1': {},
+ },
+ },
+ };
+
+ renderHookWithProvider(() => useTokenDetectionPolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(tokenDetectionStartPolling).toHaveBeenCalledTimes(0);
+ expect(tokenDetectionStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+
+ it('should not poll when no chains are provided', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: true,
+ useTokenDetection: true,
+ networkConfigurationsByChainId: {
+ '0x1': {},
+ },
+ },
+ };
+
+ renderHookWithProvider(() => useTokenDetectionPolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(tokenDetectionStartPolling).toHaveBeenCalledTimes(0);
+ expect(tokenDetectionStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+});
diff --git a/ui/hooks/useTokenRatesPolling.test.ts b/ui/hooks/useTokenRatesPolling.test.ts
new file mode 100644
index 000000000000..9383527e6fbd
--- /dev/null
+++ b/ui/hooks/useTokenRatesPolling.test.ts
@@ -0,0 +1,160 @@
+import { renderHookWithProvider } from '../../test/lib/render-helpers';
+import {
+ tokenRatesStartPolling,
+ tokenRatesStopPollingByPollingToken,
+} from '../store/actions';
+import useTokenRatesPolling from './useTokenRatesPolling';
+
+let mockPromises: Promise[];
+
+jest.mock('../store/actions', () => ({
+ tokenRatesStartPolling: jest.fn().mockImplementation((input) => {
+ const promise = Promise.resolve(`${input}_rates`);
+ mockPromises.push(promise);
+ return promise;
+ }),
+ tokenRatesStopPollingByPollingToken: jest.fn(),
+}));
+
+let originalPortfolioView: string | undefined;
+describe('useTokenRatesPolling', () => {
+ beforeEach(() => {
+ // Mock process.env.PORTFOLIO_VIEW
+ originalPortfolioView = process.env.PORTFOLIO_VIEW;
+ process.env.PORTFOLIO_VIEW = 'true'; // Set your desired mock value here
+
+ mockPromises = [];
+ jest.clearAllMocks();
+ });
+
+ afterEach(() => {
+ // Restore the original value
+ process.env.PORTFOLIO_VIEW = originalPortfolioView;
+ });
+
+ it('should poll token rates when enabled and stop on dismount', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: true,
+ useCurrencyRateCheck: true,
+ selectedNetworkClientId: 'selectedNetworkClientId',
+ networkConfigurationsByChainId: {
+ '0x1': {
+ chainId: '0x1',
+ rpcEndpoints: [
+ {
+ networkClientId: 'selectedNetworkClientId',
+ },
+ ],
+ },
+ '0x89': {
+ chainId: '0x89',
+ rpcEndpoints: [
+ {
+ networkClientId: 'selectedNetworkClientId2',
+ },
+ ],
+ },
+ },
+ },
+ };
+
+ const { unmount } = renderHookWithProvider(
+ () => useTokenRatesPolling(),
+ state,
+ );
+
+ // Should poll each chain
+ await Promise.all(mockPromises);
+ expect(tokenRatesStartPolling).toHaveBeenCalledTimes(2);
+ expect(tokenRatesStartPolling).toHaveBeenCalledWith('0x1');
+ expect(tokenRatesStartPolling).toHaveBeenCalledWith('0x89');
+ // Stop polling on dismount
+ unmount();
+ expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledTimes(2);
+ expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledWith(
+ '0x1_rates',
+ );
+ expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledWith(
+ '0x89_rates',
+ );
+ });
+
+ it('should not poll if onboarding is not completed', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: false,
+ useCurrencyRateCheck: true,
+ networkConfigurationsByChainId: {
+ '0x1': {},
+ '0x89': {},
+ },
+ },
+ };
+
+ renderHookWithProvider(() => useTokenRatesPolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(tokenRatesStartPolling).toHaveBeenCalledTimes(0);
+ expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+
+ it('should not poll when locked', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: false,
+ completedOnboarding: true,
+ useCurrencyRateCheck: true,
+ networkConfigurationsByChainId: {
+ '0x1': {},
+ '0x89': {},
+ },
+ },
+ };
+
+ renderHookWithProvider(() => useTokenRatesPolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(tokenRatesStartPolling).toHaveBeenCalledTimes(0);
+ expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+
+ it('should not poll when rate checking is disabled', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: true,
+ useCurrencyRateCheck: false,
+ networkConfigurationsByChainId: {
+ '0x1': {},
+ '0x89': {},
+ },
+ },
+ };
+
+ renderHookWithProvider(() => useTokenRatesPolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(tokenRatesStartPolling).toHaveBeenCalledTimes(0);
+ expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+
+ it('should not poll when no chains are provided', async () => {
+ const state = {
+ metamask: {
+ isUnlocked: true,
+ completedOnboarding: true,
+ useCurrencyRateCheck: true,
+ networkConfigurationsByChainId: {},
+ },
+ };
+
+ renderHookWithProvider(() => useTokenRatesPolling(), state);
+
+ await Promise.all(mockPromises);
+ expect(tokenRatesStartPolling).toHaveBeenCalledTimes(0);
+ expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledTimes(0);
+ });
+});
diff --git a/ui/hooks/useTokensWithFiltering.ts b/ui/hooks/useTokensWithFiltering.ts
index a7ff3f2513ac..d729ce3c1fdc 100644
--- a/ui/hooks/useTokensWithFiltering.ts
+++ b/ui/hooks/useTokensWithFiltering.ts
@@ -3,6 +3,7 @@ import { useSelector } from 'react-redux';
import { isEqual } from 'lodash';
import { ChainId, hexToBN } from '@metamask/controller-utils';
import { Hex } from '@metamask/utils';
+import { useParams } from 'react-router-dom';
import {
getAllTokens,
getCurrentCurrency,
@@ -39,6 +40,8 @@ export const useTokensWithFiltering = (
sortOrder: TokenBucketPriority = TokenBucketPriority.owned,
chainId?: ChainId | Hex,
) => {
+ const { token: tokenAddressFromUrl } = useParams();
+
// Only includes non-native tokens
const allDetectedTokens = useSelector(getAllTokens);
const { address: selectedAddress, balance: balanceOnActiveChain } =
@@ -123,6 +126,18 @@ export const useTokensWithFiltering = (
yield nativeToken;
}
+ if (tokenAddressFromUrl) {
+ const tokenListItem =
+ tokenList?.[tokenAddressFromUrl] ??
+ tokenList?.[tokenAddressFromUrl.toLowerCase()];
+ if (tokenListItem) {
+ const tokenWithTokenListData = buildTokenData(tokenListItem);
+ if (tokenWithTokenListData) {
+ yield tokenWithTokenListData;
+ }
+ }
+ }
+
if (sortOrder === TokenBucketPriority.owned) {
for (const tokenWithBalance of sortedErc20TokensWithBalances) {
const cachedTokenData =
@@ -171,6 +186,7 @@ export const useTokensWithFiltering = (
currentCurrency,
chainId,
tokenList,
+ tokenAddressFromUrl,
],
);
diff --git a/ui/pages/asset/components/asset-page.test.tsx b/ui/pages/asset/components/asset-page.test.tsx
index 60e322f8825e..28e232a0ba0b 100644
--- a/ui/pages/asset/components/asset-page.test.tsx
+++ b/ui/pages/asset/components/asset-page.test.tsx
@@ -45,6 +45,8 @@ jest.mock('../../../hooks/useMultiPolling', () => ({
default: jest.fn(),
}));
+const selectedAccountAddress = 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3';
+
describe('AssetPage', () => {
const mockStore = {
localeMessages: {
@@ -52,13 +54,17 @@ describe('AssetPage', () => {
},
metamask: {
tokenList: {},
- tokenBalances: {},
+ tokenBalances: {
+ [selectedAccountAddress]: {
+ [CHAIN_IDS.MAINNET]: {},
+ },
+ },
marketData: {},
allTokens: {},
accountsByChainId: {
'0x1': {
- 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3': {
- address: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3',
+ [selectedAccountAddress]: {
+ address: selectedAccountAddress,
balance: '0x00',
},
},
@@ -80,9 +86,9 @@ describe('AssetPage', () => {
preferences: {},
internalAccounts: {
accounts: {
- 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3': {
- address: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3',
- id: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3',
+ [selectedAccountAddress]: {
+ address: selectedAccountAddress,
+ id: selectedAccountAddress,
metadata: {
name: 'Test Account',
keyring: {
@@ -94,7 +100,7 @@ describe('AssetPage', () => {
type: EthAccountType.Eoa,
},
},
- selectedAccount: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3',
+ selectedAccount: selectedAccountAddress,
},
keyrings: [
{
diff --git a/ui/pages/asset/components/asset-page.tsx b/ui/pages/asset/components/asset-page.tsx
index 19ce592f0071..9100126d54fe 100644
--- a/ui/pages/asset/components/asset-page.tsx
+++ b/ui/pages/asset/components/asset-page.tsx
@@ -3,8 +3,8 @@ import { useHistory } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { EthMethod } from '@metamask/keyring-api';
import { isEqual } from 'lodash';
+import { getNativeTokenAddress } from '@metamask/assets-controllers';
import { Hex } from '@metamask/utils';
-import { zeroAddress } from 'ethereumjs-util';
import {
getCurrentCurrency,
getDataCollectionForMarketing,
@@ -54,6 +54,7 @@ import { useTokenBalances } from '../../../hooks/useTokenBalances';
import { useMultichainSelector } from '../../../hooks/useMultichainSelector';
import { getMultichainShouldShowFiat } from '../../../selectors/multichain';
import { getPortfolioUrl } from '../../../helpers/utils/portfolio';
+import { hexToDecimal } from '../../../../shared/modules/conversion.utils';
import AssetChart from './chart/asset-chart';
import TokenButtons from './token-buttons';
@@ -101,11 +102,21 @@ const AssetPage = ({
const selectedAccount = useSelector(getSelectedAccount);
const currency = useSelector(getCurrentCurrency);
const conversionRate = useSelector(getConversionRate);
- const isBridgeChain = useSelector(getIsBridgeChain);
const isBuyableChain = useSelector(getIsNativeTokenBuyable);
- const defaultSwapsToken = useSelector(getSwapsDefaultToken, isEqual);
+
+ const { chainId, type, symbol, name, image, decimals } = asset;
+
+ // These need to be specific to the asset and not the current chain
+ const defaultSwapsToken = useSelector(
+ (state) => getSwapsDefaultToken(state, chainId),
+ isEqual,
+ );
+ const isSwapsChain = useSelector((state) => getIsSwapsChain(state, chainId));
+ const isBridgeChain = useSelector((state) =>
+ getIsBridgeChain(state, chainId),
+ );
+
const account = useSelector(getSelectedInternalAccount, isEqual);
- const isSwapsChain = useSelector(getIsSwapsChain);
const isSigningEnabled =
account.methods.includes(EthMethod.SignTransaction) ||
account.methods.includes(EthMethod.SignUserOperation);
@@ -132,7 +143,6 @@ const AssetPage = ({
const selectedAccountTokenBalancesAcrossChains =
tokenBalances[selectedAccount.address];
- const { chainId, type, symbol, name, image, decimals } = asset;
const isMetaMetricsEnabled = useSelector(getParticipateInMetaMetrics);
const isMarketingEnabled = useSelector(getDataCollectionForMarketing);
const metaMetricsId = useSelector(getMetaMetricsId);
@@ -140,7 +150,10 @@ const AssetPage = ({
const address =
type === AssetType.token
? toChecksumHexAddress(asset.address)
- : zeroAddress();
+ : getNativeTokenAddress(chainId);
+
+ const tokenHexBalance =
+ selectedAccountTokenBalancesAcrossChains?.[chainId]?.[address as Hex];
const balance = calculateTokenBalance({
isNative: type === AssetType.native,
@@ -178,10 +191,10 @@ const AssetPage = ({
tokenMarketDetails.allTimeHigh > 0 ||
tokenMarketDetails.allTimeLow > 0);
- // this is needed in order to assign the correct balances to TokenButtons before sending/swapping
- // without this, the balances we be populated as zero until the user refreshes the screen: https://github.com/MetaMask/metamask-extension/issues/28509
+ // this is needed in order to assign the correct balances to TokenButtons before navigating to send/swap screens
+
asset.balance = {
- value: '', // decimal value not needed
+ value: hexToDecimal(tokenHexBalance),
display: String(balance),
fiat: String(tokenFiatAmount),
};
diff --git a/ui/pages/bridge/index.tsx b/ui/pages/bridge/index.tsx
index 687057094005..6dd54b424d06 100644
--- a/ui/pages/bridge/index.tsx
+++ b/ui/pages/bridge/index.tsx
@@ -1,6 +1,7 @@
import React, { useContext, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Switch, useHistory } from 'react-router-dom';
+import { zeroAddress } from 'ethereumjs-util';
import { I18nContext } from '../../contexts/i18n';
import { clearSwapsState } from '../../ducks/swaps/swaps';
import {
@@ -16,16 +17,25 @@ import {
ButtonIconSize,
IconName,
} from '../../components/component-library';
-import { getIsBridgeChain, getIsBridgeEnabled } from '../../selectors';
import { getProviderConfig } from '../../../shared/modules/selectors/networks';
+import {
+ getCurrentCurrency,
+ getIsBridgeChain,
+ getIsBridgeEnabled,
+} from '../../selectors';
import useBridging from '../../hooks/bridge/useBridging';
import {
Content,
Footer,
Header,
} from '../../components/multichain/pages/page';
-import { resetBridgeState, setFromChain } from '../../ducks/bridge/actions';
import { useSwapsFeatureFlags } from '../swaps/hooks/useSwapsFeatureFlags';
+import {
+ resetBridgeState,
+ setFromChain,
+ setSrcTokenExchangeRates,
+} from '../../ducks/bridge/actions';
+import { useGasFeeEstimates } from '../../hooks/useGasFeeEstimates';
import PrepareBridgePage from './prepare/prepare-bridge-page';
import { BridgeCTAButton } from './prepare/bridge-cta-button';
@@ -42,13 +52,20 @@ const CrossChainSwap = () => {
const isBridgeEnabled = useSelector(getIsBridgeEnabled);
const providerConfig = useSelector(getProviderConfig);
const isBridgeChain = useSelector(getIsBridgeChain);
+ const currency = useSelector(getCurrentCurrency);
useEffect(() => {
- isBridgeChain &&
- isBridgeEnabled &&
- providerConfig &&
+ if (isBridgeChain && isBridgeEnabled && providerConfig && currency) {
dispatch(setFromChain(providerConfig.chainId));
- }, [isBridgeChain, isBridgeEnabled, providerConfig]);
+ dispatch(
+ setSrcTokenExchangeRates({
+ chainId: providerConfig.chainId,
+ tokenAddress: zeroAddress(),
+ currency,
+ }),
+ );
+ }
+ }, [isBridgeChain, isBridgeEnabled, providerConfig, currency]);
const resetControllerAndInputStates = async () => {
await dispatch(resetBridgeState());
@@ -66,6 +83,9 @@ const CrossChainSwap = () => {
};
}, []);
+ // Needed for refreshing gas estimates
+ useGasFeeEstimates(providerConfig?.id);
+
const redirectToDefaultRoute = async () => {
history.push({
pathname: DEFAULT_ROUTE,
diff --git a/ui/pages/bridge/layout/column.tsx b/ui/pages/bridge/layout/column.tsx
new file mode 100644
index 000000000000..6f5b2847b5e5
--- /dev/null
+++ b/ui/pages/bridge/layout/column.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import {
+ Container,
+ ContainerProps,
+} from '../../../components/component-library';
+import {
+ BlockSize,
+ Display,
+ FlexDirection,
+} from '../../../helpers/constants/design-system';
+
+const Column = (props: ContainerProps<'div'>) => {
+ return (
+
+ );
+};
+
+export default Column;
diff --git a/ui/pages/bridge/layout/index.tsx b/ui/pages/bridge/layout/index.tsx
new file mode 100644
index 000000000000..d519d211f500
--- /dev/null
+++ b/ui/pages/bridge/layout/index.tsx
@@ -0,0 +1,5 @@
+import Column from './column';
+import Row from './row';
+import Tooltip from './tooltip';
+
+export { Column, Row, Tooltip };
diff --git a/ui/pages/bridge/layout/row.tsx b/ui/pages/bridge/layout/row.tsx
new file mode 100644
index 000000000000..eeb94a7e06f7
--- /dev/null
+++ b/ui/pages/bridge/layout/row.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import {
+ Container,
+ ContainerProps,
+} from '../../../components/component-library';
+import {
+ AlignItems,
+ Display,
+ FlexDirection,
+ FlexWrap,
+ JustifyContent,
+} from '../../../helpers/constants/design-system';
+
+const Row = (props: ContainerProps<'div'>) => {
+ return (
+
+ );
+};
+
+export default Row;
diff --git a/ui/pages/bridge/layout/tooltip.tsx b/ui/pages/bridge/layout/tooltip.tsx
new file mode 100644
index 000000000000..b6781c9bf480
--- /dev/null
+++ b/ui/pages/bridge/layout/tooltip.tsx
@@ -0,0 +1,83 @@
+import React, { useState } from 'react';
+import {
+ Box,
+ Popover,
+ PopoverHeader,
+ PopoverPosition,
+ PopoverProps,
+ Text,
+} from '../../../components/component-library';
+import {
+ JustifyContent,
+ TextAlign,
+ TextColor,
+} from '../../../helpers/constants/design-system';
+
+const Tooltip = React.forwardRef(
+ ({
+ children,
+ title,
+ triggerElement,
+ disabled = false,
+ ...props
+ }: PopoverProps<'div'> & {
+ triggerElement: React.ReactElement;
+ disabled?: boolean;
+ }) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [referenceElement, setReferenceElement] =
+ useState(null);
+
+ const handleMouseEnter = () => setIsOpen(true);
+ const handleMouseLeave = () => setIsOpen(false);
+ const setBoxRef = (ref: HTMLSpanElement | null) => setReferenceElement(ref);
+
+ return (
+ <>
+
+ {triggerElement}
+
+ {!disabled && (
+
+
+ {title}
+
+
+ {children}
+
+
+ )}
+ >
+ );
+ },
+);
+
+export default Tooltip;
diff --git a/ui/pages/bridge/prepare/bridge-cta-button.tsx b/ui/pages/bridge/prepare/bridge-cta-button.tsx
index 06d784f2e0ea..7355e6579dfa 100644
--- a/ui/pages/bridge/prepare/bridge-cta-button.tsx
+++ b/ui/pages/bridge/prepare/bridge-cta-button.tsx
@@ -2,14 +2,12 @@ import React, { useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Button } from '../../../components/component-library';
import {
- getBridgeQuotes,
getFromAmount,
getFromChain,
getFromToken,
- getRecommendedQuote,
- getToAmount,
getToChain,
getToToken,
+ getBridgeQuotes,
} from '../../../ducks/bridge/selectors';
import { useI18nContext } from '../../../hooks/useI18nContext';
import useSubmitBridgeTransaction from '../hooks/useSubmitBridgeTransaction';
@@ -25,15 +23,13 @@ export const BridgeCTAButton = () => {
const toChain = useSelector(getToChain);
const fromAmount = useSelector(getFromAmount);
- const toAmount = useSelector(getToAmount);
- const { isLoading } = useSelector(getBridgeQuotes);
- const quoteResponse = useSelector(getRecommendedQuote);
+ const { isLoading, activeQuote } = useSelector(getBridgeQuotes);
const { submitBridgeTransaction } = useSubmitBridgeTransaction();
const isTxSubmittable =
- fromToken && toToken && fromChain && toChain && fromAmount && toAmount;
+ fromToken && toToken && fromChain && toChain && fromAmount && activeQuote;
const label = useMemo(() => {
if (isLoading && !isTxSubmittable) {
@@ -59,7 +55,7 @@ export const BridgeCTAButton = () => {
data-testid="bridge-cta-button"
onClick={() => {
if (isTxSubmittable) {
- dispatch(submitBridgeTransaction(quoteResponse));
+ dispatch(submitBridgeTransaction(activeQuote));
}
}}
disabled={!isTxSubmittable}
diff --git a/ui/pages/bridge/prepare/bridge-input-group.tsx b/ui/pages/bridge/prepare/bridge-input-group.tsx
index 266af5b9a3cc..0dbecf6cffdd 100644
--- a/ui/pages/bridge/prepare/bridge-input-group.tsx
+++ b/ui/pages/bridge/prepare/bridge-input-group.tsx
@@ -28,10 +28,7 @@ import {
CHAIN_ID_TOKEN_IMAGE_MAP,
} from '../../../../shared/constants/network';
import useLatestBalance from '../../../hooks/bridge/useLatestBalance';
-import {
- getBridgeQuotes,
- getRecommendedQuote,
-} from '../../../ducks/bridge/selectors';
+import { getBridgeQuotes } from '../../../ducks/bridge/selectors';
const generateAssetFromToken = (
chainId: Hex,
@@ -82,8 +79,7 @@ export const BridgeInputGroup = ({
>) => {
const t = useI18nContext();
- const { isLoading } = useSelector(getBridgeQuotes);
- const recommendedQuote = useSelector(getRecommendedQuote);
+ const { isLoading, activeQuote } = useSelector(getBridgeQuotes);
const tokenFiatValue = useTokenFiatAmount(
token?.address || undefined,
@@ -134,9 +130,7 @@ export const BridgeInputGroup = ({
type={TextFieldType.Number}
className="amount-input"
placeholder={
- isLoading && !recommendedQuote
- ? t('bridgeCalculatingAmount')
- : '0'
+ isLoading && !activeQuote ? t('bridgeCalculatingAmount') : '0'
}
onChange={(e) => {
onAmountChange?.(e.target.value);
diff --git a/ui/pages/bridge/prepare/prepare-bridge-page.test.tsx b/ui/pages/bridge/prepare/prepare-bridge-page.test.tsx
index aba35d5b89be..95248bdbb0bc 100644
--- a/ui/pages/bridge/prepare/prepare-bridge-page.test.tsx
+++ b/ui/pages/bridge/prepare/prepare-bridge-page.test.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { act } from '@testing-library/react';
+import * as reactRouterUtils from 'react-router-dom-v5-compat';
import { fireEvent, renderWithProvider } from '../../../../test/jest';
import configureStore from '../../../store/store';
import { createBridgeMockStore } from '../../../../test/jest/mock-store';
@@ -23,6 +24,9 @@ describe('PrepareBridgePage', () => {
});
it('should render the component, with initial state', async () => {
+ jest
+ .spyOn(reactRouterUtils, 'useSearchParams')
+ .mockReturnValue([{ get: () => null }] as never);
const mockStore = createBridgeMockStore(
{
srcNetworkAllowlist: [CHAIN_IDS.MAINNET, CHAIN_IDS.OPTIMISM],
@@ -54,6 +58,9 @@ describe('PrepareBridgePage', () => {
});
it('should render the component, with inputs set', async () => {
+ jest
+ .spyOn(reactRouterUtils, 'useSearchParams')
+ .mockReturnValue([{ get: () => '0x3103910' }, jest.fn()] as never);
const mockStore = createBridgeMockStore(
{
srcNetworkAllowlist: [CHAIN_IDS.MAINNET, CHAIN_IDS.LINEA_MAINNET],
diff --git a/ui/pages/bridge/prepare/prepare-bridge-page.tsx b/ui/pages/bridge/prepare/prepare-bridge-page.tsx
index b0553407686d..aea037c71f13 100644
--- a/ui/pages/bridge/prepare/prepare-bridge-page.tsx
+++ b/ui/pages/bridge/prepare/prepare-bridge-page.tsx
@@ -2,16 +2,23 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import classnames from 'classnames';
import { debounce } from 'lodash';
+import { Hex } from '@metamask/utils';
+import { zeroAddress } from 'ethereumjs-util';
+import { useHistory, useLocation } from 'react-router-dom';
import {
+ setDestTokenExchangeRates,
setFromChain,
setFromToken,
setFromTokenInputValue,
+ setSrcTokenExchangeRates,
+ setSelectedQuote,
setToChain,
setToChainId,
setToToken,
updateQuoteRequestParams,
} from '../../../ducks/bridge/actions';
import {
+ getBridgeQuotes,
getFromAmount,
getFromChain,
getFromChains,
@@ -19,7 +26,6 @@ import {
getFromTokens,
getFromTopAssets,
getQuoteRequest,
- getToAmount,
getToChain,
getToChains,
getToToken,
@@ -42,6 +48,8 @@ import { calcTokenValue } from '../../../../shared/lib/swaps-utils';
import { BridgeQuoteCard } from '../quotes/bridge-quote-card';
import { isValidQuoteRequest } from '../utils/quote';
import { getProviderConfig } from '../../../../shared/modules/selectors/networks';
+import { getCurrentCurrency } from '../../../selectors';
+import { SECOND } from '../../../../shared/constants/time';
import { BridgeInputGroup } from './bridge-input-group';
const PrepareBridgePage = () => {
@@ -49,6 +57,8 @@ const PrepareBridgePage = () => {
const t = useI18nContext();
+ const currency = useSelector(getCurrentCurrency);
+
const fromToken = useSelector(getFromToken);
const fromTokens = useSelector(getFromTokens);
const fromTopAssets = useSelector(getFromTopAssets);
@@ -63,11 +73,11 @@ const PrepareBridgePage = () => {
const toChain = useSelector(getToChain);
const fromAmount = useSelector(getFromAmount);
- const toAmount = useSelector(getToAmount);
const providerConfig = useSelector(getProviderConfig);
const quoteRequest = useSelector(getQuoteRequest);
+ const { activeQuote } = useSelector(getBridgeQuotes);
const fromTokenListGenerator = useTokensWithFiltering(
fromTokens,
@@ -114,10 +124,10 @@ const PrepareBridgePage = () => {
);
const debouncedUpdateQuoteRequestInController = useCallback(
- debounce(
- (p: Partial) => dispatch(updateQuoteRequestParams(p)),
- 300,
- ),
+ debounce((p: Partial) => {
+ dispatch(updateQuoteRequestParams(p));
+ dispatch(setSelectedQuote(null));
+ }, 300),
[],
);
@@ -125,6 +135,62 @@ const PrepareBridgePage = () => {
debouncedUpdateQuoteRequestInController(quoteParams);
}, Object.values(quoteParams));
+ const debouncedFetchFromExchangeRate = debounce(
+ (chainId: Hex, tokenAddress: string) => {
+ dispatch(setSrcTokenExchangeRates({ chainId, tokenAddress, currency }));
+ },
+ SECOND,
+ );
+
+ const debouncedFetchToExchangeRate = debounce(
+ (chainId: Hex, tokenAddress: string) => {
+ dispatch(setDestTokenExchangeRates({ chainId, tokenAddress, currency }));
+ },
+ SECOND,
+ );
+
+ const { search } = useLocation();
+ const history = useHistory();
+
+ useEffect(() => {
+ if (!fromChain?.chainId || Object.keys(fromTokens).length === 0) {
+ return;
+ }
+
+ const searchParams = new URLSearchParams(search);
+ const tokenAddressFromUrl = searchParams.get('token');
+ if (!tokenAddressFromUrl) {
+ return;
+ }
+
+ const removeTokenFromUrl = () => {
+ const newParams = new URLSearchParams(searchParams);
+ newParams.delete('token');
+ history.replace({
+ search: newParams.toString(),
+ });
+ };
+
+ switch (tokenAddressFromUrl) {
+ case fromToken?.address?.toLowerCase():
+ // If the token is already set, remove the query param
+ removeTokenFromUrl();
+ break;
+ case fromTokens[tokenAddressFromUrl]?.address?.toLowerCase(): {
+ // If there is a matching fromToken, set it as the fromToken
+ const matchedToken = fromTokens[tokenAddressFromUrl];
+ dispatch(setFromToken(matchedToken));
+ debouncedFetchFromExchangeRate(fromChain.chainId, matchedToken.address);
+ removeTokenFromUrl();
+ break;
+ }
+ default:
+ // Otherwise remove query param
+ removeTokenFromUrl();
+ break;
+ }
+ }, [fromChain, fromToken, fromTokens, search]);
+
return (
@@ -138,6 +204,9 @@ const PrepareBridgePage = () => {
onAssetChange={(token) => {
dispatch(setFromToken(token));
dispatch(setFromTokenInputValue(null));
+ fromChain?.chainId &&
+ token?.address &&
+ debouncedFetchFromExchangeRate(fromChain.chainId, token.address);
}}
networkProps={{
network: fromChain,
@@ -192,6 +261,19 @@ const PrepareBridgePage = () => {
fromChain?.chainId && dispatch(setToChain(fromChain.chainId));
fromChain?.chainId && dispatch(setToChainId(fromChain.chainId));
dispatch(setToToken(fromToken));
+ fromChain?.chainId &&
+ fromToken?.address &&
+ debouncedFetchToExchangeRate(
+ fromChain.chainId,
+ fromToken.address,
+ );
+ toChain?.chainId &&
+ toToken?.address &&
+ toToken.address !== zeroAddress() &&
+ debouncedFetchFromExchangeRate(
+ toChain.chainId,
+ toToken.address,
+ );
}}
/>
@@ -200,7 +282,12 @@ const PrepareBridgePage = () => {
className="bridge-box"
header={t('bridgeTo')}
token={toToken}
- onAssetChange={(token) => dispatch(setToToken(token))}
+ onAssetChange={(token) => {
+ dispatch(setToToken(token));
+ toChain?.chainId &&
+ token?.address &&
+ debouncedFetchToExchangeRate(toChain.chainId, token.address);
+ }}
networkProps={{
network: toChain,
networks: toChains,
@@ -218,8 +305,10 @@ const PrepareBridgePage = () => {
testId: 'to-amount',
readOnly: true,
disabled: true,
- value: toAmount?.toString() ?? '0',
- className: toAmount ? 'amount-input defined' : 'amount-input',
+ value: activeQuote?.toTokenAmount?.amount.toFixed() ?? '0',
+ className: activeQuote?.toTokenAmount.amount
+ ? 'amount-input defined'
+ : 'amount-input',
}}
/>
diff --git a/ui/pages/bridge/quotes/__snapshots__/bridge-quote-card.test.tsx.snap b/ui/pages/bridge/quotes/__snapshots__/bridge-quote-card.test.tsx.snap
index cb7b5afb4c77..6b69b8ec9a6c 100644
--- a/ui/pages/bridge/quotes/__snapshots__/bridge-quote-card.test.tsx.snap
+++ b/ui/pages/bridge/quotes/__snapshots__/bridge-quote-card.test.tsx.snap
@@ -61,7 +61,7 @@ exports[`BridgeQuoteCard should render the recommended quote 1`] = `
- 1 minutes
+ 1 min
@@ -90,7 +90,7 @@ exports[`BridgeQuoteCard should render the recommended quote 1`] = `
- 1 USDC = 0.9989 USDC
+ 1 USDC = 1.00 USDC
@@ -131,13 +131,13 @@ Fees are based on network traffic and transaction complexity. MetaMask does not
- 0.01 ETH
+ 0.001000 ETH
- $0.01
+ $2.52
@@ -234,7 +234,7 @@ exports[`BridgeQuoteCard should render the recommended quote while loading new q
- 1 minutes
+ 1 min
@@ -263,7 +263,7 @@ exports[`BridgeQuoteCard should render the recommended quote while loading new q
- 1 ETH = 2465.4630 USDC
+ 1 ETH = 2443.89 USDC
@@ -304,13 +304,13 @@ Fees are based on network traffic and transaction complexity. MetaMask does not
- 0.01 ETH
+ 0.001000 ETH
- $0.01
+ $2.52
diff --git a/ui/pages/bridge/quotes/__snapshots__/bridge-quotes-modal.test.tsx.snap b/ui/pages/bridge/quotes/__snapshots__/bridge-quotes-modal.test.tsx.snap
index 41d8a03d1ac1..137dc246864e 100644
--- a/ui/pages/bridge/quotes/__snapshots__/bridge-quotes-modal.test.tsx.snap
+++ b/ui/pages/bridge/quotes/__snapshots__/bridge-quotes-modal.test.tsx.snap
@@ -33,31 +33,31 @@ exports[`BridgeQuotesModal should render the modal 1`] = `
class="mm-box mm-header-base mm-modal-header mm-box--padding-right-4 mm-box--padding-bottom-4 mm-box--padding-left-4 mm-box--display-flex mm-box--justify-content-space-between"
>
-
- Select a quote
-
-
-
+
+
+ Select a quote
+
+
-
+
- $0.01
-
-
+
+ $3 network fee
+
+
+ 14 USDC receive amount
+
+
+
- 1 minutes
-
+
+ 1 min
+
+
+ Across
+
+
-
- $0.01
-
-
+
+ $3 network fee
+
+
+ 14 USDC receive amount
+
+
+
- 26 minutes
-
+
+ 26 min
+
+
+ Celercircle
+
+
diff --git a/ui/pages/bridge/quotes/bridge-quote-card.test.tsx b/ui/pages/bridge/quotes/bridge-quote-card.test.tsx
index 274ade65a4d1..7de52fef1d58 100644
--- a/ui/pages/bridge/quotes/bridge-quote-card.test.tsx
+++ b/ui/pages/bridge/quotes/bridge-quote-card.test.tsx
@@ -20,6 +20,7 @@ describe('BridgeQuoteCard', () => {
{
srcNetworkAllowlist: [CHAIN_IDS.MAINNET, CHAIN_IDS.OPTIMISM],
destNetworkAllowlist: [CHAIN_IDS.OPTIMISM],
+ extensionConfig: { maxRefreshCount: 5, refreshRate: 30000 },
},
{ fromTokenInputValue: 1 },
{
@@ -28,9 +29,6 @@ describe('BridgeQuoteCard', () => {
quotes: mockBridgeQuotesErc20Erc20,
getQuotesLastFetched: Date.now(),
quotesLoadingStatus: RequestStatus.FETCHED,
- bridgeFeatureFlags: {
- extensionConfig: { maxRefreshCount: 5, refreshRate: 30000 },
- },
},
);
const { container } = renderWithProvider(
diff --git a/ui/pages/bridge/quotes/bridge-quote-card.tsx b/ui/pages/bridge/quotes/bridge-quote-card.tsx
index fc1176c8c3f9..8adf675afbb3 100644
--- a/ui/pages/bridge/quotes/bridge-quote-card.tsx
+++ b/ui/pages/bridge/quotes/bridge-quote-card.tsx
@@ -6,30 +6,32 @@ import {
ButtonVariant,
Text,
} from '../../../components/component-library';
-import {
- getBridgeQuotes,
- getRecommendedQuote,
-} from '../../../ducks/bridge/selectors';
+import { getBridgeQuotes } from '../../../ducks/bridge/selectors';
import { useI18nContext } from '../../../hooks/useI18nContext';
-import { getQuoteDisplayData } from '../utils/quote';
+import {
+ formatFiatAmount,
+ formatTokenAmount,
+ formatEtaInMinutes,
+} from '../utils/quote';
import { useCountdownTimer } from '../../../hooks/bridge/useCountdownTimer';
import MascotBackgroundAnimation from '../../swaps/mascot-background-animation/mascot-background-animation';
+import { getCurrentCurrency } from '../../../selectors';
+import { getNativeCurrency } from '../../../ducks/metamask/metamask';
import { QuoteInfoRow } from './quote-info-row';
import { BridgeQuotesModal } from './bridge-quotes-modal';
export const BridgeQuoteCard = () => {
const t = useI18nContext();
- const recommendedQuote = useSelector(getRecommendedQuote);
- const { isLoading, isQuoteGoingToRefresh } = useSelector(getBridgeQuotes);
-
- const { etaInMinutes, totalFees, quoteRate } =
- getQuoteDisplayData(recommendedQuote);
+ const { isLoading, isQuoteGoingToRefresh, activeQuote } =
+ useSelector(getBridgeQuotes);
+ const currency = useSelector(getCurrentCurrency);
+ const ticker = useSelector(getNativeCurrency);
const secondsUntilNextRefresh = useCountdownTimer();
const [showAllQuotes, setShowAllQuotes] = useState(false);
- if (isLoading && !recommendedQuote) {
+ if (isLoading && !activeQuote) {
return (
@@ -37,7 +39,7 @@ export const BridgeQuoteCard = () => {
);
}
- return etaInMinutes && totalFees && quoteRate ? (
+ return activeQuote ? (
{
-
-
+ {activeQuote.swapRate && (
+
+ )}
+ {activeQuote.totalNetworkFee && (
+
+ )}
diff --git a/ui/pages/bridge/quotes/bridge-quotes-modal.stories.tsx b/ui/pages/bridge/quotes/bridge-quotes-modal.stories.tsx
new file mode 100644
index 000000000000..bbdf9b47fa47
--- /dev/null
+++ b/ui/pages/bridge/quotes/bridge-quotes-modal.stories.tsx
@@ -0,0 +1,106 @@
+import React from 'react';
+import { Provider } from 'react-redux';
+import configureStore from '../../../store/store';
+import { BridgeQuotesModal } from './bridge-quotes-modal';
+import { createBridgeMockStore } from '../../../../test/jest/mock-store';
+import mockBridgeQuotesErc20Erc20 from '../../../../test/data/bridge/mock-quotes-erc20-erc20.json';
+import { SortOrder } from '../types';
+
+const storybook = {
+ title: 'Pages/Bridge/BridgeQuotesModal',
+ component: BridgeQuotesModal,
+};
+
+export const NoTokenPricesAvailableStory = () => {
+ return {}} isOpen={true} />;
+};
+NoTokenPricesAvailableStory.storyName = 'Token Prices Not Available';
+NoTokenPricesAvailableStory.decorators = [
+ (story) => (
+
+ {story()}
+
+ ),
+];
+
+export const DefaultStory = () => {
+ return {}} isOpen={true} />;
+};
+DefaultStory.storyName = 'Default';
+DefaultStory.decorators = [
+ (story) => (
+
+ {story()}
+
+ ),
+];
+
+export const PositiveArbitrage = () => {
+ return {}} isOpen={true} />;
+};
+PositiveArbitrage.decorators = [
+ (story) => (
+
+ {story()}
+
+ ),
+];
+
+export default storybook;
diff --git a/ui/pages/bridge/quotes/bridge-quotes-modal.tsx b/ui/pages/bridge/quotes/bridge-quotes-modal.tsx
index 7e78e515af6e..0f4986aa18fc 100644
--- a/ui/pages/bridge/quotes/bridge-quotes-modal.tsx
+++ b/ui/pages/bridge/quotes/bridge-quotes-modal.tsx
@@ -1,11 +1,9 @@
import React from 'react';
-import { useSelector } from 'react-redux';
import { IconName } from '@metamask/snaps-sdk/jsx';
+import { useDispatch, useSelector } from 'react-redux';
+import { startCase } from 'lodash';
import {
- Box,
- Button,
- ButtonVariant,
- Icon,
+ ButtonLink,
IconSize,
Modal,
ModalContent,
@@ -14,51 +12,198 @@ import {
Text,
} from '../../../components/component-library';
import {
+ AlignItems,
+ BackgroundColor,
TextAlign,
+ TextColor,
TextVariant,
} from '../../../helpers/constants/design-system';
-import { getBridgeQuotes } from '../../../ducks/bridge/selectors';
-import { getQuoteDisplayData } from '../utils/quote';
+import {
+ formatEtaInMinutes,
+ formatFiatAmount,
+ formatTokenAmount,
+} from '../utils/quote';
import { useI18nContext } from '../../../hooks/useI18nContext';
+import { getCurrentCurrency } from '../../../selectors';
+import { setSelectedQuote, setSortOrder } from '../../../ducks/bridge/actions';
+import { SortOrder } from '../types';
+import {
+ getBridgeQuotes,
+ getBridgeSortOrder,
+} from '../../../ducks/bridge/selectors';
+import { Column, Row } from '../layout';
+import { getNativeCurrency } from '../../../ducks/metamask/metamask';
export const BridgeQuotesModal = ({
onClose,
...modalProps
}: Omit, 'children'>) => {
- const { quotes } = useSelector(getBridgeQuotes);
const t = useI18nContext();
+ const dispatch = useDispatch();
+
+ const { sortedQuotes, activeQuote } = useSelector(getBridgeQuotes);
+ const sortOrder = useSelector(getBridgeSortOrder);
+ const currency = useSelector(getCurrentCurrency);
+ const nativeCurrency = useSelector(getNativeCurrency);
return (
-
-
+
+
+
{t('swapSelectAQuote')}
-
- {[t('bridgeOverallCost'), t('time')].map((label) => {
- return (
-
- );
- })}
-
-
- {quotes.map((quote, index) => {
- const { totalFees, etaInMinutes } = getQuoteDisplayData(quote);
+ {/* HEADERS */}
+
+ {[
+ [SortOrder.COST_ASC, t('bridgeNetCost'), IconName.Arrow2Up],
+ [SortOrder.ETA_ASC, t('time'), IconName.Arrow2Down],
+ ].map(([sortOrderOption, label, icon]) => (
+ dispatch(setSortOrder(sortOrderOption))}
+ startIconName={
+ sortOrder === sortOrderOption && sortOrder === SortOrder.ETA_ASC
+ ? icon
+ : undefined
+ }
+ startIconProps={{
+ size: IconSize.Xs,
+ }}
+ endIconName={
+ sortOrder === sortOrderOption &&
+ sortOrder === SortOrder.COST_ASC
+ ? icon
+ : undefined
+ }
+ endIconProps={{
+ size: IconSize.Xs,
+ }}
+ color={
+ sortOrder === sortOrderOption
+ ? TextColor.primaryDefault
+ : TextColor.textAlternative
+ }
+ >
+
+ {label}
+
+
+ ))}
+
+ {/* QUOTE LIST */}
+
+ {sortedQuotes.map((quote, index) => {
+ const {
+ totalNetworkFee,
+ estimatedProcessingTimeInSeconds,
+ toTokenAmount,
+ cost,
+ quote: { destAsset, bridges, requestId },
+ } = quote;
+ const isQuoteActive = requestId === activeQuote?.quote.requestId;
+
return (
-
- {totalFees?.fiat}
- {t('bridgeTimingMinutes', [etaInMinutes])}
-
+ {
+ dispatch(setSelectedQuote(quote));
+ onClose();
+ }}
+ paddingInline={4}
+ paddingTop={3}
+ paddingBottom={3}
+ style={{ position: 'relative', height: 78 }}
+ >
+ {isQuoteActive && (
+
+ )}
+
+
+ {cost.fiat && formatFiatAmount(cost.fiat, currency, 0)}
+
+ {[
+ totalNetworkFee?.fiat
+ ? t('quotedNetworkFee', [
+ formatFiatAmount(totalNetworkFee.fiat, currency, 0),
+ ])
+ : t('quotedNetworkFee', [
+ formatTokenAmount(
+ totalNetworkFee.amount,
+ nativeCurrency,
+ ),
+ ]),
+ t(
+ sortOrder === SortOrder.ETA_ASC
+ ? 'quotedReceivingAmount'
+ : 'quotedReceiveAmount',
+ [
+ formatFiatAmount(toTokenAmount.fiat, currency, 0) ??
+ formatTokenAmount(
+ toTokenAmount.amount,
+ destAsset.symbol,
+ 0,
+ ),
+ ],
+ ),
+ ]
+ [sortOrder === SortOrder.ETA_ASC ? 'reverse' : 'slice']()
+ .map((content) => (
+
+ {content}
+
+ ))}
+
+
+
+ {t('bridgeTimingMinutes', [
+ formatEtaInMinutes(estimatedProcessingTimeInSeconds),
+ ])}
+
+
+ {startCase(bridges[0])}
+
+
+
);
})}
-
+
);
diff --git a/ui/pages/bridge/quotes/index.scss b/ui/pages/bridge/quotes/index.scss
index 6d52c9e1e753..6407309220c2 100644
--- a/ui/pages/bridge/quotes/index.scss
+++ b/ui/pages/bridge/quotes/index.scss
@@ -63,21 +63,7 @@
}
}
-.quotes-modal {
- &__column-header {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- }
-
- &__quotes {
- display: flex;
- flex-direction: column;
-
- &__row {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- }
- }
+.mm-modal-content__dialog {
+ display: flex;
+ height: 100%;
}
diff --git a/ui/pages/bridge/types.ts b/ui/pages/bridge/types.ts
index a1ee163eca48..61143bb9de68 100644
--- a/ui/pages/bridge/types.ts
+++ b/ui/pages/bridge/types.ts
@@ -1,3 +1,27 @@
+import { BigNumber } from 'bignumber.js';
+
+export type L1GasFees = {
+ l1GasFeesInHexWei?: string; // l1 fees for approval and trade in hex wei, appended by controller
+};
+
+// Values derived from the quote response
+// fiat values are calculated based on the user's selected currency
+export type QuoteMetadata = {
+ gasFee: { amount: BigNumber; fiat: BigNumber | null };
+ totalNetworkFee: { amount: BigNumber; fiat: BigNumber | null }; // gasFees + relayerFees
+ toTokenAmount: { amount: BigNumber; fiat: BigNumber | null };
+ adjustedReturn: { fiat: BigNumber | null }; // destTokenAmount - totalNetworkFee
+ sentAmount: { amount: BigNumber; fiat: BigNumber | null }; // srcTokenAmount + metabridgeFee
+ swapRate: BigNumber; // destTokenAmount / sentAmount
+ cost: { fiat: BigNumber | null }; // sentAmount - adjustedReturn
+};
+
+// Sort order set by the user
+export enum SortOrder {
+ COST_ASC,
+ ETA_ASC,
+}
+
// Types copied from Metabridge API
export enum BridgeFlag {
EXTENSION_CONFIG = 'extension-config',
diff --git a/ui/pages/bridge/utils/quote.test.ts b/ui/pages/bridge/utils/quote.test.ts
new file mode 100644
index 000000000000..eec342517f83
--- /dev/null
+++ b/ui/pages/bridge/utils/quote.test.ts
@@ -0,0 +1,309 @@
+import { BigNumber } from 'bignumber.js';
+import { zeroAddress } from 'ethereumjs-util';
+import {
+ calcAdjustedReturn,
+ calcSentAmount,
+ calcSwapRate,
+ calcToAmount,
+ calcTotalGasFee,
+ calcRelayerFee,
+ formatEtaInMinutes,
+} from './quote';
+
+const ERC20_TOKEN = {
+ decimals: 6,
+ address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85'.toLowerCase(),
+};
+const NATIVE_TOKEN = { decimals: 18, address: zeroAddress() };
+
+describe('Bridge quote utils', () => {
+ // @ts-expect-error This is missing from the Mocha type definitions
+ it.each([
+ [
+ 'native',
+ NATIVE_TOKEN,
+ '1009000000000000000',
+ 2521.73,
+ { amount: '1.009', fiat: '2544.42557' },
+ ],
+ [
+ 'erc20',
+ ERC20_TOKEN,
+ '2543140000',
+ 0.999781,
+ { amount: '2543.14', fiat: '2542.58305234' },
+ ],
+ [
+ 'erc20 with null exchange rates',
+ ERC20_TOKEN,
+ '2543140000',
+ null,
+ { amount: '2543.14', fiat: undefined },
+ ],
+ ])(
+ 'calcToAmount: toToken is %s',
+ (
+ _: string,
+ destAsset: { decimals: number; address: string },
+ destTokenAmount: string,
+ toTokenExchangeRate: number,
+ { amount, fiat }: { amount: string; fiat: string },
+ ) => {
+ const result = calcToAmount(
+ {
+ destAsset,
+ destTokenAmount,
+ } as never,
+ toTokenExchangeRate,
+ );
+ expect(result.amount?.toString()).toStrictEqual(amount);
+ expect(result.fiat?.toString()).toStrictEqual(fiat);
+ },
+ );
+
+ // @ts-expect-error This is missing from the Mocha type definitions
+ it.each([
+ [
+ 'native',
+ NATIVE_TOKEN,
+ '1009000000000000000',
+ 2515.02,
+ {
+ amount: '1.143217728',
+ fiat: '2875.21545027456',
+ },
+ ],
+ [
+ 'erc20',
+ ERC20_TOKEN,
+ '100000000',
+ 0.999781,
+ { amount: '100.512', fiat: '100.489987872' },
+ ],
+ [
+ 'erc20 with null exchange rates',
+ ERC20_TOKEN,
+ '2543140000',
+ null,
+ { amount: '2543.652', fiat: undefined },
+ ],
+ ])(
+ 'calcSentAmount: fromToken is %s',
+ (
+ _: string,
+ srcAsset: { decimals: number; address: string },
+ srcTokenAmount: string,
+ fromTokenExchangeRate: number,
+ { amount, fiat }: { amount: string; fiat: string },
+ ) => {
+ const result = calcSentAmount(
+ {
+ srcAsset,
+ srcTokenAmount,
+ feeData: {
+ metabridge: {
+ amount: Math.pow(8 * 10, srcAsset.decimals / 2),
+ },
+ },
+ } as never,
+ fromTokenExchangeRate,
+ );
+ expect(result.amount?.toString()).toStrictEqual(amount);
+ expect(result.fiat?.toString()).toStrictEqual(fiat);
+ },
+ );
+
+ // @ts-expect-error This is missing from the Mocha type definitions
+ it.each([
+ [
+ 'native',
+ NATIVE_TOKEN,
+ '1000000000000000000',
+ '0x0de0b6b3a7640000',
+ { amount: '2.2351800712e-7', fiat: '0.0005626887014840304' },
+ undefined,
+ ],
+ [
+ 'erc20',
+ ERC20_TOKEN,
+ '100000000',
+ '0x00',
+ { amount: '2.2351800712e-7', fiat: '0.0005626887014840304' },
+ undefined,
+ ],
+ [
+ 'erc20 with approval',
+ ERC20_TOKEN,
+ '100000000',
+ '0x00',
+ { amount: '4.4703601424e-7', fiat: '0.0011253774029680608' },
+ 1092677,
+ ],
+ [
+ 'erc20 with relayer fee',
+ ERC20_TOKEN,
+ '100000000',
+ '0x0de0b6b3a7640000',
+ { amount: '1.00000022351800712', fiat: '2517.4205626887014840304' },
+ undefined,
+ ],
+ [
+ 'native with relayer fee',
+ NATIVE_TOKEN,
+ '1000000000000000000',
+ '0x0de1b6b3a7640000',
+ { amount: '0.000281698494717776', fiat: '0.70915342457242365792' },
+ undefined,
+ ],
+ ])(
+ 'calcTotalGasFee and calcRelayerFee: fromToken is %s',
+ (
+ _: string,
+ srcAsset: { decimals: number; address: string },
+ srcTokenAmount: string,
+ value: string,
+ { amount, fiat }: { amount: string; fiat: string },
+ approvalGasLimit?: number,
+ ) => {
+ const feeData = { metabridge: { amount: 0 } };
+ const quote = {
+ trade: { value, gasLimit: 1092677 },
+ approval: approvalGasLimit ? { gasLimit: approvalGasLimit } : undefined,
+ quote: { srcAsset, srcTokenAmount, feeData },
+ } as never;
+ const gasFee = calcTotalGasFee(quote, '0.00010456', '0.0001', 2517.42);
+ const relayerFee = calcRelayerFee(quote, 2517.42);
+ const result = {
+ amount: gasFee.amount.plus(relayerFee.amount),
+ fiat: gasFee.fiat?.plus(relayerFee.fiat || '0') ?? null,
+ };
+ expect(result.amount?.toString()).toStrictEqual(amount);
+ expect(result.fiat?.toString()).toStrictEqual(fiat);
+ },
+ );
+
+ // @ts-expect-error This is missing from the Mocha type definitions
+ it.each([
+ [
+ 'native',
+ NATIVE_TOKEN,
+ '1000000000000000000',
+ '0x0de0b6b3a7640000',
+ { amount: '0.000002832228395508', fiat: '0.00712990840741974936' },
+ undefined,
+ ],
+ [
+ 'erc20',
+ ERC20_TOKEN,
+ '100000000',
+ '0x00',
+ { amount: '0.000002832228395508', fiat: '0.00712990840741974936' },
+ undefined,
+ ],
+ [
+ 'erc20 with approval',
+ ERC20_TOKEN,
+ '100000000',
+ '0x00',
+ { amount: '0.000003055746402628', fiat: '0.00769259710890377976' },
+ 1092677,
+ ],
+ [
+ 'erc20 with relayer fee',
+ ERC20_TOKEN,
+ '100000000',
+ '0x0de0b6b3a7640000',
+ { amount: '1.000002832228395508', fiat: '2517.42712990840741974936' },
+ undefined,
+ ],
+ [
+ 'native with relayer fee',
+ NATIVE_TOKEN,
+ '1000000000000000000',
+ '0x0de1b6b3a7640000',
+ { amount: '0.000284307205106164', fiat: '0.71572064427835937688' },
+ undefined,
+ ],
+ ])(
+ 'calcTotalGasFee and calcRelayerFee: fromToken is %s with l1GasFee',
+ (
+ _: string,
+ srcAsset: { decimals: number; address: string },
+ srcTokenAmount: string,
+ value: string,
+ { amount, fiat }: { amount: string; fiat: string },
+ approvalGasLimit?: number,
+ ) => {
+ const feeData = { metabridge: { amount: 0 } };
+ const quote = {
+ trade: { value, gasLimit: 1092677 },
+ approval: approvalGasLimit ? { gasLimit: approvalGasLimit } : undefined,
+ quote: { srcAsset, srcTokenAmount, feeData },
+ l1GasFeesInHexWei: '0x25F63418AA4',
+ } as never;
+ const gasFee = calcTotalGasFee(quote, '0.00010456', '0.0001', 2517.42);
+ const relayerFee = calcRelayerFee(quote, 2517.42);
+ const result = {
+ amount: gasFee.amount.plus(relayerFee.amount),
+ fiat: gasFee.fiat?.plus(relayerFee.fiat || '0') ?? null,
+ };
+ expect(result.amount?.toString()).toStrictEqual(amount);
+ expect(result.fiat?.toString()).toStrictEqual(fiat);
+ },
+ );
+
+ // @ts-expect-error This is missing from the Mocha type definitions
+ it.each([
+ [
+ 'available',
+ new BigNumber('100'),
+ new BigNumber('5'),
+ new BigNumber('95'),
+ ],
+ ['unavailable', null, null, null],
+ ])(
+ 'calcAdjustedReturn: fiat amounts are %s',
+ (
+ _: string,
+ destTokenAmountInFiat: BigNumber,
+ totalNetworkFeeInFiat: BigNumber,
+ fiat: string,
+ ) => {
+ const result = calcAdjustedReturn(
+ destTokenAmountInFiat,
+ totalNetworkFeeInFiat,
+ );
+ expect(result.fiat).toStrictEqual(fiat);
+ },
+ );
+
+ // @ts-expect-error This is missing from the Mocha type definitions
+ it.each([
+ ['< 1', new BigNumber('100'), new BigNumber('5'), new BigNumber('0.05')],
+ ['>= 1', new BigNumber('1'), new BigNumber('2000'), new BigNumber('2000')],
+ ['0', new BigNumber('1'), new BigNumber('0'), new BigNumber('0')],
+ ])(
+ 'calcSwapRate: %s rate',
+ (
+ _: string,
+ sentAmount: BigNumber,
+ destTokenAmount: BigNumber,
+ rate: string,
+ ) => {
+ const result = calcSwapRate(sentAmount, destTokenAmount);
+ expect(result).toStrictEqual(rate);
+ },
+ );
+
+ // @ts-expect-error This is missing from the Mocha type definitions
+ it.each([
+ ['exact', 120, '2'],
+ ['rounded down', 2000, '33'],
+ ])(
+ 'formatEtaInMinutes: %s conversion',
+ (_: string, estimatedProcessingTimeInSeconds: number, minutes: string) => {
+ const result = formatEtaInMinutes(estimatedProcessingTimeInSeconds);
+ expect(result).toStrictEqual(minutes);
+ },
+ );
+});
diff --git a/ui/pages/bridge/utils/quote.ts b/ui/pages/bridge/utils/quote.ts
index b5945f64a9df..2fff7e9c1b18 100644
--- a/ui/pages/bridge/utils/quote.ts
+++ b/ui/pages/bridge/utils/quote.ts
@@ -1,5 +1,17 @@
+import { zeroAddress } from 'ethereumjs-util';
+import { BigNumber } from 'bignumber.js';
import { calcTokenAmount } from '../../../../shared/lib/transactions-controller-utils';
-import { QuoteResponse, QuoteRequest } from '../types';
+import { QuoteResponse, QuoteRequest, Quote, L1GasFees } from '../types';
+import {
+ hexToDecimal,
+ sumDecimals,
+} from '../../../../shared/modules/conversion.utils';
+import { formatCurrency } from '../../../helpers/utils/confirm-tx.util';
+import { Numeric } from '../../../../shared/modules/Numeric';
+import { EtherDenomination } from '../../../../shared/constants/common';
+import { DEFAULT_PRECISION } from '../../../hooks/useCurrencyDisplay';
+
+export const isNativeAddress = (address?: string) => address === zeroAddress();
export const isValidQuoteRequest = (
partialRequest: Partial,
@@ -33,27 +45,150 @@ export const isValidQuoteRequest = (
);
};
-export const getQuoteDisplayData = (quoteResponse?: QuoteResponse) => {
- const { quote, estimatedProcessingTimeInSeconds } = quoteResponse ?? {};
- if (!quoteResponse || !quote || !estimatedProcessingTimeInSeconds) {
- return {};
- }
+export const calcToAmount = (
+ { destTokenAmount, destAsset }: Quote,
+ exchangeRate: number | null,
+) => {
+ const normalizedDestAmount = calcTokenAmount(
+ destTokenAmount,
+ destAsset.decimals,
+ );
+ return {
+ amount: normalizedDestAmount,
+ fiat: exchangeRate
+ ? normalizedDestAmount.mul(exchangeRate.toString())
+ : null,
+ };
+};
- const etaInMinutes = (estimatedProcessingTimeInSeconds / 60).toFixed();
- const quoteRate = `1 ${quote.srcAsset.symbol} = ${calcTokenAmount(
- quote.destTokenAmount,
- quote.destAsset.decimals,
- )
- .div(calcTokenAmount(quote.srcTokenAmount, quote.srcAsset.decimals))
- .toFixed(4)
- .toString()} ${quote.destAsset.symbol}`;
+export const calcSentAmount = (
+ { srcTokenAmount, srcAsset, feeData }: Quote,
+ exchangeRate: number | null,
+) => {
+ const normalizedSentAmount = calcTokenAmount(
+ new BigNumber(srcTokenAmount).plus(feeData.metabridge.amount),
+ srcAsset.decimals,
+ );
+ return {
+ amount: normalizedSentAmount,
+ fiat: exchangeRate
+ ? normalizedSentAmount.mul(exchangeRate.toString())
+ : null,
+ };
+};
+export const calcRelayerFee = (
+ bridgeQuote: QuoteResponse,
+ nativeExchangeRate?: number,
+) => {
+ const {
+ quote: { srcAsset, srcTokenAmount, feeData },
+ trade,
+ } = bridgeQuote;
+ const relayerFeeInNative = calcTokenAmount(
+ new BigNumber(hexToDecimal(trade.value)).minus(
+ isNativeAddress(srcAsset.address)
+ ? new BigNumber(srcTokenAmount).plus(feeData.metabridge.amount)
+ : 0,
+ ),
+ 18,
+ );
return {
- etaInMinutes,
- totalFees: {
- amount: '0.01 ETH', // TODO implement gas + relayer fee
- fiat: '$0.01',
- },
- quoteRate,
+ amount: relayerFeeInNative,
+ fiat: nativeExchangeRate
+ ? relayerFeeInNative.mul(nativeExchangeRate.toString())
+ : null,
};
};
+
+export const calcTotalGasFee = (
+ bridgeQuote: QuoteResponse & L1GasFees,
+ estimatedBaseFeeInDecGwei: string,
+ maxPriorityFeePerGasInDecGwei: string,
+ nativeExchangeRate?: number,
+) => {
+ const { approval, trade, l1GasFeesInHexWei } = bridgeQuote;
+ const totalGasLimitInDec = sumDecimals(
+ trade.gasLimit?.toString() ?? '0',
+ approval?.gasLimit?.toString() ?? '0',
+ );
+ const feePerGasInDecGwei = sumDecimals(
+ estimatedBaseFeeInDecGwei,
+ maxPriorityFeePerGasInDecGwei,
+ );
+
+ const l1GasFeesInDecGWei = Numeric.from(
+ l1GasFeesInHexWei ?? '0',
+ 16,
+ EtherDenomination.WEI,
+ ).toDenomination(EtherDenomination.GWEI);
+
+ const gasFeesInDecGwei = totalGasLimitInDec
+ .times(feePerGasInDecGwei)
+ .add(l1GasFeesInDecGWei);
+
+ const gasFeesInDecEth = new BigNumber(
+ gasFeesInDecGwei.shiftedBy(9).toString(),
+ );
+ const gasFeesInUSD = nativeExchangeRate
+ ? gasFeesInDecEth.times(nativeExchangeRate.toString())
+ : null;
+
+ return {
+ amount: gasFeesInDecEth,
+ fiat: gasFeesInUSD,
+ };
+};
+
+export const calcAdjustedReturn = (
+ destTokenAmountInFiat: BigNumber | null,
+ totalNetworkFeeInFiat: BigNumber | null,
+) => ({
+ fiat:
+ destTokenAmountInFiat && totalNetworkFeeInFiat
+ ? destTokenAmountInFiat.minus(totalNetworkFeeInFiat)
+ : null,
+});
+
+export const calcSwapRate = (
+ sentAmount: BigNumber,
+ destTokenAmount: BigNumber,
+) => destTokenAmount.div(sentAmount);
+
+export const calcCost = (
+ adjustedReturnInFiat: BigNumber | null,
+ sentAmountInFiat: BigNumber | null,
+) => ({
+ fiat:
+ adjustedReturnInFiat && sentAmountInFiat
+ ? sentAmountInFiat.minus(adjustedReturnInFiat)
+ : null,
+});
+
+export const formatEtaInMinutes = (estimatedProcessingTimeInSeconds: number) =>
+ (estimatedProcessingTimeInSeconds / 60).toFixed();
+
+export const formatTokenAmount = (
+ amount: BigNumber,
+ symbol: string,
+ precision: number = 2,
+) => `${amount.toFixed(precision)} ${symbol}`;
+
+export const formatFiatAmount = (
+ amount: BigNumber | null,
+ currency: string,
+ precision: number = DEFAULT_PRECISION,
+) => {
+ if (!amount) {
+ return undefined;
+ }
+ if (precision === 0) {
+ if (amount.lt(0.01)) {
+ return `<${formatCurrency('0', currency, precision)}`;
+ }
+ if (amount.lt(1)) {
+ return formatCurrency(amount.toString(), currency, 2);
+ }
+ }
+ return formatCurrency(amount.toString(), currency, precision);
+};
diff --git a/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap
index 86779c10cad6..6d48461f1c89 100644
--- a/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap
+++ b/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap
@@ -50,6 +50,81 @@ exports[`Info renders info section for approve request 1`] = `
+
+
+
+
+
+
+ 0x2e0D7...5d09B
+
+
+
+
+
+
+
+
+
+
+ 0x2e0D7...5d09B
+
+
+
+
+
+
+
+
+
+
+
+ 0x2e0D7...5d09B
+
+
+
+
+
renders component for approve request 1`] = `
+
+
+
+
+
+
+ 0x2e0D7...5d09B
+
+
+
+
+
{showAdvancedDetails && (
<>
diff --git a/ui/pages/confirmations/components/confirm/info/approve/revoke-details/revoke-details.tsx b/ui/pages/confirmations/components/confirm/info/approve/revoke-details/revoke-details.tsx
index 49bf5e7724e1..09b2e6a809f3 100644
--- a/ui/pages/confirmations/components/confirm/info/approve/revoke-details/revoke-details.tsx
+++ b/ui/pages/confirmations/components/confirm/info/approve/revoke-details/revoke-details.tsx
@@ -1,11 +1,13 @@
import React from 'react';
import { ConfirmInfoSection } from '../../../../../../../components/app/confirm/info/row/section';
import { OriginRow } from '../../shared/transaction-details/transaction-details';
+import { SigningInWithRow } from '../../shared/sign-in-with-row/sign-in-with-row';
export const RevokeDetails = () => {
return (
+
);
};
diff --git a/ui/pages/confirmations/components/confirm/info/base-transaction-info/__snapshots__/base-transaction-info.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/base-transaction-info/__snapshots__/base-transaction-info.test.tsx.snap
index d7054050f710..10506183561d 100644
--- a/ui/pages/confirmations/components/confirm/info/base-transaction-info/__snapshots__/base-transaction-info.test.tsx.snap
+++ b/ui/pages/confirmations/components/confirm/info/base-transaction-info/__snapshots__/base-transaction-info.test.tsx.snap
@@ -233,6 +233,81 @@ exports[` renders component for contract interaction requ
+
+
+
+
+
+
+ 0x2e0D7...5d09B
+
+
+
+
+
+
{
@@ -51,9 +51,7 @@ const PersonalSignInfo: React.FC = () => {
return null;
}
- const { from } = currentConfirmation.msgParams;
const isSIWE = isSIWESignatureRequest(currentConfirmation);
- const chainId = currentConfirmation.chainId as string;
const messageText = sanitizeString(
hexToText(currentConfirmation.msgParams?.data),
);
@@ -138,15 +136,7 @@ const PersonalSignInfo: React.FC = () => {
>
- {isSIWE && (
-
-
-
- )}
+
{isSIWE ? (
diff --git a/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/__snapshots__/set-approval-for-all-info.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/__snapshots__/set-approval-for-all-info.test.tsx.snap
index 42efa15a2a5b..5da63fd56680 100644
--- a/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/__snapshots__/set-approval-for-all-info.test.tsx.snap
+++ b/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/__snapshots__/set-approval-for-all-info.test.tsx.snap
@@ -154,6 +154,81 @@ exports[` renders component for approve request 1`] = `
+
+
+
+
+
+
+ 0x2e0D7...5d09B
+
+
+
+
({
+ useAlertMetrics: jest.fn(() => ({
+ trackAlertMetrics: jest.fn(),
+ })),
+ }),
+);
+
+describe('', () => {
+ const middleware = [thunk];
+
+ it('renders component for transaction details', () => {
+ const state = getMockContractInteractionConfirmState();
+ const mockStore = configureMockStore(middleware)(state);
+ const { getByText } = renderWithConfirmContextProvider(
+ ,
+ mockStore,
+ );
+ expect(getByText('Signing in with')).toBeInTheDocument();
+ expect(getByText('0x2e0D7...5d09B')).toBeInTheDocument();
+ });
+});
diff --git a/ui/pages/confirmations/components/confirm/info/shared/sign-in-with-row/sign-in-with-row.tsx b/ui/pages/confirmations/components/confirm/info/shared/sign-in-with-row/sign-in-with-row.tsx
new file mode 100644
index 000000000000..7b20cbc08062
--- /dev/null
+++ b/ui/pages/confirmations/components/confirm/info/shared/sign-in-with-row/sign-in-with-row.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { TransactionMeta } from '@metamask/transaction-controller';
+
+import { ConfirmInfoRowAddress } from '../../../../../../../components/app/confirm/info/row';
+import { ConfirmInfoAlertRow } from '../../../../../../../components/app/confirm/info/row/alert-row/alert-row';
+import { RowAlertKey } from '../../../../../../../components/app/confirm/info/row/constants';
+import { useI18nContext } from '../../../../../../../hooks/useI18nContext';
+import { useConfirmContext } from '../../../../../context/confirm';
+import { SignatureRequestType } from '../../../../../types/confirm';
+
+export const SigningInWithRow = () => {
+ const t = useI18nContext();
+
+ const { currentConfirmation } = useConfirmContext();
+
+ const chainId = currentConfirmation?.chainId as string;
+ const from =
+ (currentConfirmation as TransactionMeta)?.txParams?.from ??
+ (currentConfirmation as SignatureRequestType)?.msgParams?.from;
+
+ if (!from) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+};
diff --git a/ui/pages/confirmations/components/confirm/info/shared/transaction-details/__snapshots__/transaction-details.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/shared/transaction-details/__snapshots__/transaction-details.test.tsx.snap
index f5183e21206c..9e9b9b23eca5 100644
--- a/ui/pages/confirmations/components/confirm/info/shared/transaction-details/__snapshots__/transaction-details.test.tsx.snap
+++ b/ui/pages/confirmations/components/confirm/info/shared/transaction-details/__snapshots__/transaction-details.test.tsx.snap
@@ -151,6 +151,81 @@ exports[` renders component for transaction details 1`] =
+
+
+
+
+
+
+ 0x2e0D7...5d09B
+
+
+
+
`;
diff --git a/ui/pages/confirmations/components/confirm/info/shared/transaction-details/transaction-details.tsx b/ui/pages/confirmations/components/confirm/info/shared/transaction-details/transaction-details.tsx
index 89c2da783a4f..79cea5963c45 100644
--- a/ui/pages/confirmations/components/confirm/info/shared/transaction-details/transaction-details.tsx
+++ b/ui/pages/confirmations/components/confirm/info/shared/transaction-details/transaction-details.tsx
@@ -20,6 +20,7 @@ import { ConfirmInfoRowCurrency } from '../../../../../../../components/app/conf
import { PRIMARY } from '../../../../../../../helpers/constants/common';
import { useUserPreferencedCurrency } from '../../../../../../../hooks/useUserPreferencedCurrency';
import { HEX_ZERO } from '../constants';
+import { SigningInWithRow } from '../sign-in-with-row/sign-in-with-row';
export const OriginRow = () => {
const t = useI18nContext();
@@ -156,6 +157,7 @@ export const TransactionDetails = () => {
{showAdvancedDetails && }
+
diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap
index 23cddb2b59b2..10955f0992bc 100644
--- a/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap
+++ b/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap
@@ -7,23 +7,85 @@ exports[` renders correctly 1`] = `
data-testid="confirmation__transaction-flow"
>
-
-
- 0x2e0D7...5d09B
-
+
+ From
+
+
+
+
+
+
+
+
+ 0x2e0D7...5d09B
+
+
+
renders correctly 1`] = `
style="mask-image: url('./images/icons/arrow-right.svg');"
/>
+
-
-
- 0x6B175...71d0F
-
+
+
+
+ 0x6B175...71d0F
+
+
+
diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.test.tsx b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.test.tsx
index c23d3645abd3..bdc6ed30678a 100644
--- a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.test.tsx
+++ b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.test.tsx
@@ -11,6 +11,17 @@ jest.mock('../hooks/useDecodedTransactionData', () => ({
useDecodedTransactionData: jest.fn(),
}));
+jest.mock(
+ '../../../../../../components/app/alert-system/contexts/alertMetricsContext.tsx',
+ () => ({
+ useAlertMetrics: jest.fn(() => ({
+ trackInlineAlertClicked: jest.fn(),
+ trackAlertRender: jest.fn(),
+ trackAlertActionClicked: jest.fn(),
+ })),
+ }),
+);
+
describe('', () => {
const useDecodedTransactionDataMock = jest.fn().mockImplementation(() => ({
pending: false,
diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx
index f5a9a46acbb2..bfa299436cc4 100644
--- a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx
+++ b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx
@@ -1,11 +1,9 @@
-import { NameType } from '@metamask/name-controller';
import {
TransactionMeta,
TransactionType,
} from '@metamask/transaction-controller';
import React from 'react';
import { ConfirmInfoSection } from '../../../../../../components/app/confirm/info/row/section';
-import Name from '../../../../../../components/app/name';
import {
Box,
Icon,
@@ -19,10 +17,15 @@ import {
IconColor,
JustifyContent,
} from '../../../../../../helpers/constants/design-system';
+import { ConfirmInfoRowAddress } from '../../../../../../components/app/confirm/info/row';
+import { ConfirmInfoAlertRow } from '../../../../../../components/app/confirm/info/row/alert-row/alert-row';
+import { RowAlertKey } from '../../../../../../components/app/confirm/info/row/constants';
+import { useI18nContext } from '../../../../../../hooks/useI18nContext';
import { useConfirmContext } from '../../../../context/confirm';
import { useDecodedTransactionData } from '../hooks/useDecodedTransactionData';
export const TransactionFlowSection = () => {
+ const t = useI18nContext();
const { currentConfirmation: transactionMeta } =
useConfirmContext();
@@ -50,24 +53,44 @@ export const TransactionFlowSection = () => {
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
alignItems={AlignItems.center}
- padding={3}
>
-
+
+
+
+
+
+
{recipientAddress && (
-
+
+
+
+
+
)}
diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign-v1/__snapshots__/typed-sign-v1.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/typed-sign-v1/__snapshots__/typed-sign-v1.test.tsx.snap
index e80d317f574b..8713c5d303ca 100644
--- a/ui/pages/confirmations/components/confirm/info/typed-sign-v1/__snapshots__/typed-sign-v1.test.tsx.snap
+++ b/ui/pages/confirmations/components/confirm/info/typed-sign-v1/__snapshots__/typed-sign-v1.test.tsx.snap
@@ -47,6 +47,81 @@ exports[`TypedSignInfo correctly renders typed sign data request 1`] = `
+
+
+
+
+
+
+ 0x935E7...05477
+
+
+
+
{
const t = useI18nContext();
@@ -42,6 +43,7 @@ const TypedSignV1Info: React.FC = () => {
url={currentConfirmation.msgParams?.origin ?? ''}
/>
+
+
+
+
+
+
+
+ 0x935E7...05477
+
+
+
+
+
+
+
+
+
+
+ 0x935E7...05477
+
+
+
+
+
+
+
+
+
+
+
+ 0x935E7...05477
+
+
+
+
+
{
it('renders component correctly', async () => {
const state = getMockTypedSignConfirmStateForRequest({
@@ -38,4 +75,24 @@ describe('DecodedSimulation', () => {
expect(container).toMatchSnapshot();
});
+
+ describe('getStateChangeToolip', () => {
+ it('return correct tooltip when permit is for listing NFT', async () => {
+ const tooltip = getStateChangeToolip(
+ decodingDataListing,
+ decodingDataListing?.[0],
+ (str: string) => str,
+ );
+ expect(tooltip).toBe('signature_decoding_list_nft_tooltip');
+ });
+ });
+
+ it('return correct tooltip when permit is for bidding NFT', async () => {
+ const tooltip = getStateChangeToolip(
+ decodingDataBidding,
+ decodingDataBidding?.[0],
+ (str: string) => str,
+ );
+ expect(tooltip).toBe('signature_decoding_bid_nft_tooltip');
+ });
});
diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.tsx
index 3798776ca85d..cf774483ee6c 100644
--- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.tsx
+++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import {
DecodingDataChangeType,
DecodingDataStateChange,
+ DecodingDataStateChanges,
} from '@metamask/signature-controller';
import { Hex } from '@metamask/utils';
@@ -11,8 +12,35 @@ import { useI18nContext } from '../../../../../../../../hooks/useI18nContext';
import { SignatureRequestType } from '../../../../../../types/confirm';
import { useConfirmContext } from '../../../../../../context/confirm';
import StaticSimulation from '../../../shared/static-simulation/static-simulation';
-import NativeValueDisplay from '../native-value-display/native-value-display';
import TokenValueDisplay from '../value-display/value-display';
+import NativeValueDisplay from '../native-value-display/native-value-display';
+
+export const getStateChangeToolip = (
+ stateChangeList: DecodingDataStateChanges | null,
+ stateChange: DecodingDataStateChange,
+ t: ReturnType,
+): string | undefined => {
+ if (stateChange.changeType === DecodingDataChangeType.Receive) {
+ if (
+ stateChangeList?.some(
+ (change) =>
+ change.changeType === DecodingDataChangeType.Listing &&
+ change.assetType === TokenStandard.ERC721,
+ )
+ ) {
+ return t('signature_decoding_list_nft_tooltip');
+ }
+ if (
+ stateChange.assetType === TokenStandard.ERC721 &&
+ stateChangeList?.some(
+ (change) => change.changeType === DecodingDataChangeType.Bidding,
+ )
+ ) {
+ return t('signature_decoding_bid_nft_tooltip');
+ }
+ }
+ return undefined;
+};
const getStateChangeLabelMap = (
t: ReturnType,
@@ -28,17 +56,23 @@ const getStateChangeLabelMap = (
}[changeType]);
const StateChangeRow = ({
+ stateChangeList,
stateChange,
chainId,
}: {
+ stateChangeList: DecodingDataStateChanges | null;
stateChange: DecodingDataStateChange;
chainId: Hex;
}) => {
const t = useI18nContext();
const { assetType, changeType, amount, contractAddress, tokenID } =
stateChange;
+ const tooltip = getStateChangeToolip(stateChangeList, stateChange, t);
return (
-
+
{(assetType === TokenStandard.ERC20 ||
assetType === TokenStandard.ERC721) && (
= () => {
const stateChangeFragment = (decodingData?.stateChanges ?? []).map(
(change: DecodingDataStateChange) => (
-
+
),
);
diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx
index 0b4d9eed22d4..86055425fa46 100644
--- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx
+++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx
@@ -11,7 +11,7 @@ const PermitSimulation: React.FC
)}
+
{
paddingLeft: 0,
paddingRight: 0,
}}
- >
- {/* Intentional fragment */}
- <>>
-
+ />
);
};
diff --git a/ui/pages/confirmations/confirm-transaction/confirm-transaction.component.js b/ui/pages/confirmations/confirm-transaction/confirm-transaction.component.js
index ef08fb6bbba1..f5858e853dd1 100644
--- a/ui/pages/confirmations/confirm-transaction/confirm-transaction.component.js
+++ b/ui/pages/confirmations/confirm-transaction/confirm-transaction.component.js
@@ -133,9 +133,7 @@ const ConfirmTransaction = () => {
});
useEffect(() => {
- if (!totalUnapproved && !sendTo) {
- history.replace(mostRecentOverviewPage);
- } else {
+ if (totalUnapproved || sendTo) {
const { txParams: { data } = {}, origin } = transaction;
if (origin !== ORIGIN_METAMASK) {
diff --git a/ui/pages/confirmations/confirm-transaction/confirm-transaction.test.js b/ui/pages/confirmations/confirm-transaction/confirm-transaction.test.js
index fbd8840de4ec..3c374fd7d0c0 100644
--- a/ui/pages/confirmations/confirm-transaction/confirm-transaction.test.js
+++ b/ui/pages/confirmations/confirm-transaction/confirm-transaction.test.js
@@ -270,26 +270,5 @@ describe('Confirmation Transaction Page', () => {
expect(replaceSpy).not.toHaveBeenCalled();
});
});
-
- describe('when no unapproved transactions and no sendTo recipient exist', () => {
- it('should call history.replace(mostRecentOverviewPage)', () => {
- const mockStore = configureMockStore(middleware)({
- ...mockState,
- metamask: {
- ...mockState.metamask,
- transactions: [],
- },
- });
- const replaceSpy = jest.fn();
- jest.spyOn(ReactRouterDOM, 'useHistory').mockImplementation(() => {
- return {
- replace: replaceSpy,
- };
- });
-
- renderWithProvider(, mockStore, '/asdfb');
- expect(replaceSpy).toHaveBeenCalled();
- });
- });
});
});
diff --git a/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap b/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap
index 2fff18bc9084..9f718a4b8a03 100644
--- a/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap
+++ b/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap
@@ -179,6 +179,81 @@ exports[`Confirm matches snapshot for signature - personal sign type 1`] = `
+
+
+
+
+
+
+
+ 0x935E7...05477
+
+
+
+
+
+
+
+
+
+
+ 0x935E7...05477
+
+
+
+
+
+
+
+
+
+
+
+ 0x935E7...05477
+
+
+
+
+
+
+
+
+
+
+ 0x935E7...05477
+
+
+
+
{
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('returns no alerts if no confirmation', () => {
+ expect(runHook()).toEqual([]);
+ });
+
+ it('returns no alerts if no transactions', () => {
+ expect(
+ runHook({
+ currentConfirmation: CONFIRMATION_MOCK,
+ transactions: [],
+ }),
+ ).toEqual([]);
+ });
+
+ it('returns no alerts if firstTimeInteraction is false', () => {
+ const notFirstTimeConfirmation = {
+ ...TRANSACTION_META_MOCK,
+ firstTimeInteraction: false,
+ };
+ expect(
+ runHook({
+ currentConfirmation: notFirstTimeConfirmation,
+ }),
+ ).toEqual([]);
+ });
+
+ it('returns no alerts if firstTimeInteraction is undefined', () => {
+ const notFirstTimeConfirmation = {
+ ...TRANSACTION_META_MOCK,
+ firstTimeInteraction: undefined,
+ };
+ expect(
+ runHook({
+ currentConfirmation: notFirstTimeConfirmation,
+ }),
+ ).toEqual([]);
+ });
+
+ it('returns alert if isFirstTimeInteraction is true', () => {
+ const firstTimeConfirmation = {
+ ...CONFIRMATION_MOCK,
+ isFirstTimeInteraction: true,
+ };
+ const alerts = runHook({
+ currentConfirmation: firstTimeConfirmation,
+ });
+
+ expect(alerts).toEqual([
+ {
+ actions: [],
+ field: RowAlertKey.FirstTimeInteraction,
+ isBlocking: false,
+ key: 'firstTimeInteractionTitle',
+ message:
+ "You're interacting with this address for the first time. Make sure that it's correct before you continue.",
+ reason: '1st interaction',
+ severity: Severity.Warning,
+ },
+ ]);
+ });
+});
diff --git a/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.ts b/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.ts
new file mode 100644
index 000000000000..7e4a86c3802f
--- /dev/null
+++ b/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.ts
@@ -0,0 +1,35 @@
+import { useMemo } from 'react';
+import { TransactionMeta } from '@metamask/transaction-controller';
+
+import { Alert } from '../../../../../ducks/confirm-alerts/confirm-alerts';
+import { useI18nContext } from '../../../../../hooks/useI18nContext';
+import { Severity } from '../../../../../helpers/constants/design-system';
+import { RowAlertKey } from '../../../../../components/app/confirm/info/row/constants';
+import { useConfirmContext } from '../../../context/confirm';
+
+export function useFirstTimeInteractionAlert(): Alert[] {
+ const t = useI18nContext();
+ const { currentConfirmation } = useConfirmContext
();
+
+ const { isFirstTimeInteraction } = currentConfirmation ?? {};
+
+ return useMemo(() => {
+ // If isFirstTimeInteraction is undefined that means it's either disabled or error in accounts API
+ // If it's false that means account relationship found
+ if (!isFirstTimeInteraction) {
+ return [];
+ }
+
+ return [
+ {
+ actions: [],
+ field: RowAlertKey.FirstTimeInteraction,
+ isBlocking: false,
+ key: 'firstTimeInteractionTitle',
+ message: t('alertMessageFirstTimeInteraction'),
+ reason: t('alertReasonFirstTimeInteraction'),
+ severity: Severity.Warning,
+ },
+ ];
+ }, [isFirstTimeInteraction, t]);
+}
diff --git a/ui/pages/confirmations/hooks/alerts/useSelectedAccountAlerts.test.ts b/ui/pages/confirmations/hooks/alerts/useSelectedAccountAlerts.test.ts
new file mode 100644
index 000000000000..fb69efb86ba1
--- /dev/null
+++ b/ui/pages/confirmations/hooks/alerts/useSelectedAccountAlerts.test.ts
@@ -0,0 +1,79 @@
+import { TransactionMeta } from '@metamask/transaction-controller';
+
+import mockState from '../../../../../test/data/mock-state.json';
+import { genUnapprovedContractInteractionConfirmation } from '../../../../../test/data/confirmations/contract-interaction';
+import {
+ getMockConfirmStateForTransaction,
+ getMockPersonalSignConfirmStateForRequest,
+} from '../../../../../test/data/confirmations/helper';
+import { renderHookWithConfirmContextProvider } from '../../../../../test/lib/confirmations/render-helpers';
+import { unapprovedPersonalSignMsg } from '../../../../../test/data/confirmations/personal_sign';
+import { SignatureRequestType } from '../../types/confirm';
+import { useSelectedAccountAlerts } from './useSelectedAccountAlerts';
+
+const expectedAlert = [
+ {
+ key: 'selectedAccountWarning',
+ message:
+ 'This request is for a different account than the one selected in your wallet. To use another account, connect it to the site.',
+ reason: 'Different account selected',
+ severity: 'warning',
+ field: 'signingInWith',
+ },
+];
+
+describe('useSelectedAccountAlerts', () => {
+ it('returns an empty array when there is no current confirmation', () => {
+ const { result } = renderHookWithConfirmContextProvider(
+ () => useSelectedAccountAlerts(),
+ mockState,
+ );
+ expect(result.current).toEqual([]);
+ });
+
+ it('returns an alert for signature if signing account is different from selected account', () => {
+ const { result } = renderHookWithConfirmContextProvider(
+ () => useSelectedAccountAlerts(),
+ getMockPersonalSignConfirmStateForRequest({
+ ...unapprovedPersonalSignMsg,
+ msgParams: {
+ ...unapprovedPersonalSignMsg.msgParams,
+ from: '0x0',
+ },
+ } as SignatureRequestType),
+ );
+ expect(result.current).toEqual(expectedAlert);
+ });
+
+ it('does not returns an alert for signature if signing account is same as selected account', () => {
+ const { result } = renderHookWithConfirmContextProvider(
+ () => useSelectedAccountAlerts(),
+ getMockPersonalSignConfirmStateForRequest(
+ unapprovedPersonalSignMsg as SignatureRequestType,
+ ),
+ );
+ expect(result.current).toEqual([]);
+ });
+
+ it('returns an alert for transaction if signing account is different from selected account', () => {
+ const contractInteraction = genUnapprovedContractInteractionConfirmation({
+ address: '0x0',
+ });
+ const { result } = renderHookWithConfirmContextProvider(
+ () => useSelectedAccountAlerts(),
+ getMockConfirmStateForTransaction(contractInteraction as TransactionMeta),
+ );
+ expect(result.current).toEqual(expectedAlert);
+ });
+
+ it('does not returns an alert for transaction if signing account is same as selected account', () => {
+ const contractInteraction = genUnapprovedContractInteractionConfirmation({
+ address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
+ });
+ const { result } = renderHookWithConfirmContextProvider(
+ () => useSelectedAccountAlerts(),
+ getMockConfirmStateForTransaction(contractInteraction as TransactionMeta),
+ );
+ expect(result.current).toEqual([]);
+ });
+});
diff --git a/ui/pages/confirmations/hooks/alerts/useSelectedAccountAlerts.ts b/ui/pages/confirmations/hooks/alerts/useSelectedAccountAlerts.ts
new file mode 100644
index 000000000000..6e4be13b1ae5
--- /dev/null
+++ b/ui/pages/confirmations/hooks/alerts/useSelectedAccountAlerts.ts
@@ -0,0 +1,41 @@
+import { TransactionMeta } from '@metamask/transaction-controller';
+import { useMemo } from 'react';
+import { useSelector } from 'react-redux';
+
+import { Alert } from '../../../../ducks/confirm-alerts/confirm-alerts';
+import { RowAlertKey } from '../../../../components/app/confirm/info/row/constants';
+import { Severity } from '../../../../helpers/constants/design-system';
+import { getSelectedAccount } from '../../../../selectors';
+import { useI18nContext } from '../../../../hooks/useI18nContext';
+import { SignatureRequestType } from '../../types/confirm';
+import { useConfirmContext } from '../../context/confirm';
+
+export const useSelectedAccountAlerts = (): Alert[] => {
+ const t = useI18nContext();
+
+ const { currentConfirmation } = useConfirmContext();
+ const selectedAccount = useSelector(getSelectedAccount);
+
+ const fromAccount =
+ (currentConfirmation as SignatureRequestType)?.msgParams?.from ??
+ (currentConfirmation as TransactionMeta)?.txParams?.from;
+ const confirmationAccountSameAsSelectedAccount =
+ !fromAccount ||
+ fromAccount.toLowerCase() === selectedAccount?.address?.toLowerCase();
+
+ return useMemo((): Alert[] => {
+ if (confirmationAccountSameAsSelectedAccount) {
+ return [];
+ }
+
+ return [
+ {
+ key: 'selectedAccountWarning',
+ reason: t('selectedAccountMismatch'),
+ field: RowAlertKey.SigningInWith,
+ severity: Severity.Warning,
+ message: t('alertSelectedAccountWarning'),
+ },
+ ];
+ }, [confirmationAccountSameAsSelectedAccount, t]);
+};
diff --git a/ui/pages/confirmations/hooks/useConfirmationAlerts.ts b/ui/pages/confirmations/hooks/useConfirmationAlerts.ts
index c5f77f143cb6..382c7cdea511 100644
--- a/ui/pages/confirmations/hooks/useConfirmationAlerts.ts
+++ b/ui/pages/confirmations/hooks/useConfirmationAlerts.ts
@@ -11,11 +11,13 @@ import { useNoGasPriceAlerts } from './alerts/transactions/useNoGasPriceAlerts';
import { usePendingTransactionAlerts } from './alerts/transactions/usePendingTransactionAlerts';
import { useQueuedConfirmationsAlerts } from './alerts/transactions/useQueuedConfirmationsAlerts';
import { useResimulationAlert } from './alerts/transactions/useResimulationAlert';
+import { useFirstTimeInteractionAlert } from './alerts/transactions/useFirstTimeInteractionAlert';
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
import { useSigningOrSubmittingAlerts } from './alerts/transactions/useSigningOrSubmittingAlerts';
///: END:ONLY_INCLUDE_IF
import useConfirmationOriginAlerts from './alerts/useConfirmationOriginAlerts';
import useBlockaidAlerts from './alerts/useBlockaidAlerts';
+import { useSelectedAccountAlerts } from './alerts/useSelectedAccountAlerts';
function useSignatureAlerts(): Alert[] {
const accountMismatchAlerts = useAccountMismatchAlerts();
@@ -36,10 +38,12 @@ function useTransactionAlerts(): Alert[] {
const noGasPriceAlerts = useNoGasPriceAlerts();
const pendingTransactionAlerts = usePendingTransactionAlerts();
const resimulationAlert = useResimulationAlert();
+ const firstTimeInteractionAlert = useFirstTimeInteractionAlert();
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
const signingOrSubmittingAlerts = useSigningOrSubmittingAlerts();
///: END:ONLY_INCLUDE_IF
const queuedConfirmationsAlerts = useQueuedConfirmationsAlerts();
+
return useMemo(
() => [
...gasEstimateFailedAlerts,
@@ -50,6 +54,7 @@ function useTransactionAlerts(): Alert[] {
...noGasPriceAlerts,
...pendingTransactionAlerts,
...resimulationAlert,
+ ...firstTimeInteractionAlert,
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
...signingOrSubmittingAlerts,
///: END:ONLY_INCLUDE_IF
@@ -64,6 +69,7 @@ function useTransactionAlerts(): Alert[] {
noGasPriceAlerts,
pendingTransactionAlerts,
resimulationAlert,
+ firstTimeInteractionAlert,
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
signingOrSubmittingAlerts,
///: END:ONLY_INCLUDE_IF
@@ -77,6 +83,7 @@ export default function useConfirmationAlerts(): Alert[] {
const confirmationOriginAlerts = useConfirmationOriginAlerts();
const signatureAlerts = useSignatureAlerts();
const transactionAlerts = useTransactionAlerts();
+ const selectedAccountAlerts = useSelectedAccountAlerts();
return useMemo(
() => [
@@ -84,12 +91,14 @@ export default function useConfirmationAlerts(): Alert[] {
...confirmationOriginAlerts,
...signatureAlerts,
...transactionAlerts,
+ ...selectedAccountAlerts,
],
[
blockaidAlerts,
confirmationOriginAlerts,
signatureAlerts,
transactionAlerts,
+ selectedAccountAlerts,
],
);
}
diff --git a/ui/pages/permissions-connect/connect-page/connect-page.test.tsx b/ui/pages/permissions-connect/connect-page/connect-page.test.tsx
index ef705e474ad9..86fa769c3206 100644
--- a/ui/pages/permissions-connect/connect-page/connect-page.test.tsx
+++ b/ui/pages/permissions-connect/connect-page/connect-page.test.tsx
@@ -7,34 +7,42 @@ import {
EndowmentTypes,
RestrictedMethods,
} from '../../../../shared/constants/permissions';
-import { ConnectPage, ConnectPageRequest } from './connect-page';
+import { overrideAccountsFromMockState } from '../../../../test/jest/mocks';
+import {
+ MOCK_ACCOUNT_BIP122_P2WPKH,
+ MOCK_ACCOUNT_EOA,
+} from '../../../../test/data/mock-accounts';
+import { ConnectPage, ConnectPageProps } from './connect-page';
+
+const mockTestDappUrl = 'https://test.dapp';
const render = (
- props: {
- request: ConnectPageRequest;
- permissionsRequestId: string;
- rejectPermissionsRequest: (id: string) => void;
- approveConnection: (request: ConnectPageRequest) => void;
- activeTabOrigin: string;
- } = {
- request: {
- id: '1',
- origin: 'https://test.dapp',
- },
- permissionsRequestId: '1',
- rejectPermissionsRequest: jest.fn(),
- approveConnection: jest.fn(),
- activeTabOrigin: 'https://test.dapp',
- },
- state = {},
+ options: {
+ props?: ConnectPageProps;
+ state?: object;
+ } = {},
) => {
+ const {
+ props = {
+ request: {
+ id: '1',
+ origin: mockTestDappUrl,
+ },
+ permissionsRequestId: '1',
+ rejectPermissionsRequest: jest.fn(),
+ approveConnection: jest.fn(),
+ activeTabOrigin: mockTestDappUrl,
+ },
+ state,
+ } = options;
+
const store = configureStore({
...mockState,
metamask: {
...mockState.metamask,
...state,
permissionHistory: {
- 'https://test.dapp': {
+ mockTestDappUrl: {
eth_accounts: {
accounts: {
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': 1709225290848,
@@ -44,7 +52,7 @@ const render = (
},
},
activeTab: {
- origin: 'https://test.dapp',
+ origin: mockTestDappUrl,
},
});
return renderWithProvider(, store);
@@ -82,33 +90,56 @@ describe('ConnectPage', () => {
it('should render with defaults from the requested permissions', () => {
const { container } = render({
- request: {
- id: '1',
- origin: 'https://test.dapp',
- permissions: {
- [RestrictedMethods.eth_accounts]: {
- caveats: [
- {
- type: CaveatTypes.restrictReturnedAccounts,
- value: ['0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'],
- },
- ],
- },
- [EndowmentTypes.permittedChains]: {
- caveats: [
- {
- type: CaveatTypes.restrictNetworkSwitching,
- value: ['0x1'],
- },
- ],
+ props: {
+ request: {
+ id: '1',
+ origin: mockTestDappUrl,
+ permissions: {
+ [RestrictedMethods.eth_accounts]: {
+ caveats: [
+ {
+ type: CaveatTypes.restrictReturnedAccounts,
+ value: ['0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'],
+ },
+ ],
+ },
+ [EndowmentTypes.permittedChains]: {
+ caveats: [
+ {
+ type: CaveatTypes.restrictNetworkSwitching,
+ value: ['0x1'],
+ },
+ ],
+ },
},
},
+ permissionsRequestId: '1',
+ rejectPermissionsRequest: jest.fn(),
+ approveConnection: jest.fn(),
+ activeTabOrigin: mockTestDappUrl,
},
- permissionsRequestId: '1',
- rejectPermissionsRequest: jest.fn(),
- approveConnection: jest.fn(),
- activeTabOrigin: 'https://test.dapp',
});
expect(container).toMatchSnapshot();
});
+
+ it('should render a disabled confirm if current account is a non-EVM account', () => {
+ // NOTE: We select the non-EVM account by default here!
+ const mockSelectedAccountId = MOCK_ACCOUNT_BIP122_P2WPKH.id;
+ const mockAccounts = [MOCK_ACCOUNT_EOA, MOCK_ACCOUNT_BIP122_P2WPKH];
+ const mockAccountsState = overrideAccountsFromMockState(
+ mockState,
+ mockAccounts,
+ mockSelectedAccountId,
+ );
+
+ const { getByText } = render({
+ state: mockAccountsState.metamask,
+ });
+ const confirmButton = getByText('Connect');
+ const cancelButton = getByText('Cancel');
+ // The currently selected account is a Bitcoin account, the "connecting account list" would be
+ // empty by default and thus, we cannot confirm without explictly select an EVM account.
+ expect(confirmButton).toBeDisabled();
+ expect(cancelButton).toBeDefined();
+ });
});
diff --git a/ui/pages/permissions-connect/connect-page/connect-page.tsx b/ui/pages/permissions-connect/connect-page/connect-page.tsx
index ba9bcc6bf674..32001a75d3a7 100644
--- a/ui/pages/permissions-connect/connect-page/connect-page.tsx
+++ b/ui/pages/permissions-connect/connect-page/connect-page.tsx
@@ -22,7 +22,7 @@ import {
Header,
Page,
} from '../../../components/multichain/pages/page';
-import { SiteCell } from '../../../components/multichain/pages/review-permissions-page';
+import { SiteCell } from '../../../components/multichain/pages/review-permissions-page/site-cell/site-cell';
import {
BackgroundColor,
BlockSize,
@@ -50,7 +50,7 @@ export type ConnectPageRequest = {
>;
};
-type ConnectPageProps = {
+export type ConnectPageProps = {
request: ConnectPageRequest;
permissionsRequestId: string;
rejectPermissionsRequest: (id: string) => void;
@@ -124,10 +124,11 @@ export const ConnectPage: React.FC = ({
}, [accounts, internalAccounts]);
const currentAccount = useSelector(getSelectedInternalAccount);
+ const currentAccountAddress = isEvmAccountType(currentAccount.type)
+ ? [currentAccount.address]
+ : []; // We do not support non-EVM accounts connections
const defaultAccountsAddresses =
- requestedAccounts.length > 0
- ? requestedAccounts
- : [currentAccount?.address];
+ requestedAccounts.length > 0 ? requestedAccounts : currentAccountAddress;
const [selectedAccountAddresses, setSelectedAccountAddresses] = useState(
defaultAccountsAddresses,
);
diff --git a/ui/pages/permissions-connect/permissions-connect.component.js b/ui/pages/permissions-connect/permissions-connect.component.js
index e32f85609406..6d37b46e39e2 100644
--- a/ui/pages/permissions-connect/permissions-connect.component.js
+++ b/ui/pages/permissions-connect/permissions-connect.component.js
@@ -41,10 +41,12 @@ function getDefaultSelectedAccounts(currentAddress, permissionsRequest) {
return new Set(
requestedAccounts
.map((address) => address.toLowerCase())
+ // We only consider EVM accounts here (used for `eth_requestAccounts` or `eth_accounts`)
.filter(isEthAddress),
);
}
+ // We only consider EVM accounts here (used for `eth_requestAccounts` or `eth_accounts`)
return new Set(isEthAddress(currentAddress) ? [currentAddress] : []);
}
diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js
index edf1ff8bbe22..206998cf82ba 100644
--- a/ui/pages/routes/routes.component.js
+++ b/ui/pages/routes/routes.component.js
@@ -1,27 +1,12 @@
import classnames from 'classnames';
import PropTypes from 'prop-types';
-import React, { Component } from 'react';
+import React, { Component, Suspense } from 'react';
import { matchPath, Route, Switch } from 'react-router-dom';
import IdleTimer from 'react-idle-timer';
-import Swaps from '../swaps';
-import ConfirmTransaction from '../confirmations/confirm-transaction';
-import Home from '../home';
-import {
- PermissionsPage,
- Connections,
- ReviewPermissions,
-} from '../../components/multichain/pages';
-import Settings from '../settings';
import Authenticated from '../../helpers/higher-order-components/authenticated';
import Initialized from '../../helpers/higher-order-components/initialized';
-import Lock from '../lock';
import PermissionsConnect from '../permissions-connect';
-import RestoreVaultPage from '../keychains/restore-vault';
-import RevealSeedConfirmation from '../keychains/reveal-seed';
-import ConfirmAddSuggestedTokenPage from '../confirm-add-suggested-token';
-import CreateAccountPage from '../create-account/create-account.component';
-import ConfirmAddSuggestedNftPage from '../confirm-add-suggested-nft';
import Loading from '../../components/ui/loading-screen';
import LoadingNetwork from '../../components/app/loading-network-screen';
import { Modal } from '../../components/app/modals';
@@ -34,15 +19,8 @@ import {
ImportNftsModal,
ImportTokensModal,
} from '../../components/multichain';
-import UnlockPage from '../unlock-page';
import Alerts from '../../components/app/alerts';
-import Asset from '../asset';
import OnboardingAppHeader from '../onboarding-flow/onboarding-app-header/onboarding-app-header';
-import Notifications from '../notifications';
-import NotificationsSettings from '../notifications-settings';
-import NotificationDetails from '../notification-details';
-import SnapList from '../snaps/snaps-list';
-import SnapView from '../snaps/snap-view';
///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
import InstitutionalEntityDonePage from '../institutional/institutional-entity-done-page';
import InteractiveReplacementTokenNotification from '../../components/institutional/interactive-replacement-token-notification';
@@ -95,8 +73,6 @@ import {
// TODO: Remove restricted import
// eslint-disable-next-line import/no-restricted-paths
import { getEnvironmentType } from '../../../app/scripts/lib/util';
-import ConfirmationPage from '../confirmations/confirmation';
-import OnboardingFlow from '../onboarding-flow/onboarding-flow';
import QRHardwarePopover from '../../components/app/qr-hardware-popover';
import DeprecatedNetworks from '../../components/ui/deprecated-networks/deprecated-networks';
import NewNetworkInfo from '../../components/ui/new-network-info/new-network-info';
@@ -107,13 +83,13 @@ import { BasicConfigurationModal } from '../../components/app/basic-configuratio
import KeyringSnapRemovalResult from '../../components/app/modals/keyring-snap-removal-modal';
///: END:ONLY_INCLUDE_IF
-import { SendPage } from '../../components/multichain/pages/send';
import { DeprecatedNetworkModal } from '../settings/deprecated-network-modal/DeprecatedNetworkModal';
import { MultichainMetaFoxLogo } from '../../components/multichain/app-header/multichain-meta-fox-logo';
import NetworkConfirmationPopover from '../../components/multichain/network-list-menu/network-confirmation-popover/network-confirmation-popover';
-import NftFullImage from '../../components/app/assets/nfts/nft-details/nft-full-image';
-import CrossChainSwap from '../bridge';
import { ToastMaster } from '../../components/app/toast-master/toast-master';
+import { mmLazy } from '../../helpers/utils/mm-lazy';
+import { InternalAccountPropType } from '../../selectors/multichain';
+import { isCurrentChainCompatibleWithAccount } from '../../../shared/lib/multichain';
import {
isCorrectDeveloperTransactionType,
isCorrectSignatureApprovalType,
@@ -126,10 +102,59 @@ import {
showOnboardingHeader,
} from './utils';
+// Begin Lazy Routes
+const OnboardingFlow = mmLazy(() =>
+ import('../onboarding-flow/onboarding-flow'),
+);
+const Lock = mmLazy(() => import('../lock'));
+const UnlockPage = mmLazy(() => import('../unlock-page'));
+const RestoreVaultPage = mmLazy(() => import('../keychains/restore-vault'));
+const RevealSeedConfirmation = mmLazy(() => import('../keychains/reveal-seed'));
+const Settings = mmLazy(() => import('../settings'));
+const NotificationsSettings = mmLazy(() => import('../notifications-settings'));
+const NotificationDetails = mmLazy(() => import('../notification-details'));
+const Notifications = mmLazy(() => import('../notifications'));
+const SnapList = mmLazy(() => import('../snaps/snaps-list'));
+const SnapView = mmLazy(() => import('../snaps/snap-view'));
+const ConfirmTransaction = mmLazy(() =>
+ import('../confirmations/confirm-transaction'),
+);
+const SendPage = mmLazy(() => import('../../components/multichain/pages/send'));
+const Swaps = mmLazy(() => import('../swaps'));
+const CrossChainSwap = mmLazy(() => import('../bridge'));
+const ConfirmAddSuggestedTokenPage = mmLazy(() =>
+ import('../confirm-add-suggested-token'),
+);
+const ConfirmAddSuggestedNftPage = mmLazy(() =>
+ import('../confirm-add-suggested-nft'),
+);
+const ConfirmationPage = mmLazy(() => import('../confirmations/confirmation'));
+const CreateAccountPage = mmLazy(() =>
+ import('../create-account/create-account.component'),
+);
+const NftFullImage = mmLazy(() =>
+ import('../../components/app/assets/nfts/nft-details/nft-full-image'),
+);
+const Asset = mmLazy(() => import('../asset'));
+const PermissionsPage = mmLazy(() =>
+ import('../../components/multichain/pages/permissions-page/permissions-page'),
+);
+const Connections = mmLazy(() =>
+ import('../../components/multichain/pages/connections'),
+);
+const ReviewPermissions = mmLazy(() =>
+ import(
+ '../../components/multichain/pages/review-permissions-page/review-permissions-page'
+ ),
+);
+const Home = mmLazy(() => import('../home'));
+// End Lazy Routes
+
export default class Routes extends Component {
static propTypes = {
currentCurrency: PropTypes.string,
activeTabOrigin: PropTypes.string,
+ account: InternalAccountPropType,
setCurrentCurrencyToUSD: PropTypes.func,
isLoading: PropTypes.bool,
loadingMessage: PropTypes.string,
@@ -268,122 +293,127 @@ export default class Routes extends Component {
const RestoreVaultComponent = forgottenPassword ? Route : Initialized;
const routes = (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- ///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
- }
-
-
-
-
-
-
- {
- ///: END:ONLY_INCLUDE_IF
- }
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {/* since the loading time is less than 200ms, we decided not to show a spinner fallback or anything */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ ///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
+ }
+
+
+
+
+
+
+ {
+ ///: END:ONLY_INCLUDE_IF
+ }
+
+
+
+
+
+
+
+
+
+
+
+
);
if (autoLockTimeLimit > 0) {
@@ -410,6 +440,7 @@ export default class Routes extends Component {
isNetworkUsed,
allAccountsOnNetworkAreEmpty,
isTestNet,
+ account,
currentChainId,
shouldShowSeedPhraseReminder,
isCurrentProviderCustom,
@@ -455,7 +486,8 @@ export default class Routes extends Component {
});
const shouldShowNetworkInfo =
isUnlocked &&
- currentChainId &&
+ account &&
+ isCurrentChainCompatibleWithAccount(currentChainId, account) &&
!isTestNet &&
!isSendRoute &&
!isNetworkUsed &&
diff --git a/ui/pages/routes/routes.component.test.js b/ui/pages/routes/routes.component.test.js
index 1b1823728a2f..4318310c6ef5 100644
--- a/ui/pages/routes/routes.component.test.js
+++ b/ui/pages/routes/routes.component.test.js
@@ -1,6 +1,6 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
-import { act } from '@testing-library/react';
+import { act, waitFor } from '@testing-library/react';
import thunk from 'redux-thunk';
import { BtcAccountType } from '@metamask/keyring-api';
import { SEND_STAGES } from '../../ducks/send';
@@ -15,6 +15,10 @@ import { useIsOriginalNativeTokenSymbol } from '../../hooks/useIsOriginalNativeT
import { createMockInternalAccount } from '../../../test/jest/mocks';
import { CHAIN_IDS } from '../../../shared/constants/network';
import { mockNetworkState } from '../../../test/stub/networks';
+import {
+ MOCK_ACCOUNT_BIP122_P2WPKH,
+ MOCK_ACCOUNT_EOA,
+} from '../../../test/data/mock-accounts';
import useMultiPolling from '../../hooks/useMultiPolling';
import Routes from '.';
@@ -22,6 +26,7 @@ const middlewares = [thunk];
const mockShowNetworkDropdown = jest.fn();
const mockHideNetworkDropdown = jest.fn();
+const mockFetchWithCache = jest.fn();
jest.mock('webextension-polyfill', () => ({
runtime: {
@@ -34,6 +39,7 @@ jest.mock('webextension-polyfill', () => ({
}));
jest.mock('../../store/actions', () => ({
+ ...jest.requireActual('../../store/actions'),
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
gasFeeStartPollingByNetworkClientId: jest
.fn()
@@ -92,6 +98,11 @@ jest.mock(
'../../components/app/metamask-template-renderer/safe-component-list',
);
+jest.mock(
+ '../../../shared/lib/fetch-with-cache',
+ () => () => mockFetchWithCache,
+);
+
jest.mock('../../hooks/useMultiPolling', () => ({
__esModule: true,
default: jest.fn(),
@@ -180,6 +191,102 @@ describe('Routes Component', () => {
expect(getByTestId('account-menu-icon')).not.toBeDisabled();
});
});
+
+ describe('new network popup', () => {
+ const mockBtcAccount = MOCK_ACCOUNT_BIP122_P2WPKH;
+ const mockEvmAccount = MOCK_ACCOUNT_EOA;
+
+ const mockNewlyAddedNetwork = {
+ chainId: CHAIN_IDS.BASE,
+ name: 'Base',
+ nativeCurrency: 'ETH',
+ defaultRpcEndpointIndex: 0,
+ rpcEndpoints: [
+ {
+ type: 'custom',
+ url: 'https://base.com',
+ networkClientId: CHAIN_IDS.BASE,
+ },
+ ],
+ };
+
+ const renderPopup = async (account) => {
+ // This popup does not show up for tests, so we have to disable this:
+ process.env.IN_TEST = '';
+ const state = {
+ ...mockSendState,
+ metamask: {
+ ...mockState.metamask,
+ completedOnboarding: true,
+ selectedNetworkClientId: mockNewlyAddedNetwork.chainId,
+ internalAccounts: {
+ accounts: {
+ [account.id]: account,
+ },
+ selectedAccount: account.id,
+ },
+ usedNetworks: {
+ '0x1': true,
+ '0x5': true,
+ '0x539': true,
+ [mockNewlyAddedNetwork.chainId]: false,
+ },
+ networkConfigurationsByChainId: {
+ ...mockState.metamask.networkConfigurationsByChainId,
+ [mockNewlyAddedNetwork.chainId]: mockNewlyAddedNetwork,
+ },
+ networksMetadata: {
+ ...mockState.metamask.networksMetadata,
+ [mockNewlyAddedNetwork.chainId]: {
+ EIPS: {
+ 1559: true,
+ },
+ status: 'available',
+ },
+ },
+ tokens: [],
+ swapsState: { swapsFeatureIsLive: false },
+ announcements: {},
+ pendingApprovals: {},
+ termsOfUseLastAgreed: new Date('2999-03-25'),
+ shouldShowSeedPhraseReminder: false,
+ useExternalServices: true,
+ },
+ send: {
+ ...mockSendState.send,
+ stage: SEND_STAGES.INACTIVE,
+ currentTransactionUUID: null,
+ draftTransactions: {},
+ },
+ appState: {
+ ...mockSendState.appState,
+ showWhatsNewPopup: false,
+ onboardedInThisUISession: false,
+ },
+ };
+ return await render(['/'], state);
+ };
+
+ it('displays new EVM network popup for EVM accounts', async () => {
+ const { getAllByText, getByTestId } = await renderPopup(mockEvmAccount);
+
+ const networkInfo = getByTestId('new-network-info__bullet-paragraph');
+
+ await waitFor(() => {
+ expect(getAllByText(mockNewlyAddedNetwork.name).length).toBeGreaterThan(
+ 0,
+ );
+ expect(networkInfo).toBeInTheDocument();
+ });
+ });
+
+ it('does not display new EVM network popup for non-EVM accounts', async () => {
+ const { queryByTestId } = await renderPopup(mockBtcAccount);
+
+ const networkInfo = queryByTestId('new-network-info__bullet-paragraph');
+ expect(networkInfo).not.toBeInTheDocument();
+ });
+ });
});
describe('toast display', () => {
diff --git a/ui/pages/routes/routes.container.js b/ui/pages/routes/routes.container.js
index bb02e0ebaaa9..c155be4ba488 100644
--- a/ui/pages/routes/routes.container.js
+++ b/ui/pages/routes/routes.container.js
@@ -15,12 +15,12 @@ import {
getUnapprovedConfirmations,
///: END:ONLY_INCLUDE_IF
getShowExtensionInFullSizeView,
- getSelectedAccount,
getSwitchedNetworkDetails,
getNetworkToAutomaticallySwitchTo,
getNumberOfAllUnapprovedTransactionsAndMessages,
getUseRequestQueue,
getCurrentNetwork,
+ getSelectedInternalAccount,
oldestPendingConfirmationSelector,
getUnapprovedTransactions,
getPendingApprovals,
@@ -64,7 +64,7 @@ function mapStateToProps(state) {
// If there is more than one connected account to activeTabOrigin,
// *BUT* the current account is not one of them, show the banner
- const account = getSelectedAccount(state);
+ const account = getSelectedInternalAccount(state);
const activeTabOrigin = activeTab?.origin;
const currentNetwork = getCurrentNetwork(state);
diff --git a/ui/pages/settings/contact-list-tab/add-contact/add-contact.test.js b/ui/pages/settings/contact-list-tab/add-contact/add-contact.test.js
index e162f84ba40f..1983a0143174 100644
--- a/ui/pages/settings/contact-list-tab/add-contact/add-contact.test.js
+++ b/ui/pages/settings/contact-list-tab/add-contact/add-contact.test.js
@@ -178,12 +178,15 @@ describe('AddContact component', () => {
expect(saveButton).toBeDisabled();
});
- it('should display error message when name entered is an existing account name', () => {
+ it('should display error message when name entered is an existing account name', async () => {
const duplicateName = 'Account 1';
const store = configureMockStore(middleware)(state);
- const { getByText } = renderWithProvider(, store);
+ const { getByText, findByText } = renderWithProvider(
+ ,
+ store,
+ );
const nameInput = document.getElementById('nickname');
@@ -191,7 +194,7 @@ describe('AddContact component', () => {
const saveButton = getByText('Save');
- expect(getByText('Name is already in use')).toBeDefined();
+ expect(await findByText('Name is already in use')).toBeDefined();
expect(saveButton).toBeDisabled();
});
@@ -212,10 +215,10 @@ describe('AddContact component', () => {
expect(saveButton).toBeDisabled();
});
- it('should display error when ENS inserts a name that is already in use', () => {
+ it('should display error when ENS inserts a name that is already in use', async () => {
const store = configureMockStore(middleware)(state);
- const { getByTestId, getByText } = renderWithProvider(
+ const { getByTestId, getByText, findByText } = renderWithProvider(
,
store,
);
@@ -231,7 +234,7 @@ describe('AddContact component', () => {
const saveButton = getByText('Save');
- expect(getByText('Name is already in use')).toBeDefined();
+ expect(await findByText('Name is already in use')).toBeDefined();
expect(saveButton).toBeDisabled();
});
});
diff --git a/ui/pages/settings/experimental-tab/experimental-tab.component.tsx b/ui/pages/settings/experimental-tab/experimental-tab.component.tsx
index d81eb04966a9..5bea76d80dbe 100644
--- a/ui/pages/settings/experimental-tab/experimental-tab.component.tsx
+++ b/ui/pages/settings/experimental-tab/experimental-tab.component.tsx
@@ -178,6 +178,7 @@ export default class ExperimentalTab extends PureComponent
description: t('redesignedTransactionsToggleDescription'),
toggleValue: redesignedTransactionsEnabled,
toggleCallback: (value) => setRedesignedTransactionsEnabled(!value),
+ toggleContainerDataTestId: 'toggle-redesigned-transactions-container',
toggleDataTestId: 'toggle-redesigned-transactions',
toggleOffLabel: t('off'),
toggleOnLabel: t('on'),
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 0927d04f89cb..23d2b80519a3 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
@@ -126,7 +126,7 @@ exports[`Security Tab should match snapshot 1`] = `
>
- This feature alerts you to malicious activity by actively reviewing transaction and signature requests.
+ This feature alerts you to malicious or unusual activity by actively reviewing transaction and signature requests.
- Turn this on to estimate balance changes of transactions before you confirm them. This doesn't guarantee the final outcome of your transactions.
+ Turn this on to estimate balance changes of transactions and signatures before you confirm them. This doesn't guarantee their final outcome.
+ )[selectedAccount.type];
+ return providerConfig.chainId === mainnet ?? false;
}
export function getMultichainIsTestnet(
@@ -370,12 +375,17 @@ export const getMultichainCoinRates = (state: MultichainState) => {
return state.metamask.rates;
};
-function getBtcCachedBalance(state: MultichainState) {
+function getNonEvmCachedBalance(state: MultichainState) {
const balances = getMultichainBalances(state);
const account = getSelectedInternalAccount(state);
- const asset = getMultichainIsMainnet(state)
- ? MultichainNativeAssets.BITCOIN
- : MultichainNativeAssets.BITCOIN_TESTNET;
+ const network = getMultichainCurrentNetwork(state);
+
+ // We assume that there's at least one asset type in and that is the native
+ // token for that network.
+ const asset =
+ MULTICHAIN_NETWORK_TO_ASSET_TYPES[
+ network.chainId as MultichainNetworks
+ ]?.[0];
return balances?.[account.id]?.[asset]?.amount;
}
@@ -394,7 +404,7 @@ export function getMultichainSelectedAccountCachedBalance(
) {
return getMultichainIsEvm(state)
? getSelectedAccountCachedBalance(state)
- : getBtcCachedBalance(state);
+ : getNonEvmCachedBalance(state);
}
export const getMultichainSelectedAccountCachedBalanceIsZero = createSelector(
diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js
index b6df07bd2bd0..154864dc9af4 100644
--- a/ui/selectors/selectors.js
+++ b/ui/selectors/selectors.js
@@ -1495,14 +1495,17 @@ export function getWeb3ShimUsageStateForOrigin(state, origin) {
* objects, per the above description.
*
* @param {object} state - the redux state object
+ * @param {string} overrideChainId - the chainId to override the current chainId
* @returns {SwapsEthToken} The token object representation of the currently
* selected account's ETH balance, as expected by the Swaps API.
*/
-export function getSwapsDefaultToken(state) {
+export function getSwapsDefaultToken(state, overrideChainId = null) {
const selectedAccount = getSelectedAccount(state);
const balance = selectedAccount?.balance;
- const chainId = getCurrentChainId(state);
+ const currentChainId = getCurrentChainId(state);
+
+ const chainId = overrideChainId ?? currentChainId;
const defaultTokenObject = SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId];
return {
@@ -1516,8 +1519,9 @@ export function getSwapsDefaultToken(state) {
};
}
-export function getIsSwapsChain(state) {
- const chainId = getCurrentChainId(state);
+export function getIsSwapsChain(state, overrideChainId) {
+ const currentChainId = getCurrentChainId(state);
+ const chainId = overrideChainId ?? currentChainId;
const isNotDevelopment =
process.env.METAMASK_ENVIRONMENT !== 'development' &&
process.env.METAMASK_ENVIRONMENT !== 'testing';
@@ -1526,8 +1530,9 @@ export function getIsSwapsChain(state) {
: ALLOWED_DEV_SWAPS_CHAIN_IDS.includes(chainId);
}
-export function getIsBridgeChain(state) {
- const chainId = getCurrentChainId(state);
+export function getIsBridgeChain(state, overrideChainId) {
+ const currentChainId = getCurrentChainId(state);
+ const chainId = overrideChainId ?? currentChainId;
return ALLOWED_BRIDGE_CHAIN_IDS.includes(chainId);
}
@@ -2364,6 +2369,42 @@ export const getAllEnabledNetworks = createDeepEqualSelector(
),
);
+/*
+ * USE THIS WITH CAUTION
+ *
+ * Only use this selector if you are absolutely sure that your UI component needs
+ * data from _all chains_ to compute a value. Else, use `getChainIdsToPoll`.
+ *
+ * Examples:
+ * - Components that should NOT use this selector:
+ * - Token list: This only needs to poll for chains based on the network filter
+ * (potentially only one chain). In this case, use `getChainIdsToPoll`.
+ * - Components that SHOULD use this selector:
+ * - Aggregated balance: This needs to display data regardless of network filter
+ * selection (always showing aggregated balances across all chains).
+ *
+ * Key Considerations:
+ * - This selector can cause expensive computations. It should only be used when
+ * necessary, and where possible, optimized to use `getChainIdsToPoll` instead.
+ * - Logic Overview:
+ * - If `PORTFOLIO_VIEW` is not enabled, the selector returns only the `currentChainId`.
+ * - Otherwise, it includes all chains from `networkConfigurations`, excluding
+ * `TEST_CHAINS`, while ensuring the `currentChainId` is included.
+ */
+export const getAllChainsToPoll = createDeepEqualSelector(
+ getNetworkConfigurationsByChainId,
+ getCurrentChainId,
+ (networkConfigurations, currentChainId) => {
+ if (!process.env.PORTFOLIO_VIEW) {
+ return [currentChainId];
+ }
+
+ return Object.keys(networkConfigurations).filter(
+ (chainId) => chainId === currentChainId || !TEST_CHAINS.includes(chainId),
+ );
+ },
+);
+
export const getChainIdsToPoll = createDeepEqualSelector(
getNetworkConfigurationsByChainId,
getCurrentChainId,
diff --git a/ui/selectors/selectors.test.js b/ui/selectors/selectors.test.js
index b2c3cd894e44..c749d8ff3fe7 100644
--- a/ui/selectors/selectors.test.js
+++ b/ui/selectors/selectors.test.js
@@ -1978,4 +1978,203 @@ describe('#getConnectedSitesList', () => {
expect(selectors.getSelectedEvmInternalAccount(state)).toBe(undefined);
});
});
+
+ describe('getSwapsDefaultToken', () => {
+ it('returns the token object for the current chainId when no overrideChainId is provided', () => {
+ const expectedToken = {
+ symbol: 'ETH',
+ name: 'Ether',
+ address: '0x0000000000000000000000000000000000000000',
+ decimals: 18,
+ balance: '966987986469506564059',
+ string: '966.988',
+ iconUrl: './images/black-eth-logo.svg',
+ };
+
+ const result = selectors.getSwapsDefaultToken(mockState);
+
+ expect(result).toStrictEqual(expectedToken);
+ });
+
+ it('returns the token object for the overridden chainId when overrideChainId is provided', () => {
+ const getCurrentChainIdSpy = jest.spyOn(selectors, 'getCurrentChainId');
+ const expectedToken = {
+ symbol: 'POL',
+ name: 'Polygon',
+ address: '0x0000000000000000000000000000000000000000',
+ decimals: 18,
+ balance: '966987986469506564059',
+ string: '966.988',
+ iconUrl: './images/pol-token.svg',
+ };
+
+ const result = selectors.getSwapsDefaultToken(
+ mockState,
+ CHAIN_IDS.POLYGON,
+ );
+
+ expect(result).toStrictEqual(expectedToken);
+ expect(getCurrentChainIdSpy).not.toHaveBeenCalled(); // Ensure overrideChainId is used
+ });
+ });
+
+ describe('getIsSwapsChain', () => {
+ it('returns true for an allowed chainId in production environment', () => {
+ process.env.METAMASK_ENVIRONMENT = 'production';
+
+ const state = {
+ ...mockState,
+ metamask: {
+ ...mockState.metamask,
+ selectedNetworkClientId: 'testNetworkConfigurationId', // corresponds to mainnet RPC in mockState
+ },
+ };
+
+ const result = selectors.getIsSwapsChain(state);
+
+ expect(result).toBe(true);
+ });
+
+ it('returns true for an allowed chainId in development environment', () => {
+ process.env.METAMASK_ENVIRONMENT = 'development';
+
+ const state = {
+ ...mockState,
+ metamask: {
+ ...mockState.metamask,
+ selectedNetworkClientId: 'goerli',
+ },
+ };
+
+ const result = selectors.getIsSwapsChain(state);
+
+ expect(result).toBe(true);
+ });
+
+ it('returns false for a disallowed chainId in production environment', () => {
+ process.env.METAMASK_ENVIRONMENT = 'production';
+
+ const state = {
+ ...mockState,
+ metamask: {
+ ...mockState.metamask,
+ selectedNetworkClientId: 'fooChain', // corresponds to mainnet RPC in mockState
+ networkConfigurationsByChainId: {
+ '0x8080': {
+ chainId: '0x8080',
+ name: 'Custom Mainnet RPC',
+ nativeCurrency: 'ETH',
+ defaultRpcEndpointIndex: 0,
+ rpcEndpoints: [
+ {
+ type: 'custom',
+ url: 'https://testrpc.com',
+ networkClientId: 'fooChain',
+ },
+ ],
+ },
+ },
+ },
+ };
+
+ const result = selectors.getIsSwapsChain(state);
+
+ expect(result).toBe(false);
+ });
+
+ it('returns false for a disallowed chainId in development environment', () => {
+ process.env.METAMASK_ENVIRONMENT = 'development';
+
+ const state = {
+ ...mockState,
+ metamask: {
+ ...mockState.metamask,
+ selectedNetworkClientId: 'fooChain', // corresponds to mainnet RPC in mockState
+ networkConfigurationsByChainId: {
+ '0x8080': {
+ chainId: '0x8080',
+ name: 'Custom Mainnet RPC',
+ nativeCurrency: 'ETH',
+ defaultRpcEndpointIndex: 0,
+ rpcEndpoints: [
+ {
+ type: 'custom',
+ url: 'https://testrpc.com',
+ networkClientId: 'fooChain',
+ },
+ ],
+ },
+ },
+ },
+ };
+
+ const result = selectors.getIsSwapsChain(state);
+
+ expect(result).toBe(false);
+ });
+
+ it('respects the overrideChainId parameter', () => {
+ process.env.METAMASK_ENVIRONMENT = 'production';
+
+ const getCurrentChainIdSpy = jest.spyOn(selectors, 'getCurrentChainId');
+
+ const result = selectors.getIsSwapsChain(mockState, '0x89');
+ expect(result).toBe(true);
+ expect(getCurrentChainIdSpy).not.toHaveBeenCalled(); // Ensure overrideChainId is used
+ });
+ });
+
+ describe('getIsBridgeChain', () => {
+ it('returns true for an allowed bridge chainId', () => {
+ const state = {
+ ...mockState,
+ metamask: {
+ ...mockState.metamask,
+ selectedNetworkClientId: 'testNetworkConfigurationId', // corresponds to mainnet RPC in mockState
+ },
+ };
+
+ const result = selectors.getIsBridgeChain(state);
+
+ expect(result).toBe(true);
+ });
+
+ it('returns false for a disallowed bridge chainId', () => {
+ const state = {
+ ...mockState,
+ metamask: {
+ ...mockState.metamask,
+ selectedNetworkClientId: 'fooChain', // corresponds to mainnet RPC in mockState
+ networkConfigurationsByChainId: {
+ '0x8080': {
+ chainId: '0x8080',
+ name: 'Custom Mainnet RPC',
+ nativeCurrency: 'ETH',
+ defaultRpcEndpointIndex: 0,
+ rpcEndpoints: [
+ {
+ type: 'custom',
+ url: 'https://testrpc.com',
+ networkClientId: 'fooChain',
+ },
+ ],
+ },
+ },
+ },
+ };
+
+ const result = selectors.getIsBridgeChain(state);
+
+ expect(result).toBe(false);
+ });
+
+ it('respects the overrideChainId parameter', () => {
+ const getCurrentChainIdSpy = jest.spyOn(selectors, 'getCurrentChainId');
+
+ const result = selectors.getIsBridgeChain(mockState, '0x89');
+
+ expect(result).toBe(true);
+ expect(getCurrentChainIdSpy).not.toHaveBeenCalled(); // Ensure overrideChainId is used
+ });
+ });
});
diff --git a/yarn.lock b/yarn.lock
index bc208e1a8f92..968594154f06 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4934,9 +4934,9 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/assets-controllers@npm:45.0.0":
- version: 45.0.0
- resolution: "@metamask/assets-controllers@npm:45.0.0"
+"@metamask/assets-controllers@npm:45.1.0":
+ version: 45.1.0
+ resolution: "@metamask/assets-controllers@npm:45.1.0"
dependencies:
"@ethereumjs/util": "npm:^8.1.0"
"@ethersproject/abi": "npm:^5.7.0"
@@ -4969,13 +4969,13 @@ __metadata:
"@metamask/keyring-controller": ^19.0.0
"@metamask/network-controller": ^22.0.0
"@metamask/preferences-controller": ^15.0.0
- checksum: 10/0ad51464cf060f1c2cab56c2c8d9daa5f29987e8ead69c0e029fb8357fa5c629434116de5663dc38a57c11b3736b6c7d9b1db9b6892a453fbc3f9c6965d42295
+ checksum: 10/7e366739c2b3fc8000aaa8cd302d3e2c3958e29e7c88f3e7e188c4ec46454cf9e894c1e230a84092bba8e6c5274b301dfdb4e55a0ba4322bdcb9e7325ad5a5e5
languageName: node
linkType: hard
-"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch":
- version: 45.0.0
- resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch::version=45.0.0&hash=8e5354"
+"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch":
+ version: 45.1.0
+ resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch::version=45.1.0&hash=cfcadc"
dependencies:
"@ethereumjs/util": "npm:^8.1.0"
"@ethersproject/abi": "npm:^5.7.0"
@@ -5008,7 +5008,7 @@ __metadata:
"@metamask/keyring-controller": ^19.0.0
"@metamask/network-controller": ^22.0.0
"@metamask/preferences-controller": ^15.0.0
- checksum: 10/823627b5bd23829d81a54291f74c4ddf52d0732a840c121c4ae7f1fc468dd98f3fc1e64b7f8a9bbaaa76cd6670082f2976e5e6ecf872e04c212a5c8ec5fe4916
+ checksum: 10/d2f7d5bb07feceb5b972beda019f411cd073ece3ed682b21373fc6d4c06812ec10245b40c78ce6316c5fb1718278fd269b73e13d37c2ff07b5bb3ecdfd8278f7
languageName: node
linkType: hard
@@ -6457,9 +6457,9 @@ __metadata:
linkType: hard
"@metamask/slip44@npm:^4.0.0":
- version: 4.0.0
- resolution: "@metamask/slip44@npm:4.0.0"
- checksum: 10/3e47e8834b0fbdabe1f126fd78665767847ddc1f9ccc8defb23007dd71fcd2e4899c8ca04857491be3630668a3765bad1e40fdfca9a61ef33236d8d08e51535e
+ version: 4.1.0
+ resolution: "@metamask/slip44@npm:4.1.0"
+ checksum: 10/4265254a1800a24915bd1de15f86f196737132f9af2a084c2efc885decfc5dd87ad8f0687269d90b35e2ec64d3ea4fbff0caa793bcea6e585b1f3a290952b750
languageName: node
linkType: hard
@@ -6486,9 +6486,9 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/snaps-controllers@npm:^9.10.0, @metamask/snaps-controllers@npm:^9.13.0":
- version: 9.13.0
- resolution: "@metamask/snaps-controllers@npm:9.13.0"
+"@metamask/snaps-controllers@npm:^9.10.0, @metamask/snaps-controllers@npm:^9.14.0":
+ version: 9.14.0
+ resolution: "@metamask/snaps-controllers@npm:9.14.0"
dependencies:
"@metamask/approval-controller": "npm:^7.1.1"
"@metamask/base-controller": "npm:^7.0.2"
@@ -6500,8 +6500,8 @@ __metadata:
"@metamask/post-message-stream": "npm:^8.1.1"
"@metamask/rpc-errors": "npm:^7.0.1"
"@metamask/snaps-registry": "npm:^3.2.2"
- "@metamask/snaps-rpc-methods": "npm:^11.5.1"
- "@metamask/snaps-sdk": "npm:^6.11.0"
+ "@metamask/snaps-rpc-methods": "npm:^11.6.0"
+ "@metamask/snaps-sdk": "npm:^6.12.0"
"@metamask/snaps-utils": "npm:^8.6.0"
"@metamask/utils": "npm:^10.0.0"
"@xstate/fsm": "npm:^2.0.0"
@@ -6520,7 +6520,7 @@ __metadata:
peerDependenciesMeta:
"@metamask/snaps-execution-environments":
optional: true
- checksum: 10/bcf60b61de067f89439cb15acbdf6f808b4bcda8e1cbc9debd693ca2c545c9d38c4e6f380191c4703bd9d28d7dd41e4ce5111664d7b474d5e86e460bcefc3637
+ checksum: 10/cce5a4d7af65d70a2a2902f9a89b15145590edccf8171b3994e2ddde74c6700abf49d7b800275d7ab3b216ef3dfca3be82c4145b5986d20ec1df8b4c50b95314
languageName: node
linkType: hard
@@ -6555,32 +6555,32 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/snaps-rpc-methods@npm:^11.5.1":
- version: 11.5.1
- resolution: "@metamask/snaps-rpc-methods@npm:11.5.1"
+"@metamask/snaps-rpc-methods@npm:^11.6.0":
+ version: 11.6.0
+ resolution: "@metamask/snaps-rpc-methods@npm:11.6.0"
dependencies:
"@metamask/key-tree": "npm:^9.1.2"
"@metamask/permission-controller": "npm:^11.0.3"
"@metamask/rpc-errors": "npm:^7.0.1"
- "@metamask/snaps-sdk": "npm:^6.10.0"
- "@metamask/snaps-utils": "npm:^8.5.0"
+ "@metamask/snaps-sdk": "npm:^6.12.0"
+ "@metamask/snaps-utils": "npm:^8.6.0"
"@metamask/superstruct": "npm:^3.1.0"
"@metamask/utils": "npm:^10.0.0"
"@noble/hashes": "npm:^1.3.1"
- checksum: 10/0f999a5dd64f1b1123366f448ae833f0e95a415791600bb535959ba67d2269fbe3c4504d47f04db71bafa79a9a87d6b832fb2e2b5ef29567078c95bce2638f35
+ checksum: 10/6788717e1ccab8eb40876fce15a4b66b44f267b33fe953efa56c0fee94f651391704556eaf9f3f9e6787e0bbbe6b0fb470a9da45d68d78b587ad96b6a3f246ac
languageName: node
linkType: hard
-"@metamask/snaps-sdk@npm:^6.11.0":
- version: 6.11.0
- resolution: "@metamask/snaps-sdk@npm:6.11.0"
+"@metamask/snaps-sdk@npm:^6.12.0":
+ version: 6.12.0
+ resolution: "@metamask/snaps-sdk@npm:6.12.0"
dependencies:
"@metamask/key-tree": "npm:^9.1.2"
"@metamask/providers": "npm:^18.1.1"
"@metamask/rpc-errors": "npm:^7.0.1"
"@metamask/superstruct": "npm:^3.1.0"
"@metamask/utils": "npm:^10.0.0"
- checksum: 10/0f9b507139d1544b1b3d85ff8de81b800d543012d3ee9414c607c23abe9562e0dca48de089ed94be69f5ad981730a0f443371edfe6bc2d5ffb140b28e437bfd2
+ checksum: 10/b0e24fee2c90ac2f456aeb5babc180c74f56b8cf94f38abdd9b022c65a9cac4a10015e3d4053784292c184238f97704cb2b6a41f6e7a04f23ffaa8d519d2a39e
languageName: node
linkType: hard
@@ -6615,7 +6615,7 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/snaps-utils@npm:^8.3.0, @metamask/snaps-utils@npm:^8.5.0, @metamask/snaps-utils@npm:^8.6.0":
+"@metamask/snaps-utils@npm:^8.3.0, @metamask/snaps-utils@npm:^8.6.0":
version: 8.6.0
resolution: "@metamask/snaps-utils@npm:8.6.0"
dependencies:
@@ -6738,9 +6738,9 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/transaction-controller@npm:^40.0.0":
- version: 40.0.0
- resolution: "@metamask/transaction-controller@npm:40.0.0"
+"@metamask/transaction-controller@npm:^40.1.0":
+ version: 40.1.0
+ resolution: "@metamask/transaction-controller@npm:40.1.0"
dependencies:
"@ethereumjs/common": "npm:^3.2.0"
"@ethereumjs/tx": "npm:^4.2.0"
@@ -6767,7 +6767,7 @@ __metadata:
"@metamask/approval-controller": ^7.0.0
"@metamask/gas-fee-controller": ^22.0.0
"@metamask/network-controller": ^22.0.0
- checksum: 10/1325f5d264e4351dfeee664bba601d873b2204eb82ccb840ab7934fa27f48e31c5f47a60f0a4b4baa94b41ac801121c498bb28102a65cbe59a0456630d4e0138
+ checksum: 10/1057af5b0da2d51e46e7568fc0e7fdbe6aed34a013cf56a5a35ad694cbedcb726a5823bbe70b980d1dc9560138acf9d82ac5f0e06f7d17e11b46abacd466dc42
languageName: node
linkType: hard
@@ -26816,7 +26816,7 @@ __metadata:
"@metamask/announcement-controller": "npm:^7.0.0"
"@metamask/api-specs": "npm:^0.9.3"
"@metamask/approval-controller": "npm:^7.0.0"
- "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A45.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch"
+ "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch"
"@metamask/auto-changelog": "npm:^2.1.0"
"@metamask/base-controller": "npm:^7.0.0"
"@metamask/bitcoin-wallet-snap": "npm:^0.8.2"
@@ -26883,15 +26883,15 @@ __metadata:
"@metamask/selected-network-controller": "npm:^18.0.2"
"@metamask/signature-controller": "npm:^23.0.0"
"@metamask/smart-transactions-controller": "npm:^13.0.0"
- "@metamask/snaps-controllers": "npm:^9.13.0"
+ "@metamask/snaps-controllers": "npm:^9.14.0"
"@metamask/snaps-execution-environments": "npm:^6.10.0"
- "@metamask/snaps-rpc-methods": "npm:^11.5.1"
- "@metamask/snaps-sdk": "npm:^6.11.0"
+ "@metamask/snaps-rpc-methods": "npm:^11.6.0"
+ "@metamask/snaps-sdk": "npm:^6.12.0"
"@metamask/snaps-utils": "npm:^8.6.0"
"@metamask/solana-wallet-snap": "npm:^0.1.9"
"@metamask/test-bundler": "npm:^1.0.0"
"@metamask/test-dapp": "npm:8.13.0"
- "@metamask/transaction-controller": "npm:^40.0.0"
+ "@metamask/transaction-controller": "npm:^40.1.0"
"@metamask/user-operation-controller": "npm:^13.0.0"
"@metamask/utils": "npm:^10.0.1"
"@ngraveio/bc-ur": "npm:^1.1.12"