diff --git a/.circleci/config.yml b/.circleci/config.yml
index be6f2b4e772f..531912bff9ff 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -105,6 +105,9 @@ workflows:
- prep-deps
- check-pr-tag
- prep-deps
+ - get-changed-files-with-git-diff:
+ requires:
+ - prep-deps
- test-deps-audit:
requires:
- prep-deps
@@ -187,41 +190,51 @@ workflows:
- test-e2e-chrome:
requires:
- prep-build-test
+ - get-changed-files-with-git-diff
- test-e2e-chrome-confirmation-redesign:
requires:
- prep-build-confirmation-redesign-test
+ - get-changed-files-with-git-diff
- test-e2e-firefox:
requires:
- prep-build-test-mv2
+ - get-changed-files-with-git-diff
- test-e2e-firefox-confirmation-redesign:
<<: *develop_master_rc_only
requires:
- prep-build-confirmation-redesign-test-mv2
+ - get-changed-files-with-git-diff
- test-e2e-chrome-rpc:
requires:
- prep-build-test
+ - get-changed-files-with-git-diff
- test-api-specs:
requires:
- prep-build-test
- test-e2e-chrome-multiple-providers:
requires:
- prep-build-test
+ - get-changed-files-with-git-diff
- test-e2e-chrome-flask:
requires:
- prep-build-test-flask
+ - get-changed-files-with-git-diff
- test-e2e-firefox-flask:
<<: *develop_master_rc_only
requires:
- prep-build-test-flask-mv2
+ - get-changed-files-with-git-diff
- test-e2e-chrome-mmi:
requires:
- prep-build-test-mmi
+ - get-changed-files-with-git-diff
- test-e2e-mmi-playwright - OPTIONAL:
requires:
- prep-build-test-mmi-playwright
- test-e2e-chrome-rpc-mmi:
requires:
- prep-build-test-mmi
+ - get-changed-files-with-git-diff
- test-e2e-chrome-vault-decryption:
filters:
branches:
@@ -230,6 +243,7 @@ workflows:
- /^Version-v(\d+)[.](\d+)[.](\d+)/
requires:
- prep-build
+ - get-changed-files-with-git-diff
- test-unit-global:
requires:
- prep-deps
@@ -459,6 +473,23 @@ jobs:
- node_modules
- build-artifacts
+ # This job is used for the e2e quality gate.
+ # It must be run before any job which uses the run-all.js script.
+ get-changed-files-with-git-diff:
+ executor: node-browsers-small
+ steps:
+ - run: *shallow-git-clone
+ - run: sudo corepack enable
+ - attach_workspace:
+ at: .
+ - run:
+ name: Get changed files with git diff
+ command: npx tsx .circleci/scripts/git-diff-develop.ts
+ - persist_to_workspace:
+ root: .
+ paths:
+ - changed-files
+
validate-lavamoat-allow-scripts:
executor: node-browsers-small
steps:
@@ -1121,7 +1152,7 @@ jobs:
fi
no_output_timeout: 5m
environment:
- ENABLE_CONFIRMATION_REDESIGN: "true"
+ ENABLE_CONFIRMATION_REDESIGN: 'true'
- store_artifacts:
path: test-artifacts
destination: test-artifacts
@@ -1412,7 +1443,7 @@ jobs:
fi
no_output_timeout: 5m
environment:
- ENABLE_CONFIRMATION_REDESIGN: "true"
+ ENABLE_CONFIRMATION_REDESIGN: 'true'
- store_artifacts:
path: test-artifacts
destination: test-artifacts
diff --git a/.circleci/scripts/git-diff-develop.ts b/.circleci/scripts/git-diff-develop.ts
new file mode 100644
index 000000000000..8b5680b17d3f
--- /dev/null
+++ b/.circleci/scripts/git-diff-develop.ts
@@ -0,0 +1,99 @@
+import { hasProperty } from '@metamask/utils';
+import { exec as execCallback } from 'child_process';
+import fs from 'fs';
+import path from 'path';
+import { promisify } from 'util';
+
+const exec = promisify(execCallback);
+
+/**
+ * Fetches the git repository with a specified depth.
+ *
+ * @param depth - The depth to use for the fetch command.
+ * @returns True if the fetch is successful, otherwise false.
+ */
+async function fetchWithDepth(depth: number): Promise {
+ try {
+ await exec(`git fetch --depth ${depth} origin develop`);
+ await exec(`git fetch --depth ${depth} origin ${process.env.CIRCLE_BRANCH}`);
+ return true;
+ } catch (error: unknown) {
+ console.error(`Failed to fetch with depth ${depth}:`, error);
+ return false;
+ }
+}
+
+/**
+ * Attempts to fetch the necessary commits until the merge base is found.
+ * It tries different fetch depths and performs a full fetch if needed.
+ *
+ * @throws If an unexpected error occurs during the execution of git commands.
+ */
+async function fetchUntilMergeBaseFound() {
+ const depths = [1, 10, 100];
+ for (const depth of depths) {
+ console.log(`Attempting git diff with depth ${depth}...`);
+ await fetchWithDepth(depth);
+
+ try {
+ await exec(`git merge-base origin/HEAD HEAD`);
+ return;
+ } catch (error: unknown) {
+ if (
+ error instanceof Error &&
+ hasProperty(error, 'code') &&
+ error.code === 1
+ ) {
+ console.error(`Error 'no merge base' encountered with depth ${depth}. Incrementing depth...`);
+ } else {
+ throw error;
+ }
+ }
+ }
+ await exec(`git fetch --unshallow origin develop`);
+}
+
+/**
+ * Performs a git diff command to get the list of files changed between the current branch and the origin.
+ * It first ensures that the necessary commits are fetched until the merge base is found.
+ *
+ * @returns The output of the git diff command, listing the changed files.
+ * @throws If unable to get the diff after fetching the merge base or if an unexpected error occurs.
+ */
+async function gitDiff(): Promise {
+ await fetchUntilMergeBaseFound();
+ const { stdout: diffResult } = await exec(`git diff --name-only origin/HEAD...${process.env.CIRCLE_BRANCH}`);
+ if (!diffResult) {
+ throw new Error('Unable to get diff after full checkout.');
+ }
+ return diffResult;
+}
+
+/**
+ * Stores the output of git diff to a file.
+ *
+ * @returns Returns a promise that resolves when the git diff output is successfully stored.
+ */
+async function storeGitDiffOutput() {
+ try {
+ console.log("Attempting to get git diff...");
+ const diffOutput = await gitDiff();
+ console.log(diffOutput);
+
+ // Create the directory
+ const outputDir = 'changed-files';
+ fs.mkdirSync(outputDir, { recursive: true });
+
+ // Store the output of git diff
+ const outputPath = path.resolve(outputDir, 'changed-files.txt');
+ fs.writeFileSync(outputPath, diffOutput);
+
+ console.log(`Git diff results saved to ${outputPath}`);
+ process.exit(0);
+ } catch (error: any) {
+ console.error('An error occurred:', error.message);
+ process.exit(1);
+ }
+}
+
+storeGitDiffOutput();
diff --git a/.eslintrc.js b/.eslintrc.js
index 9f7fed5928ed..0aea1e739e3f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -281,6 +281,7 @@ module.exports = {
'app/scripts/controllers/mmi-controller.test.ts',
'app/scripts/metamask-controller.actions.test.js',
'app/scripts/detect-multiple-instances.test.js',
+ 'app/scripts/controllers/bridge.test.ts',
'app/scripts/controllers/swaps.test.js',
'app/scripts/controllers/metametrics.test.js',
'app/scripts/controllers/permissions/**/*.test.js',
diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml
index 61d549728d99..f5e1a0552dd1 100644
--- a/.github/workflows/sonar.yml
+++ b/.github/workflows/sonar.yml
@@ -1,18 +1,11 @@
name: Sonar
on:
- workflow_call:
- secrets:
- SONAR_TOKEN:
- required: true
-# pull_request:
-# branches:
-# - develop
-# types:
-# - opened
-# - reopened
-# - synchronize
-# - labeled
-# - unlabeled
+ push:
+ branches:
+ - develop
+ pull_request:
+ branches:
+ - develop
jobs:
sonarcloud:
@@ -25,8 +18,5 @@ jobs:
- name: SonarCloud Scan
# This is SonarSource/sonarcloud-github-action@v2.0.0
uses: SonarSource/sonarcloud-github-action@4b4d7634dab97dcee0b75763a54a6dc92a9e6bc1
- with:
- args: >
- -Dsonar.javascript.lcov.reportPaths=tests/coverage/lcov.info
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
diff --git a/.metamaskrc.dist b/.metamaskrc.dist
index c7431cc0719b..429addc860be 100644
--- a/.metamaskrc.dist
+++ b/.metamaskrc.dist
@@ -6,6 +6,7 @@ INFURA_PROJECT_ID=00000000000
;PASSWORD=METAMASK PASSWORD
;SEGMENT_WRITE_KEY=
+;BRIDGE_USE_DEV_APIS=
;SWAPS_USE_DEV_APIS=
;PORTFOLIO_URL=
;TRANSACTION_SECURITY_PROVIDER=
diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json
index 2500ff05f254..0160c032ed88 100644
--- a/app/_locales/de/messages.json
+++ b/app/_locales/de/messages.json
@@ -838,7 +838,7 @@
"confirm": {
"message": "Bestätigen"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "Ich habe die Benachrichtigungen zur Kenntnis genommen und möchte trotzdem fortfahren"
},
"confirmAlertModalDetails": {
@@ -2122,9 +2122,6 @@
"initialTransactionConfirmed": {
"message": "Ihre erste Transaktion wurde vom Netzwerk bestätigt. Klicken Sie auf „OK“, um zurückzukehren."
},
- "inlineAlert": {
- "message": "Warnung"
- },
"inputLogicEmptyState": {
"message": "Geben Sie nur eine Nummer ein, die Sie den Drittanbieter jetzt oder in Zukunft ausgeben lassen möchten. Sie können die Ausgabenobergrenze später jederzeit ändern."
},
diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json
index bff686407193..a53b48a7be40 100644
--- a/app/_locales/el/messages.json
+++ b/app/_locales/el/messages.json
@@ -838,7 +838,7 @@
"confirm": {
"message": "Επιβεβαίωση"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "Έχω ενημερωθεί για τις ειδοποιήσεις και εξακολουθώ να θέλω να συνεχίσω"
},
"confirmAlertModalDetails": {
@@ -2122,9 +2122,6 @@
"initialTransactionConfirmed": {
"message": "Η αρχική σας συναλλαγή επιβεβαιώθηκε από το δίκτυο. Κάντε κλικ στο OK για να επιστρέψετε."
},
- "inlineAlert": {
- "message": "Ειδοποίηση"
- },
"inputLogicEmptyState": {
"message": "Πληκτρολογήστε μόνο έναν αριθμό που σας βολεύει να ξοδέψει ο τρίτος τώρα ή στο μέλλον. Μπορείτε πάντα να αυξήσετε το ανώτατο όριο δαπανών αργότερα."
},
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 53961ff86b35..25f3c5436158 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -311,6 +311,9 @@
"message": "Can’t find a token? You can manually add any token by pasting its address. Token contract addresses can be found on $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
+ "addUrl": {
+ "message": "Add URL"
+ },
"addingCustomNetwork": {
"message": "Adding Network"
},
@@ -320,6 +323,9 @@
"additionalNetworks": {
"message": "Additional networks"
},
+ "additionalRpcUrl": {
+ "message": "Additional RPC URL"
+ },
"address": {
"message": "Address"
},
@@ -412,6 +418,9 @@
"alertMessagePendingTransactions": {
"message": "This transaction won’t go through until a previous transaction is complete. Learn how to cancel or speed up a transaction."
},
+ "alertMessageSignInDomainMismatch": {
+ "message": "The site making the request is not the site you’re signing into. This could be an attempt to steal your login credentials."
+ },
"alertMessageSigningOrSubmitting": {
"message": "This transaction will only go through once your previous transaction is complete."
},
@@ -445,6 +454,9 @@
"alertReasonPendingTransactions": {
"message": "Pending transaction"
},
+ "alertReasonSignIn": {
+ "message": "Suspicious sign-in request"
+ },
"alertSettingsUnconnectedAccount": {
"message": "Browsing a website with an unconnected account selected"
},
@@ -945,10 +957,10 @@
"confirm": {
"message": "Confirm"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "I have acknowledged the alerts and still want to proceed"
},
- "confirmAlertModalAcknowledgeBlockaid": {
+ "confirmAlertModalAcknowledgeSingle": {
"message": "I have acknowledged the alert and still want to proceed"
},
"confirmAlertModalDetails": {
@@ -966,6 +978,9 @@
"confirmConnectionTitle": {
"message": "Confirm connection to $1"
},
+ "confirmDeletion": {
+ "message": "Confirm deletion"
+ },
"confirmFieldPaymaster": {
"message": "Fee paid by"
},
@@ -978,6 +993,9 @@
"confirmRecoveryPhrase": {
"message": "Confirm Secret Recovery Phrase"
},
+ "confirmRpcUrlDeletionMessage": {
+ "message": "Are you sure you want to delete the RPC URL? Your information will not be saved for this network."
+ },
"confirmTitleDescContractInteractionTransaction": {
"message": "Only confirm this transaction if you fully understand the content and trust the requesting site."
},
@@ -1457,6 +1475,9 @@
"message": "Delete $1 network?",
"description": "$1 represents the name of the network"
},
+ "deleteRpcUrl": {
+ "message": "Delete RPC URL"
+ },
"deposit": {
"message": "Deposit"
},
@@ -2295,9 +2316,6 @@
"initialTransactionConfirmed": {
"message": "Your initial transaction was confirmed by the network. Click OK to go back."
},
- "inlineAlert": {
- "message": "Alert"
- },
"inputLogicEmptyState": {
"message": "Only enter a number that you're comfortable with the third party spending now or in the future. You can always increase the spending cap later."
},
@@ -2933,6 +2951,9 @@
"networkNameBase": {
"message": "Base"
},
+ "networkNameBitcoin": {
+ "message": "Bitcoin"
+ },
"networkNameDefinition": {
"message": "The name associated with this network."
},
diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json
index 6a70bf40e68f..244c70313634 100644
--- a/app/_locales/es/messages.json
+++ b/app/_locales/es/messages.json
@@ -835,7 +835,7 @@
"confirm": {
"message": "Confirmar"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "Soy consciente de las alertas y aun así deseo continuar"
},
"confirmAlertModalDetails": {
@@ -2119,9 +2119,6 @@
"initialTransactionConfirmed": {
"message": "La red confirmó la transacción inicial. Haga clic en Aceptar para volver."
},
- "inlineAlert": {
- "message": "Alerta"
- },
"inputLogicEmptyState": {
"message": "Ingrese solo una cantidad que esté dispuesto a gastar en el tercero ahora o en el futuro. Siempre puede aumentar el límite de gastos más adelante."
},
diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json
index 0cd50d4788f8..1b2a54f3c4c1 100644
--- a/app/_locales/fr/messages.json
+++ b/app/_locales/fr/messages.json
@@ -838,7 +838,7 @@
"confirm": {
"message": "Confirmer"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "J’ai pris connaissance des alertes, mais je souhaite quand même continuer"
},
"confirmAlertModalDetails": {
@@ -2122,9 +2122,6 @@
"initialTransactionConfirmed": {
"message": "Votre transaction initiale a été confirmée par le réseau. Cliquez sur OK pour retourner à l’écran précédent."
},
- "inlineAlert": {
- "message": "Alerte"
- },
"inputLogicEmptyState": {
"message": "N'entrez qu'une somme que vous pouvez accepter que le tiers dépense aujourd'hui ou à l'avenir. Vous pourrez toujours augmenter le plafond de dépenses ultérieurement."
},
diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json
index 5f83ad80bfdf..dc6318004958 100644
--- a/app/_locales/hi/messages.json
+++ b/app/_locales/hi/messages.json
@@ -835,7 +835,7 @@
"confirm": {
"message": "कन्फर्म करें"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "मैंने एलर्ट को स्वीकार कर लिया है और इसके बावजूद आगे बढ़ना चाहता/चाहती हूं"
},
"confirmAlertModalDetails": {
@@ -2119,9 +2119,6 @@
"initialTransactionConfirmed": {
"message": "नेटवर्क द्वारा आपके प्रारंभिक ट्रांसेक्शन को कन्फर्म किया गया था। वापस जाने के लिए ठीक पर क्लिक करें।"
},
- "inlineAlert": {
- "message": "एलर्ट"
- },
"inputLogicEmptyState": {
"message": "केवल वही संख्या डालें जो आप अभी या भविष्य में थर्ड पार्टी खर्च के साथ सहज महसूस करते हैं। आप बाद में कभी भी खर्च करने की लिमिट बढ़ा सकते हैं।"
},
diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json
index 45073d2af42b..0faef246fd39 100644
--- a/app/_locales/id/messages.json
+++ b/app/_locales/id/messages.json
@@ -838,7 +838,7 @@
"confirm": {
"message": "Konfirmasikan"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "Saya telah mengetahui peringatannya dan tetap ingin melanjutkan"
},
"confirmAlertModalDetails": {
@@ -2122,9 +2122,6 @@
"initialTransactionConfirmed": {
"message": "Transaksi awal Anda dikonfirmasikan oleh jaringan. Klik Oke untuk kembali."
},
- "inlineAlert": {
- "message": "Peringatan"
- },
"inputLogicEmptyState": {
"message": "Masukkan angka yang menurut Anda dapat digunakan pihak ketiga sekarang atau di masa mendatang. Anda selalu dapat meningkatkan batas penggunaan nantinya."
},
diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json
index ad69880ffd8e..4308665d7bcb 100644
--- a/app/_locales/ja/messages.json
+++ b/app/_locales/ja/messages.json
@@ -835,7 +835,7 @@
"confirm": {
"message": "確認"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "アラートを確認したうえで続行します"
},
"confirmAlertModalDetails": {
@@ -2119,9 +2119,6 @@
"initialTransactionConfirmed": {
"message": "最初のトランザクションはネットワークによって承認されました。戻るには「OK」をクリックします。"
},
- "inlineAlert": {
- "message": "アラート"
- },
"inputLogicEmptyState": {
"message": "現在または今後サードパーティが使用しても構わない額のみを入力してください。使用上限は後でいつでも増額できます。"
},
diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json
index 9121a9b84281..464c3bf9e250 100644
--- a/app/_locales/ko/messages.json
+++ b/app/_locales/ko/messages.json
@@ -835,7 +835,7 @@
"confirm": {
"message": "컨펌"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "경고를 인지했으며, 계속 진행합니다"
},
"confirmAlertModalDetails": {
@@ -2119,9 +2119,6 @@
"initialTransactionConfirmed": {
"message": "최초 트랜잭션을 네트워크에서 컨펌했습니다. 돌아가려면 컨펌을 클릭하세요."
},
- "inlineAlert": {
- "message": "경고"
- },
"inputLogicEmptyState": {
"message": "타사에서 현재나 추후 지출하기에 무리가 없는 금액만 입력하세요. 지출 한도는 나중에 언제든지 상향할 수 있습니다."
},
diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json
index e83e1b6d4eea..f201f475a4ec 100644
--- a/app/_locales/pt/messages.json
+++ b/app/_locales/pt/messages.json
@@ -838,7 +838,7 @@
"confirm": {
"message": "Confirmar"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "Confirmo que recebi os alertas e ainda quero prosseguir"
},
"confirmAlertModalDetails": {
@@ -2122,9 +2122,6 @@
"initialTransactionConfirmed": {
"message": "Sua transação inicial foi confirmada pela rede. Clique em OK para voltar."
},
- "inlineAlert": {
- "message": "Alerta"
- },
"inputLogicEmptyState": {
"message": "Somente insira um número com o qual esteja confortável de o terceiro gastar agora ou no futuro. Você pode aumentar o limite de gastos a qualquer momento."
},
diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json
index cb41df257fe1..3c419a274034 100644
--- a/app/_locales/ru/messages.json
+++ b/app/_locales/ru/messages.json
@@ -838,7 +838,7 @@
"confirm": {
"message": "Подтвердить"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "Я подтвердил(-а) получение оповещений и все еще хочу продолжить"
},
"confirmAlertModalDetails": {
@@ -2122,9 +2122,6 @@
"initialTransactionConfirmed": {
"message": "Ваша первоначальная транзакция подтверждена сетью. Нажмите ОК, чтобы вернуться."
},
- "inlineAlert": {
- "message": "Оповещение"
- },
"inputLogicEmptyState": {
"message": "Введите только ту сумму, которую третья сторона, по вашему мнению, может тратить сейчас или в будущем. Вы всегда можете увеличить лимит расходов позже."
},
diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json
index cbd81acfcbc6..b6d454c012af 100644
--- a/app/_locales/tl/messages.json
+++ b/app/_locales/tl/messages.json
@@ -835,7 +835,7 @@
"confirm": {
"message": "Kumpirmahin"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "Kinikilala ko ang mga alerto at nais ko pa rin magpatuloy"
},
"confirmAlertModalDetails": {
@@ -2119,9 +2119,6 @@
"initialTransactionConfirmed": {
"message": "Nakumpirma na ng network ang iyong inisyal na transaksyon. I-click ang OK para bumalik."
},
- "inlineAlert": {
- "message": "Alerto"
- },
"inputLogicEmptyState": {
"message": "Maglagay lamang ng numero na komportable ka sa paggastos ng third party ngayon o sa hinaharap. Maaari mong palaging taasan ang limitasyon sa paggastos sa ibang pagkakataon."
},
diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json
index 13887164bfd1..cd6508892738 100644
--- a/app/_locales/tr/messages.json
+++ b/app/_locales/tr/messages.json
@@ -838,7 +838,7 @@
"confirm": {
"message": "Onayla"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "Uyarıları kabul ediyorum ve yine de ilerlemek istiyorum"
},
"confirmAlertModalDetails": {
@@ -2122,9 +2122,6 @@
"initialTransactionConfirmed": {
"message": "İlk işleminiz ağ tarafından onaylanmıştır. Geri gitmek için Tamam düğmesine tıklayın."
},
- "inlineAlert": {
- "message": "Uyarı"
- },
"inputLogicEmptyState": {
"message": "Sadece şu anda ya da gelecekte üçüncü taraf harcaması konusunda rahat olduğunuz bir sayı girin. Harcama üst limitini daha sonra dilediğiniz zaman artırabilirsiniz."
},
diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json
index 3afd6bf2403e..ffc867cf6e76 100644
--- a/app/_locales/vi/messages.json
+++ b/app/_locales/vi/messages.json
@@ -835,7 +835,7 @@
"confirm": {
"message": "Xác nhận"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "Tôi đã hiểu rõ các cảnh báo và vẫn muốn tiếp tục"
},
"confirmAlertModalDetails": {
@@ -2119,9 +2119,6 @@
"initialTransactionConfirmed": {
"message": "Mạng đã xác nhận giao dịch ban đầu của bạn. Nhấp OK để quay lại."
},
- "inlineAlert": {
- "message": "Cảnh báo"
- },
"inputLogicEmptyState": {
"message": "Chỉ nhập số mà bạn cảm thấy thoải mái đối với hạn mức chi tiêu ở hiện tại hoặc trong tương lai của bên thứ ba. Bạn luôn có thể tăng hạn mức chi tiêu sau này."
},
diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json
index 6cdb6aaf1e3f..14c8e355be1f 100644
--- a/app/_locales/zh_CN/messages.json
+++ b/app/_locales/zh_CN/messages.json
@@ -835,7 +835,7 @@
"confirm": {
"message": "确认"
},
- "confirmAlertModalAcknowledge": {
+ "confirmAlertModalAcknowledgeMultiple": {
"message": "我已知晓提醒并仍想继续"
},
"confirmAlertModalDetails": {
@@ -2119,9 +2119,6 @@
"initialTransactionConfirmed": {
"message": "您的初始交易已被网络确认。请点击“确定”返回。"
},
- "inlineAlert": {
- "message": "提醒"
- },
"inputLogicEmptyState": {
"message": "仅需输入一个您觉得比较恰当的现在或将来第三方支出的数字。以后您可以随时提高支出上限。"
},
diff --git a/app/scripts/controllers/bridge.test.ts b/app/scripts/controllers/bridge.test.ts
new file mode 100644
index 000000000000..a6001f7aa0d7
--- /dev/null
+++ b/app/scripts/controllers/bridge.test.ts
@@ -0,0 +1,29 @@
+import BridgeController from './bridge';
+
+const EMPTY_INIT_STATE = {
+ bridgeState: {
+ bridgeFeatureFlags: {
+ extensionSupport: false,
+ },
+ },
+};
+
+describe('BridgeController', function () {
+ let bridgeController: BridgeController;
+
+ beforeAll(function () {
+ bridgeController = new BridgeController();
+ });
+
+ it('constructor should setup correctly', function () {
+ expect(bridgeController.store.getState()).toStrictEqual(EMPTY_INIT_STATE);
+ });
+
+ it('setBridgeFeatureFlags should set the bridge feature flags', function () {
+ const featureFlagsResponse = { extensionSupport: true };
+ bridgeController.setBridgeFeatureFlags(featureFlagsResponse);
+ expect(
+ bridgeController.store.getState().bridgeState.bridgeFeatureFlags,
+ ).toStrictEqual(featureFlagsResponse);
+ });
+});
diff --git a/app/scripts/controllers/bridge.ts b/app/scripts/controllers/bridge.ts
new file mode 100644
index 000000000000..23323371ea4c
--- /dev/null
+++ b/app/scripts/controllers/bridge.ts
@@ -0,0 +1,36 @@
+import { ObservableStore } from '@metamask/obs-store';
+
+export enum BridgeFeatureFlagsKey {
+ EXTENSION_SUPPORT = 'extensionSupport',
+}
+
+export type BridgeFeatureFlags = {
+ [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: boolean;
+};
+
+const initialState = {
+ bridgeState: {
+ bridgeFeatureFlags: {
+ [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: false,
+ },
+ },
+};
+
+export default class BridgeController {
+ store = new ObservableStore(initialState);
+
+ resetState = () => {
+ this.store.updateState({
+ bridgeState: {
+ ...initialState.bridgeState,
+ },
+ });
+ };
+
+ setBridgeFeatureFlags = (bridgeFeatureFlags: BridgeFeatureFlags) => {
+ const { bridgeState } = this.store.getState();
+ this.store.updateState({
+ bridgeState: { ...bridgeState, bridgeFeatureFlags },
+ });
+ };
+}
diff --git a/app/scripts/lib/accounts/BalancesController.test.ts b/app/scripts/lib/accounts/BalancesController.test.ts
index 01ce1f88c608..02627c6aa201 100644
--- a/app/scripts/lib/accounts/BalancesController.test.ts
+++ b/app/scripts/lib/accounts/BalancesController.test.ts
@@ -9,9 +9,10 @@ import { createMockInternalAccount } from '../../../../test/jest/mocks';
import {
BalancesController,
AllowedActions,
- BalancesControllerEvents,
+ AllowedEvents,
BalancesControllerState,
defaultState,
+ BalancesControllerMessenger,
} from './BalancesController';
import { Poller } from './Poller';
@@ -46,14 +47,15 @@ const setupController = ({
} = {}) => {
const controllerMessenger = new ControllerMessenger<
AllowedActions,
- BalancesControllerEvents
+ AllowedEvents
>();
- const balancesControllerMessenger = controllerMessenger.getRestricted({
- name: 'BalancesController',
- allowedActions: ['SnapController:handleRequest'],
- allowedEvents: [],
- });
+ const balancesControllerMessenger: BalancesControllerMessenger =
+ controllerMessenger.getRestricted({
+ name: 'BalancesController',
+ allowedActions: ['SnapController:handleRequest'],
+ allowedEvents: ['AccountsController:stateChange'],
+ });
const mockSnapHandleRequest = jest.fn();
controllerMessenger.registerActionHandler(
diff --git a/app/scripts/lib/accounts/BalancesController.ts b/app/scripts/lib/accounts/BalancesController.ts
index eee4ac11889a..ab1eb8c6cfe6 100644
--- a/app/scripts/lib/accounts/BalancesController.ts
+++ b/app/scripts/lib/accounts/BalancesController.ts
@@ -12,11 +12,17 @@ import {
type Balance,
type CaipAssetType,
type InternalAccount,
+ isEvmAccountType,
} from '@metamask/keyring-api';
import type { HandleSnapRequest } from '@metamask/snaps-controllers';
import type { SnapId } from '@metamask/snaps-sdk';
import { HandlerType } from '@metamask/snaps-utils';
import type { Draft } from 'immer';
+import type {
+ AccountsControllerChangeEvent,
+ AccountsControllerState,
+} from '@metamask/accounts-controller';
+import { isBtcMainnetAddress } from '../../../../shared/lib/multichain';
import { Poller } from './Poller';
const controllerName = 'BalancesController';
@@ -81,15 +87,20 @@ export type BalancesControllerEvents = BalancesControllerStateChange;
*/
export type AllowedActions = HandleSnapRequest;
+/**
+ * Events that this controller is allowed to subscribe.
+ */
+export type AllowedEvents = AccountsControllerChangeEvent;
+
/**
* Messenger type for the BalancesController.
*/
export type BalancesControllerMessenger = RestrictedControllerMessenger<
typeof controllerName,
BalancesControllerActions | AllowedActions,
- BalancesControllerEvents,
+ BalancesControllerEvents | AllowedEvents,
AllowedActions['type'],
- never
+ AllowedEvents['type']
>;
/**
@@ -110,21 +121,6 @@ const BTC_TESTNET_ASSETS = ['bip122:000000000933ea01ad0ee984209779ba/slip44:0'];
const BTC_MAINNET_ASSETS = ['bip122:000000000019d6689c085ae165831e93/slip44:0'];
export const BTC_AVG_BLOCK_TIME = 600000; // 10 minutes in milliseconds
-/**
- * Returns whether an address is on the Bitcoin mainnet.
- *
- * This function only checks the prefix of the address to determine if it's on
- * the mainnet or not. It doesn't validate the address itself, and should only
- * be used as a temporary solution until this information is included in the
- * account object.
- *
- * @param address - The address to check.
- * @returns `true` if the address is on the Bitcoin mainnet, `false` otherwise.
- */
-function isBtcMainnet(address: string): boolean {
- return address.startsWith('bc1') || address.startsWith('1');
-}
-
/**
* The BalancesController is responsible for fetching and caching account
* balances.
@@ -158,6 +154,11 @@ export class BalancesController extends BaseController<
},
});
+ this.messagingSystem.subscribe(
+ 'AccountsController:stateChange',
+ (newState) => this.#handleOnAccountsControllerChange(newState),
+ );
+
this.#listMultichainAccounts = listMultichainAccounts;
this.#poller = new Poller(() => this.updateBalances(), BTC_AVG_BLOCK_TIME);
}
@@ -203,7 +204,7 @@ export class BalancesController extends BaseController<
partialState.balances[account.id] = await this.#getBalances(
account.id,
account.metadata.snap.id,
- isBtcMainnet(account.address)
+ isBtcMainnetAddress(account.address)
? BTC_MAINNET_ASSETS
: BTC_TESTNET_ASSETS,
);
@@ -216,6 +217,21 @@ export class BalancesController extends BaseController<
}));
}
+ /**
+ * Handles changes in the accounts state, specifically when new non-EVM accounts are added.
+ *
+ * @param newState - The new state of the accounts controller.
+ */
+ #handleOnAccountsControllerChange(newState: AccountsControllerState) {
+ // If we have any new non-EVM accounts, we just update non-EVM balances
+ const newNonEvmAccounts = Object.values(
+ newState.internalAccounts.accounts,
+ ).filter((account) => !isEvmAccountType(account.type));
+ if (newNonEvmAccounts.length) {
+ this.updateBalances();
+ }
+ }
+
/**
* Get the balances for an account.
*
diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js
index 92173fc5f72c..a6b47eef9376 100644
--- a/app/scripts/lib/setupSentry.js
+++ b/app/scripts/lib/setupSentry.js
@@ -120,6 +120,13 @@ export const SENTRY_BACKGROUND_STATE = {
MultichainBalancesController: {
balances: false,
},
+ BridgeController: {
+ bridgeState: {
+ bridgeFeatureFlags: {
+ extensionSupport: false,
+ },
+ },
+ },
CronjobController: {
jobs: false,
},
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 1799b36e6538..88491e2af62c 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -139,6 +139,8 @@ import {
} from '@metamask/snaps-utils';
///: END:ONLY_INCLUDE_IF
+import { Interface } from '@ethersproject/abi';
+import { abiERC1155, abiERC721 } from '@metamask/metamask-eth-abis';
import { isEvmAccountType } from '@metamask/keyring-api';
import {
methodsRequiringNetworkSwitch,
@@ -207,6 +209,10 @@ import {
} from '../../shared/modules/selectors';
import { createCaipStream } from '../../shared/modules/caip-stream';
import { BaseUrl } from '../../shared/constants/urls';
+import {
+ TOKEN_TRANSFER_LOG_TOPIC_HASH,
+ TRANSFER_SINFLE_LOG_TOPIC_HASH,
+} from '../../shared/lib/transactions-controller-utils';
import { BalancesController as MultichainBalancesController } from './lib/accounts/BalancesController';
import {
///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
@@ -315,6 +321,7 @@ import { createTxVerificationMiddleware } from './lib/tx-verification/tx-verific
import { updateSecurityAlertResponse } from './lib/ppom/ppom-util';
import createEvmMethodsToNonEvmAccountReqFilterMiddleware from './lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware';
import { isEthAddress } from './lib/multichain/address';
+import BridgeController from './controllers/bridge';
export const METAMASK_CONTROLLER_EVENTS = {
// Fired after state changes that impact the extension badge (unapproved msg count)
@@ -517,12 +524,6 @@ export default class MetamaskController extends EventEmitter {
this.networkController.getProviderAndBlockTracker().blockTracker;
this.deprecatedNetworkVersions = {};
- const tokenListMessenger = this.controllerMessenger.getRestricted({
- name: 'TokenListController',
- allowedEvents: ['NetworkController:stateChange'],
- allowedActions: ['NetworkController:getNetworkClientById'],
- });
-
const accountsControllerMessenger = this.controllerMessenger.getRestricted({
name: 'AccountsController',
allowedEvents: [
@@ -560,6 +561,12 @@ export default class MetamaskController extends EventEmitter {
networkConfigurations: this.networkController.state.networkConfigurations,
});
+ const tokenListMessenger = this.controllerMessenger.getRestricted({
+ name: 'TokenListController',
+ allowedActions: ['NetworkController:getNetworkClientById'],
+ allowedEvents: ['NetworkController:stateChange'],
+ });
+
this.tokenListController = new TokenListController({
chainId: this.networkController.state.providerConfig.chainId,
preventPollingOnNetworkRestart: !this.#isTokenListPollingRequired(
@@ -597,49 +604,21 @@ export default class MetamaskController extends EventEmitter {
allowedActions: [
'ApprovalController:addRequest',
'NetworkController:getNetworkClientById',
+ 'AccountsController:getSelectedAccount',
+ 'AccountsController:getAccount',
],
allowedEvents: [
'NetworkController:networkDidChange',
- 'AccountsController:selectedAccountChange',
+ 'AccountsController:selectedEvmAccountChange',
'PreferencesController:stateChange',
'TokenListController:stateChange',
],
});
this.tokensController = new TokensController({
+ state: initState.TokensController,
+ provider: this.provider,
messenger: tokensControllerMessenger,
chainId: this.networkController.state.providerConfig.chainId,
- // TODO: The tokens controller currently does not support internalAccounts. This is done to match the behavior of the previous tokens controller subscription.
- onPreferencesStateChange: (listener) =>
- this.controllerMessenger.subscribe(
- `AccountsController:selectedAccountChange`,
- (newlySelectedInternalAccount) => {
- listener({ selectedAddress: newlySelectedInternalAccount.address });
- },
- ),
- onNetworkDidChange: (cb) =>
- networkControllerMessenger.subscribe(
- 'NetworkController:networkDidChange',
- () => {
- const networkState = this.networkController.state;
- return cb(networkState);
- },
- ),
- onTokenListStateChange: (listener) =>
- this.controllerMessenger.subscribe(
- `${this.tokenListController.name}:stateChange`,
- listener,
- ),
- getNetworkClientById: this.networkController.getNetworkClientById.bind(
- this.networkController,
- ),
- config: {
- provider: this.provider,
- selectedAddress:
- initState.AccountsController?.internalAccounts?.accounts[
- initState.AccountsController?.internalAccounts?.selectedAccount
- ]?.address ?? '',
- },
- state: initState.TokensController,
});
const nftControllerMessenger = this.controllerMessenger.getRestricted({
@@ -657,15 +636,9 @@ export default class MetamaskController extends EventEmitter {
],
});
this.nftController = new NftController({
+ state: initState.NftController,
messenger: nftControllerMessenger,
chainId: this.networkController.state.providerConfig.chainId,
- onPreferencesStateChange: this.preferencesController.store.subscribe.bind(
- this.preferencesController.store,
- ),
- onNetworkStateChange: networkControllerMessenger.subscribe.bind(
- networkControllerMessenger,
- 'NetworkController:stateChange',
- ),
getERC721AssetName: this.assetsContractController.getERC721AssetName.bind(
this.assetsContractController,
),
@@ -699,10 +672,6 @@ export default class MetamaskController extends EventEmitter {
source,
},
}),
- getNetworkClientById: this.networkController.getNetworkClientById.bind(
- this.networkController,
- ),
- state: initState.NftController,
});
this.nftController.setApiKey(process.env.OPENSEA_KEY);
@@ -711,14 +680,13 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger.getRestricted({
name: 'NftDetectionController',
allowedEvents: [
- 'PreferencesController:stateChange',
'NetworkController:stateChange',
+ 'PreferencesController:stateChange',
],
allowedActions: [
'ApprovalController:addRequest',
'NetworkController:getState',
'NetworkController:getNetworkClientById',
- 'PreferencesController:getState',
'AccountsController:getSelectedAccount',
],
});
@@ -726,21 +694,12 @@ export default class MetamaskController extends EventEmitter {
this.nftDetectionController = new NftDetectionController({
messenger: nftDetectionControllerMessenger,
chainId: this.networkController.state.providerConfig.chainId,
- onNftsStateChange: (listener) => this.nftController.subscribe(listener),
- onPreferencesStateChange: this.preferencesController.store.subscribe.bind(
- this.preferencesController.store,
- ),
- onNetworkStateChange: networkControllerMessenger.subscribe.bind(
- networkControllerMessenger,
- 'NetworkController:stateChange',
- ),
getOpenSeaApiKey: () => this.nftController.openSeaApiKey,
getBalancesInSingleCall:
this.assetsContractController.getBalancesInSingleCall.bind(
this.assetsContractController,
),
addNft: this.nftController.addNft.bind(this.nftController),
- getNftApi: this.nftController.getNftApi.bind(this.nftController),
getNftState: () => this.nftController.state,
// added this to track previous value of useNftDetection, should be true on very first initializing of controller[]
disabled:
@@ -748,11 +707,6 @@ export default class MetamaskController extends EventEmitter {
undefined
? false // the detection is enabled by default
: !this.preferencesController.store.getState().useNftDetection,
- selectedAddress:
- this.preferencesController.store.getState().selectedAddress,
- getNetworkClientById: this.networkController.getNetworkClientById.bind(
- this.networkController,
- ),
});
this.metaMetricsController = new MetaMetricsController({
@@ -921,7 +875,7 @@ export default class MetamaskController extends EventEmitter {
const multichainBalancesControllerMessenger =
this.controllerMessenger.getRestricted({
name: 'BalancesController',
- allowedEvents: [],
+ allowedEvents: ['AccountsController:stateChange'],
allowedActions: ['SnapController:handleRequest'],
});
@@ -946,55 +900,29 @@ export default class MetamaskController extends EventEmitter {
fetchMultiExchangeRate,
});
- const tokenRatesControllerMessenger =
- this.controllerMessenger.getRestricted({
- name: 'TokenRatesController',
- allowedEvents: [
- 'PreferencesController:stateChange',
- 'TokensController:stateChange',
- 'NetworkController:stateChange',
- ],
- allowedActions: [
- 'TokensController:getState',
- 'NetworkController:getNetworkClientById',
- 'NetworkController:getState',
- 'PreferencesController:getState',
- ],
- });
+ const tokenRatesMessenger = this.controllerMessenger.getRestricted({
+ name: 'TokenRatesController',
+ allowedActions: [
+ 'TokensController:getState',
+ 'NetworkController:getNetworkClientById',
+ 'NetworkController:getState',
+ 'AccountsController:getAccount',
+ 'AccountsController:getSelectedAccount',
+ ],
+ allowedEvents: [
+ 'NetworkController:stateChange',
+ 'AccountsController:selectedEvmAccountChange',
+ 'PreferencesController:stateChange',
+ 'TokensController:stateChange',
+ ],
+ });
// token exchange rate tracker
- this.tokenRatesController = new TokenRatesController(
- {
- messenger: tokenRatesControllerMessenger,
- chainId: this.networkController.state.providerConfig.chainId,
- ticker: this.networkController.state.providerConfig.ticker,
- selectedAddress: this.accountsController.getSelectedAccount().address,
- onTokensStateChange: (listener) =>
- this.tokensController.subscribe(listener),
- onNetworkStateChange: networkControllerMessenger.subscribe.bind(
- networkControllerMessenger,
- 'NetworkController:stateChange',
- ),
- onPreferencesStateChange: (listener) =>
- this.controllerMessenger.subscribe(
- `AccountsController:selectedAccountChange`,
- (newlySelectedInternalAccount) => {
- listener({
- selectedAddress: newlySelectedInternalAccount.address,
- });
- },
- ),
- tokenPricesService: new CodefiTokenPricesServiceV2(),
- getNetworkClientById: this.networkController.getNetworkClientById.bind(
- this.networkController,
- ),
- },
- {
- allTokens: this.tokensController.state.allTokens,
- allDetectedTokens: this.tokensController.state.allDetectedTokens,
- },
- initState.TokenRatesController,
- );
+ this.tokenRatesController = new TokenRatesController({
+ state: initState.TokenRatesController,
+ messenger: tokenRatesMessenger,
+ tokenPricesService: new CodefiTokenPricesServiceV2(),
+ });
this.preferencesController.store.subscribe(
previousValueComparator((prevState, currState) => {
@@ -1621,6 +1549,7 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger.getRestricted({
name: 'TokenDetectionController',
allowedActions: [
+ 'AccountsController:getAccount',
'AccountsController:getSelectedAccount',
'KeyringController:getState',
'NetworkController:getNetworkClientById',
@@ -1632,7 +1561,7 @@ export default class MetamaskController extends EventEmitter {
'TokensController:addDetectedTokens',
],
allowedEvents: [
- 'AccountsController:selectedAccountChange',
+ 'AccountsController:selectedEvmAccountChange',
'KeyringController:lock',
'KeyringController:unlock',
'NetworkController:networkDidChange',
@@ -1967,6 +1896,7 @@ export default class MetamaskController extends EventEmitter {
},
initState.SwapsController,
);
+ this.bridgeController = new BridgeController();
this.smartTransactionsController = new SmartTransactionsController(
{
getNetworkClientById: this.networkController.getNetworkClientById.bind(
@@ -2196,6 +2126,7 @@ export default class MetamaskController extends EventEmitter {
EncryptionPublicKeyController: this.encryptionPublicKeyController,
SignatureController: this.signatureController,
SwapsController: this.swapsController.store,
+ BridgeController: this.bridgeController.store,
EnsController: this.ensController,
ApprovalController: this.approvalController,
PPOMController: this.ppomController,
@@ -3016,6 +2947,7 @@ export default class MetamaskController extends EventEmitter {
appMetadataController,
permissionController,
preferencesController,
+ bridgeController,
swapsController,
tokensController,
smartTransactionsController,
@@ -3621,6 +3553,10 @@ export default class MetamaskController extends EventEmitter {
setSwapsQuotesPollingLimitEnabled:
swapsController.setSwapsQuotesPollingLimitEnabled.bind(swapsController),
+ // Bridge
+ setBridgeFeatureFlags:
+ bridgeController.setBridgeFeatureFlags.bind(bridgeController),
+
// Smart Transactions
fetchSmartTransactionFees: smartTransactionsController.getFees.bind(
smartTransactionsController,
@@ -6344,7 +6280,7 @@ export default class MetamaskController extends EventEmitter {
}
await this._createTransactionNotifcation(transactionMeta);
- this._updateNFTOwnership(transactionMeta);
+ await this._updateNFTOwnership(transactionMeta);
this._trackTransactionFailure(transactionMeta);
}
@@ -6372,46 +6308,158 @@ export default class MetamaskController extends EventEmitter {
}
}
- _updateNFTOwnership(transactionMeta) {
+ async _updateNFTOwnership(transactionMeta) {
// if this is a transferFrom method generated from within the app it may be an NFT transfer transaction
// in which case we will want to check and update ownership status of the transferred NFT.
- const { type, txParams, chainId } = transactionMeta;
+ const { type, txParams, chainId, txReceipt } = transactionMeta;
+ const selectedAddress =
+ this.accountsController.getSelectedAccount().address;
- if (
- type !== TransactionType.tokenMethodTransferFrom ||
- txParams === undefined
- ) {
+ const { allNfts } = this.nftController.state;
+ const txReceiptLogs = txReceipt?.logs;
+
+ const isContractInteractionTx =
+ type === TransactionType.contractInteraction && txReceiptLogs;
+ const isTransferFromTx =
+ (type === TransactionType.tokenMethodTransferFrom ||
+ type === TransactionType.tokenMethodSafeTransferFrom) &&
+ txParams !== undefined;
+
+ if (!isContractInteractionTx && !isTransferFromTx) {
return;
}
- const { data, to: contractAddress, from: userAddress } = txParams;
- const transactionData = parseStandardTokenTransactionData(data);
- // Sometimes the tokenId value is parsed as "_value" param. Not seeing this often any more, but still occasionally:
- // i.e. call approve() on BAYC contract - https://etherscan.io/token/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d#writeContract, and tokenId shows up as _value,
- // not sure why since it doesn't match the ERC721 ABI spec we use to parse these transactions - https://github.com/MetaMask/metamask-eth-abis/blob/d0474308a288f9252597b7c93a3a8deaad19e1b2/src/abis/abiERC721.ts#L62.
- const transactionDataTokenId =
- getTokenIdParam(transactionData) ?? getTokenValueParam(transactionData);
+ if (isTransferFromTx) {
+ const { data, to: contractAddress, from: userAddress } = txParams;
+ const transactionData = parseStandardTokenTransactionData(data);
+ // Sometimes the tokenId value is parsed as "_value" param. Not seeing this often any more, but still occasionally:
+ // i.e. call approve() on BAYC contract - https://etherscan.io/token/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d#writeContract, and tokenId shows up as _value,
+ // not sure why since it doesn't match the ERC721 ABI spec we use to parse these transactions - https://github.com/MetaMask/metamask-eth-abis/blob/d0474308a288f9252597b7c93a3a8deaad19e1b2/src/abis/abiERC721.ts#L62.
+ const transactionDataTokenId =
+ getTokenIdParam(transactionData) ?? getTokenValueParam(transactionData);
+
+ // check if its a known NFT
+ const knownNft = allNfts?.[userAddress]?.[chainId]?.find(
+ ({ address, tokenId }) =>
+ isEqualCaseInsensitive(address, contractAddress) &&
+ tokenId === transactionDataTokenId,
+ );
- const { allNfts } = this.nftController.state;
+ // if it is we check and update ownership status.
+ if (knownNft) {
+ this.nftController.checkAndUpdateSingleNftOwnershipStatus(
+ knownNft,
+ false,
+ // TODO add networkClientId once it is available in the transactionMeta
+ // the chainId previously passed here didn't actually allow us to check for ownership on a non globally selected network
+ // because the check would use the provider for the globally selected network, not the chainId passed here.
+ { userAddress },
+ );
+ }
+ } else {
+ // Else if contract interaction we will parse the logs
+
+ const allNftTransferLog = txReceiptLogs.map((txReceiptLog) => {
+ const isERC1155NftTransfer =
+ txReceiptLog.topics &&
+ txReceiptLog.topics[0] === TRANSFER_SINFLE_LOG_TOPIC_HASH;
+ const isERC721NftTransfer =
+ txReceiptLog.topics &&
+ txReceiptLog.topics[0] === TOKEN_TRANSFER_LOG_TOPIC_HASH;
+ let isTransferToSelectedAddress;
+
+ if (isERC1155NftTransfer) {
+ isTransferToSelectedAddress =
+ txReceiptLog.topics &&
+ txReceiptLog.topics[3] &&
+ txReceiptLog.topics[3].match(selectedAddress?.slice(2));
+ }
- // check if its a known NFT
- const knownNft = allNfts?.[userAddress]?.[chainId]?.find(
- ({ address, tokenId }) =>
- isEqualCaseInsensitive(address, contractAddress) &&
- tokenId === transactionDataTokenId,
- );
+ if (isERC721NftTransfer) {
+ isTransferToSelectedAddress =
+ txReceiptLog.topics &&
+ txReceiptLog.topics[2] &&
+ txReceiptLog.topics[2].match(selectedAddress?.slice(2));
+ }
- // if it is we check and update ownership status.
- if (knownNft) {
- this.nftController.checkAndUpdateSingleNftOwnershipStatus(
- knownNft,
- false,
- // TODO add networkClientId once it is available in the transactionMeta
- // the chainId previously passed here didn't actually allow us to check for ownership on a non globally selected network
- // because the check would use the provider for the globally selected network, not the chainId passed here.
- { userAddress },
- );
+ return {
+ isERC1155NftTransfer,
+ isERC721NftTransfer,
+ isTransferToSelectedAddress,
+ ...txReceiptLog,
+ };
+ });
+ if (allNftTransferLog.length !== 0) {
+ const allNftParsedLog = [];
+ allNftTransferLog.forEach((singleLog) => {
+ if (
+ singleLog.isTransferToSelectedAddress &&
+ (singleLog.isERC1155NftTransfer || singleLog.isERC721NftTransfer)
+ ) {
+ let iface;
+ if (singleLog.isERC1155NftTransfer) {
+ iface = new Interface(abiERC1155);
+ } else {
+ iface = new Interface(abiERC721);
+ }
+ try {
+ const parsedLog = iface.parseLog({
+ data: singleLog.data,
+ topics: singleLog.topics,
+ });
+ allNftParsedLog.push({
+ contract: singleLog.address,
+ ...parsedLog,
+ });
+ } catch (err) {
+ // ignore
+ }
+ }
+ });
+ // Filter known nfts and new Nfts
+ const knownNFTs = [];
+ const newNFTs = [];
+ allNftParsedLog.forEach((single) => {
+ const tokenIdFromLog = getTokenIdParam(single);
+ const existingNft = allNfts?.[selectedAddress]?.[chainId]?.find(
+ ({ address, tokenId }) => {
+ return (
+ isEqualCaseInsensitive(address, single.contract) &&
+ tokenId === tokenIdFromLog
+ );
+ },
+ );
+ if (existingNft) {
+ knownNFTs.push(existingNft);
+ } else {
+ newNFTs.push({
+ tokenId: tokenIdFromLog,
+ ...single,
+ });
+ }
+ });
+ // For known nfts only refresh ownership
+ const refreshOwnershipNFts = knownNFTs.map(async (singleNft) => {
+ return this.nftController.checkAndUpdateSingleNftOwnershipStatus(
+ singleNft,
+ false,
+ // TODO add networkClientId once it is available in the transactionMeta
+ // the chainId previously passed here didn't actually allow us to check for ownership on a non globally selected network
+ // because the check would use the provider for the globally selected network, not the chainId passed here.
+ { selectedAddress },
+ );
+ });
+ await Promise.allSettled(refreshOwnershipNFts);
+ // For new nfts, add them to state
+ const addNftPromises = newNFTs.map(async (singleNft) => {
+ return this.nftController.addNft(
+ singleNft.contract,
+ singleNft.tokenId,
+ );
+ });
+ await Promise.allSettled(addNftPromises);
+ }
}
}
diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js
index 771378568020..1ad12736fe2a 100644
--- a/app/scripts/metamask-controller.test.js
+++ b/app/scripts/metamask-controller.test.js
@@ -662,59 +662,46 @@ describe('MetaMaskController', () => {
it('should clear previous identities after vault restoration', async () => {
jest.spyOn(metamaskController, 'getBalance').mockResolvedValue('0x0');
- let startTime = Date.now();
await metamaskController.createNewVaultAndRestore(
'foobar1337',
TEST_SEED,
);
- let endTime = Date.now();
- const firstVaultIdentities = cloneDeep(
- metamaskController.getState().identities,
+ const firstVaultAccounts = cloneDeep(
+ metamaskController.accountsController.listAccounts(),
);
- expect(
- firstVaultIdentities[TEST_ADDRESS].lastSelected >= startTime &&
- firstVaultIdentities[TEST_ADDRESS].lastSelected <= endTime,
- ).toStrictEqual(true);
- delete firstVaultIdentities[TEST_ADDRESS].lastSelected;
- expect(firstVaultIdentities).toStrictEqual({
- [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL },
- });
+ expect(firstVaultAccounts).toHaveLength(1);
+ expect(firstVaultAccounts[0].address).toBe(TEST_ADDRESS);
- await metamaskController.preferencesController.setAccountLabel(
- TEST_ADDRESS,
+ const selectedAccount =
+ metamaskController.accountsController.getSelectedAccount();
+ metamaskController.accountsController.setAccountName(
+ selectedAccount.id,
'Account Foo',
);
- const labelledFirstVaultIdentities = cloneDeep(
- metamaskController.getState().identities,
+ const labelledFirstVaultAccounts = cloneDeep(
+ metamaskController.accountsController.listAccounts(),
);
- delete labelledFirstVaultIdentities[TEST_ADDRESS].lastSelected;
- expect(labelledFirstVaultIdentities).toStrictEqual({
- [TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' },
- });
- startTime = Date.now();
+ expect(labelledFirstVaultAccounts[0].address).toBe(TEST_ADDRESS);
+ expect(labelledFirstVaultAccounts[0].metadata.name).toBe('Account Foo');
+
await metamaskController.createNewVaultAndRestore(
'foobar1337',
TEST_SEED_ALT,
);
- endTime = Date.now();
- const secondVaultIdentities = cloneDeep(
- metamaskController.getState().identities,
+ const secondVaultAccounts = cloneDeep(
+ metamaskController.accountsController.listAccounts(),
);
+
+ expect(secondVaultAccounts).toHaveLength(1);
expect(
- secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected >= startTime &&
- secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected <= endTime,
- ).toStrictEqual(true);
- delete secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected;
- expect(secondVaultIdentities).toStrictEqual({
- [TEST_ADDRESS_ALT]: {
- address: TEST_ADDRESS_ALT,
- name: DEFAULT_LABEL,
- },
- });
+ metamaskController.accountsController.getSelectedAccount().address,
+ ).toBe(TEST_ADDRESS_ALT);
+ expect(secondVaultAccounts[0].address).toBe(TEST_ADDRESS_ALT);
+ expect(secondVaultAccounts[0].metadata.name).toBe(DEFAULT_LABEL);
});
it('should restore any consecutive accounts with balances without extra zero balance accounts', async () => {
@@ -748,29 +735,29 @@ describe('MetaMaskController', () => {
allDetectedTokens: { '0x1': { [TEST_ADDRESS_2]: [{}] } },
});
- const startTime = Date.now();
await metamaskController.createNewVaultAndRestore(
'foobar1337',
TEST_SEED,
);
// Expect first account to be selected
- const identities = cloneDeep(metamaskController.getState().identities);
- expect(
- identities[TEST_ADDRESS].lastSelected >= startTime &&
- identities[TEST_ADDRESS].lastSelected <= Date.now(),
- ).toStrictEqual(true);
-
- // Expect first 2 accounts to be restored
- delete identities[TEST_ADDRESS].lastSelected;
- expect(identities).toStrictEqual({
- [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL },
- [TEST_ADDRESS_2]: {
- address: TEST_ADDRESS_2,
- name: 'Account 2',
- lastSelected: expect.any(Number),
- },
- });
+ const accounts = cloneDeep(
+ metamaskController.accountsController.listAccounts(),
+ );
+
+ const selectedAccount =
+ metamaskController.accountsController.getSelectedAccount();
+
+ expect(selectedAccount.address).toBe(TEST_ADDRESS);
+ expect(accounts).toHaveLength(2);
+ expect(accounts[0].address).toBe(TEST_ADDRESS);
+ expect(accounts[0].metadata.name).toBe(DEFAULT_LABEL);
+ expect(accounts[1].address).toBe(TEST_ADDRESS_2);
+ expect(accounts[1].metadata.name).toBe('Account 2');
+ // TODO: Handle last selected in the update of the next accounts controller.
+ // expect(accounts[1].metadata.lastSelected).toBeGreaterThan(
+ // accounts[0].metadata.lastSelected,
+ // );
});
});
@@ -1596,14 +1583,12 @@ describe('MetaMaskController', () => {
symbol: 'FOO',
};
- metamaskController.tokensController.update((state) => {
- state.tokens = [
- {
- address: '0x6b175474e89094c44da98b954eedeac495271d0f',
- ...tokenData,
- },
- ];
- });
+ await metamaskController.tokensController.addTokens([
+ {
+ address: '0x6b175474e89094c44da98b954eedeac495271d0f',
+ ...tokenData,
+ },
+ ]);
metamaskController.provider = provider;
const tokenDetails =
diff --git a/builds.yml b/builds.yml
index 2e449c687591..0e83d86303b0 100644
--- a/builds.yml
+++ b/builds.yml
@@ -135,6 +135,7 @@ features:
# env object supports both declarations (- FOO), and definitions (- FOO: BAR).
# Variables that were declared have to be defined somewhere in the load chain before usage
env:
+ - BRIDGE_USE_DEV_APIS: false
- SWAPS_USE_DEV_APIS: false
- PORTFOLIO_URL: https://portfolio.metamask.io
- TOKEN_ALLOWANCE_IMPROVEMENTS: false
@@ -266,6 +267,8 @@ env:
# Enables the notifications feature within the build:
- NOTIFICATIONS: ''
+ - METAMASK_RAMP_API_CONTENT_BASE_URL: https://on-ramp-content.api.cx.metamask.io
+
###
# Meta variables
###
diff --git a/development/lib/retry.js b/development/lib/retry.js
index e6e5dfc040af..813a63aa44e4 100644
--- a/development/lib/retry.js
+++ b/development/lib/retry.js
@@ -11,12 +11,12 @@
* @param {string} [args.rejectionMessage] - The message for the rejected promise
* this function will return in the event of failure. (Default: "Retry limit
* reached")
- * @param {boolean} [args.retryUntilFailure] - Retries until the function fails.
+ * @param {boolean} [args.stopAfterOneFailure] - Retries until the function fails.
* @param {Function} functionToRetry - The function that is run and tested for
* failure.
* @returns {Promise<* | null | Error>} a promise that either resolves with one of the following:
* - If successful, resolves with the return value of functionToRetry.
- * - If functionToRetry fails while retryUntilFailure is true, resolves with null.
+ * - If functionToRetry fails while stopAfterOneFailure is true, resolves with null.
* - Otherwise it is rejected with rejectionMessage.
*/
async function retry(
@@ -24,7 +24,7 @@ async function retry(
retries,
delay = 0,
rejectionMessage = 'Retry limit reached',
- retryUntilFailure = false,
+ stopAfterOneFailure = false,
},
functionToRetry,
) {
@@ -36,7 +36,7 @@ async function retry(
try {
const result = await functionToRetry();
- if (!retryUntilFailure) {
+ if (!stopAfterOneFailure) {
return result;
}
} catch (error) {
@@ -46,18 +46,22 @@ async function retry(
console.error('error caught in retry():', error);
}
- if (attempts < retries) {
- console.log('Ready to retry() again');
+ if (stopAfterOneFailure) {
+ throw new Error('Test failed. No more retries will be performed');
}
- if (retryUntilFailure) {
- return null;
+ if (attempts < retries) {
+ console.log('Ready to retry() again');
}
} finally {
attempts += 1;
}
}
+ if (stopAfterOneFailure) {
+ return null;
+ }
+
throw new Error(rejectionMessage);
}
diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json
index d17b42a08fc8..6e8a55b7124a 100644
--- a/lavamoat/browserify/beta/policy.json
+++ b/lavamoat/browserify/beta/policy.json
@@ -800,7 +800,6 @@
"@metamask/eth-snap-keyring": true,
"@metamask/keyring-api": true,
"@metamask/keyring-controller": true,
- "@metamask/snaps-utils": true,
"@metamask/utils": true,
"uuid": true
}
diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json
index d17b42a08fc8..6e8a55b7124a 100644
--- a/lavamoat/browserify/flask/policy.json
+++ b/lavamoat/browserify/flask/policy.json
@@ -800,7 +800,6 @@
"@metamask/eth-snap-keyring": true,
"@metamask/keyring-api": true,
"@metamask/keyring-controller": true,
- "@metamask/snaps-utils": true,
"@metamask/utils": true,
"uuid": true
}
diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json
index d17b42a08fc8..6e8a55b7124a 100644
--- a/lavamoat/browserify/main/policy.json
+++ b/lavamoat/browserify/main/policy.json
@@ -800,7 +800,6 @@
"@metamask/eth-snap-keyring": true,
"@metamask/keyring-api": true,
"@metamask/keyring-controller": true,
- "@metamask/snaps-utils": true,
"@metamask/utils": true,
"uuid": true
}
diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json
index b1576a7d4f79..08b8c7e19958 100644
--- a/lavamoat/browserify/mmi/policy.json
+++ b/lavamoat/browserify/mmi/policy.json
@@ -892,7 +892,6 @@
"@metamask/eth-snap-keyring": true,
"@metamask/keyring-api": true,
"@metamask/keyring-controller": true,
- "@metamask/snaps-utils": true,
"@metamask/utils": true,
"uuid": true
}
diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json
index 02078b5d39fc..a01cf9c55765 100644
--- a/lavamoat/build-system/policy.json
+++ b/lavamoat/build-system/policy.json
@@ -2124,8 +2124,8 @@
},
"chokidar>anymatch": {
"packages": {
- "chokidar>anymatch>picomatch": true,
- "chokidar>normalize-path": true
+ "chokidar>anymatch>normalize-path": true,
+ "chokidar>anymatch>picomatch": true
}
},
"chokidar>anymatch>picomatch": {
@@ -3945,8 +3945,8 @@
},
"gulp-sourcemaps>@gulp-sourcemaps/identity-map": {
"packages": {
- "chokidar>normalize-path": true,
"gulp-sourcemaps>@gulp-sourcemaps/identity-map>acorn": true,
+ "gulp-sourcemaps>@gulp-sourcemaps/identity-map>normalize-path": true,
"gulp-sourcemaps>@gulp-sourcemaps/identity-map>postcss": true,
"gulp-sourcemaps>@gulp-sourcemaps/identity-map>source-map": true,
"gulp-sourcemaps>@gulp-sourcemaps/identity-map>through2": true
@@ -3997,13 +3997,8 @@
},
"gulp-sourcemaps>@gulp-sourcemaps/map-sources": {
"packages": {
- "gulp-sourcemaps>@gulp-sourcemaps/map-sources>normalize-path": true,
- "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2": true
- }
- },
- "gulp-sourcemaps>@gulp-sourcemaps/map-sources>normalize-path": {
- "packages": {
- "vinyl>remove-trailing-separator": true
+ "gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2": true,
+ "gulp-watch>anymatch>normalize-path": true
}
},
"gulp-sourcemaps>@gulp-sourcemaps/map-sources>through2": {
@@ -4254,10 +4249,10 @@
"setTimeout": true
},
"packages": {
+ "chokidar": true,
"eslint>glob-parent": true,
"gulp-watch>ansi-colors": true,
"gulp-watch>anymatch": true,
- "gulp-watch>chokidar": true,
"gulp-watch>fancy-log": true,
"gulp-watch>path-is-absolute": true,
"gulp-watch>readable-stream": true,
@@ -4314,7 +4309,7 @@
"packages": {
"gulp-watch>anymatch>micromatch>braces>expand-range": true,
"gulp-watch>anymatch>micromatch>braces>preserve": true,
- "gulp-watch>chokidar>braces>repeat-element": true
+ "gulp-watch>anymatch>micromatch>braces>repeat-element": true
}
},
"gulp-watch>anymatch>micromatch>braces>expand-range": {
@@ -4327,7 +4322,7 @@
"gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>is-number": true,
"gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>isobject": true,
"gulp-watch>anymatch>micromatch>braces>expand-range>fill-range>randomatic": true,
- "gulp-watch>chokidar>braces>repeat-element": true,
+ "gulp-watch>anymatch>micromatch>braces>repeat-element": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true
}
},
@@ -4455,8 +4450,6 @@
},
"packages": {
"chokidar>normalize-path": true,
- "del>is-glob": true,
- "eslint>glob-parent": true,
"eslint>is-glob": true,
"gulp-watch>chokidar>anymatch": true,
"gulp-watch>chokidar>async-each": true,
@@ -4470,750 +4463,234 @@
"pumpify>inherits": true
}
},
- "gulp-watch>chokidar>anymatch": {
+ "gulp-watch>chokidar>fsevents": {
"builtin": {
- "path.sep": true
+ "events.EventEmitter": true,
+ "fs.stat": true,
+ "path.join": true,
+ "util.inherits": true
+ },
+ "globals": {
+ "__dirname": true,
+ "console.assert": true,
+ "process.nextTick": true,
+ "process.platform": true,
+ "setImmediate": true
},
"packages": {
- "gulp-watch>chokidar>anymatch>micromatch": true,
- "gulp-watch>chokidar>anymatch>normalize-path": true
+ "gulp-watch>chokidar>fsevents>node-pre-gyp": true
}
},
- "gulp-watch>chokidar>anymatch>micromatch": {
+ "gulp-watch>chokidar>fsevents>node-pre-gyp": {
"builtin": {
- "path.basename": true,
- "path.sep": true,
- "util.inspect": true
+ "events.EventEmitter": true,
+ "fs.existsSync": true,
+ "fs.readFileSync": true,
+ "fs.renameSync": true,
+ "path.dirname": true,
+ "path.existsSync": true,
+ "path.join": true,
+ "path.resolve": true,
+ "url.parse": true,
+ "url.resolve": true,
+ "util.inherits": true
},
"globals": {
- "process.platform": true
+ "__dirname": true,
+ "console.log": true,
+ "process.arch": true,
+ "process.cwd": true,
+ "process.env": true,
+ "process.platform": true,
+ "process.version.substr": true,
+ "process.versions": true
},
"packages": {
- "@babel/register>clone-deep>kind-of": true,
- "gulp-watch>chokidar>anymatch>micromatch>define-property": true,
- "gulp-watch>chokidar>anymatch>micromatch>extglob": true,
- "gulp-watch>chokidar>braces": true,
- "gulp-watch>chokidar>braces>array-unique": true,
- "gulp-watch>chokidar>braces>snapdragon": true,
- "gulp-watch>chokidar>braces>to-regex": true,
- "gulp-zip>plugin-error>arr-diff": true,
- "gulp-zip>plugin-error>extend-shallow": true,
- "gulp>gulp-cli>liftoff>fined>object.pick": true,
- "gulp>gulp-cli>matchdep>micromatch>fragment-cache": true,
- "gulp>gulp-cli>matchdep>micromatch>nanomatch": true,
- "gulp>gulp-cli>matchdep>micromatch>regex-not": true
- }
- },
- "gulp-watch>chokidar>anymatch>micromatch>define-property": {
- "packages": {
- "gulp>gulp-cli>isobject": true,
- "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true
- }
- },
- "gulp-watch>chokidar>anymatch>micromatch>extglob": {
- "packages": {
- "gulp-watch>chokidar>anymatch>micromatch>extglob>define-property": true,
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets": true,
- "gulp-watch>chokidar>anymatch>micromatch>extglob>extend-shallow": true,
- "gulp-watch>chokidar>braces>array-unique": true,
- "gulp-watch>chokidar>braces>snapdragon": true,
- "gulp-watch>chokidar>braces>to-regex": true,
- "gulp>gulp-cli>matchdep>micromatch>fragment-cache": true,
- "gulp>gulp-cli>matchdep>micromatch>regex-not": true
- }
- },
- "gulp-watch>chokidar>anymatch>micromatch>extglob>define-property": {
- "packages": {
- "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true
+ "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog": true,
+ "gulp-watch>chokidar>fsevents>node-pre-gyp>detect-libc": true,
+ "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt": true,
+ "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf": true,
+ "gulp-watch>chokidar>fsevents>node-pre-gyp>semver": true
}
},
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets": {
- "globals": {
- "__filename": true
+ "gulp-watch>chokidar>fsevents>node-pre-gyp>detect-libc": {
+ "builtin": {
+ "child_process.spawnSync": true,
+ "fs.readdirSync": true,
+ "os.platform": true
},
- "packages": {
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>debug": true,
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>define-property": true,
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>extend-shallow": true,
- "gulp-watch>chokidar>braces>snapdragon": true,
- "gulp-watch>chokidar>braces>to-regex": true,
- "gulp>gulp-cli>matchdep>micromatch>extglob>expand-brackets>posix-character-classes": true,
- "gulp>gulp-cli>matchdep>micromatch>regex-not": true
+ "globals": {
+ "process.env": true
}
},
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>debug": {
+ "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt": {
"builtin": {
- "fs.SyncWriteStream": true,
- "net.Socket": true,
- "tty.WriteStream": true,
- "tty.isatty": true,
- "util": true
+ "path": true,
+ "stream.Stream": true,
+ "url": true
},
"globals": {
- "chrome": true,
"console": true,
- "document": true,
- "localStorage": true,
- "navigator": true,
- "process": true
+ "process.argv": true,
+ "process.env.DEBUG_NOPT": true,
+ "process.env.NOPT_DEBUG": true,
+ "process.platform": true
},
"packages": {
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>debug>ms": true
- }
- },
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>define-property": {
- "packages": {
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor": true
- }
- },
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor": {
- "packages": {
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor": true,
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor": true,
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>kind-of": true
- }
- },
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor": {
- "packages": {
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor>kind-of": true
- }
- },
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor>kind-of": {
- "packages": {
- "browserify>insert-module-globals>is-buffer": true
+ "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>nopt>abbrev": true,
+ "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv": true
}
},
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor": {
+ "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv": {
+ "builtin": {
+ "child_process.exec": true,
+ "path": true
+ },
+ "globals": {
+ "process.env.COMPUTERNAME": true,
+ "process.env.ComSpec": true,
+ "process.env.EDITOR": true,
+ "process.env.HOSTNAME": true,
+ "process.env.PATH": true,
+ "process.env.PROMPT": true,
+ "process.env.PS1": true,
+ "process.env.Path": true,
+ "process.env.SHELL": true,
+ "process.env.USER": true,
+ "process.env.USERDOMAIN": true,
+ "process.env.USERNAME": true,
+ "process.env.VISUAL": true,
+ "process.env.path": true,
+ "process.nextTick": true,
+ "process.platform": true
+ },
"packages": {
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor>kind-of": true
+ "@storybook/core>@storybook/core-server>x-default-browser>default-browser-id>untildify>os-homedir": true,
+ "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": true
}
},
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor>kind-of": {
- "packages": {
- "browserify>insert-module-globals>is-buffer": true
+ "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": {
+ "globals": {
+ "process.env.SystemRoot": true,
+ "process.env.TEMP": true,
+ "process.env.TMP": true,
+ "process.env.TMPDIR": true,
+ "process.env.windir": true,
+ "process.platform": true
}
},
- "gulp-watch>chokidar>anymatch>micromatch>extglob>expand-brackets>extend-shallow": {
+ "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf": {
+ "builtin": {
+ "assert": true,
+ "fs": true,
+ "path.join": true
+ },
+ "globals": {
+ "process.platform": true,
+ "setTimeout": true
+ },
"packages": {
- "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
+ "nyc>glob": true
}
},
- "gulp-watch>chokidar>anymatch>micromatch>extglob>extend-shallow": {
- "packages": {
- "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
+ "gulp-watch>chokidar>fsevents>node-pre-gyp>semver": {
+ "globals": {
+ "console": true,
+ "process": true
}
},
- "gulp-watch>chokidar>anymatch>normalize-path": {
+ "gulp-watch>fancy-log": {
+ "globals": {
+ "console": true,
+ "process.argv.indexOf": true,
+ "process.stderr.write": true,
+ "process.stdout.write": true
+ },
"packages": {
- "vinyl>remove-trailing-separator": true
+ "fancy-log>ansi-gray": true,
+ "fancy-log>color-support": true,
+ "fancy-log>time-stamp": true
}
},
- "gulp-watch>chokidar>async-each": {
+ "gulp-watch>path-is-absolute": {
"globals": {
- "define": true
- }
- },
- "gulp-watch>chokidar>braces": {
- "packages": {
- "gulp-watch>chokidar>braces>array-unique": true,
- "gulp-watch>chokidar>braces>extend-shallow": true,
- "gulp-watch>chokidar>braces>fill-range": true,
- "gulp-watch>chokidar>braces>repeat-element": true,
- "gulp-watch>chokidar>braces>snapdragon": true,
- "gulp-watch>chokidar>braces>snapdragon-node": true,
- "gulp-watch>chokidar>braces>split-string": true,
- "gulp-watch>chokidar>braces>to-regex": true,
- "gulp>gulp-cli>isobject": true,
- "gulp>undertaker>arr-flatten": true
+ "process.platform": true
}
},
- "gulp-watch>chokidar>braces>extend-shallow": {
+ "gulp-watch>readable-stream": {
+ "builtin": {
+ "events.EventEmitter": true,
+ "stream": true,
+ "util": true
+ },
+ "globals": {
+ "process.browser": true,
+ "process.env.READABLE_STREAM": true,
+ "process.stderr": true,
+ "process.stdout": true,
+ "process.version.slice": true,
+ "setImmediate": true
+ },
"packages": {
- "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
+ "gulp-watch>readable-stream>isarray": true,
+ "gulp-watch>readable-stream>safe-buffer": true,
+ "gulp-watch>readable-stream>string_decoder": true,
+ "pumpify>inherits": true,
+ "readable-stream-2>core-util-is": true,
+ "readable-stream-2>process-nextick-args": true,
+ "readable-stream>util-deprecate": true
}
},
- "gulp-watch>chokidar>braces>fill-range": {
+ "gulp-watch>readable-stream>safe-buffer": {
"builtin": {
- "util.inspect": true
- },
- "packages": {
- "gulp-watch>chokidar>braces>fill-range>extend-shallow": true,
- "gulp-watch>chokidar>braces>fill-range>is-number": true,
- "gulp-watch>chokidar>braces>fill-range>to-regex-range": true,
- "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true
+ "buffer": true
}
},
- "gulp-watch>chokidar>braces>fill-range>extend-shallow": {
+ "gulp-watch>readable-stream>string_decoder": {
"packages": {
- "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
+ "gulp-watch>readable-stream>safe-buffer": true
}
},
- "gulp-watch>chokidar>braces>fill-range>is-number": {
+ "gulp-watch>vinyl-file": {
+ "builtin": {
+ "path.resolve": true
+ },
+ "globals": {
+ "process.cwd": true
+ },
"packages": {
- "gulp-watch>chokidar>braces>fill-range>is-number>kind-of": true
+ "del>graceful-fs": true,
+ "gh-pages>globby>pinkie-promise": true,
+ "gulp-watch>vinyl-file>pify": true,
+ "gulp-watch>vinyl-file>strip-bom": true,
+ "gulp-watch>vinyl-file>strip-bom-stream": true,
+ "gulp-watch>vinyl-file>vinyl": true
}
},
- "gulp-watch>chokidar>braces>fill-range>is-number>kind-of": {
+ "gulp-watch>vinyl-file>strip-bom": {
+ "globals": {
+ "Buffer.isBuffer": true
+ },
"packages": {
- "browserify>insert-module-globals>is-buffer": true
+ "gulp>vinyl-fs>remove-bom-buffer>is-utf8": true
}
},
- "gulp-watch>chokidar>braces>fill-range>to-regex-range": {
+ "gulp-watch>vinyl-file>strip-bom-stream": {
"packages": {
- "gulp-watch>chokidar>braces>fill-range>is-number": true,
- "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true
+ "gulp-watch>vinyl-file>strip-bom": true,
+ "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream": true
}
},
- "gulp-watch>chokidar>braces>snapdragon": {
+ "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream": {
"builtin": {
- "fs.readFileSync": true,
- "path.dirname": true,
- "util.inspect": true
+ "util.inherits": true
},
"globals": {
- "__filename": true
+ "Buffer.concat": true,
+ "setImmediate": true
},
"packages": {
- "gulp-watch>chokidar>braces>snapdragon>base": true,
- "gulp-watch>chokidar>braces>snapdragon>debug": true,
- "gulp-watch>chokidar>braces>snapdragon>define-property": true,
- "gulp-watch>chokidar>braces>snapdragon>extend-shallow": true,
- "gulp-watch>chokidar>braces>snapdragon>map-cache": true,
- "gulp-watch>chokidar>braces>snapdragon>source-map": true,
- "gulp-watch>chokidar>braces>snapdragon>use": true,
- "resolve-url-loader>rework>css>source-map-resolve": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon-node": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon-node>define-property": true,
- "gulp-watch>chokidar>braces>snapdragon-node>snapdragon-util": true,
- "gulp>gulp-cli>isobject": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon-node>define-property": {
- "packages": {
- "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon-node>snapdragon-util": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon-node>snapdragon-util>kind-of": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon-node>snapdragon-util>kind-of": {
- "packages": {
- "browserify>insert-module-globals>is-buffer": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base": {
- "builtin": {
- "util.inherits": true
- },
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base": true,
- "gulp-watch>chokidar>braces>snapdragon>base>class-utils": true,
- "gulp-watch>chokidar>braces>snapdragon>base>component-emitter": true,
- "gulp-watch>chokidar>braces>snapdragon>base>define-property": true,
- "gulp-watch>chokidar>braces>snapdragon>base>mixin-deep": true,
- "gulp-watch>chokidar>braces>snapdragon>base>pascalcase": true,
- "gulp>gulp-cli>isobject": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>collection-visit": true,
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>has-value": true,
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>set-value": true,
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>to-object-path": true,
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>union-value": true,
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>unset-value": true,
- "gulp-watch>chokidar>braces>snapdragon>base>component-emitter": true,
- "gulp>gulp-cli>array-sort>get-value": true,
- "gulp>gulp-cli>isobject": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>collection-visit": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>collection-visit>map-visit": true,
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>collection-visit>object-visit": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>collection-visit>map-visit": {
- "builtin": {
- "util.inspect": true
- },
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>collection-visit>object-visit": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>collection-visit>object-visit": {
- "packages": {
- "gulp>gulp-cli>isobject": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>has-value": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>has-value>has-values": true,
- "gulp>gulp-cli>array-sort>get-value": true,
- "gulp>gulp-cli>isobject": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>has-value>has-values": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>has-value>has-values>is-number": true,
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>has-value>has-values>kind-of": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>has-value>has-values>is-number": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>has-value>has-values>is-number>kind-of": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>has-value>has-values>is-number>kind-of": {
- "packages": {
- "browserify>insert-module-globals>is-buffer": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>has-value>has-values>kind-of": {
- "packages": {
- "browserify>insert-module-globals>is-buffer": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>set-value": {
- "packages": {
- "@babel/register>clone-deep>is-plain-object": true,
- "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true,
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>set-value>extend-shallow": true,
- "gulp-watch>chokidar>braces>split-string": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>set-value>extend-shallow": {
- "packages": {
- "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>to-object-path": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>to-object-path>kind-of": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>to-object-path>kind-of": {
- "packages": {
- "browserify>insert-module-globals>is-buffer": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>union-value": {
- "packages": {
- "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true,
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>set-value": true,
- "gulp-zip>plugin-error>arr-union": true,
- "gulp>gulp-cli>array-sort>get-value": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>unset-value": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>unset-value>has-value": true,
- "gulp>gulp-cli>isobject": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>unset-value>has-value": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>unset-value>has-value>has-values": true,
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>unset-value>has-value>isobject": true,
- "gulp>gulp-cli>array-sort>get-value": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>unset-value>has-value>isobject": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>cache-base>unset-value>has-value>isobject>isarray": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>class-utils": {
- "builtin": {
- "util": true
- },
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>class-utils>static-extend": true,
- "gulp-watch>chokidar>braces>snapdragon>define-property": true,
- "gulp-zip>plugin-error>arr-union": true,
- "gulp>gulp-cli>isobject": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>class-utils>static-extend": {
- "builtin": {
- "util.inherits": true
- },
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>class-utils>static-extend>object-copy": true,
- "gulp-watch>chokidar>braces>snapdragon>define-property": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>class-utils>static-extend>object-copy": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>class-utils>static-extend>object-copy>copy-descriptor": true,
- "gulp-watch>chokidar>braces>snapdragon>base>class-utils>static-extend>object-copy>kind-of": true,
- "gulp-watch>chokidar>braces>snapdragon>define-property": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>class-utils>static-extend>object-copy>kind-of": {
- "packages": {
- "browserify>insert-module-globals>is-buffer": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>define-property": {
- "packages": {
- "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>mixin-deep": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>base>mixin-deep>is-extendable": true,
- "gulp>undertaker>object.reduce>for-own>for-in": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>base>mixin-deep>is-extendable": {
- "packages": {
- "@babel/register>clone-deep>is-plain-object": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>debug": {
- "builtin": {
- "fs.SyncWriteStream": true,
- "net.Socket": true,
- "tty.WriteStream": true,
- "tty.isatty": true,
- "util": true
- },
- "globals": {
- "chrome": true,
- "console": true,
- "document": true,
- "localStorage": true,
- "navigator": true,
- "process": true
- },
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>debug>ms": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>define-property": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>define-property>is-descriptor": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>define-property>is-descriptor": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>define-property>is-descriptor>is-accessor-descriptor": true,
- "gulp-watch>chokidar>braces>snapdragon>define-property>is-descriptor>is-data-descriptor": true,
- "gulp-watch>chokidar>braces>snapdragon>define-property>is-descriptor>kind-of": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>define-property>is-descriptor>is-accessor-descriptor": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>define-property>is-descriptor>is-data-descriptor>kind-of": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>define-property>is-descriptor>is-data-descriptor": {
- "packages": {
- "gulp-watch>chokidar>braces>snapdragon>define-property>is-descriptor>is-data-descriptor>kind-of": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>define-property>is-descriptor>is-data-descriptor>kind-of": {
- "packages": {
- "browserify>insert-module-globals>is-buffer": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>extend-shallow": {
- "packages": {
- "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
- }
- },
- "gulp-watch>chokidar>braces>snapdragon>use": {
- "packages": {
- "@babel/register>clone-deep>kind-of": true
- }
- },
- "gulp-watch>chokidar>braces>split-string": {
- "packages": {
- "gulp-zip>plugin-error>extend-shallow": true
- }
- },
- "gulp-watch>chokidar>braces>to-regex": {
- "packages": {
- "gulp-watch>chokidar>braces>to-regex>define-property": true,
- "gulp-watch>chokidar>braces>to-regex>safe-regex": true,
- "gulp-zip>plugin-error>extend-shallow": true,
- "gulp>gulp-cli>matchdep>micromatch>regex-not": true
- }
- },
- "gulp-watch>chokidar>braces>to-regex>define-property": {
- "packages": {
- "gulp>gulp-cli>isobject": true,
- "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true
- }
- },
- "gulp-watch>chokidar>braces>to-regex>safe-regex": {
- "packages": {
- "gulp-watch>chokidar>braces>to-regex>safe-regex>ret": true
- }
- },
- "gulp-watch>chokidar>fsevents": {
- "builtin": {
- "events.EventEmitter": true,
- "fs.stat": true,
- "path.join": true,
- "util.inherits": true
- },
- "globals": {
- "__dirname": true,
- "console.assert": true,
- "process.nextTick": true,
- "process.platform": true,
- "setImmediate": true
- },
- "packages": {
- "gulp-watch>chokidar>fsevents>node-pre-gyp": true
- }
- },
- "gulp-watch>chokidar>fsevents>node-pre-gyp": {
- "builtin": {
- "events.EventEmitter": true,
- "fs.existsSync": true,
- "fs.readFileSync": true,
- "fs.renameSync": true,
- "path.dirname": true,
- "path.existsSync": true,
- "path.join": true,
- "path.resolve": true,
- "url.parse": true,
- "url.resolve": true,
- "util.inherits": true
- },
- "globals": {
- "__dirname": true,
- "console.log": true,
- "process.arch": true,
- "process.cwd": true,
- "process.env": true,
- "process.platform": true,
- "process.version.substr": true,
- "process.versions": true
- },
- "packages": {
- "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog": true,
- "gulp-watch>chokidar>fsevents>node-pre-gyp>detect-libc": true,
- "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt": true,
- "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf": true,
- "gulp-watch>chokidar>fsevents>node-pre-gyp>semver": true
- }
- },
- "gulp-watch>chokidar>fsevents>node-pre-gyp>detect-libc": {
- "builtin": {
- "child_process.spawnSync": true,
- "fs.readdirSync": true,
- "os.platform": true
- },
- "globals": {
- "process.env": true
- }
- },
- "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt": {
- "builtin": {
- "path": true,
- "stream.Stream": true,
- "url": true
- },
- "globals": {
- "console": true,
- "process.argv": true,
- "process.env.DEBUG_NOPT": true,
- "process.env.NOPT_DEBUG": true,
- "process.platform": true
- },
- "packages": {
- "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>nopt>abbrev": true,
- "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv": true
- }
- },
- "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv": {
- "builtin": {
- "child_process.exec": true,
- "path": true
- },
- "globals": {
- "process.env.COMPUTERNAME": true,
- "process.env.ComSpec": true,
- "process.env.EDITOR": true,
- "process.env.HOSTNAME": true,
- "process.env.PATH": true,
- "process.env.PROMPT": true,
- "process.env.PS1": true,
- "process.env.Path": true,
- "process.env.SHELL": true,
- "process.env.USER": true,
- "process.env.USERDOMAIN": true,
- "process.env.USERNAME": true,
- "process.env.VISUAL": true,
- "process.env.path": true,
- "process.nextTick": true,
- "process.platform": true
- },
- "packages": {
- "@storybook/core>@storybook/core-server>x-default-browser>default-browser-id>untildify>os-homedir": true,
- "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": true
- }
- },
- "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": {
- "globals": {
- "process.env.SystemRoot": true,
- "process.env.TEMP": true,
- "process.env.TMP": true,
- "process.env.TMPDIR": true,
- "process.env.windir": true,
- "process.platform": true
- }
- },
- "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf": {
- "builtin": {
- "assert": true,
- "fs": true,
- "path.join": true
- },
- "globals": {
- "process.platform": true,
- "setTimeout": true
- },
- "packages": {
- "nyc>glob": true
- }
- },
- "gulp-watch>chokidar>fsevents>node-pre-gyp>semver": {
- "globals": {
- "console": true,
- "process": true
- }
- },
- "gulp-watch>chokidar>is-binary-path": {
- "builtin": {
- "path.extname": true
- },
- "packages": {
- "gulp-watch>chokidar>is-binary-path>binary-extensions": true
- }
- },
- "gulp-watch>chokidar>readdirp": {
- "builtin": {
- "path.join": true,
- "path.relative": true,
- "util.inherits": true
- },
- "globals": {
- "setImmediate": true
- },
- "packages": {
- "del>graceful-fs": true,
- "gulp-watch>chokidar>anymatch>micromatch": true,
- "gulp-watch>readable-stream": true
- }
- },
- "gulp-watch>chokidar>upath": {
- "builtin": {
- "path": true
- }
- },
- "gulp-watch>fancy-log": {
- "globals": {
- "console": true,
- "process.argv.indexOf": true,
- "process.stderr.write": true,
- "process.stdout.write": true
- },
- "packages": {
- "fancy-log>ansi-gray": true,
- "fancy-log>color-support": true,
- "fancy-log>time-stamp": true
- }
- },
- "gulp-watch>path-is-absolute": {
- "globals": {
- "process.platform": true
- }
- },
- "gulp-watch>readable-stream": {
- "builtin": {
- "events.EventEmitter": true,
- "stream": true,
- "util": true
- },
- "globals": {
- "process.browser": true,
- "process.env.READABLE_STREAM": true,
- "process.stderr": true,
- "process.stdout": true,
- "process.version.slice": true,
- "setImmediate": true
- },
- "packages": {
- "gulp-watch>readable-stream>isarray": true,
- "gulp-watch>readable-stream>safe-buffer": true,
- "gulp-watch>readable-stream>string_decoder": true,
- "pumpify>inherits": true,
- "readable-stream-2>core-util-is": true,
- "readable-stream-2>process-nextick-args": true,
- "readable-stream>util-deprecate": true
- }
- },
- "gulp-watch>readable-stream>safe-buffer": {
- "builtin": {
- "buffer": true
- }
- },
- "gulp-watch>readable-stream>string_decoder": {
- "packages": {
- "gulp-watch>readable-stream>safe-buffer": true
- }
- },
- "gulp-watch>vinyl-file": {
- "builtin": {
- "path.resolve": true
- },
- "globals": {
- "process.cwd": true
- },
- "packages": {
- "del>graceful-fs": true,
- "gh-pages>globby>pinkie-promise": true,
- "gulp-watch>vinyl-file>pify": true,
- "gulp-watch>vinyl-file>strip-bom": true,
- "gulp-watch>vinyl-file>strip-bom-stream": true,
- "gulp-watch>vinyl-file>vinyl": true
- }
- },
- "gulp-watch>vinyl-file>strip-bom": {
- "globals": {
- "Buffer.isBuffer": true
- },
- "packages": {
- "gulp>vinyl-fs>remove-bom-buffer>is-utf8": true
- }
- },
- "gulp-watch>vinyl-file>strip-bom-stream": {
- "packages": {
- "gulp-watch>vinyl-file>strip-bom": true,
- "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream": true
- }
- },
- "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream": {
- "builtin": {
- "util.inherits": true
- },
- "globals": {
- "Buffer.concat": true,
- "setImmediate": true
- },
- "packages": {
- "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream": true
+ "gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream": true
}
},
"gulp-watch>vinyl-file>strip-bom-stream>first-chunk-stream>readable-stream": {
@@ -5368,9 +4845,9 @@
},
"gulp>glob-watcher": {
"packages": {
+ "chokidar": true,
"gulp>glob-watcher>anymatch": true,
"gulp>glob-watcher>async-done": true,
- "gulp>glob-watcher>chokidar": true,
"gulp>glob-watcher>is-negated-glob": true,
"gulp>glob-watcher>just-debounce": true,
"gulp>undertaker>object.defaults": true
@@ -5381,390 +4858,585 @@
"path.sep": true
},
"packages": {
- "gulp>glob-watcher>anymatch>micromatch": true,
- "gulp>glob-watcher>anymatch>normalize-path": true
+ "gulp-watch>anymatch>normalize-path": true,
+ "gulp>glob-watcher>anymatch>micromatch": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch": {
+ "builtin": {
+ "path.basename": true,
+ "path.sep": true,
+ "util.inspect": true
+ },
+ "globals": {
+ "process.platform": true
+ },
+ "packages": {
+ "@babel/register>clone-deep>kind-of": true,
+ "gulp-zip>plugin-error>arr-diff": true,
+ "gulp-zip>plugin-error>extend-shallow": true,
+ "gulp>glob-watcher>anymatch>micromatch>braces": true,
+ "gulp>glob-watcher>anymatch>micromatch>define-property": true,
+ "gulp>glob-watcher>anymatch>micromatch>extglob": true,
+ "gulp>gulp-cli>liftoff>fined>object.pick": true,
+ "gulp>gulp-cli>matchdep>micromatch>array-unique": true,
+ "gulp>gulp-cli>matchdep>micromatch>fragment-cache": true,
+ "gulp>gulp-cli>matchdep>micromatch>nanomatch": true,
+ "gulp>gulp-cli>matchdep>micromatch>regex-not": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon": true,
+ "gulp>gulp-cli>matchdep>micromatch>to-regex": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>braces": {
+ "packages": {
+ "gulp-watch>anymatch>micromatch>braces>repeat-element": true,
+ "gulp>glob-watcher>anymatch>micromatch>braces>extend-shallow": true,
+ "gulp>glob-watcher>anymatch>micromatch>braces>fill-range": true,
+ "gulp>gulp-cli>isobject": true,
+ "gulp>gulp-cli>matchdep>micromatch>array-unique": true,
+ "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node": true,
+ "gulp>gulp-cli>matchdep>micromatch>braces>split-string": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon": true,
+ "gulp>gulp-cli>matchdep>micromatch>to-regex": true,
+ "gulp>undertaker>arr-flatten": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>braces>extend-shallow": {
+ "packages": {
+ "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>braces>fill-range": {
+ "builtin": {
+ "util.inspect": true
+ },
+ "packages": {
+ "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>extend-shallow": true,
+ "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number": true,
+ "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>to-regex-range": true,
+ "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>extend-shallow": {
+ "packages": {
+ "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number": {
+ "packages": {
+ "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number>kind-of": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number>kind-of": {
+ "packages": {
+ "browserify>insert-module-globals>is-buffer": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>to-regex-range": {
+ "packages": {
+ "gulp>glob-watcher>anymatch>micromatch>braces>fill-range>is-number": true,
+ "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>define-property": {
+ "packages": {
+ "gulp>gulp-cli>isobject": true,
+ "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>extglob": {
+ "packages": {
+ "gulp>glob-watcher>anymatch>micromatch>extglob>define-property": true,
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets": true,
+ "gulp>glob-watcher>anymatch>micromatch>extglob>extend-shallow": true,
+ "gulp>gulp-cli>matchdep>micromatch>array-unique": true,
+ "gulp>gulp-cli>matchdep>micromatch>fragment-cache": true,
+ "gulp>gulp-cli>matchdep>micromatch>regex-not": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon": true,
+ "gulp>gulp-cli>matchdep>micromatch>to-regex": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>extglob>define-property": {
+ "packages": {
+ "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets": {
+ "globals": {
+ "__filename": true
+ },
+ "packages": {
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>debug": true,
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property": true,
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>extend-shallow": true,
+ "gulp>gulp-cli>matchdep>micromatch>extglob>expand-brackets>posix-character-classes": true,
+ "gulp>gulp-cli>matchdep>micromatch>regex-not": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon": true,
+ "gulp>gulp-cli>matchdep>micromatch>to-regex": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>debug": {
+ "builtin": {
+ "fs.SyncWriteStream": true,
+ "net.Socket": true,
+ "tty.WriteStream": true,
+ "tty.isatty": true,
+ "util": true
+ },
+ "globals": {
+ "chrome": true,
+ "console": true,
+ "document": true,
+ "localStorage": true,
+ "navigator": true,
+ "process": true
+ },
+ "packages": {
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>debug>ms": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property": {
+ "packages": {
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor": {
+ "packages": {
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor": true,
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor": true,
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>kind-of": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor": {
+ "packages": {
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor>kind-of": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor>kind-of": {
+ "packages": {
+ "browserify>insert-module-globals>is-buffer": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor": {
+ "packages": {
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor>kind-of": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor>kind-of": {
+ "packages": {
+ "browserify>insert-module-globals>is-buffer": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>extend-shallow": {
+ "packages": {
+ "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
+ }
+ },
+ "gulp>glob-watcher>anymatch>micromatch>extglob>extend-shallow": {
+ "packages": {
+ "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
+ }
+ },
+ "gulp>glob-watcher>async-done": {
+ "globals": {
+ "process.nextTick": true
+ },
+ "packages": {
+ "@metamask/object-multiplex>once": true,
+ "duplexify>end-of-stream": true,
+ "gulp>glob-watcher>async-done>stream-exhaust": true,
+ "readable-stream-2>process-nextick-args": true
+ }
+ },
+ "gulp>glob-watcher>async-done>stream-exhaust": {
+ "builtin": {
+ "stream.Writable": true,
+ "util.inherits": true
+ },
+ "globals": {
+ "setImmediate": true
+ }
+ },
+ "gulp>glob-watcher>chokidar": {
+ "packages": {
+ "gulp>glob-watcher>chokidar>fsevents": true
+ }
+ },
+ "gulp>glob-watcher>chokidar>fsevents": {
+ "builtin": {
+ "events.EventEmitter": true,
+ "fs.stat": true,
+ "path.join": true,
+ "util.inherits": true
+ },
+ "globals": {
+ "__dirname": true,
+ "console.assert": true,
+ "process.nextTick": true,
+ "process.platform": true,
+ "setImmediate": true
+ },
+ "packages": {
+ "gulp-watch>chokidar>fsevents>node-pre-gyp": true
+ }
+ },
+ "gulp>glob-watcher>just-debounce": {
+ "globals": {
+ "clearTimeout": true,
+ "setTimeout": true
+ }
+ },
+ "gulp>gulp-cli>liftoff>fined>object.pick": {
+ "packages": {
+ "gulp>gulp-cli>isobject": true
+ }
+ },
+ "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node": {
+ "packages": {
+ "gulp>gulp-cli>isobject": true,
+ "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>define-property": true,
+ "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>snapdragon-util": true
+ }
+ },
+ "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>define-property": {
+ "packages": {
+ "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true
+ }
+ },
+ "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>snapdragon-util": {
+ "packages": {
+ "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>snapdragon-util>kind-of": true
+ }
+ },
+ "gulp>gulp-cli>matchdep>micromatch>braces>snapdragon-node>snapdragon-util>kind-of": {
+ "packages": {
+ "browserify>insert-module-globals>is-buffer": true
+ }
+ },
+ "gulp>gulp-cli>matchdep>micromatch>braces>split-string": {
+ "packages": {
+ "gulp-zip>plugin-error>extend-shallow": true
+ }
+ },
+ "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": {
+ "packages": {
+ "@babel/register>clone-deep>kind-of": true,
+ "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-accessor-descriptor": true,
+ "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-data-descriptor": true
+ }
+ },
+ "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-accessor-descriptor": {
+ "packages": {
+ "@babel/register>clone-deep>kind-of": true
+ }
+ },
+ "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-data-descriptor": {
+ "packages": {
+ "@babel/register>clone-deep>kind-of": true
+ }
+ },
+ "gulp>gulp-cli>matchdep>micromatch>fragment-cache": {
+ "packages": {
+ "gulp>gulp-cli>liftoff>fined>parse-filepath>map-cache": true
}
},
- "gulp>glob-watcher>anymatch>micromatch": {
+ "gulp>gulp-cli>matchdep>micromatch>nanomatch": {
"builtin": {
"path.basename": true,
"path.sep": true,
"util.inspect": true
},
- "globals": {
- "process.platform": true
- },
"packages": {
"@babel/register>clone-deep>kind-of": true,
- "gulp-watch>chokidar>braces>array-unique": true,
- "gulp-watch>chokidar>braces>snapdragon": true,
- "gulp-watch>chokidar>braces>to-regex": true,
"gulp-zip>plugin-error>arr-diff": true,
"gulp-zip>plugin-error>extend-shallow": true,
- "gulp>glob-watcher>anymatch>micromatch>define-property": true,
- "gulp>glob-watcher>anymatch>micromatch>extglob": true,
- "gulp>glob-watcher>chokidar>braces": true,
"gulp>gulp-cli>liftoff>fined>object.pick": true,
+ "gulp>gulp-cli>matchdep>micromatch>array-unique": true,
"gulp>gulp-cli>matchdep>micromatch>fragment-cache": true,
- "gulp>gulp-cli>matchdep>micromatch>nanomatch": true,
- "gulp>gulp-cli>matchdep>micromatch>regex-not": true
+ "gulp>gulp-cli>matchdep>micromatch>nanomatch>define-property": true,
+ "gulp>gulp-cli>matchdep>micromatch>regex-not": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon": true,
+ "gulp>gulp-cli>matchdep>micromatch>to-regex": true,
+ "gulp>gulp-cli>replace-homedir>is-absolute>is-windows": true
}
},
- "gulp>glob-watcher>anymatch>micromatch>define-property": {
+ "gulp>gulp-cli>matchdep>micromatch>nanomatch>define-property": {
"packages": {
"gulp>gulp-cli>isobject": true,
"gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true
}
},
- "gulp>glob-watcher>anymatch>micromatch>extglob": {
- "packages": {
- "gulp-watch>chokidar>braces>array-unique": true,
- "gulp-watch>chokidar>braces>snapdragon": true,
- "gulp-watch>chokidar>braces>to-regex": true,
- "gulp>glob-watcher>anymatch>micromatch>extglob>define-property": true,
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets": true,
- "gulp>glob-watcher>anymatch>micromatch>extglob>extend-shallow": true,
- "gulp>gulp-cli>matchdep>micromatch>fragment-cache": true,
- "gulp>gulp-cli>matchdep>micromatch>regex-not": true
- }
- },
- "gulp>glob-watcher>anymatch>micromatch>extglob>define-property": {
+ "gulp>gulp-cli>matchdep>micromatch>regex-not": {
"packages": {
- "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true
+ "gulp-zip>plugin-error>extend-shallow": true,
+ "gulp>gulp-cli>matchdep>micromatch>to-regex>safe-regex": true
}
},
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon": {
+ "builtin": {
+ "fs.readFileSync": true,
+ "path.dirname": true,
+ "util.inspect": true
+ },
"globals": {
"__filename": true
},
"packages": {
- "gulp-watch>chokidar>braces>snapdragon": true,
- "gulp-watch>chokidar>braces>to-regex": true,
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>debug": true,
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property": true,
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>extend-shallow": true,
- "gulp>gulp-cli>matchdep>micromatch>extglob>expand-brackets>posix-character-classes": true,
- "gulp>gulp-cli>matchdep>micromatch>regex-not": true
+ "gulp>gulp-cli>liftoff>fined>parse-filepath>map-cache": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>debug": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>extend-shallow": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>source-map": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>use": true,
+ "resolve-url-loader>rework>css>source-map-resolve": true
}
},
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>debug": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base": {
"builtin": {
- "fs.SyncWriteStream": true,
- "net.Socket": true,
- "tty.WriteStream": true,
- "tty.isatty": true,
- "util": true
+ "util.inherits": true
},
- "globals": {
- "chrome": true,
- "console": true,
- "document": true,
- "localStorage": true,
- "navigator": true,
- "process": true
+ "packages": {
+ "gulp>gulp-cli>isobject": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>component-emitter": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>define-property": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>mixin-deep": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>pascalcase": true
+ }
+ },
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base": {
+ "packages": {
+ "gulp>gulp-cli>array-sort>get-value": true,
+ "gulp>gulp-cli>isobject": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>to-object-path": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>union-value": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>component-emitter": true
+ }
+ },
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit": {
+ "packages": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>map-visit": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>object-visit": true
+ }
+ },
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>map-visit": {
+ "builtin": {
+ "util.inspect": true
},
"packages": {
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>debug>ms": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>object-visit": true
}
},
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>collection-visit>object-visit": {
"packages": {
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor": true
+ "gulp>gulp-cli>isobject": true
}
},
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value": {
"packages": {
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor": true,
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor": true,
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>kind-of": true
+ "gulp>gulp-cli>array-sort>get-value": true,
+ "gulp>gulp-cli>isobject": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values": true
}
},
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values": {
"packages": {
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor>kind-of": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>is-number": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>kind-of": true
}
},
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-accessor-descriptor>kind-of": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>is-number": {
"packages": {
- "browserify>insert-module-globals>is-buffer": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>is-number>kind-of": true
}
},
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>is-number>kind-of": {
"packages": {
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor>kind-of": true
+ "browserify>insert-module-globals>is-buffer": true
}
},
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>define-property>is-descriptor>is-data-descriptor>kind-of": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>has-value>has-values>kind-of": {
"packages": {
"browserify>insert-module-globals>is-buffer": true
}
},
- "gulp>glob-watcher>anymatch>micromatch>extglob>expand-brackets>extend-shallow": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value": {
"packages": {
- "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
+ "@babel/register>clone-deep>is-plain-object": true,
+ "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true,
+ "gulp>gulp-cli>matchdep>micromatch>braces>split-string": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value>extend-shallow": true
}
},
- "gulp>glob-watcher>anymatch>micromatch>extglob>extend-shallow": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value>extend-shallow": {
"packages": {
"gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
}
},
- "gulp>glob-watcher>anymatch>normalize-path": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>to-object-path": {
"packages": {
- "vinyl>remove-trailing-separator": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>to-object-path>kind-of": true
}
},
- "gulp>glob-watcher>async-done": {
- "globals": {
- "process.nextTick": true
- },
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>to-object-path>kind-of": {
"packages": {
- "@metamask/object-multiplex>once": true,
- "duplexify>end-of-stream": true,
- "gulp>glob-watcher>async-done>stream-exhaust": true,
- "readable-stream-2>process-nextick-args": true
+ "browserify>insert-module-globals>is-buffer": true
}
},
- "gulp>glob-watcher>async-done>stream-exhaust": {
- "builtin": {
- "stream.Writable": true,
- "util.inherits": true
- },
- "globals": {
- "setImmediate": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>union-value": {
+ "packages": {
+ "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true,
+ "gulp-zip>plugin-error>arr-union": true,
+ "gulp>gulp-cli>array-sort>get-value": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>set-value": true
}
},
- "gulp>glob-watcher>chokidar": {
- "builtin": {
- "events.EventEmitter": true,
- "fs": true,
- "path.basename": true,
- "path.dirname": true,
- "path.extname": true,
- "path.join": true,
- "path.relative": true,
- "path.resolve": true,
- "path.sep": true
- },
- "globals": {
- "clearTimeout": true,
- "console.error": true,
- "process.env.CHOKIDAR_INTERVAL": true,
- "process.env.CHOKIDAR_PRINT_FSEVENTS_REQUIRE_ERROR": true,
- "process.env.CHOKIDAR_USEPOLLING": true,
- "process.nextTick": true,
- "process.platform": true,
- "setTimeout": true
- },
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value": {
"packages": {
- "chokidar>normalize-path": true,
- "del>is-glob": true,
- "eslint>glob-parent": true,
- "gulp-watch>chokidar>async-each": true,
- "gulp-watch>path-is-absolute": true,
- "gulp>glob-watcher>anymatch": true,
- "gulp>glob-watcher>chokidar>braces": true,
- "gulp>glob-watcher>chokidar>fsevents": true,
- "gulp>glob-watcher>chokidar>is-binary-path": true,
- "gulp>glob-watcher>chokidar>readdirp": true,
- "gulp>glob-watcher>chokidar>upath": true,
- "pumpify>inherits": true
+ "gulp>gulp-cli>isobject": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value": true
}
},
- "gulp>glob-watcher>chokidar>braces": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value": {
"packages": {
- "gulp-watch>chokidar>braces>array-unique": true,
- "gulp-watch>chokidar>braces>repeat-element": true,
- "gulp-watch>chokidar>braces>snapdragon": true,
- "gulp-watch>chokidar>braces>snapdragon-node": true,
- "gulp-watch>chokidar>braces>split-string": true,
- "gulp-watch>chokidar>braces>to-regex": true,
- "gulp>glob-watcher>chokidar>braces>extend-shallow": true,
- "gulp>glob-watcher>chokidar>braces>fill-range": true,
- "gulp>gulp-cli>isobject": true,
- "gulp>undertaker>arr-flatten": true
+ "gulp>gulp-cli>array-sort>get-value": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value>has-values": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value>isobject": true
}
},
- "gulp>glob-watcher>chokidar>braces>extend-shallow": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value>isobject": {
"packages": {
- "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>cache-base>unset-value>has-value>isobject>isarray": true
}
},
- "gulp>glob-watcher>chokidar>braces>fill-range": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils": {
"builtin": {
- "util.inspect": true
+ "util": true
},
"packages": {
- "gulp>glob-watcher>chokidar>braces>fill-range>extend-shallow": true,
- "gulp>glob-watcher>chokidar>braces>fill-range>is-number": true,
- "gulp>glob-watcher>chokidar>braces>fill-range>to-regex-range": true,
- "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true
+ "gulp-zip>plugin-error>arr-union": true,
+ "gulp>gulp-cli>isobject": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": true
}
},
- "gulp>glob-watcher>chokidar>braces>fill-range>extend-shallow": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend": {
+ "builtin": {
+ "util.inherits": true
+ },
"packages": {
- "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": true
}
},
- "gulp>glob-watcher>chokidar>braces>fill-range>is-number": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy": {
"packages": {
- "gulp>glob-watcher>chokidar>braces>fill-range>is-number>kind-of": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy>copy-descriptor": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy>kind-of": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": true
}
},
- "gulp>glob-watcher>chokidar>braces>fill-range>is-number>kind-of": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>class-utils>static-extend>object-copy>kind-of": {
"packages": {
"browserify>insert-module-globals>is-buffer": true
}
},
- "gulp>glob-watcher>chokidar>braces>fill-range>to-regex-range": {
- "packages": {
- "gulp>glob-watcher>chokidar>braces>fill-range>is-number": true,
- "stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true
- }
- },
- "gulp>glob-watcher>chokidar>fsevents": {
- "builtin": {
- "events.EventEmitter": true,
- "fs.stat": true,
- "path.join": true,
- "util.inherits": true
- },
- "globals": {
- "__dirname": true,
- "console.assert": true,
- "process.nextTick": true,
- "process.platform": true,
- "setImmediate": true
- },
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>define-property": {
"packages": {
- "gulp-watch>chokidar>fsevents>node-pre-gyp": true
+ "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true
}
},
- "gulp>glob-watcher>chokidar>is-binary-path": {
- "builtin": {
- "path.extname": true
- },
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>mixin-deep": {
"packages": {
- "gulp>glob-watcher>chokidar>is-binary-path>binary-extensions": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>mixin-deep>is-extendable": true,
+ "gulp>undertaker>object.reduce>for-own>for-in": true
}
},
- "gulp>glob-watcher>chokidar>readdirp": {
- "builtin": {
- "path.join": true,
- "path.relative": true,
- "util.inherits": true
- },
- "globals": {
- "setImmediate": true
- },
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>base>mixin-deep>is-extendable": {
"packages": {
- "del>graceful-fs": true,
- "gulp>glob-watcher>anymatch>micromatch": true,
- "gulp>glob-watcher>chokidar>readdirp>readable-stream": true
+ "@babel/register>clone-deep>is-plain-object": true
}
},
- "gulp>glob-watcher>chokidar>readdirp>readable-stream": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>debug": {
"builtin": {
- "events.EventEmitter": true,
- "stream": true,
+ "fs.SyncWriteStream": true,
+ "net.Socket": true,
+ "tty.WriteStream": true,
+ "tty.isatty": true,
"util": true
},
"globals": {
- "process.browser": true,
- "process.env.READABLE_STREAM": true,
- "process.stderr": true,
- "process.stdout": true,
- "process.version.slice": true,
- "setImmediate": true
+ "chrome": true,
+ "console": true,
+ "document": true,
+ "localStorage": true,
+ "navigator": true,
+ "process": true
},
"packages": {
- "gulp>glob-watcher>chokidar>readdirp>readable-stream>isarray": true,
- "gulp>glob-watcher>chokidar>readdirp>readable-stream>safe-buffer": true,
- "gulp>glob-watcher>chokidar>readdirp>readable-stream>string_decoder": true,
- "pumpify>inherits": true,
- "readable-stream-2>core-util-is": true,
- "readable-stream-2>process-nextick-args": true,
- "readable-stream>util-deprecate": true
- }
- },
- "gulp>glob-watcher>chokidar>readdirp>readable-stream>safe-buffer": {
- "builtin": {
- "buffer": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>debug>ms": true
}
},
- "gulp>glob-watcher>chokidar>readdirp>readable-stream>string_decoder": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property": {
"packages": {
- "gulp>glob-watcher>chokidar>readdirp>readable-stream>safe-buffer": true
- }
- },
- "gulp>glob-watcher>chokidar>upath": {
- "builtin": {
- "path": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor": true
}
},
- "gulp>glob-watcher>just-debounce": {
- "globals": {
- "clearTimeout": true,
- "setTimeout": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor": {
+ "packages": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-accessor-descriptor": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor": true,
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>kind-of": true
}
},
- "gulp>gulp-cli>liftoff>fined>object.pick": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-accessor-descriptor": {
"packages": {
- "gulp>gulp-cli>isobject": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor>kind-of": true
}
},
- "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor": {
"packages": {
- "@babel/register>clone-deep>kind-of": true,
- "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-accessor-descriptor": true,
- "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-data-descriptor": true
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor>kind-of": true
}
},
- "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-accessor-descriptor": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>define-property>is-descriptor>is-data-descriptor>kind-of": {
"packages": {
- "@babel/register>clone-deep>kind-of": true
+ "browserify>insert-module-globals>is-buffer": true
}
},
- "gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor>is-data-descriptor": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>extend-shallow": {
"packages": {
- "@babel/register>clone-deep>kind-of": true
+ "gulp-watch>anymatch>micromatch>object.omit>is-extendable": true
}
},
- "gulp>gulp-cli>matchdep>micromatch>fragment-cache": {
+ "gulp>gulp-cli>matchdep>micromatch>snapdragon>use": {
"packages": {
- "gulp-watch>chokidar>braces>snapdragon>map-cache": true
+ "@babel/register>clone-deep>kind-of": true
}
},
- "gulp>gulp-cli>matchdep>micromatch>nanomatch": {
- "builtin": {
- "path.basename": true,
- "path.sep": true,
- "util.inspect": true
- },
+ "gulp>gulp-cli>matchdep>micromatch>to-regex": {
"packages": {
- "@babel/register>clone-deep>kind-of": true,
- "gulp-watch>chokidar>braces>array-unique": true,
- "gulp-watch>chokidar>braces>snapdragon": true,
- "gulp-watch>chokidar>braces>to-regex": true,
- "gulp-zip>plugin-error>arr-diff": true,
"gulp-zip>plugin-error>extend-shallow": true,
- "gulp>gulp-cli>liftoff>fined>object.pick": true,
- "gulp>gulp-cli>matchdep>micromatch>fragment-cache": true,
- "gulp>gulp-cli>matchdep>micromatch>nanomatch>define-property": true,
"gulp>gulp-cli>matchdep>micromatch>regex-not": true,
- "gulp>gulp-cli>replace-homedir>is-absolute>is-windows": true
+ "gulp>gulp-cli>matchdep>micromatch>to-regex>define-property": true,
+ "gulp>gulp-cli>matchdep>micromatch>to-regex>safe-regex": true
}
},
- "gulp>gulp-cli>matchdep>micromatch>nanomatch>define-property": {
+ "gulp>gulp-cli>matchdep>micromatch>to-regex>define-property": {
"packages": {
"gulp>gulp-cli>isobject": true,
"gulp>gulp-cli>matchdep>micromatch>define-property>is-descriptor": true
}
},
- "gulp>gulp-cli>matchdep>micromatch>regex-not": {
+ "gulp>gulp-cli>matchdep>micromatch>to-regex>safe-regex": {
"packages": {
- "gulp-watch>chokidar>braces>to-regex>safe-regex": true,
- "gulp-zip>plugin-error>extend-shallow": true
+ "gulp>gulp-cli>matchdep>micromatch>to-regex>safe-regex>ret": true
}
},
"gulp>gulp-cli>replace-homedir>is-absolute": {
@@ -6505,9 +6177,9 @@
"packages": {
"del>graceful-fs": true,
"gulp-sourcemaps>convert-source-map": true,
+ "gulp-watch>anymatch>normalize-path": true,
"gulp>vinyl-fs>remove-bom-buffer": true,
"gulp>vinyl-fs>vinyl-sourcemap>append-buffer": true,
- "gulp>vinyl-fs>vinyl-sourcemap>normalize-path": true,
"gulp>vinyl-fs>vinyl-sourcemap>now-and-later": true,
"vinyl": true
}
@@ -6528,11 +6200,6 @@
"buffer.Buffer.isBuffer": true
}
},
- "gulp>vinyl-fs>vinyl-sourcemap>normalize-path": {
- "packages": {
- "vinyl>remove-trailing-separator": true
- }
- },
"gulp>vinyl-fs>vinyl-sourcemap>now-and-later": {
"packages": {
"@metamask/object-multiplex>once": true
diff --git a/package.json b/package.json
index f1c90da5fc8a..f9dbb8dbeb77 100644
--- a/package.json
+++ b/package.json
@@ -125,6 +125,7 @@
"attributions:generate": "./development/generate-attributions.sh"
},
"resolutions": {
+ "chokidar": "^3.6.0",
"simple-update-notifier@^1.0.0": "^2.0.0",
"@babel/core": "patch:@babel/core@npm%3A7.23.2#~/.yarn/patches/@babel-core-npm-7.23.2-b93f586907.patch",
"@types/react": "^16.9.53",
@@ -288,7 +289,7 @@
"@metamask/address-book-controller": "^4.0.1",
"@metamask/announcement-controller": "^6.1.0",
"@metamask/approval-controller": "^7.0.0",
- "@metamask/assets-controllers": "^33.0.0",
+ "@metamask/assets-controllers": "^34.0.0",
"@metamask/base-controller": "^5.0.1",
"@metamask/browser-passworder": "^4.3.0",
"@metamask/contract-metadata": "^2.5.0",
@@ -528,7 +529,7 @@
"bify-module-groups": "^2.0.0",
"browserify": "^17.0.0",
"chalk": "^4.1.2",
- "chokidar": "^3.5.3",
+ "chokidar": "^3.6.0",
"concurrently": "^8.2.2",
"copy-webpack-plugin": "^12.0.2",
"cross-spawn": "^7.0.3",
diff --git a/privacy-snapshot.json b/privacy-snapshot.json
index b45cf79a6e8f..fe6579bfab73 100644
--- a/privacy-snapshot.json
+++ b/privacy-snapshot.json
@@ -30,6 +30,8 @@
"phishing-detection.api.cx.metamask.io",
"portfolio.metamask.io",
"price.api.cx.metamask.io",
+ "on-ramp-content.api.cx.metamask.io",
+ "on-ramp-content.uat-api.cx.metamask.io",
"proxy.api.cx.metamask.io",
"raw.githubusercontent.com",
"registry.npmjs.org",
diff --git a/shared/constants/bridge.ts b/shared/constants/bridge.ts
index e02c992bbbba..6ac9cfd4245e 100644
--- a/shared/constants/bridge.ts
+++ b/shared/constants/bridge.ts
@@ -1,5 +1,6 @@
import { CHAIN_IDS } from './network';
+// TODO read from feature flags
export const ALLOWED_BRIDGE_CHAIN_IDS = [
CHAIN_IDS.MAINNET,
CHAIN_IDS.BSC,
@@ -11,3 +12,11 @@ export const ALLOWED_BRIDGE_CHAIN_IDS = [
CHAIN_IDS.LINEA_MAINNET,
CHAIN_IDS.BASE,
];
+
+const BRIDGE_DEV_API_BASE_URL = 'https://bridge.dev-api.cx.metamask.io';
+const BRIDGE_PROD_API_BASE_URL = 'https://bridge.api.cx.metamask.io';
+export const BRIDGE_API_BASE_URL = process.env.BRIDGE_USE_DEV_APIS
+ ? BRIDGE_DEV_API_BASE_URL
+ : BRIDGE_PROD_API_BASE_URL;
+
+export const BRIDGE_CLIENT_ID = 'extension';
diff --git a/shared/constants/methods-tags.ts b/shared/constants/methods-tags.ts
index a770fd733cd2..89cce5f67c8d 100644
--- a/shared/constants/methods-tags.ts
+++ b/shared/constants/methods-tags.ts
@@ -12,6 +12,8 @@ export const methodsRequiringNetworkSwitch = [
'wallet_switchEthereumChain',
'wallet_addEthereumChain',
'wallet_watchAsset',
+ 'eth_signTypedData',
+ 'eth_signTypedData_v3',
'eth_signTypedData_v4',
'personal_sign',
] as const;
diff --git a/shared/constants/multichain/assets.ts b/shared/constants/multichain/assets.ts
index b4b2a74f6c22..988a9fbad624 100644
--- a/shared/constants/multichain/assets.ts
+++ b/shared/constants/multichain/assets.ts
@@ -3,3 +3,8 @@ import { MultichainNetworks } from './networks';
export const MULTICHAIN_NATIVE_CURRENCY_TO_CAIP19 = {
BTC: `${MultichainNetworks.BITCOIN}/slip44:0`,
} as const;
+
+export enum MultichainNativeAssets {
+ BITCOIN = `${MultichainNetworks.BITCOIN}/slip44:0`,
+ BITCOIN_TESTNET = `${MultichainNetworks.BITCOIN_TESTNET}/slip44:0`,
+}
diff --git a/shared/constants/multichain/networks.ts b/shared/constants/multichain/networks.ts
index de5e50639374..7f629ca49c6b 100644
--- a/shared/constants/multichain/networks.ts
+++ b/shared/constants/multichain/networks.ts
@@ -1,12 +1,17 @@
import { ProviderConfig } from '@metamask/network-controller';
import { CaipChainId } from '@metamask/utils';
+import { isBtcMainnetAddress, isBtcTestnetAddress } from '../../lib/multichain';
export type ProviderConfigWithImageUrl = Omit & {
rpcPrefs?: { imageUrl?: string };
};
export type MultichainProviderConfig = ProviderConfigWithImageUrl & {
+ nickname: string;
chainId: CaipChainId;
+ // NOTE: For now we use a callback to check if the address is compatible with
+ // the given network or not
+ isAddressCompatible: (address: string) => boolean;
};
export enum MultichainNetworks {
@@ -34,5 +39,18 @@ export const MULTICHAIN_PROVIDER_CONFIGS: Record<
rpcPrefs: {
imageUrl: MULTICHAIN_TOKEN_IMAGE_MAP[MultichainNetworks.BITCOIN],
},
+ isAddressCompatible: isBtcMainnetAddress,
+ },
+ [MultichainNetworks.BITCOIN_TESTNET]: {
+ chainId: MultichainNetworks.BITCOIN_TESTNET,
+ rpcUrl: '', // not used
+ ticker: 'BTC',
+ nickname: 'Bitcoin (testnet)',
+ id: 'btc-testnet',
+ type: 'rpc',
+ rpcPrefs: {
+ imageUrl: MULTICHAIN_TOKEN_IMAGE_MAP[MultichainNetworks.BITCOIN],
+ },
+ isAddressCompatible: isBtcTestnetAddress,
},
};
diff --git a/shared/constants/network.ts b/shared/constants/network.ts
index 452f0584ffaa..955c9b2decc2 100644
--- a/shared/constants/network.ts
+++ b/shared/constants/network.ts
@@ -66,22 +66,6 @@ export type RPCDefinition = {
rpcPrefs: RPCPreferences;
};
-/**
- * For each chain that we support fiat onramps for, we provide a set of
- * configuration options that help for initializing the connectiong to the
- * onramp providers.
- */
-type BuyableChainSettings = {
- /**
- * The native currency for the given chain
- */
- nativeCurrency: CurrencySymbol | TestNetworkCurrencySymbol;
- /**
- * The network name or identifier
- */
- network: string;
-};
-
/**
* Throughout the extension we set the current provider by referencing its
* "type", which can be any of the values in the below object. These values
@@ -296,6 +280,7 @@ 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',
@@ -908,108 +893,6 @@ export const UNSUPPORTED_RPC_METHODS = new Set([
export const IPFS_DEFAULT_GATEWAY_URL = 'dweb.link';
-// The first item in transakCurrencies must be the
-// default crypto currency for the network
-const BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME = 'ethereum';
-
-export const BUYABLE_CHAINS_MAP: {
- [K in Exclude<
- ChainId,
- | typeof CHAIN_IDS.LOCALHOST
- | typeof CHAIN_IDS.OPTIMISM_TESTNET
- | typeof CHAIN_IDS.OPTIMISM_GOERLI
- | typeof CHAIN_IDS.BASE_TESTNET
- | typeof CHAIN_IDS.OPBNB_TESTNET
- | typeof CHAIN_IDS.OPBNB
- | typeof CHAIN_IDS.BSC_TESTNET
- | typeof CHAIN_IDS.POLYGON_TESTNET
- | typeof CHAIN_IDS.AVALANCHE_TESTNET
- | typeof CHAIN_IDS.FANTOM_TESTNET
- | typeof CHAIN_IDS.MOONBEAM_TESTNET
- | typeof CHAIN_IDS.LINEA_GOERLI
- | typeof CHAIN_IDS.LINEA_SEPOLIA
- | typeof CHAIN_IDS.GOERLI
- | typeof CHAIN_IDS.SEPOLIA
- | typeof CHAIN_IDS.GNOSIS
- | typeof CHAIN_IDS.AURORA
- | typeof CHAIN_IDS.ARBITRUM_GOERLI
- | typeof CHAIN_IDS.BLAST
- | typeof CHAIN_IDS.FILECOIN
- | typeof CHAIN_IDS.POLYGON_ZKEVM
- | typeof CHAIN_IDS.SCROLL
- | typeof CHAIN_IDS.SCROLL_SEPOLIA
- | typeof CHAIN_IDS.WETHIO
- | typeof CHAIN_IDS.CHZ
- | typeof CHAIN_IDS.NUMBERS
- | typeof CHAIN_IDS.SEI
- >]: BuyableChainSettings;
-} = {
- [CHAIN_IDS.MAINNET]: {
- nativeCurrency: CURRENCY_SYMBOLS.ETH,
- network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME,
- },
- [CHAIN_IDS.BSC]: {
- nativeCurrency: CURRENCY_SYMBOLS.BNB,
- network: 'bsc',
- },
- [CHAIN_IDS.POLYGON]: {
- nativeCurrency: CURRENCY_SYMBOLS.MATIC,
- network: 'polygon',
- },
- [CHAIN_IDS.AVALANCHE]: {
- nativeCurrency: CURRENCY_SYMBOLS.AVALANCHE,
- network: 'avaxcchain',
- },
- [CHAIN_IDS.FANTOM]: {
- nativeCurrency: CURRENCY_SYMBOLS.FANTOM,
- network: 'fantom',
- },
- [CHAIN_IDS.CELO]: {
- nativeCurrency: CURRENCY_SYMBOLS.CELO,
- network: 'celo',
- },
- [CHAIN_IDS.OPTIMISM]: {
- nativeCurrency: CURRENCY_SYMBOLS.ETH,
- network: 'optimism',
- },
- [CHAIN_IDS.ARBITRUM]: {
- nativeCurrency: CURRENCY_SYMBOLS.ARBITRUM,
- network: 'arbitrum',
- },
- [CHAIN_IDS.CRONOS]: {
- nativeCurrency: CURRENCY_SYMBOLS.CRONOS,
- network: 'cronos',
- },
- [CHAIN_IDS.MOONBEAM]: {
- nativeCurrency: CURRENCY_SYMBOLS.GLIMMER,
- network: 'moonbeam',
- },
- [CHAIN_IDS.MOONRIVER]: {
- nativeCurrency: CURRENCY_SYMBOLS.MOONRIVER,
- network: 'moonriver',
- },
- [CHAIN_IDS.HARMONY]: {
- nativeCurrency: CURRENCY_SYMBOLS.ONE,
- network: 'harmony',
- },
- [CHAIN_IDS.PALM]: {
- nativeCurrency: CURRENCY_SYMBOLS.PALM,
- network: 'palm',
- },
- [CHAIN_IDS.LINEA_MAINNET]: {
- nativeCurrency: CURRENCY_SYMBOLS.ETH,
- network: 'linea',
- },
- [CHAIN_IDS.ZKSYNC_ERA]: {
- nativeCurrency: CURRENCY_SYMBOLS.ETH,
- network: 'zksync',
- },
- [CHAIN_IDS.BASE]: {
- nativeCurrency: CURRENCY_SYMBOLS.ETH,
- network: 'base',
- },
-};
-
export const FEATURED_RPCS: RPCDefinition[] = [
{
chainId: CHAIN_IDS.ARBITRUM,
diff --git a/shared/lib/multichain.test.ts b/shared/lib/multichain.test.ts
new file mode 100644
index 000000000000..6c59f506e721
--- /dev/null
+++ b/shared/lib/multichain.test.ts
@@ -0,0 +1,49 @@
+import { isBtcMainnetAddress, isBtcTestnetAddress } from './multichain';
+
+const MAINNET_ADDRESSES = [
+ // P2WPKH
+ 'bc1qwl8399fz829uqvqly9tcatgrgtwp3udnhxfq4k',
+ // P2PKH
+ '1P5ZEDWTKTFGxQjZphgWPQUpe554WKDfHQ',
+];
+
+const TESTNET_ADDRESSES = [
+ // P2WPKH
+ 'tb1q6rmsq3vlfdhjdhtkxlqtuhhlr6pmj09y6w43g8',
+];
+
+const ETH_ADDRESSES = ['0x6431726EEE67570BF6f0Cf892aE0a3988F03903F'];
+
+describe('multichain', () => {
+ // @ts-expect-error This is missing from the Mocha type definitions
+ it.each(MAINNET_ADDRESSES)(
+ 'returns true if address is compatible with BTC mainnet: %s',
+ (address: string) => {
+ expect(isBtcMainnetAddress(address)).toBe(true);
+ },
+ );
+
+ // @ts-expect-error This is missing from the Mocha type definitions
+ it.each([...TESTNET_ADDRESSES, ...ETH_ADDRESSES])(
+ 'returns false if address is not compatible with BTC mainnet: %s',
+ (address: string) => {
+ expect(isBtcMainnetAddress(address)).toBe(false);
+ },
+ );
+
+ // @ts-expect-error This is missing from the Mocha type definitions
+ it.each(TESTNET_ADDRESSES)(
+ 'returns true if address is compatible with BTC testnet: %s',
+ (address: string) => {
+ expect(isBtcTestnetAddress(address)).toBe(true);
+ },
+ );
+
+ // @ts-expect-error This is missing from the Mocha type definitions
+ it.each([...MAINNET_ADDRESSES, ...ETH_ADDRESSES])(
+ 'returns false if address is compatible with BTC testnet: %s',
+ (address: string) => {
+ expect(isBtcTestnetAddress(address)).toBe(false);
+ },
+ );
+});
diff --git a/shared/lib/multichain.ts b/shared/lib/multichain.ts
new file mode 100644
index 000000000000..07466e439266
--- /dev/null
+++ b/shared/lib/multichain.ts
@@ -0,0 +1,31 @@
+import { isEthAddress } from '../../app/scripts/lib/multichain/address';
+
+/**
+ * Returns whether an address is on the Bitcoin mainnet.
+ *
+ * This function only checks the prefix of the address to determine if it's on
+ * the mainnet or not. It doesn't validate the address itself, and should only
+ * be used as a temporary solution until this information is included in the
+ * account object.
+ *
+ * @param address - The address to check.
+ * @returns `true` if the address is on the Bitcoin mainnet, `false` otherwise.
+ */
+export function isBtcMainnetAddress(address: string): boolean {
+ return (
+ !isEthAddress(address) &&
+ (address.startsWith('bc1') || address.startsWith('1'))
+ );
+}
+
+/**
+ * Returns whether an address is on the Bitcoin testnet.
+ *
+ * See {@link isBtcMainnetAddress} for implementation details.
+ *
+ * @param address - The address to check.
+ * @returns `true` if the address is on the Bitcoin testnet, `false` otherwise.
+ */
+export function isBtcTestnetAddress(address: string): boolean {
+ return !isEthAddress(address) && !isBtcMainnetAddress(address);
+}
diff --git a/shared/lib/transactions-controller-utils.js b/shared/lib/transactions-controller-utils.js
index 425ff33b6f32..073ff922af67 100644
--- a/shared/lib/transactions-controller-utils.js
+++ b/shared/lib/transactions-controller-utils.js
@@ -9,6 +9,9 @@ export const TOKEN_TRANSFER_LOG_TOPIC_HASH =
export const TRANSACTION_NO_CONTRACT_ERROR_KEY = 'transactionErrorNoContract';
+export const TRANSFER_SINFLE_LOG_TOPIC_HASH =
+ '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62';
+
export const TEN_SECONDS_IN_MILLISECONDS = 10_000;
export function calcGasTotal(gasLimit = '0', gasPrice = '0') {
diff --git a/sonar-project.properties b/sonar-project.properties
index de14094b965e..0455fa9634e2 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -1,18 +1,14 @@
-sonar.projectKey=metamask-extension
-sonar.organization=consensys
+sonar.projectKey=metamask-extension-private
+sonar.organization=metamask
-# This is the name and version displayed in the SonarCloud UI.
-sonar.projectName=MetaMask Extension
-#sonar.projectVersion=1.0
+# Source
+sonar.sources=app,development,offscreen,shared,types,ui
+sonar.exclusions=**/*.test.**,**/*.spec.**,app/images
-# Root for sonar analysis.
-sonar.sources=app/
+# Tests
+sonar.tests=app,test,development,offscreen,shared,types,ui
+sonar.test.inclusions=**/*.test.**,**/*.spec.**
+sonar.javascript.lcov.reportPaths=tests/coverage/lcov.info
-# Excluded project files from analysis.
-#sonar.exclusions=
-
-# Inclusions for test files.
-sonar.test.inclusions=**.test.**
-
-# Encoding of the source code. Default is default system encoding
-sonar.sourceEncoding=UTF-8
+# Fail CI job if quality gate failures
+sonar.qualitygate.wait=false
\ No newline at end of file
diff --git a/test/data/confirmations/personal_sign.ts b/test/data/confirmations/personal_sign.ts
index bac5b3f9838a..6ac82dfe57bb 100644
--- a/test/data/confirmations/personal_sign.ts
+++ b/test/data/confirmations/personal_sign.ts
@@ -35,15 +35,20 @@ export const signatureRequestSIWE = {
siwe: {
isSIWEMessage: true,
parsedMessage: {
- domain: 'metamask.github.io',
address: '0x935e73edb9ff52e23bac7f7e049a1ecd06d05477',
+ chainId: 1,
+ domain: 'metamask.github.io',
+ expirationTime: null,
+ issuedAt: '2021-09-30T16:25:24.000Z',
+ nonce: '32891757',
+ notBefore: '2022-03-17T12:45:13.610Z',
+ requestId: 'some_id',
+ scheme: null,
statement:
'I accept the MetaMask Terms of Service: https://community.metamask.io/tos',
uri: 'https://metamask.github.io',
version: '1',
- chainId: 1,
- nonce: '32891757',
- issuedAt: '2021-09-30T16:25:24.000Z',
+ resources: null,
},
},
},
@@ -67,17 +72,19 @@ export const SignatureRequestSIWEWithResources = {
siwe: {
isSIWEMessage: true,
parsedMessage: {
- domain: 'metamask.github.io',
address: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477',
- statement:
- 'I accept the MetaMask Terms of Service: https://community.metamask.io/tos',
- uri: 'https://metamask.github.io',
- version: '1',
chainId: 1,
- nonce: '32891757',
+ domain: 'metamask.github.io',
+ expirationTime: null,
issuedAt: '2021-09-30T16:25:24.000Z',
+ nonce: '32891757',
notBefore: '2022-03-17T12:45:13.610Z',
requestId: 'some_id',
+ scheme: null,
+ statement:
+ 'I accept the MetaMask Terms of Service: https://community.metamask.io/tos',
+ uri: 'https://metamask.github.io',
+ version: '1',
resources: [
'ipfs://Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu',
'https://example.com/my-web2-claim.json',
diff --git a/test/data/mock-accounts.ts b/test/data/mock-accounts.ts
index ff6009ebd555..2273915f7a5f 100644
--- a/test/data/mock-accounts.ts
+++ b/test/data/mock-accounts.ts
@@ -40,7 +40,7 @@ export const MOCK_ACCOUNT_ERC4337: InternalAccount = {
export const MOCK_ACCOUNT_BIP122_P2WPKH: InternalAccount = {
id: 'ae247df6-3911-47f7-9e36-28e6a7d96078',
- address: 'bc1qaabb',
+ address: 'bc1qwl8399fz829uqvqly9tcatgrgtwp3udnhxfq4k',
options: {},
methods: [BtcMethod.SendMany],
type: BtcAccountType.P2wpkh,
@@ -52,8 +52,23 @@ export const MOCK_ACCOUNT_BIP122_P2WPKH: InternalAccount = {
},
};
+export const MOCK_ACCOUNT_BIP122_P2WPKH_TESTNET: InternalAccount = {
+ id: 'fcdafe8b-4bdf-4e25-9051-e255b2a0af5f',
+ address: 'tb1q6rmsq3vlfdhjdhtkxlqtuhhlr6pmj09y6w43g8',
+ options: {},
+ methods: [BtcMethod.SendMany],
+ type: BtcAccountType.P2wpkh,
+ metadata: {
+ name: 'Bitcoin Testnet Account',
+ keyring: { type: KeyringTypes.snap },
+ importTime: 1691565967600,
+ lastSelected: 1955565967656,
+ },
+};
+
export const MOCK_ACCOUNTS = {
[MOCK_ACCOUNT_EOA.id]: MOCK_ACCOUNT_EOA,
[MOCK_ACCOUNT_ERC4337.id]: MOCK_ACCOUNT_ERC4337,
[MOCK_ACCOUNT_BIP122_P2WPKH.id]: MOCK_ACCOUNT_BIP122_P2WPKH,
+ [MOCK_ACCOUNT_BIP122_P2WPKH_TESTNET.id]: MOCK_ACCOUNT_BIP122_P2WPKH_TESTNET,
};
diff --git a/test/data/mock-send-state.json b/test/data/mock-send-state.json
index c8604c96c055..5beee30824ce 100644
--- a/test/data/mock-send-state.json
+++ b/test/data/mock-send-state.json
@@ -1242,6 +1242,143 @@
],
"swapsState": {}
},
+ "ramps": {
+ "buyableChains": [
+ {
+ "active": true,
+ "chainId": 1,
+ "chainName": "Ethereum Mainnet",
+ "shortName": "Ethereum",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 10,
+ "chainName": "Optimism Mainnet",
+ "shortName": "Optimism",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 25,
+ "chainName": "Cronos Mainnet",
+ "shortName": "Cronos",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 56,
+ "chainName": "BNB Chain Mainnet",
+ "shortName": "BNB Chain",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 100,
+ "chainName": "Gnosis Mainnet",
+ "shortName": "Gnosis",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 137,
+ "chainName": "Polygon Mainnet",
+ "shortName": "Polygon",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 250,
+ "chainName": "Fantom Mainnet",
+ "shortName": "Fantom",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 324,
+ "chainName": "zkSync Era Mainnet",
+ "shortName": "zkSync Era",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 1101,
+ "chainName": "Polygon zkEVM",
+ "shortName": "Polygon zkEVM",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 1284,
+ "chainName": "Moonbeam Mainnet",
+ "shortName": "Moonbeam",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 1285,
+ "chainName": "Moonriver Mainnet",
+ "shortName": "Moonriver",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 8453,
+ "chainName": "Base Mainnet",
+ "shortName": "Base",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 42161,
+ "chainName": "Arbitrum Mainnet",
+ "shortName": "Arbitrum",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 42220,
+ "chainName": "Celo Mainnet",
+ "shortName": "Celo",
+ "nativeTokenSupported": false
+ },
+ {
+ "active": true,
+ "chainId": 43114,
+ "chainName": "Avalanche C-Chain Mainnet",
+ "shortName": "Avalanche C-Chain",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 59144,
+ "chainName": "Linea",
+ "shortName": "Linea",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 1313161554,
+ "chainName": "Aurora Mainnet",
+ "shortName": "Aurora",
+ "nativeTokenSupported": false
+ },
+ {
+ "active": true,
+ "chainId": 1666600000,
+ "chainName": "Harmony Mainnet (Shard 0)",
+ "shortName": "Harmony (Shard 0)",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 11297108109,
+ "chainName": "Palm Mainnet",
+ "shortName": "Palm",
+ "nativeTokenSupported": false
+ }
+ ]
+ },
"send": {
"amountMode": "INPUT",
"currentTransactionUUID": "1-tx",
diff --git a/test/data/mock-state.json b/test/data/mock-state.json
index 920533023a58..9e595ad8f0e0 100644
--- a/test/data/mock-state.json
+++ b/test/data/mock-state.json
@@ -1943,6 +1943,143 @@
}
}
},
+ "ramps": {
+ "buyableChains": [
+ {
+ "active": true,
+ "chainId": 1,
+ "chainName": "Ethereum Mainnet",
+ "shortName": "Ethereum",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 10,
+ "chainName": "Optimism Mainnet",
+ "shortName": "Optimism",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 25,
+ "chainName": "Cronos Mainnet",
+ "shortName": "Cronos",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 56,
+ "chainName": "BNB Chain Mainnet",
+ "shortName": "BNB Chain",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 100,
+ "chainName": "Gnosis Mainnet",
+ "shortName": "Gnosis",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 137,
+ "chainName": "Polygon Mainnet",
+ "shortName": "Polygon",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 250,
+ "chainName": "Fantom Mainnet",
+ "shortName": "Fantom",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 324,
+ "chainName": "zkSync Era Mainnet",
+ "shortName": "zkSync Era",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 1101,
+ "chainName": "Polygon zkEVM",
+ "shortName": "Polygon zkEVM",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 1284,
+ "chainName": "Moonbeam Mainnet",
+ "shortName": "Moonbeam",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 1285,
+ "chainName": "Moonriver Mainnet",
+ "shortName": "Moonriver",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 8453,
+ "chainName": "Base Mainnet",
+ "shortName": "Base",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 42161,
+ "chainName": "Arbitrum Mainnet",
+ "shortName": "Arbitrum",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 42220,
+ "chainName": "Celo Mainnet",
+ "shortName": "Celo",
+ "nativeTokenSupported": false
+ },
+ {
+ "active": true,
+ "chainId": 43114,
+ "chainName": "Avalanche C-Chain Mainnet",
+ "shortName": "Avalanche C-Chain",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 59144,
+ "chainName": "Linea",
+ "shortName": "Linea",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 1313161554,
+ "chainName": "Aurora Mainnet",
+ "shortName": "Aurora",
+ "nativeTokenSupported": false
+ },
+ {
+ "active": true,
+ "chainId": 1666600000,
+ "chainName": "Harmony Mainnet (Shard 0)",
+ "shortName": "Harmony (Shard 0)",
+ "nativeTokenSupported": true
+ },
+ {
+ "active": true,
+ "chainId": 11297108109,
+ "chainName": "Palm Mainnet",
+ "shortName": "Palm",
+ "nativeTokenSupported": false
+ }
+ ]
+ },
"send": {
"amountMode": "INPUT",
"currentTransactionUUID": null,
diff --git a/test/e2e/changedFilesUtil.js b/test/e2e/changedFilesUtil.js
new file mode 100644
index 000000000000..5ead76203db0
--- /dev/null
+++ b/test/e2e/changedFilesUtil.js
@@ -0,0 +1,44 @@
+const fs = require('fs').promises;
+const path = require('path');
+
+const BASE_PATH = path.resolve(__dirname, '..', '..');
+const CHANGED_FILES_PATH = path.join(
+ BASE_PATH,
+ 'changed-files',
+ 'changed-files.txt',
+);
+
+/**
+ * Reads the list of changed files from the git diff file.
+ *
+ * @returns {Promise} An array of changed file paths.
+ */
+async function readChangedFiles() {
+ try {
+ const data = await fs.readFile(CHANGED_FILES_PATH, 'utf8');
+ const changedFiles = data.split('\n');
+ return changedFiles;
+ } catch (error) {
+ console.error('Error reading from file:', error);
+ return '';
+ }
+}
+
+/**
+ * Filters the list of changed files to include only E2E test files within the 'test/e2e/' directory.
+ *
+ * @returns {Promise} An array of filtered E2E test file paths.
+ */
+async function filterE2eChangedFiles() {
+ const changedFiles = await readChangedFiles();
+ const e2eChangedFiles = changedFiles
+ .filter(
+ (file) =>
+ file.startsWith('test/e2e/') &&
+ (file.endsWith('.spec.js') || file.endsWith('.spec.ts')),
+ )
+ .map((file) => `${BASE_PATH}/${file}`);
+ return e2eChangedFiles;
+}
+
+module.exports = { filterE2eChangedFiles };
diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js
index 41951f973aa9..ba32c94cabec 100644
--- a/test/e2e/helpers.js
+++ b/test/e2e/helpers.js
@@ -664,6 +664,7 @@ const closeSRPReveal = async (driver) => {
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';
+const DAPP_TWO_URL = 'http://127.0.0.1:8082';
const openDapp = async (driver, contract = null, dappURL = DAPP_URL) => {
return contract
@@ -1121,6 +1122,7 @@ module.exports = {
DAPP_HOST_ADDRESS,
DAPP_URL,
DAPP_ONE_URL,
+ DAPP_TWO_URL,
TEST_SEED_PHRASE,
TEST_SEED_PHRASE_TWO,
PRIVATE_KEY,
diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js
index f3279377114d..50bd7633f8d3 100644
--- a/test/e2e/mock-e2e.js
+++ b/test/e2e/mock-e2e.js
@@ -629,6 +629,15 @@ async function setupMocking(
return [...privacyReport].sort();
}
+ /**
+ * Excludes hosts from the privacyReport if they are refered to by the MetaMask Portfolio
+ * in a different tab. This is because the Portfolio is a separate application
+ *
+ * @param request
+ */
+ const portfolioRequestsMatcher = (request) =>
+ request.headers.referer === 'https://portfolio.metamask.io/';
+
/**
* Listen for requests and add the hostname to the privacy report if it did
* not previously exist. This is used to track which hosts are requested
@@ -638,7 +647,10 @@ async function setupMocking(
* operation. See the browserAPIRequestDomains regex above.
*/
server.on('request-initiated', (request) => {
- if (request.headers.host.match(browserAPIRequestDomains) === null) {
+ if (
+ request.headers.host.match(browserAPIRequestDomains) === null &&
+ !portfolioRequestsMatcher(request)
+ ) {
privacyReport.add(request.headers.host);
}
});
diff --git a/test/e2e/run-all.js b/test/e2e/run-all.js
index 0ff043261a7b..d52a37e9afe6 100644
--- a/test/e2e/run-all.js
+++ b/test/e2e/run-all.js
@@ -6,6 +6,7 @@ const { hideBin } = require('yargs/helpers');
const { runInShell } = require('../../development/lib/run-command');
const { exitWithError } = require('../../development/lib/exit-with-error');
const { loadBuildTypesConfig } = require('../../development/lib/build-type');
+const { filterE2eChangedFiles } = require('./changedFilesUtil');
// These tests should only be run on Flask for now.
const FLASK_ONLY_TESTS = ['test-snap-namelookup.spec.js'];
@@ -30,9 +31,47 @@ const getTestPathsForTestDir = async (testDir) => {
return testPaths;
};
+// Quality Gate Retries
+const RETRIES_FOR_NEW_OR_CHANGED_TESTS = 5;
+
+/**
+ * Runs the quality gate logic to filter and append changed or new tests if present.
+ *
+ * @param {string} fullTestList - List of test paths to be considered.
+ * @param {string[]} changedOrNewTests - List of changed or new test paths.
+ * @returns {string} The updated full test list.
+ */
+async function applyQualityGate(fullTestList, changedOrNewTests) {
+ let qualityGatedList = fullTestList;
+
+ if (changedOrNewTests.length > 0) {
+ // Filter to include only the paths present in fullTestList
+ const filteredTests = changedOrNewTests.filter((test) =>
+ fullTestList.includes(test),
+ );
+
+ // If there are any filtered tests, append them to fullTestList
+ if (filteredTests.length > 0) {
+ const filteredTestsString = filteredTests.join('\n');
+ for (let i = 0; i < RETRIES_FOR_NEW_OR_CHANGED_TESTS; i++) {
+ qualityGatedList += `\n${filteredTestsString}`;
+ }
+ }
+ }
+
+ return qualityGatedList;
+}
+
// For running E2Es in parallel in CI
-function runningOnCircleCI(testPaths) {
- const fullTestList = testPaths.join('\n');
+async function runningOnCircleCI(testPaths) {
+ const changedOrNewTests = await filterE2eChangedFiles();
+ console.log('Changed or new test list:', changedOrNewTests);
+
+ const fullTestList = await applyQualityGate(
+ testPaths.join('\n'),
+ changedOrNewTests,
+ );
+
console.log('Full test list:', fullTestList);
fs.writeFileSync('test/test-results/fullTestList.txt', fullTestList);
@@ -46,7 +85,7 @@ function runningOnCircleCI(testPaths) {
// Report if no tests found, exit gracefully
if (result.indexOf('There were no tests found') !== -1) {
console.log(`run-all.js info: Skipping this node because "${result}"`);
- return [];
+ return { fullTestList: [] };
}
// If there's no text file, it means this node has no tests, so exit gracefully
@@ -54,13 +93,15 @@ function runningOnCircleCI(testPaths) {
console.log(
'run-all.js info: Skipping this node because there is no myTestList.txt',
);
- return [];
+ return { fullTestList: [] };
}
// take the space-delimited result and split into an array
- return fs
+ const myTestList = fs
.readFileSync('test/test-results/myTestList.txt', { encoding: 'utf8' })
.split(' ');
+
+ return { fullTestList: myTestList, changedOrNewTests };
}
async function main() {
@@ -204,8 +245,10 @@ async function main() {
await fs.promises.mkdir('test/test-results/e2e', { recursive: true });
let myTestList;
+ let changedOrNewTests;
if (process.env.CIRCLECI) {
- myTestList = runningOnCircleCI(testPaths);
+ ({ fullTestList: myTestList, changedOrNewTests = [] } =
+ await runningOnCircleCI(testPaths));
} else {
myTestList = testPaths;
}
@@ -217,7 +260,12 @@ async function main() {
if (testPath !== '') {
testPath = testPath.replace('\n', ''); // sometimes there's a newline at the end of the testPath
console.log(`\nExecuting testPath: ${testPath}\n`);
- await runInShell('node', [...args, testPath]);
+
+ const isTestChangedOrNew = changedOrNewTests?.includes(testPath);
+ const qualityGateArg = isTestChangedOrNew
+ ? ['--stop-after-one-failure']
+ : [];
+ await runInShell('node', [...args, ...qualityGateArg, testPath]);
}
}
}
diff --git a/test/e2e/run-e2e-test.js b/test/e2e/run-e2e-test.js
index a4c0496dbda6..0acf0e571cdb 100644
--- a/test/e2e/run-e2e-test.js
+++ b/test/e2e/run-e2e-test.js
@@ -35,7 +35,7 @@ async function main() {
'Set how many times the test should be retried upon failure.',
type: 'number',
})
- .option('retry-until-failure', {
+ .option('stop-after-one-failure', {
default: false,
description: 'Retries until the test fails',
type: 'boolean',
@@ -73,7 +73,7 @@ async function main() {
mmi,
e2eTestPath,
retries,
- retryUntilFailure,
+ stopAfterOneFailure,
leaveRunning,
updateSnapshot,
updatePrivacySnapshot,
@@ -141,7 +141,7 @@ async function main() {
const dir = 'test/test-results/e2e';
fs.mkdir(dir, { recursive: true });
- await retry({ retries, retryUntilFailure }, async () => {
+ await retry({ retries, stopAfterOneFailure }, async () => {
await runInShell('yarn', [
'mocha',
`--config=${configFile}`,
diff --git a/test/e2e/tests/bridge/bridge-click-from-asset-overview.spec.ts b/test/e2e/tests/bridge/bridge-click-from-asset-overview.spec.ts
new file mode 100644
index 000000000000..24f0bc0fb233
--- /dev/null
+++ b/test/e2e/tests/bridge/bridge-click-from-asset-overview.spec.ts
@@ -0,0 +1,42 @@
+import { Suite } from 'mocha';
+import { withFixtures, logInWithBalanceValidation } from '../../helpers';
+import { Ganache } from '../../seeder/ganache';
+import GanacheContractAddressRegistry from '../../seeder/ganache-contract-address-registry';
+import { Driver } from '../../webdriver/driver';
+import { BridgePage, getBridgeFixtures } from './bridge-test-utils';
+
+describe('Click bridge button from asset page @no-mmi', function (this: Suite) {
+ it('loads portfolio tab when flag is turned off', async function () {
+ await withFixtures(
+ getBridgeFixtures(this.test?.fullTitle()),
+ async ({
+ driver,
+ ganacheServer,
+ contractRegistry,
+ }: {
+ driver: Driver;
+ ganacheServer: Ganache;
+ contractRegistry: GanacheContractAddressRegistry;
+ }) => {
+ const bridgePage = new BridgePage(driver);
+ await logInWithBalanceValidation(driver, ganacheServer);
+
+ // ETH
+ await bridgePage.loadAssetPage(contractRegistry);
+ await bridgePage.load('coin-overview');
+ await bridgePage.verifyPortfolioTab(
+ 'https://portfolio.metamask.io/bridge?metametricsId=null',
+ );
+
+ await bridgePage.reloadHome();
+
+ // TST
+ await bridgePage.loadAssetPage(contractRegistry, 'TST');
+ await bridgePage.load('token-overview');
+ await bridgePage.verifyPortfolioTab(
+ 'https://portfolio.metamask.io/bridge?metametricsId=null',
+ );
+ },
+ );
+ });
+});
diff --git a/test/e2e/tests/bridge/bridge-click-from-eth-overview.spec.ts b/test/e2e/tests/bridge/bridge-click-from-eth-overview.spec.ts
new file mode 100644
index 000000000000..0a6098e01592
--- /dev/null
+++ b/test/e2e/tests/bridge/bridge-click-from-eth-overview.spec.ts
@@ -0,0 +1,27 @@
+import { Suite } from 'mocha';
+import { withFixtures, logInWithBalanceValidation } from '../../helpers';
+import { Ganache } from '../../seeder/ganache';
+import { Driver } from '../../webdriver/driver';
+import { BridgePage, getBridgeFixtures } from './bridge-test-utils';
+
+describe('Click bridge button from wallet overview @no-mmi', function (this: Suite) {
+ it('loads portfolio tab when flag is turned off', async function () {
+ await withFixtures(
+ getBridgeFixtures(this.test?.fullTitle()),
+ async ({
+ driver,
+ ganacheServer,
+ }: {
+ driver: Driver;
+ ganacheServer: Ganache;
+ }) => {
+ const bridgePage = new BridgePage(driver);
+ await logInWithBalanceValidation(driver, ganacheServer);
+ await bridgePage.load();
+ await bridgePage.verifyPortfolioTab(
+ 'https://portfolio.metamask.io/bridge?metametricsId=null',
+ );
+ },
+ );
+ });
+});
diff --git a/test/e2e/tests/bridge/bridge-test-utils.ts b/test/e2e/tests/bridge/bridge-test-utils.ts
new file mode 100644
index 000000000000..157876f43769
--- /dev/null
+++ b/test/e2e/tests/bridge/bridge-test-utils.ts
@@ -0,0 +1,128 @@
+import { strict as assert } from 'assert';
+import { Mockttp } from 'mockttp';
+import FixtureBuilder from '../../fixture-builder';
+import {
+ WINDOW_TITLES,
+ clickNestedButton,
+ generateGanacheOptions,
+} from '../../helpers';
+import { SMART_CONTRACTS } from '../../seeder/smart-contracts';
+import { CHAIN_IDS } from '../../../../shared/constants/network';
+import GanacheContractAddressRegistry from '../../seeder/ganache-contract-address-registry';
+import { Driver } from '../../webdriver/driver';
+
+export class BridgePage {
+ driver: Driver;
+
+ constructor(driver: Driver) {
+ this.driver = driver;
+ }
+
+ load = async (
+ location:
+ | 'wallet-overview'
+ | 'coin-overview'
+ | 'token-overview' = 'wallet-overview',
+ ) => {
+ let bridgeButtonTestIdPrefix;
+ switch (location) {
+ case 'wallet-overview':
+ bridgeButtonTestIdPrefix = 'eth';
+ break;
+ case 'coin-overview': // native asset page
+ bridgeButtonTestIdPrefix = 'coin';
+ break;
+ case 'token-overview':
+ default:
+ bridgeButtonTestIdPrefix = 'token';
+ }
+ await this.driver.clickElement(
+ `[data-testid="${bridgeButtonTestIdPrefix}-overview-bridge"]`,
+ );
+ };
+
+ reloadHome = async (shouldCloseWindow = true) => {
+ if (shouldCloseWindow) {
+ await this.driver.closeWindow();
+ await this.driver.delay(2000);
+ await this.driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+ }
+ await this.driver.navigate();
+ };
+
+ loadAssetPage = async (
+ contractRegistry: GanacheContractAddressRegistry,
+ symbol?: string,
+ ) => {
+ let tokenListItem;
+
+ if (symbol) {
+ // Import token
+ const contractAddress = await contractRegistry.getContractAddress(
+ SMART_CONTRACTS.HST,
+ );
+ await this.driver.clickElement({
+ text: 'Import tokens',
+ tag: 'button',
+ });
+ await clickNestedButton(this.driver, 'Custom token');
+ await this.driver.fill(
+ '[data-testid="import-tokens-modal-custom-address"]',
+ contractAddress,
+ );
+ await this.driver.waitForSelector(
+ '[data-testid="import-tokens-modal-custom-decimals"]',
+ );
+ await this.driver.clickElement({
+ text: 'Next',
+ tag: 'button',
+ });
+ await this.driver.clickElement(
+ '[data-testid="import-tokens-modal-import-button"]',
+ );
+ await this.driver.delay(2000);
+ tokenListItem = await this.driver.findElement({ text: symbol });
+ } else {
+ tokenListItem = await this.driver.findElement(
+ '[data-testid="multichain-token-list-button"]',
+ );
+ }
+ await tokenListItem.click();
+ assert.ok((await this.driver.getCurrentUrl()).includes('asset'));
+ };
+
+ verifyPortfolioTab = async (url: string) => {
+ await this.driver.delay(4000);
+ await this.driver.switchToWindowWithTitle('MetaMask Portfolio - Bridge');
+ assert.equal(await this.driver.getCurrentUrl(), url);
+ };
+
+ verifySwapPage = async () => {
+ const currentUrl = await this.driver.getCurrentUrl();
+ assert.ok(currentUrl.includes('cross-chain/swaps'));
+ };
+}
+
+export const getBridgeFixtures = (
+ title?: string,
+ testSpecificMock?: (server: Mockttp) => Promise,
+) => {
+ return {
+ driverOptions: {
+ openDevToolsForTabs: true,
+ },
+ fixtures: new FixtureBuilder({ inputChainId: CHAIN_IDS.MAINNET })
+ .withNetworkControllerOnMainnet()
+ .withTokensControllerERC20()
+ .build(),
+ testSpecificMock,
+ smartContract: SMART_CONTRACTS.HST,
+ ganacheOptions: generateGanacheOptions({
+ hardfork: 'london',
+ chain: { chainId: CHAIN_IDS.MAINNET },
+ }),
+ title,
+ };
+};
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 84fef8e26a90..a7169143d03e 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
@@ -59,6 +59,13 @@
"approvalFlows": "object"
},
"AuthenticationController": { "isSignedIn": "boolean" },
+ "BridgeController": {
+ "bridgeState": {
+ "bridgeFeatureFlags": {
+ "extensionSupport": "boolean"
+ }
+ }
+ },
"CronjobController": { "jobs": "object" },
"CurrencyController": {
"currencyRates": {
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 1dd7775ec70f..a79a2e543573 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
@@ -2,6 +2,7 @@
"DNS": "object",
"activeTab": "object",
"appState": "object",
+ "bridge": "object",
"confirm": "object",
"confirmAlerts": "object",
"confirmTransaction": "object",
@@ -60,6 +61,11 @@
},
"connectedStatusPopoverHasBeenShown": true,
"defaultHomeActiveTabName": null,
+ "bridgeState": {
+ "bridgeFeatureFlags": {
+ "extensionSupport": "boolean"
+ }
+ },
"browserEnvironment": { "os": "string", "browser": "string" },
"popupGasPollTokens": "object",
"notificationGasPollTokens": "object",
@@ -257,6 +263,7 @@
"encryptionKey": "string",
"encryptionSalt": "string"
},
+ "ramps": "object",
"send": "object",
"swaps": "object",
"unconnectedAccount": { "state": "CLOSED" }
diff --git a/test/e2e/tests/network/add-custom-network.spec.js b/test/e2e/tests/network/add-custom-network.spec.js
index 0cd18c13a9bf..fd133fa47e47 100644
--- a/test/e2e/tests/network/add-custom-network.spec.js
+++ b/test/e2e/tests/network/add-custom-network.spec.js
@@ -88,6 +88,14 @@ const selectors = {
},
suggestedTicker: '[data-testid="network-form-ticker-suggestion"]',
tickerWarning: '[data-testid="network-form-ticker-warning"]',
+ suggestedTickerForXDAI: {
+ css: '[data-testid="network-form-ticker-suggestion"]',
+ text: 'Suggested ticker symbol: XDAI',
+ },
+ tickerWarningTokenSymbol: {
+ css: '[data-testid="network-form-ticker-warning"]',
+ text: "This token symbol doesn't match the network name or chain ID entered.",
+ },
tickerButton: { text: 'PETH', tag: 'button' },
networkAdded: { text: 'Network added successfully!', tag: 'h4' },
@@ -735,11 +743,11 @@ describe('Custom network', function () {
await driver.fill(selectors.explorerInputField, 'https://test.com');
const suggestedTicker = await driver.isElementPresent(
- selectors.suggestedTicker,
+ selectors.suggestedTickerForXDAI,
);
const tickerWarning = await driver.isElementPresent(
- selectors.tickerWarning,
+ selectors.tickerWarningTokenSymbol,
);
assert.equal(suggestedTicker, false);
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
new file mode 100644
index 000000000000..cd197970baea
--- /dev/null
+++ b/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js
@@ -0,0 +1,148 @@
+const FixtureBuilder = require('../../fixture-builder');
+const {
+ withFixtures,
+ openDapp,
+ unlockWallet,
+ DAPP_URL,
+ DAPP_ONE_URL,
+ regularDelayMs,
+ defaultGanacheOptions,
+ WINDOW_TITLES,
+} = require('../../helpers');
+
+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,
+ },
+ ],
+ },
+ 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.delay(regularDelayMs);
+
+ await driver.waitUntilXWindowHandles(3);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({
+ text: 'Next',
+ tag: 'button',
+ css: '[data-testid="page-container-footer-next"]',
+ });
+
+ await driver.clickElement({
+ text: 'Confirm',
+ tag: 'button',
+ css: '[data-testid="page-container-footer-next"]',
+ });
+
+ await driver.waitUntilXWindowHandles(2);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
+
+ // 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.waitUntilXWindowHandles(4);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({
+ text: 'Next',
+ tag: 'button',
+ css: '[data-testid="page-container-footer-next"]',
+ });
+
+ await driver.clickElement({
+ text: 'Confirm',
+ tag: 'button',
+ css: '[data-testid="page-container-footer-next"]',
+ });
+
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ // switch chain for Dapp One
+ const 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.waitUntilXWindowHandles(4);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ await driver.clickElement({ text: 'Switch network', tag: 'button' });
+
+ await driver.switchToWindowWithUrl(DAPP_URL);
+
+ // eth_sendTransaction request
+ await driver.clickElement('#sendButton');
+
+ 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.findElement({
+ css: '[data-testid="network-display"]',
+ text: 'Localhost 7777',
+ });
+
+ await driver.clickElement({ text: 'Confirm', tag: 'button' });
+
+ await driver.waitUntilXWindowHandles(4);
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+
+ // Check correct network on the signTypedData confirmation.
+ await driver.findElement({
+ css: '[data-testid="signature-request-network-display"]',
+ text: 'Localhost 8545',
+ });
+ },
+ );
+ });
+});
diff --git a/test/e2e/tests/request-queuing/ui.spec.js b/test/e2e/tests/request-queuing/ui.spec.js
index e85d87226e84..45429ad32263 100644
--- a/test/e2e/tests/request-queuing/ui.spec.js
+++ b/test/e2e/tests/request-queuing/ui.spec.js
@@ -1,4 +1,5 @@
const { strict: assert } = require('assert');
+const { Browser } = require('selenium-webdriver');
const FixtureBuilder = require('../../fixture-builder');
const {
withFixtures,
@@ -11,21 +12,33 @@ const {
defaultGanacheOptions,
switchToNotificationWindow,
veryLargeDelayMs,
+ DAPP_TWO_URL,
} = require('../../helpers');
const { PAGES } = require('../../webdriver/driver');
-async function openDappAndSwitchChain(driver, dappUrl, chainId) {
- const notificationWindowIndex = chainId ? 4 : 3;
+// Window handle adjustments will need to be made for Non-MV3 Firefox
+// due to OffscreenDocument. Additionally Firefox continually bombs
+// with a "NoSuchWindowError: Browsing context has been discarded" whenever
+// we try to open a third dapp, so this test run in Firefox will
+// validate two dapps instead of 3
+const IS_FIREFOX = process.env.SELENIUM_BROWSER === Browser.FIREFOX;
+async function openDappAndSwitchChain(
+ driver,
+ dappUrl,
+ chainId,
+ notificationWindowIndex = 3,
+) {
// Open the dapp
await openDapp(driver, undefined, dappUrl);
- await driver.delay(regularDelayMs);
// Connect to the dapp
await driver.findClickableElement({ text: 'Connect', tag: 'button' });
await driver.clickElement('#connectButton');
await driver.delay(regularDelayMs);
+
await switchToNotificationWindow(driver, notificationWindowIndex);
+
await driver.clickElement({
text: 'Next',
tag: 'button',
@@ -62,39 +75,99 @@ async function openDappAndSwitchChain(driver, dappUrl, chainId) {
}
}
-async function selectDappClickSendGetNetwork(driver, dappUrl) {
+async function selectDappClickSend(driver, dappUrl) {
await driver.switchToWindowWithUrl(dappUrl);
- // Windows: MetaMask, TestDapp1, TestDapp2
- const expectedWindowHandles = 3;
- await driver.waitUntilXWindowHandles(expectedWindowHandles);
- const currentWindowHandles = await driver.getAllWindowHandles();
await driver.clickElement('#sendButton');
+}
- // Under mv3, we don't need to add to the current number of window handles
- // because the offscreen document returned by getAllWindowHandles provides
- // an extra window handle
- const newWindowHandles = await driver.waitUntilXWindowHandles(
- process.env.ENABLE_MV3 === 'true' || process.env.ENABLE_MV3 === undefined
- ? currentWindowHandles.length
- : currentWindowHandles.length + 1,
- );
- const [newNotificationWindowHandle] = newWindowHandles.filter(
- (h) => !currentWindowHandles.includes(h),
- );
- await driver.switchToWindow(newNotificationWindowHandle);
+async function switchToNotificationPopoverValidateDetails(
+ driver,
+ expectedDetails,
+) {
+ // Switches to the MetaMask Dialog window for confirmation
+ const windowHandles = await driver.getAllWindowHandles();
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog, windowHandles);
+ // Get UI details
const networkPill = await driver.findElement(
'[data-testid="network-display"]',
);
const networkText = await networkPill.getText();
- await driver.clickElement({ css: 'button', text: 'Reject' });
- return networkText;
+ const originElement = await driver.findElement(
+ '.confirm-page-container-summary__origin bdi',
+ );
+ const originText = await originElement.getText();
+
+ // Get state details
+ const notificationWindowState = await driver.executeScript(() =>
+ window.stateHooks?.getCleanAppState?.(),
+ );
+ const { chainId } = notificationWindowState.metamask.providerConfig;
+
+ // Ensure accuracy
+ validateConfirmationDetails(
+ { networkText, originText, chainId },
+ expectedDetails,
+ );
+}
+
+async function rejectTransaction(driver) {
+ await driver.clickElement({ tag: 'button', text: 'Reject' });
+}
+
+async function confirmTransaction(driver) {
+ await driver.clickElement({ tag: 'button', text: 'Confirm' });
+}
+
+function validateConfirmationDetails(
+ { chainId, networkText, originText },
+ expected,
+) {
+ assert.equal(chainId, expected.chainId);
+ assert.equal(networkText, expected.networkText);
+ assert.equal(originText, expected.originText);
+}
+
+async function switchToNetworkByName(driver, networkName) {
+ await driver.clickElement('[data-testid="network-display"]');
+ await driver.clickElement(`[data-testid="${networkName}"]`);
+}
+
+async function validateBalanceAndActivity(
+ driver,
+ expectedBalance,
+ expectedActivityEntries = 1,
+) {
+ // Ensure the balance changed if the the transaction was confirmed
+ await driver.waitForSelector({
+ css: '[data-testid="eth-overview__primary-currency"] .currency-display-component__text',
+ text: expectedBalance,
+ });
+
+ // Ensure there's an activity entry of "Send" and "Confirmed"
+ if (expectedActivityEntries) {
+ await driver.clickElement('[data-testid="account-overview__activity-tab"]');
+ assert.equal(
+ (
+ await driver.findElements({
+ css: '[data-testid="activity-list-item-action"]',
+ text: 'Send',
+ })
+ ).length,
+ expectedActivityEntries,
+ );
+ assert.equal(
+ (await driver.findElements('.transaction-status-label--confirmed'))
+ .length,
+ expectedActivityEntries,
+ );
+ }
}
describe('Request-queue UI changes', function () {
it('UI should show network specific to domain @no-mmi', async function () {
const port = 8546;
- const chainId = 1338;
+ const chainId = 1338; // 0x53a
await withFixtures(
{
dapp: true,
@@ -126,7 +199,7 @@ describe('Request-queue UI changes', function () {
await openDappAndSwitchChain(driver, DAPP_URL);
// Open the second dapp and switch chains
- await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1');
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a', 4);
// Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet
await driver.switchToWindowWithTitle(
@@ -134,22 +207,151 @@ describe('Request-queue UI changes', function () {
);
await driver.findElement({
css: '[data-testid="network-display"]',
- text: 'Ethereum Mainnet',
+ text: 'Localhost 8546',
});
// Go to the first dapp, ensure it uses localhost
- const dappOneNetworkPillText = await selectDappClickSendGetNetwork(
- driver,
- DAPP_URL,
- );
- assert.equal(dappOneNetworkPillText, 'Localhost 8545');
+ await selectDappClickSend(driver, DAPP_URL);
+ await switchToNotificationPopoverValidateDetails(driver, {
+ chainId: '0x539',
+ networkText: 'Localhost 8545',
+ originText: DAPP_URL,
+ });
+ await rejectTransaction(driver);
// Go to the second dapp, ensure it uses Ethereum Mainnet
- const dappTwoNetworkPillText = await selectDappClickSendGetNetwork(
- driver,
- DAPP_ONE_URL,
+ await selectDappClickSend(driver, DAPP_ONE_URL);
+ await switchToNotificationPopoverValidateDetails(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()
+ .withSelectedNetworkControllerPerDomain()
+ .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);
+
+ // Navigate to extension home screen
+ await driver.navigate(PAGES.HOME);
+
+ // Open the first dapp
+ await openDappAndSwitchChain(driver, DAPP_URL);
+
+ // Open the second dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a', 4);
+
+ if (!IS_FIREFOX) {
+ // Open the third dapp and switch chains
+ await openDappAndSwitchChain(driver, DAPP_TWO_URL, '0x3e8', 5);
+ }
+
+ // 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 switchToNotificationPopoverValidateDetails(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 switchToNotificationPopoverValidateDetails(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
+ await switchToNotificationPopoverValidateDetails(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"]',
);
- assert.equal(dappTwoNetworkPillText, 'Ethereum Mainnet');
+
+ // Switch to first network, whose send transaction was just confirmed
+ await switchToNetworkByName(driver, 'Localhost 8545');
+ await validateBalanceAndActivity(driver, '24.9998');
},
);
});
diff --git a/test/e2e/tests/swap-send/swap-send-eth.spec.ts b/test/e2e/tests/swap-send/swap-send-eth.spec.ts
index 244693d513a2..e4be22ae1fa3 100644
--- a/test/e2e/tests/swap-send/swap-send-eth.spec.ts
+++ b/test/e2e/tests/swap-send/swap-send-eth.spec.ts
@@ -74,12 +74,6 @@ describe('Swap-Send ETH', function () {
// TODO assert swap api request payload
await swapSendPage.submitSwap();
- await swapSendPage.verifyHistoryEntry(
- 'Send ETH as TST',
- 'Pending',
- '-1 ETH',
- '',
- );
await swapSendPage.verifyHistoryEntry(
'Send ETH as TST',
'Confirmed',
diff --git a/test/e2e/tests/tokens/nft/erc721-interaction.spec.js b/test/e2e/tests/tokens/nft/erc721-interaction.spec.js
index 37516fd716b8..a323077aa692 100644
--- a/test/e2e/tests/tokens/nft/erc721-interaction.spec.js
+++ b/test/e2e/tests/tokens/nft/erc721-interaction.spec.js
@@ -13,6 +13,73 @@ const FixtureBuilder = require('../../../fixture-builder');
describe('ERC721 NFTs testdapp interaction', function () {
const smartContract = SMART_CONTRACTS.NFTS;
+ it('should add NFTs to state by parsing tx logs without having to click on watch NFT', async function () {
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withPermissionControllerConnectedToTestDapp()
+ .build(),
+ ganacheOptions: defaultGanacheOptions,
+ smartContract,
+ title: this.test.fullTitle(),
+ },
+ async ({ driver, _, contractRegistry }) => {
+ const contract = contractRegistry.getContractAddress(smartContract);
+ await unlockWallet(driver);
+
+ // Open Dapp and wait for deployed contract
+ await openDapp(driver, contract);
+ await driver.findClickableElement('#deployButton');
+
+ // mint NFTs
+ await driver.fill('#mintAmountInput', '5');
+ await driver.clickElement({ text: 'Mint', tag: 'button' });
+
+ // Notification
+ await driver.waitUntilXWindowHandles(3);
+ const windowHandles = await driver.getAllWindowHandles();
+ const [extension] = windowHandles;
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.Dialog,
+ windowHandles,
+ );
+ await driver.waitForSelector({
+ css: '.confirm-page-container-summary__action__name',
+ text: 'Deposit',
+ });
+ await driver.clickElement({ text: 'Confirm', tag: 'button' });
+ await driver.waitUntilXWindowHandles(2);
+ await driver.switchToWindow(extension);
+ await driver.clickElement(
+ '[data-testid="account-overview__activity-tab"]',
+ );
+ const transactionItem = await driver.waitForSelector({
+ css: '[data-testid="activity-list-item-action"]',
+ text: 'Deposit',
+ });
+ assert.equal(await transactionItem.isDisplayed(), true);
+
+ // verify the mint transaction has finished
+ await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles);
+ const nftsMintStatus = await driver.findElement({
+ css: '#nftsStatus',
+ text: 'Mint completed',
+ });
+ assert.equal(await nftsMintStatus.isDisplayed(), true);
+
+ await driver.switchToWindow(extension);
+
+ await clickNestedButton(driver, 'NFTs');
+ await driver.findElement({ text: 'TestDappNFTs (5)' });
+ const nftsListItemsFirstCheck = await driver.findElements(
+ '.nft-item__container',
+ );
+ assert.equal(nftsListItemsFirstCheck.length, 5);
+ },
+ );
+ });
+
it('should prompt users to add their NFTs to their wallet (one by one) @no-mmi', async function () {
await withFixtures(
{
@@ -97,14 +164,16 @@ describe('ERC721 NFTs testdapp interaction', function () {
await driver.clickElement({ text: 'Add NFTs', tag: 'button' });
await driver.switchToWindow(extension);
await clickNestedButton(driver, 'NFTs');
- await driver.findElement({ text: 'TestDappNFTs (3)' });
+ // Changed this check from 3 to 6, because after mint all nfts has been added to state,
+ await driver.findElement({ text: 'TestDappNFTs (6)' });
const nftsListItemsFirstCheck = await driver.findElements(
'.nft-item__container',
);
- assert.equal(nftsListItemsFirstCheck.length, 3);
+ assert.equal(nftsListItemsFirstCheck.length, 6);
await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles);
await driver.fill('#watchNFTInput', '4');
+
await driver.clickElement({ text: 'Watch NFT', tag: 'button' });
await driver.fill('#watchNFTInput', '5');
await driver.clickElement({ text: 'Watch NFT', tag: 'button' });
diff --git a/test/e2e/tests/tokens/nft/send-nft.spec.js b/test/e2e/tests/tokens/nft/send-nft.spec.js
index 7df8febcab56..a9b89a2abb9b 100644
--- a/test/e2e/tests/tokens/nft/send-nft.spec.js
+++ b/test/e2e/tests/tokens/nft/send-nft.spec.js
@@ -145,8 +145,6 @@ describe('Send NFT', function () {
// Go back to NFTs tab and check the imported NFT is shown as previously owned
await driver.clickElement('[data-testid="account-overview__nfts-tab"]');
- await driver.clickElement('[data-testid="refresh-list-button"]');
-
const previouslyOwnedNft = await driver.findElement({
css: 'h5',
text: 'Previously Owned',
diff --git a/test/jest/mock-store.js b/test/jest/mock-store.js
index 365d9b00bdbd..85f617450136 100644
--- a/test/jest/mock-store.js
+++ b/test/jest/mock-store.js
@@ -666,3 +666,22 @@ export const createSwapsMockStore = () => {
},
};
};
+
+export const createBridgeMockStore = () => {
+ const swapsStore = createSwapsMockStore();
+ return {
+ ...swapsStore,
+ bridge: {
+ toChain: null,
+ },
+ metamask: {
+ ...swapsStore.metamask,
+ bridgeState: {
+ ...(swapsStore.metamask.bridgeState ?? {}),
+ bridgeFeatureFlags: {
+ extensionSupport: false,
+ },
+ },
+ },
+ };
+};
diff --git a/test/setup.js b/test/setup.js
index f96e744a917a..17f9c45249ab 100644
--- a/test/setup.js
+++ b/test/setup.js
@@ -7,3 +7,10 @@ window.SVGPathElement = window.SVGPathElement || { prototype: {} };
global.indexedDB = {};
// scrollIntoView is not available in JSDOM
window.HTMLElement.prototype.scrollIntoView = () => undefined
+
+global.platform = {
+ // Required for: coin overviews components
+ openTab: () => undefined,
+ // Required for: settings info tab
+ getVersion: () => '',
+};
diff --git a/ui/components/app/add-network/add-network.test.js b/ui/components/app/add-network/add-network.test.js
index 3a15f4d33c3e..f3084b1db065 100644
--- a/ui/components/app/add-network/add-network.test.js
+++ b/ui/components/app/add-network/add-network.test.js
@@ -6,6 +6,7 @@ import mockState from '../../../../test/data/mock-state.json';
import AddNetwork from './add-network';
jest.mock('../../../selectors', () => ({
+ ...jest.requireActual('../../../selectors'),
getNetworkConfigurations: () => ({
networkConfigurationId: {
chainId: '0x539',
diff --git a/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.tsx b/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.tsx
index c9647cedf633..0c4165182b18 100644
--- a/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.tsx
+++ b/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.tsx
@@ -180,8 +180,8 @@ export function ConfirmAlertModal({
onCheckboxClick={handleConfirmCheckbox}
label={
selectedAlert?.provider === SecurityProvider.Blockaid
- ? t('confirmAlertModalAcknowledgeBlockaid')
- : t('confirmAlertModalAcknowledge')
+ ? t('confirmAlertModalAcknowledgeSingle')
+ : t('confirmAlertModalAcknowledgeMultiple')
}
/>
}
diff --git a/ui/components/app/alert-system/inline-alert/__snapshots__/inline-alert.test.tsx.snap b/ui/components/app/alert-system/inline-alert/__snapshots__/inline-alert.test.tsx.snap
index ef56b906038f..d869e2f978fa 100644
--- a/ui/components/app/alert-system/inline-alert/__snapshots__/inline-alert.test.tsx.snap
+++ b/ui/components/app/alert-system/inline-alert/__snapshots__/inline-alert.test.tsx.snap
@@ -16,7 +16,7 @@ exports[`Inline Alert renders alert with danger severity 1`] = `
- [inlineAlert]
+ [alert]
- [inlineAlert]
+ [alert]
- [inlineAlert]
+ [alert]
- {t('inlineAlert')}
+ {t('alert')}
diff --git a/ui/components/app/asset-list/asset-list.js b/ui/components/app/asset-list/asset-list.js
index 82c45a27134e..8e493037da44 100644
--- a/ui/components/app/asset-list/asset-list.js
+++ b/ui/components/app/asset-list/asset-list.js
@@ -5,13 +5,9 @@ import TokenList from '../token-list';
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
import { useUserPreferencedCurrency } from '../../../hooks/useUserPreferencedCurrency';
import {
- getSelectedAccountCachedBalance,
getDetectedTokensInCurrentNetwork,
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork,
getShouldHideZeroBalanceTokens,
- ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
- getIsBuyableChain,
- ///: END:ONLY_INCLUDE_IF
getSelectedAccount,
getPreferences,
} from '../../../selectors';
@@ -22,6 +18,7 @@ import {
getMultichainShouldShowFiat,
getMultichainCurrencyImage,
getMultichainIsMainnet,
+ getMultichainSelectedAccountCachedBalance,
} from '../../../selectors/multichain';
import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay';
import { MetaMetricsContext } from '../../../contexts/metametrics';
@@ -48,11 +45,11 @@ import {
RAMPS_CARD_VARIANT_TYPES,
RampsCard,
} from '../../multichain/ramps-card/ramps-card';
+import { getIsNativeTokenBuyable } from '../../../ducks/ramps';
///: END:ONLY_INCLUDE_IF
-const AssetList = ({ onClickAsset }) => {
+const AssetList = ({ onClickAsset, showTokensLinks }) => {
const [showDetectedTokens, setShowDetectedTokens] = useState(false);
- const selectedAccountBalance = useSelector(getSelectedAccountCachedBalance);
const nativeCurrency = useSelector(getMultichainNativeCurrency);
const showFiat = useSelector(getMultichainShouldShowFiat);
const isMainnet = useSelector(getMultichainIsMainnet);
@@ -67,7 +64,7 @@ const AssetList = ({ onClickAsset }) => {
rpcUrl,
);
const trackEvent = useContext(MetaMetricsContext);
- const balance = useSelector(getSelectedAccountCachedBalance);
+ const balance = useSelector(getMultichainSelectedAccountCachedBalance);
const balanceIsLoading = !balance;
const selectedAccount = useSelector(getSelectedAccount);
const shouldHideZeroBalanceTokens = useSelector(
@@ -84,13 +81,13 @@ const AssetList = ({ onClickAsset }) => {
} = useUserPreferencedCurrency(SECONDARY, { ethNumberOfDecimals: 4 });
const [primaryCurrencyDisplay, primaryCurrencyProperties] =
- useCurrencyDisplay(selectedAccountBalance, {
+ useCurrencyDisplay(balance, {
numberOfDecimals: primaryNumberOfDecimals,
currency: primaryCurrency,
});
const [secondaryCurrencyDisplay, secondaryCurrencyProperties] =
- useCurrencyDisplay(selectedAccountBalance, {
+ useCurrencyDisplay(balance, {
numberOfDecimals: secondaryNumberOfDecimals,
currency: secondaryCurrency,
});
@@ -109,12 +106,16 @@ const AssetList = ({ onClickAsset }) => {
});
const balanceIsZero = Number(totalFiatBalance) === 0;
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
- const isBuyableChain = useSelector(getIsBuyableChain);
+ const isBuyableChain = useSelector(getIsNativeTokenBuyable);
const shouldShowBuy = isBuyableChain && balanceIsZero;
///: END:ONLY_INCLUDE_IF
const isEvm = useSelector(getMultichainIsEvm);
+ // NOTE: Since we can parametrize it now, we keep the original behavior
+ // for EVM assets
+ const shouldShowTokensLinks = showTokensLinks ?? isEvm;
+
let isStakeable = isMainnet && isEvm;
///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
isStakeable = false;
@@ -183,18 +184,22 @@ const AssetList = ({ onClickAsset }) => {
});
}}
/>
- {balanceIsZero && (
- 0 ? 0 : 4}
- />
+ {shouldShowTokensLinks && (
+ <>
+ {balanceIsZero && (
+ 0 ? 0 : 4}
+ />
+ )}
+ 0 && !balanceIsZero ? 0 : 2}
+ />
+ >
)}
- 0 && !balanceIsZero ? 0 : 2}
- />
{showDetectedTokens && (
)}
@@ -204,6 +209,7 @@ const AssetList = ({ onClickAsset }) => {
AssetList.propTypes = {
onClickAsset: PropTypes.func.isRequired,
+ showTokensLinks: PropTypes.bool,
};
export default AssetList;
diff --git a/ui/components/app/modals/confirm-delete-rpc-url-modal/confirm-delete-rpc-url-modal.tsx b/ui/components/app/modals/confirm-delete-rpc-url-modal/confirm-delete-rpc-url-modal.tsx
new file mode 100644
index 000000000000..3eb9a9323048
--- /dev/null
+++ b/ui/components/app/modals/confirm-delete-rpc-url-modal/confirm-delete-rpc-url-modal.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import { useDispatch } from 'react-redux';
+import {
+ BlockSize,
+ Display,
+} from '../../../../helpers/constants/design-system';
+import {
+ Box,
+ ButtonPrimary,
+ ButtonPrimarySize,
+ ButtonSecondary,
+ ButtonSecondarySize,
+ Modal,
+ ModalBody,
+ ModalContent,
+ ModalHeader,
+ ModalOverlay,
+} from '../../../component-library';
+import { useI18nContext } from '../../../../hooks/useI18nContext';
+import {
+ hideModal,
+ setEditedNetwork,
+ toggleNetworkMenu,
+} from '../../../../store/actions';
+
+const ConfirmDeleteRpcUrlModal = () => {
+ const t = useI18nContext();
+ const dispatch = useDispatch();
+ return (
+ {
+ dispatch(setEditedNetwork());
+ dispatch(hideModal());
+ }}
+ >
+
+
+ {t('confirmDeletion')}
+
+ {t('confirmRpcUrlDeletionMessage')}
+
+ {
+ dispatch(hideModal());
+ dispatch(toggleNetworkMenu());
+ }}
+ >
+ {t('back')}
+
+ {
+ console.log('TODO: Delete RPc URL');
+ }}
+ >
+ {t('deleteRpcUrl')}
+
+
+
+
+
+ );
+};
+
+export default ConfirmDeleteRpcUrlModal;
diff --git a/ui/components/app/modals/modal.js b/ui/components/app/modals/modal.js
index f3eb1a950a40..9d546e5de74d 100644
--- a/ui/components/app/modals/modal.js
+++ b/ui/components/app/modals/modal.js
@@ -38,6 +38,7 @@ import TransactionAlreadyConfirmed from './transaction-already-confirmed';
// Metamask Notifications
import ConfirmTurnOffProfileSyncing from './confirm-turn-off-profile-syncing';
import TurnOnMetamaskNotifications from './turn-on-metamask-notifications/turn-on-metamask-notifications';
+import ConfirmDeleteRpcUrlModal from './confirm-delete-rpc-url-modal/confirm-delete-rpc-url-modal';
const modalContainerBaseStyle = {
transform: 'translate3d(-50%, 0, 0px)',
@@ -230,6 +231,19 @@ const MODALS = {
},
},
+ CONFIRM_DELETE_RPC_URL: {
+ contents: ,
+ mobileModalStyle: {
+ ...modalContainerMobileStyle,
+ },
+ laptopModalStyle: {
+ ...modalContainerLaptopStyle,
+ },
+ contentStyle: {
+ borderRadius: '8px',
+ },
+ },
+
EDIT_APPROVAL_PERMISSION: {
contents: ,
mobileModalStyle: {
diff --git a/ui/components/app/network-account-balance-header/network-account-balance-header.js b/ui/components/app/network-account-balance-header/network-account-balance-header.js
index 98c91acfe81a..9137d53f1f14 100644
--- a/ui/components/app/network-account-balance-header/network-account-balance-header.js
+++ b/ui/components/app/network-account-balance-header/network-account-balance-header.js
@@ -68,6 +68,7 @@ export default function NetworkAccountBalanceHeader({
variant={TextVariant.bodySm}
as="h6"
color={TextColor.textAlternative}
+ data-testid="signature-request-network-display"
>
{networkName}
diff --git a/ui/components/app/nfts-tab/nfts-tab.js b/ui/components/app/nfts-tab/nfts-tab.js
index bc54ca4c9766..43a0bd1a9d64 100644
--- a/ui/components/app/nfts-tab/nfts-tab.js
+++ b/ui/components/app/nfts-tab/nfts-tab.js
@@ -17,7 +17,6 @@ import { useNftsCollections } from '../../../hooks/useNftsCollections';
import {
getCurrentNetwork,
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
- getIsBuyableChain,
getShouldHideZeroBalanceTokens,
getSelectedAccount,
///: END:ONLY_INCLUDE_IF
@@ -49,6 +48,7 @@ import {
RampsCard,
} from '../../multichain/ramps-card/ramps-card';
import { useAccountTotalFiatBalance } from '../../../hooks/useAccountTotalFiatBalance';
+import { getIsNativeTokenBuyable } from '../../../ducks/ramps';
///: END:ONLY_INCLUDE_IF
import Spinner from '../../ui/spinner';
@@ -73,7 +73,7 @@ export default function NftsTab() {
shouldHideZeroBalanceTokens,
);
const balanceIsZero = Number(totalFiatBalance) === 0;
- const isBuyableChain = useSelector(getIsBuyableChain);
+ const isBuyableChain = useSelector(getIsNativeTokenBuyable);
const showRampsCard = isBuyableChain && balanceIsZero;
///: END:ONLY_INCLUDE_IF
diff --git a/ui/components/app/selected-account/selected-account-component.test.js b/ui/components/app/selected-account/selected-account-component.test.js
index a48e24a661c6..ae8a3ff000db 100644
--- a/ui/components/app/selected-account/selected-account-component.test.js
+++ b/ui/components/app/selected-account/selected-account-component.test.js
@@ -52,6 +52,7 @@ jest.mock('../../../selectors', () => {
return {
getAccountType: mockGetAccountType,
getSelectedInternalAccount: mockGetSelectedAccount,
+ getCurrentChainId: jest.fn(() => '0x1'),
};
});
diff --git a/ui/components/app/transaction-list/transaction-list.component.js b/ui/components/app/transaction-list/transaction-list.component.js
index b47130c5d739..20c41ef33e4d 100644
--- a/ui/components/app/transaction-list/transaction-list.component.js
+++ b/ui/components/app/transaction-list/transaction-list.component.js
@@ -10,7 +10,6 @@ import {
getCurrentChainId,
getSelectedAccount,
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
- getIsBuyableChain,
getShouldHideZeroBalanceTokens,
///: END:ONLY_INCLUDE_IF
} from '../../../selectors';
@@ -33,6 +32,7 @@ import {
RAMPS_CARD_VARIANT_TYPES,
RampsCard,
} from '../../multichain/ramps-card/ramps-card';
+import { getIsNativeTokenBuyable } from '../../../ducks/ramps';
///: END:ONLY_INCLUDE_IF
const PAGE_INCREMENT = 10;
@@ -141,8 +141,7 @@ export default function TransactionList({
shouldHideZeroBalanceTokens,
);
const balanceIsZero = Number(totalFiatBalance) === 0;
- const isBuyableChain = useSelector(getIsBuyableChain);
-
+ const isBuyableChain = useSelector(getIsNativeTokenBuyable);
const showRampsCard = isBuyableChain && balanceIsZero;
///: END:ONLY_INCLUDE_IF
diff --git a/ui/components/app/wallet-overview/btc-overview.stories.tsx b/ui/components/app/wallet-overview/btc-overview.stories.tsx
new file mode 100644
index 000000000000..43dff2554bef
--- /dev/null
+++ b/ui/components/app/wallet-overview/btc-overview.stories.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import BtcOverview from './btc-overview';
+
+export default {
+ title: 'Components/App/WalletOverview/BtcOverview',
+ component: BtcOverview,
+ parameters: {
+ docs: {
+ description: {
+ component:
+ 'A component that displays an overview of Bitcoin wallet information.',
+ },
+ },
+ },
+};
+
+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/btc-overview.test.tsx
new file mode 100644
index 000000000000..233096f0918c
--- /dev/null
+++ b/ui/components/app/wallet-overview/btc-overview.test.tsx
@@ -0,0 +1,177 @@
+import React from 'react';
+import configureMockStore from 'redux-mock-store';
+import { fireEvent } from '@testing-library/react';
+import thunk from 'redux-thunk';
+import { Cryptocurrency } from '@metamask/assets-controllers';
+import { BtcAccountType, BtcMethod } from '@metamask/keyring-api';
+import { MultichainNativeAssets } from '../../../../shared/constants/multichain/assets';
+import mockState from '../../../../test/data/mock-state.json';
+import { renderWithProvider } from '../../../../test/jest/rendering';
+import { MultichainNetworks } from '../../../../shared/constants/multichain/networks';
+import { RampsMetaMaskEntry } from '../../../hooks/ramps/useRamps/useRamps';
+import BtcOverview from './btc-overview';
+
+const PORTOFOLIO_URL = 'https://portfolio.test';
+
+const BTC_OVERVIEW_BUY = 'coin-overview-buy';
+const BTC_OVERVIEW_BRIDGE = 'coin-overview-bridge';
+const BTC_OVERVIEW_PORTFOLIO = 'coin-overview-portfolio';
+const BTC_OVERVIEW_SWAP = 'token-overview-button-swap';
+const BTC_OVERVIEW_SEND = 'coin-overview-send';
+const BTC_OVERVIEW_PRIMARY_CURRENCY = 'coin-overview__primary-currency';
+
+const mockMetaMetricsId = 'deadbeef';
+const mockNonEvmBalance = '1';
+const mockNonEvmAccount = {
+ address: 'bc1qwl8399fz829uqvqly9tcatgrgtwp3udnhxfq4k',
+ id: '542490c8-d178-433b-9f31-f680b11f45a5',
+ metadata: {
+ name: 'Bitcoin Account',
+ keyring: {
+ type: 'Snap Keyring',
+ },
+ snap: {
+ id: 'btc-snap-id',
+ name: 'btc-snap-name',
+ },
+ },
+ options: {},
+ methods: [BtcMethod.SendMany],
+ type: BtcAccountType.P2wpkh,
+};
+
+function getStore(state?: Record) {
+ return configureMockStore([thunk])({
+ metamask: {
+ ...mockState.metamask,
+ internalAccounts: {
+ accounts: {
+ [mockNonEvmAccount.id]: mockNonEvmAccount,
+ },
+ selectedAccount: mockNonEvmAccount.id,
+ },
+ // (Multichain) BalancesController
+ balances: {
+ [mockNonEvmAccount.id]: {
+ [MultichainNativeAssets.BITCOIN]: {
+ amount: mockNonEvmBalance,
+ unit: 'BTC',
+ },
+ },
+ },
+ // (Multichain) RatesController
+ fiatCurrency: 'usd',
+ rates: {
+ [Cryptocurrency.Btc]: {
+ conversionRate: '1.000',
+ conversionDate: 0,
+ },
+ },
+ cryptocurrencies: [Cryptocurrency.Btc],
+ // Required, during onboarding, the extension will assume we're in an "EVM context", meaning
+ // most multichain selectors will not use non-EVM logic despite having a non-EVM
+ // selected account
+ completedOnboarding: true,
+ // Used when clicking on some buttons
+ metaMetricsId: mockMetaMetricsId,
+ // Override state if provided
+ ...state,
+ },
+ });
+}
+
+function makePortfolioUrl(path: string, getParams: Record) {
+ const params = new URLSearchParams(getParams);
+ return `${PORTOFOLIO_URL}/${path}?${params.toString()}`;
+}
+
+describe('BtcOverview', () => {
+ it('shows the primary balance', async () => {
+ const { queryByTestId, queryByText } = renderWithProvider(
+ ,
+ getStore(),
+ );
+
+ const primaryBalance = queryByTestId(BTC_OVERVIEW_PRIMARY_CURRENCY);
+ expect(primaryBalance).toBeInTheDocument();
+ expect(primaryBalance).toHaveTextContent(`${mockNonEvmBalance}BTC`);
+ // For now we consider balance to be always cached
+ expect(queryByText('*')).toBeInTheDocument();
+ });
+
+ it('shows a spinner if balance is not available', async () => {
+ const { container } = renderWithProvider(
+ ,
+ getStore({
+ // The balances won't be available
+ balances: {},
+ }),
+ );
+
+ const spinner = container.querySelector(
+ '.coin-overview__balance .coin-overview__primary-container .spinner',
+ );
+ expect(spinner).toBeInTheDocument();
+ });
+
+ it('buttons Send/Swap/Bridge are disabled', () => {
+ const { queryByTestId } = renderWithProvider(, getStore());
+
+ for (const buttonTestId of [
+ BTC_OVERVIEW_SEND,
+ BTC_OVERVIEW_SWAP,
+ BTC_OVERVIEW_BRIDGE,
+ ]) {
+ const button = queryByTestId(buttonTestId);
+ expect(button).toBeInTheDocument();
+ expect(button).toBeDisabled();
+ }
+ });
+
+ it('shows the "Buy & Sell" button', () => {
+ const { queryByTestId } = renderWithProvider(, getStore());
+ const buyButton = queryByTestId(BTC_OVERVIEW_BUY);
+ expect(buyButton).toBeInTheDocument();
+ });
+
+ it('opens the Portfolio "Buy & Sell" URI when clicking on "Buy & Sell" button', async () => {
+ const { queryByTestId } = renderWithProvider(, getStore());
+ const openTabSpy = jest.spyOn(global.platform, 'openTab');
+
+ const buyButton = queryByTestId(BTC_OVERVIEW_BUY);
+ expect(buyButton).toBeInTheDocument();
+ fireEvent.click(buyButton as HTMLElement);
+
+ expect(openTabSpy).toHaveBeenCalledTimes(1);
+ expect(openTabSpy).toHaveBeenCalledWith({
+ url: makePortfolioUrl('buy', {
+ metamaskEntry: RampsMetaMaskEntry.BuySellButton,
+ chainId: MultichainNetworks.BITCOIN,
+ metametricsId: mockMetaMetricsId,
+ }),
+ });
+ });
+
+ it('always show the Portfolio button', () => {
+ const { queryByTestId } = renderWithProvider(, getStore());
+ const portfolioButton = queryByTestId(BTC_OVERVIEW_PORTFOLIO);
+ expect(portfolioButton).toBeInTheDocument();
+ });
+
+ it('open the Portfolio URI when clicking on Portfolio button', async () => {
+ const { queryByTestId } = renderWithProvider(, getStore());
+ const openTabSpy = jest.spyOn(global.platform, 'openTab');
+
+ const portfolioButton = queryByTestId(BTC_OVERVIEW_PORTFOLIO);
+ expect(portfolioButton).toBeInTheDocument();
+ fireEvent.click(portfolioButton as HTMLElement);
+
+ expect(openTabSpy).toHaveBeenCalledTimes(1);
+ expect(openTabSpy).toHaveBeenCalledWith({
+ url: makePortfolioUrl('', {
+ metamaskEntry: 'ext_portfolio_button',
+ metametricsId: mockMetaMetricsId,
+ }),
+ });
+ });
+});
diff --git a/ui/components/app/wallet-overview/btc-overview.tsx b/ui/components/app/wallet-overview/btc-overview.tsx
new file mode 100644
index 000000000000..3703252f205a
--- /dev/null
+++ b/ui/components/app/wallet-overview/btc-overview.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+
+import { useSelector } from 'react-redux';
+import {
+ getMultichainProviderConfig,
+ getMultichainSelectedAccountCachedBalance,
+} from '../../../selectors/multichain';
+import { CoinOverview } from './coin-overview';
+
+type BtcOverviewProps = {
+ className?: string;
+};
+
+const BtcOverview = ({ className }: BtcOverviewProps) => {
+ const { chainId } = useSelector(getMultichainProviderConfig);
+ const balance = useSelector(getMultichainSelectedAccountCachedBalance);
+
+ return (
+
+ );
+};
+
+export default BtcOverview;
diff --git a/ui/components/app/wallet-overview/coin-buttons.tsx b/ui/components/app/wallet-overview/coin-buttons.tsx
index 5bba897121e4..b1774897edc4 100644
--- a/ui/components/app/wallet-overview/coin-buttons.tsx
+++ b/ui/components/app/wallet-overview/coin-buttons.tsx
@@ -6,9 +6,20 @@ import {
useLocation,
///: END:ONLY_INCLUDE_IF
} from 'react-router-dom';
+///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
+import { toHex } from '@metamask/controller-utils';
+///: END:ONLY_INCLUDE_IF
+import {
+ ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
+ isCaipChainId,
+ ///: END:ONLY_INCLUDE_IF
+ CaipChainId,
+} from '@metamask/utils';
+///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
+import { ChainId } from '../../../../shared/constants/network';
+///: END:ONLY_INCLUDE_IF
///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
-import { CaipChainId } from '@metamask/utils';
import {
getMmiPortfolioEnabled,
getMmiPortfolioUrl,
@@ -22,11 +33,9 @@ import {
SEND_ROUTE,
} from '../../../helpers/constants/routes';
import {
- SwapsEthToken,
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
+ SwapsEthToken,
getCurrentKeyring,
- ///: END:ONLY_INCLUDE_IF
- ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
getMetaMetricsId,
///: END:ONLY_INCLUDE_IF
getUseExternalServices,
@@ -55,7 +64,8 @@ import { Box, Icon, IconName } from '../../component-library';
import IconButton from '../../ui/icon-button';
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
import { getPortfolioUrl } from '../../../helpers/utils/portfolio';
-import useRamps from '../../../hooks/experiences/useRamps';
+import useRamps from '../../../hooks/ramps/useRamps/useRamps';
+
///: END:ONLY_INCLUDE_IF
const CoinButtons = ({
@@ -65,17 +75,23 @@ const CoinButtons = ({
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
isBridgeChain,
isBuyableChain,
+ // TODO: Remove this logic once `isNativeTokenBuyable` has been
+ // merged (see: https://github.com/MetaMask/metamask-extension/pull/24041)
+ isBuyableChainWithoutSigning = false,
defaultSwapsToken,
///: END:ONLY_INCLUDE_IF
classPrefix = 'coin',
}: {
- classPrefix?: string;
- isBuyableChain: boolean;
- isSigningEnabled: boolean;
+ chainId: `0x${string}` | CaipChainId | number;
isSwapsChain: boolean;
+ isSigningEnabled: boolean;
+ ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
isBridgeChain: boolean;
- chainId: `0x${string}` | CaipChainId | number;
+ isBuyableChain: boolean;
+ isBuyableChainWithoutSigning?: boolean;
defaultSwapsToken?: SwapsEthToken;
+ ///: END:ONLY_INCLUDE_IF
+ classPrefix?: string;
}) => {
const t = useContext(I18nContext);
const dispatch = useDispatch();
@@ -96,7 +112,10 @@ const CoinButtons = ({
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
{ condition: !isBuyableChain, message: '' },
///: END:ONLY_INCLUDE_IF
- { condition: !isSigningEnabled, message: 'methodNotSupported' },
+ {
+ condition: !(isSigningEnabled || isBuyableChainWithoutSigning),
+ message: 'methodNotSupported',
+ },
],
sendButton: [
{ condition: !isSigningEnabled, message: 'methodNotSupported' },
@@ -129,6 +148,16 @@ const CoinButtons = ({
return contents;
};
+ ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
+ const getChainId = (): CaipChainId | ChainId => {
+ if (isCaipChainId(chainId)) {
+ return chainId as CaipChainId;
+ }
+ // Otherwise we assume that's an EVM chain ID, so use the usual 0x prefix
+ return toHex(chainId) as ChainId;
+ };
+ ///: END:ONLY_INCLUDE_IF
+
///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
const mmiPortfolioEnabled = useSelector(getMmiPortfolioEnabled);
const mmiPortfolioUrl = useSelector(getMmiPortfolioUrl);
@@ -247,7 +276,7 @@ const CoinButtons = ({
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
const handleBuyAndSellOnClick = useCallback(() => {
- openBuyCryptoInPdapp();
+ openBuyCryptoInPdapp(getChainId());
trackEvent({
event: MetaMetricsEventName.NavBuyButtonClicked,
category: MetaMetricsEventCategory.Navigation,
@@ -310,7 +339,10 @@ const CoinButtons = ({
Icon={
}
- disabled={!isBuyableChain || !isSigningEnabled}
+ disabled={
+ !isBuyableChain ||
+ !(isSigningEnabled || isBuyableChainWithoutSigning)
+ }
data-testid={`${classPrefix}-overview-buy`}
label={t('buyAndSell')}
onClick={handleBuyAndSellOnClick}
diff --git a/ui/components/app/wallet-overview/coin-overview.tsx b/ui/components/app/wallet-overview/coin-overview.tsx
index abf1f6ca7158..6364b0231e82 100644
--- a/ui/components/app/wallet-overview/coin-overview.tsx
+++ b/ui/components/app/wallet-overview/coin-overview.tsx
@@ -3,15 +3,13 @@ import { useSelector } from 'react-redux';
import classnames from 'classnames';
import { zeroAddress } from 'ethereumjs-util';
-///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
import { CaipChainId } from '@metamask/utils';
-///: END:ONLY_INCLUDE_IF
+import type { Hex } from '@metamask/utils';
import { I18nContext } from '../../../contexts/i18n';
import Tooltip from '../../ui/tooltip';
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display';
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
import {
- getShouldShowFiat,
getPreferences,
getTokensMarketData,
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
@@ -20,24 +18,28 @@ import {
} from '../../../selectors';
import Spinner from '../../ui/spinner';
import { useIsOriginalNativeTokenSymbol } from '../../../hooks/useIsOriginalNativeTokenSymbol';
-import { getProviderConfig } from '../../../ducks/metamask/metamask';
import { showPrimaryCurrency } from '../../../../shared/modules/currency-display.utils';
import { PercentageAndAmountChange } from '../../multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change';
+import {
+ getMultichainIsEvm,
+ getMultichainProviderConfig,
+ getMultichainShouldShowFiat,
+} from '../../../selectors/multichain';
import WalletOverview from './wallet-overview';
import CoinButtons from './coin-buttons';
export type CoinOverviewProps = {
balance: string;
balanceIsCached: boolean;
- className: string;
- classPrefix: string;
- chainId: CaipChainId | number;
- showAddress: boolean;
+ className?: string;
+ classPrefix?: string;
+ chainId: CaipChainId | Hex;
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
// FIXME: This seems to be for Ethereum only
defaultSwapsToken?: SwapsEthToken;
isBridgeChain: boolean;
isBuyableChain: boolean;
+ isBuyableChainWithoutSigning: boolean;
///: END:ONLY_INCLUDE_IF
isSwapsChain: boolean;
isSigningEnabled: boolean;
@@ -53,6 +55,7 @@ export const CoinOverview = ({
defaultSwapsToken,
isBridgeChain,
isBuyableChain,
+ isBuyableChainWithoutSigning,
///: END:ONLY_INCLUDE_IF
isSwapsChain,
isSigningEnabled,
@@ -65,9 +68,10 @@ export const CoinOverview = ({
///: END:ONLY_INCLUDE_IF
const t = useContext(I18nContext);
- const showFiat = useSelector(getShouldShowFiat);
+ const isEvm = useSelector(getMultichainIsEvm);
+ const showFiat = useSelector(getMultichainShouldShowFiat);
const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences);
- const { ticker, type, rpcUrl } = useSelector(getProviderConfig);
+ const { ticker, type, rpcUrl } = useSelector(getMultichainProviderConfig);
const isOriginalNativeSymbol = useIsOriginalNativeTokenSymbol(
chainId,
ticker,
@@ -131,9 +135,11 @@ export const CoinOverview = ({
hideTitle
/>
)}
-
+ {isEvm && (
+
+ )}
}
@@ -146,6 +152,7 @@ export const CoinOverview = ({
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
isBridgeChain,
isBuyableChain,
+ isBuyableChainWithoutSigning,
defaultSwapsToken,
///: END:ONLY_INCLUDE_IF
classPrefix,
diff --git a/ui/components/app/wallet-overview/eth-overview.js b/ui/components/app/wallet-overview/eth-overview.js
index 5eeec7a59cbf..684c6cffbf6b 100644
--- a/ui/components/app/wallet-overview/eth-overview.js
+++ b/ui/components/app/wallet-overview/eth-overview.js
@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
-
import { EthMethod } from '@metamask/keyring-api';
import { isEqual } from 'lodash';
import {
@@ -13,15 +12,17 @@ import {
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
getSwapsDefaultToken,
getIsBridgeChain,
- getIsBuyableChain,
///: END:ONLY_INCLUDE_IF
} from '../../../selectors';
+///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
+import { getIsNativeTokenBuyable } from '../../../ducks/ramps';
+///: END:ONLY_INCLUDE_IF
import { CoinOverview } from './coin-overview';
const EthOverview = ({ className }) => {
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
const isBridgeChain = useSelector(getIsBridgeChain);
- const isBuyableChain = useSelector(getIsBuyableChain);
+ const isBuyableChain = useSelector(getIsNativeTokenBuyable);
// FIXME: This causes re-renders, so use isEqual to avoid this
const defaultSwapsToken = useSelector(getSwapsDefaultToken, isEqual);
///: END:ONLY_INCLUDE_IF
diff --git a/ui/components/app/wallet-overview/eth-overview.test.js b/ui/components/app/wallet-overview/eth-overview.test.js
index 0d13bff7e1ef..0d079a32f104 100644
--- a/ui/components/app/wallet-overview/eth-overview.test.js
+++ b/ui/components/app/wallet-overview/eth-overview.test.js
@@ -12,27 +12,11 @@ import {
import { renderWithProvider } from '../../../../test/jest/rendering';
import { KeyringType } from '../../../../shared/constants/keyring';
import { useIsOriginalNativeTokenSymbol } from '../../../hooks/useIsOriginalNativeTokenSymbol';
+import { defaultBuyableChains } from '../../../ducks/ramps/constants';
import { ETH_EOA_METHODS } from '../../../../shared/constants/eth-methods';
import { getIntlLocale } from '../../../ducks/locale/locale';
import EthOverview from './eth-overview';
-// Mock BUYABLE_CHAINS_MAP
-jest.mock('../../../../shared/constants/network', () => ({
- ...jest.requireActual('../../../../shared/constants/network'),
- BUYABLE_CHAINS_MAP: {
- // MAINNET
- '0x1': {
- nativeCurrency: 'ETH',
- network: 'ethereum',
- },
- // POLYGON
- '0x89': {
- nativeCurrency: 'MATIC',
- network: 'polygon',
- },
- },
-}));
-
jest.mock('../../../hooks/useIsOriginalNativeTokenSymbol', () => {
return {
useIsOriginalNativeTokenSymbol: jest.fn(),
@@ -138,6 +122,9 @@ describe('EthOverview', () => {
},
],
},
+ ramps: {
+ buyableChains: defaultBuyableChains,
+ },
};
const store = configureMockStore([thunk])(mockStore);
@@ -181,6 +168,7 @@ describe('EthOverview', () => {
it('should show the cached primary balance', async () => {
const mockedStoreWithCachedBalance = {
+ ...mockStore,
metamask: {
...mockStore.metamask,
accounts: {
@@ -267,6 +255,7 @@ describe('EthOverview', () => {
it('should open the MMI PD Swaps URI when clicking on Swap button with a Custody account', async () => {
const mockedStoreWithCustodyKeyring = {
+ ...mockStore,
metamask: {
...mockStore.metamask,
mmiConfiguration: {
@@ -375,6 +364,7 @@ describe('EthOverview', () => {
it('should have the Buy native token button disabled if chain id is not part of supported buyable chains', () => {
const mockedStoreWithUnbuyableChainId = {
+ ...mockStore,
metamask: {
...mockStore.metamask,
providerConfig: {
@@ -399,6 +389,7 @@ describe('EthOverview', () => {
it('should have the Buy native token enabled if chain id is part of supported buyable chains', () => {
const mockedStoreWithUnbuyableChainId = {
+ ...mockStore,
metamask: {
...mockStore.metamask,
providerConfig: {
@@ -432,6 +423,7 @@ describe('EthOverview', () => {
it('should open the Buy native token URI when clicking on Buy button for a buyable chain ID', async () => {
const mockedStoreWithBuyableChainId = {
+ ...mockStore,
metamask: {
...mockStore.metamask,
providerConfig: {
diff --git a/ui/components/app/wallet-overview/index.js b/ui/components/app/wallet-overview/index.js
index 2eb058f81afd..54536007bc41 100644
--- a/ui/components/app/wallet-overview/index.js
+++ b/ui/components/app/wallet-overview/index.js
@@ -1 +1,2 @@
export { default as EthOverview } from './eth-overview';
+export { default as BtcOverview } from './btc-overview';
diff --git a/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap b/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap
index d97d0fd70fbe..0b2b13a29277 100644
--- a/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap
+++ b/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap
@@ -32,7 +32,7 @@ exports[`AccountListItem renders AccountListItem component and shows account nam
class="mm-avatar-account__jazzicon"
>