diff --git a/.circleci/config.yml b/.circleci/config.yml index 552aa3305509..60bb80eaf449 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,10 +25,6 @@ executors: resource_class: medium+ environment: NODE_OPTIONS: --max_old_space_size=4096 - shellcheck: - docker: - - image: koalaman/shellcheck-alpine@sha256:dfaf08fab58c158549d3be64fb101c626abc5f16f341b569092577ae207db199 - resource_class: small playwright: docker: - image: mcr.microsoft.com/playwright:v1.44.1-focal @@ -127,15 +123,6 @@ workflows: - master requires: - prep-deps - - test-deps-audit: - requires: - - prep-deps - - test-deps-depcheck: - requires: - - prep-deps - - test-yarn-dedupe: - requires: - - prep-deps - validate-lavamoat-allow-scripts: requires: - prep-deps @@ -193,16 +180,6 @@ workflows: - prep-build-ts-migration-dashboard: requires: - prep-deps - - test-lint: - requires: - - prep-deps - - test-lint-shellcheck - - test-lint-lockfile: - requires: - - prep-deps - - test-lint-changelog: - requires: - - prep-deps - test-e2e-chrome-webpack: <<: *main_master_rc_only requires: @@ -291,14 +268,9 @@ workflows: - prep-build-flask-mv2 - all-tests-pass: requires: - - test-deps-depcheck - validate-lavamoat-allow-scripts - validate-lavamoat-policy-build - validate-lavamoat-policy-webapp - - test-lint - - test-lint-shellcheck - - test-lint-lockfile - - test-lint-changelog - validate-source-maps - validate-source-maps-beta - validate-source-maps-flask @@ -964,31 +936,6 @@ jobs: name: Rerun workflows from failed command: yarn ci-rerun-from-failed - test-yarn-dedupe: - executor: node-browsers-small - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: Detect yarn lock deduplications - command: yarn dedupe --check - - test-lint: - executor: node-browsers-medium - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: Lint - command: yarn lint - - run: - name: Verify locales - command: yarn verify-locales --quiet - test-storybook: executor: node-browsers-medium-plus steps: @@ -1003,78 +950,6 @@ jobs: name: Test Storybook command: yarn test-storybook:ci - test-lint-shellcheck: - executor: shellcheck - steps: - - checkout - - run: apk add --no-cache bash jq yarn - - run: - name: ShellCheck Lint - command: ./development/shellcheck.sh - - test-lint-lockfile: - executor: node-browsers-medium-plus - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: lockfile-lint - command: yarn lint:lockfile - - run: - name: check yarn resolutions - command: yarn --check-resolutions - - test-lint-changelog: - executor: node-browsers-small - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - when: - condition: - not: - matches: - pattern: /^Version-v(\d+)[.](\d+)[.](\d+)$/ - value: << pipeline.git.branch >> - steps: - - run: - name: Validate changelog - command: yarn lint:changelog - - when: - condition: - matches: - pattern: /^Version-v(\d+)[.](\d+)[.](\d+)$/ - value: << pipeline.git.branch >> - steps: - - run: - name: Validate release candidate changelog - command: .circleci/scripts/validate-changelog-in-rc.sh - - test-deps-audit: - executor: node-browsers-small - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: yarn audit - command: yarn audit - - test-deps-depcheck: - executor: node-browsers-small - steps: - - run: *shallow-git-clone-and-enable-vnc - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: depcheck - command: yarn depcheck - test-e2e-chrome-webpack: executor: node-browsers-medium-plus parallelism: 20 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c7907455701d..ee29c54e94ac 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,6 +28,34 @@ jobs: run: ${{ steps.download-actionlint.outputs.executable }} -color shell: bash + test-lint-shellcheck: + name: Test lint shellcheck + uses: ./.github/workflows/test-lint-shellcheck.yml + + test-lint: + name: Test lint + uses: ./.github/workflows/test-lint.yml + + test-lint-changelog: + name: Test lint changelog + uses: ./.github/workflows/test-lint-changelog.yml + + test-lint-lockfile: + name: Test lint lockfile + uses: ./.github/workflows/test-lint-lockfile.yml + + test-deps-audit: + name: Test deps audit + uses: ./.github/workflows/test-deps-audit.yml + + test-yarn-dedupe: + name: Test yarn dedupe + uses: ./.github/workflows/test-yarn-dedupe.yml + + test-deps-depcheck: + name: Test deps depcheck + uses: ./.github/workflows/test-deps-depcheck.yml + run-tests: name: Run tests uses: ./.github/workflows/run-tests.yml @@ -41,6 +69,12 @@ jobs: runs-on: ubuntu-latest needs: - check-workflows + - test-lint-shellcheck + - test-lint + - test-lint-changelog + - test-lint-lockfile + - test-yarn-dedupe + - test-deps-depcheck - run-tests - wait-for-circleci-workflow-status outputs: diff --git a/.github/workflows/test-deps-audit.yml b/.github/workflows/test-deps-audit.yml new file mode 100644 index 000000000000..271746da2429 --- /dev/null +++ b/.github/workflows/test-deps-audit.yml @@ -0,0 +1,18 @@ +name: Test deps audit + +on: + workflow_call: + +jobs: + test-deps-audit: + name: Test deps audit + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Run audit + run: yarn audit diff --git a/.github/workflows/test-deps-depcheck.yml b/.github/workflows/test-deps-depcheck.yml new file mode 100644 index 000000000000..3860c485f25b --- /dev/null +++ b/.github/workflows/test-deps-depcheck.yml @@ -0,0 +1,18 @@ +name: Test deps depcheck + +on: + workflow_call: + +jobs: + test-deps-depcheck: + name: Test deps depcheck + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Run depcheck + run: yarn depcheck diff --git a/.github/workflows/test-lint-changelog.yml b/.github/workflows/test-lint-changelog.yml new file mode 100644 index 000000000000..66c0219551f4 --- /dev/null +++ b/.github/workflows/test-lint-changelog.yml @@ -0,0 +1,23 @@ +name: Test lint changelog + +on: + workflow_call: + +jobs: + test-lint-changelog: + name: Test lint changelog + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Validate changelog + if: ${{ !startsWith(github.head_ref || github.ref_name, 'Version-v') }} + run: yarn lint:changelog + + - name: Validate release candidate changelog + if: ${{ startsWith(github.head_ref || github.ref_name, 'Version-v') }} + run: .circleci/scripts/validate-changelog-in-rc.sh diff --git a/.github/workflows/test-lint-lockfile.yml b/.github/workflows/test-lint-lockfile.yml new file mode 100644 index 000000000000..cc84318624ce --- /dev/null +++ b/.github/workflows/test-lint-lockfile.yml @@ -0,0 +1,21 @@ +name: Test lint lockfile + +on: + workflow_call: + +jobs: + test-lint-lockfile: + name: Test lint lockfile + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Lint lockfile + run: yarn lint:lockfile + + - name: Check yarn resolutions + run: yarn --check-resolutions diff --git a/.github/workflows/test-lint-shellcheck.yml b/.github/workflows/test-lint-shellcheck.yml new file mode 100644 index 000000000000..c4127902a2f4 --- /dev/null +++ b/.github/workflows/test-lint-shellcheck.yml @@ -0,0 +1,15 @@ +name: Test lint shellcheck + +on: + workflow_call: + +jobs: + test-lint-shellcheck: + name: Test lint shellcheck + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: ShellCheck Lint + run: ./development/shellcheck.sh diff --git a/.github/workflows/test-lint.yml b/.github/workflows/test-lint.yml new file mode 100644 index 000000000000..df40a3a7ef27 --- /dev/null +++ b/.github/workflows/test-lint.yml @@ -0,0 +1,21 @@ +name: Test lint + +on: + workflow_call: + +jobs: + test-lint: + name: Test lint + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Lint + run: yarn lint + + - name: Verify locales + run: yarn verify-locales --quiet diff --git a/.github/workflows/test-yarn-dedupe.yml b/.github/workflows/test-yarn-dedupe.yml new file mode 100644 index 000000000000..40bda1dfb3d2 --- /dev/null +++ b/.github/workflows/test-yarn-dedupe.yml @@ -0,0 +1,18 @@ +name: Test yarn dedupe + +on: + workflow_call: + +jobs: + test-yarn-dedupe: + name: Test yarn dedupe + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: Detect yarn lock deduplications + run: yarn dedupe --check diff --git a/app/_locales/am/messages.json b/app/_locales/am/messages.json index ccb81d489af7..fac85861828c 100644 --- a/app/_locales/am/messages.json +++ b/app/_locales/am/messages.json @@ -428,9 +428,6 @@ "noConversionRateAvailable": { "message": "ምንም የልወጣ ተመን አይገኝም" }, - "noTransactions": { - "message": "ግብይቶች የሉዎትም" - }, "noWebcamFound": { "message": "የኮምፒዩተርዎ ካሜራ አልተገኘም። እባክዎ እንደገና ይሞክሩ።" }, diff --git a/app/_locales/ar/messages.json b/app/_locales/ar/messages.json index 00685f39df87..33585243d016 100644 --- a/app/_locales/ar/messages.json +++ b/app/_locales/ar/messages.json @@ -444,9 +444,6 @@ "noConversionRateAvailable": { "message": "لا يوجد معدل تحويل متاح" }, - "noTransactions": { - "message": "لا توجد لديك معاملات" - }, "noWebcamFound": { "message": "لم يتم العثور على كاميرا ويب للكمبيوتر الخاص بك. حاول مرة اخرى." }, diff --git a/app/_locales/bg/messages.json b/app/_locales/bg/messages.json index 2e51cf4b24b4..a6dbac690242 100644 --- a/app/_locales/bg/messages.json +++ b/app/_locales/bg/messages.json @@ -443,9 +443,6 @@ "noConversionRateAvailable": { "message": "Няма наличен процент на преобръщане" }, - "noTransactions": { - "message": "Нямате транзакции" - }, "noWebcamFound": { "message": "Уеб камерата на компютърa Ви не беше намерена. Моля, опитайте отново." }, diff --git a/app/_locales/bn/messages.json b/app/_locales/bn/messages.json index 6f3bb290215d..1df74dc0e941 100644 --- a/app/_locales/bn/messages.json +++ b/app/_locales/bn/messages.json @@ -437,9 +437,6 @@ "noConversionRateAvailable": { "message": "কোনো বিনিময় হার উপলভ্য নয়" }, - "noTransactions": { - "message": "আপনার কোনো লেনদেন নেই" - }, "noWebcamFound": { "message": "আপনার কম্পিউটারের ওয়েবক্যাম খুঁজে পাওয়া যায়নি। অনুগ্রহ করে আবার চেষ্টা করুন।" }, diff --git a/app/_locales/ca/messages.json b/app/_locales/ca/messages.json index c54e236d8a21..b988ae488143 100644 --- a/app/_locales/ca/messages.json +++ b/app/_locales/ca/messages.json @@ -431,9 +431,6 @@ "noConversionRateAvailable": { "message": "No hi ha cap tarifa de conversió disponible" }, - "noTransactions": { - "message": "No tens transaccions" - }, "noWebcamFound": { "message": "No s'ha trovat la webcam del teu ordinador. Si us plau prova de nou." }, diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json index b6161b00a979..adf67dbda77d 100644 --- a/app/_locales/cs/messages.json +++ b/app/_locales/cs/messages.json @@ -210,9 +210,6 @@ "next": { "message": "Další" }, - "noTransactions": { - "message": "Žádné transakce" - }, "passwordNotLongEnough": { "message": "Heslo není dost dlouhé" }, diff --git a/app/_locales/da/messages.json b/app/_locales/da/messages.json index e9c884f28dbb..080dd75f22d6 100644 --- a/app/_locales/da/messages.json +++ b/app/_locales/da/messages.json @@ -431,9 +431,6 @@ "noConversionRateAvailable": { "message": "Ingen tilgængelig omregningskurs" }, - "noTransactions": { - "message": "Du har ingen transaktioner" - }, "noWebcamFound": { "message": "Din computers webkamera blev ikke fundet. Prøv venligst igen." }, diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 04c45bd81348..5a499ce7dc6d 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "Nein, danke!" }, - "noTransactions": { - "message": "Keine Transaktionen" - }, "noWebcamFound": { "message": "Die Webcam Ihres Computers wurde nicht gefunden. Bitte versuchen Sie es erneut." }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "Smart Contracts dekodieren" }, - "use4ByteResolutionDescription": { - "message": "Um das Benutzererlebnis zu verbessern, passen wir die Aktivitätsregisterkarte mit Nachrichten an, die auf den Smart Contracts basieren, mit denen Sie interagieren. MetaMask verwendet einen Dienst namens 4byte.directory, um Daten zu entschlüsseln und Ihnen eine Version eines Smart Contracts anzuzeigen, die leichter zu lesen ist. Dies trägt dazu bei, die Wahrscheinlichkeit zu verringern, dass Sie bösartige Smart-Contract-Aktionen genehmigen, kann aber dazu führen, dass Ihre IP-Adresse weitergegeben wird." - }, "useMultiAccountBalanceChecker": { "message": "Kontoguthaben-Anfragen sammeln" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 24da4df88460..d930bafaa3d7 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "Όχι, ευχαριστώ" }, - "noTransactions": { - "message": "Δεν έχετε καμιά συναλλαγή" - }, "noWebcamFound": { "message": "Η κάμερα του υπολογιστή σας δεν βρέθηκε. Προσπαθήστε ξανά." }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "Αποκωδικοποίηση έξυπνων συμβολαίων" }, - "use4ByteResolutionDescription": { - "message": "Για να βελτιώσουμε την εμπειρία του χρήστη, προσαρμόζουμε την καρτέλα δραστηριότητας με μηνύματα που βασίζονται στα έξυπνα συμβόλαια με τα οποία αλληλεπιδράτε. Το MetaMask χρησιμοποιεί μια υπηρεσία που ονομάζεται 4byte.directory για την αποκωδικοποίηση δεδομένων και την εμφάνιση μιας έκδοσης ενός έξυπνου συμβολαίου που είναι πιο ευανάγνωστο. Αυτό συμβάλλει στη μείωση των πιθανοτήτων σας να εγκρίνετε κακόβουλες ενέργειες έξυπνων συμβολαίων, αλλά μπορεί να έχει ως αποτέλεσμα την κοινοποίηση της διεύθυνσης IP σας." - }, "useMultiAccountBalanceChecker": { "message": "Μαζικά αιτήματα υπολοίπου λογαριασμού" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 9e9b335cf874..be2f7e4f6046 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -3498,16 +3498,6 @@ "noThanks": { "message": "No thanks" }, - "noTransactions": { - "message": "You have no transactions" - }, - "noTransactionsChainIdMismatch": { - "message": "Please switch network to view transactions" - }, - "noTransactionsNetworkName": { - "message": "Please switch to $1 network to view transactions", - "description": "$1 represents the network name" - }, "noWebcamFound": { "message": "Your computer's webcam was not found. Please try again." }, @@ -6278,6 +6268,9 @@ "message": "To: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleDecodeDescription": { + "message": "We use 4byte.directory and Sourcify services to decode and display more readable transaction data. This helps you understand the outcome of pending and past transactions, but can result in your IP address being shared." + }, "toggleRequestQueueDescription": { "message": "This allows you to select a network for each site instead of a single selected network for all sites. This feature will prevent you from switching networks manually, which may break your user experience on certain sites." }, @@ -6423,6 +6416,9 @@ "transactionFailed": { "message": "Transaction Failed" }, + "transactionFailedBannerMessage": { + "message": "This transaction would have cost you extra fees, so we stopped it. Your money is still in your wallet." + }, "transactionFee": { "message": "Transaction fee" }, @@ -6641,9 +6637,6 @@ "use4ByteResolution": { "message": "Decode smart contracts" }, - "use4ByteResolutionDescription": { - "message": "To improve user experience, we customize the activity tab with messages based on the smart contracts you interact with. MetaMask uses a service called 4byte.directory to decode data and show you a version of a smart contract that's easier to read. This helps reduce your chances of approving malicious smart contract actions, but can result in your IP address being shared." - }, "useMultiAccountBalanceChecker": { "message": "Batch account balance requests" }, diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index d242dc7e88f8..f66111064a90 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -3141,9 +3141,6 @@ "noThanks": { "message": "No thanks" }, - "noTransactions": { - "message": "You have no transactions" - }, "noWebcamFound": { "message": "Your computer's webcam was not found. Please try again." }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 0f24f5bc5b1a..bfb53061f73b 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "No, gracias" }, - "noTransactions": { - "message": "No tiene transacciones" - }, "noWebcamFound": { "message": "No se encontró la cámara web del equipo. Vuelva a intentarlo." }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "Decodificar contratos inteligentes" }, - "use4ByteResolutionDescription": { - "message": "Para mejorar la experiencia del usuario, personalizamos la pestaña de actividad con mensajes basados en los contratos inteligentes con los que interactúa. MetaMask usa un servicio llamado 4byte.directory para decodificar datos y mostrarle una versión de un contrato inteligente que es más fácil de leer. Esto ayuda a reducir sus posibilidades de aprobar acciones de contratos inteligentes maliciosos, pero puede resultar en que se comparta su dirección IP." - }, "useMultiAccountBalanceChecker": { "message": "Solicitudes de saldo de cuenta por lotes" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 5d940a269091..4e82c875c760 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -1273,9 +1273,6 @@ "noConversionRateAvailable": { "message": "No hay tasa de conversión disponible" }, - "noTransactions": { - "message": "No tiene transacciones" - }, "noWebcamFound": { "message": "No se encontró la cámara web del equipo. Vuelva a intentarlo." }, diff --git a/app/_locales/et/messages.json b/app/_locales/et/messages.json index c5f55c6d3327..a2a85b36d987 100644 --- a/app/_locales/et/messages.json +++ b/app/_locales/et/messages.json @@ -437,9 +437,6 @@ "noConversionRateAvailable": { "message": "Ühtegi vahetuskurssi pole saadaval" }, - "noTransactions": { - "message": "Teil ei ole tehinguid" - }, "noWebcamFound": { "message": "Teie arvuti veebikaamerat ei leitud. Proovige uuesti." }, diff --git a/app/_locales/fa/messages.json b/app/_locales/fa/messages.json index 8399ecb91aec..3ec5211c2dfb 100644 --- a/app/_locales/fa/messages.json +++ b/app/_locales/fa/messages.json @@ -443,9 +443,6 @@ "noConversionRateAvailable": { "message": "هیچ نرخ تغییر موجود نمیباشد" }, - "noTransactions": { - "message": "شما هیچ معامله ندارید" - }, "noWebcamFound": { "message": "وب کم کمپیوتر تان پیدا نشد. لطفًا دوباره کوشش کنید." }, diff --git a/app/_locales/fi/messages.json b/app/_locales/fi/messages.json index 5002c4eed2b3..c49ea583598d 100644 --- a/app/_locales/fi/messages.json +++ b/app/_locales/fi/messages.json @@ -443,9 +443,6 @@ "noConversionRateAvailable": { "message": "Vaihtokurssi ei saatavilla" }, - "noTransactions": { - "message": "Sinulla ei ole tapahtumia" - }, "noWebcamFound": { "message": "Tietokoneesi verkkokameraa ei löytynyt. Ole hyvä ja yritä uudestaan." }, diff --git a/app/_locales/fil/messages.json b/app/_locales/fil/messages.json index abee8f9c0a5e..14f91bc5c16a 100644 --- a/app/_locales/fil/messages.json +++ b/app/_locales/fil/messages.json @@ -381,9 +381,6 @@ "noConversionRateAvailable": { "message": "Walang Presyo ng Palitan na Available" }, - "noTransactions": { - "message": "Wala kang mga transaksyon" - }, "noWebcamFound": { "message": "Hindi nakita ang webcam ng iyong computer. Pakisubukang muli." }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index ce615bddd591..a86d465b786d 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "Non merci" }, - "noTransactions": { - "message": "Aucune transaction" - }, "noWebcamFound": { "message": "La caméra de votre ordinateur n’a pas été trouvée. Veuillez réessayer." }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "Décoder les contrats intelligents" }, - "use4ByteResolutionDescription": { - "message": "Pour améliorer l’expérience utilisateur, nous personnalisons les messages qui s’affichent dans l’onglet d’activité en fonction des contrats intelligents avec lesquels vous interagissez. MetaMask utilise un service appelé 4byte.directory pour décoder les données et vous montrer une version plus facile à lire des contrats intelligents. Ainsi vous aurez moins de chances d’approuver l’exécution de contrats intelligents malveillants, mais cela peut nécessiter le partage de votre adresse IP." - }, "useMultiAccountBalanceChecker": { "message": "Demandes d’informations concernant le solde de plusieurs comptes" }, diff --git a/app/_locales/he/messages.json b/app/_locales/he/messages.json index 3bd6b6b67408..c9360ff612de 100644 --- a/app/_locales/he/messages.json +++ b/app/_locales/he/messages.json @@ -440,9 +440,6 @@ "noConversionRateAvailable": { "message": "אין שער המרה זמין" }, - "noTransactions": { - "message": "אין לך עסקאות" - }, "noWebcamFound": { "message": "מצלמת הרשת של מחשבך לא נמצאה. נא לנסות שוב." }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 05488711d94f..144db40e41a3 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "जी नहीं, धन्यवाद" }, - "noTransactions": { - "message": "आपके पास कोई ट्रांसेक्शन नहीं है" - }, "noWebcamFound": { "message": "आपके कंप्यूटर का वेबकैम नहीं मिला। कृपया फिर से कोशिश करें।" }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "स्मार्ट कॉन्ट्रैक्ट्स को डीकोड करें" }, - "use4ByteResolutionDescription": { - "message": "यूज़र के अनुभव को बेहतर बनाने के लिए, आपके द्वारा इंटरैक्ट किए गए स्मार्ट कॉन्ट्रैक्ट्स के आधार पर हम एक्टिविटी टैब को मैसेज के साथ कस्टमाइज़ करते हैं। डेटा को डीकोड करने और आसानी से पढ़े जा सकने वाले स्मार्ट कॉन्ट्रैक्ट्स का एक वर्शन आपको दिखाने के लिए MetaMask एक सर्विस इस्तेमाल करता है जिसका नाम 4byte.directory है। इससे आपके द्वारा बुरी नीयत वाले स्मार्ट कॉन्ट्रैक्ट एक्शन को मंजूरी देने की संभावनाओं को कम करने में मदद मिलती है। हालांकि, इसमें आपका IP एड्रेस शेयर होने का खतरा हो सकता है।" - }, "useMultiAccountBalanceChecker": { "message": "अकाउंट के बैलेंस के रिक्वेस्ट्स को बैच करें" }, diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index f6bb938c4886..cecc6f26385b 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -193,9 +193,6 @@ "next": { "message": "अगला" }, - "noTransactions": { - "message": "कोई लेन-देन नहीं" - }, "pastePrivateKey": { "message": "यहां अपनी निजी कुंजी स्ट्रिंग चिपकाएं:", "description": "For importing an account from a private key" diff --git a/app/_locales/hr/messages.json b/app/_locales/hr/messages.json index d1ca9bac8057..77b864172ae2 100644 --- a/app/_locales/hr/messages.json +++ b/app/_locales/hr/messages.json @@ -440,9 +440,6 @@ "noConversionRateAvailable": { "message": "Nijedan konverzijski tečaj nije dostupan" }, - "noTransactions": { - "message": "Nemate transkacija" - }, "noWebcamFound": { "message": "Mrežna kamera vašeg računala nije pronađena. Pokušajte ponovno." }, diff --git a/app/_locales/ht/messages.json b/app/_locales/ht/messages.json index 8d0e7e703559..29df50803fa9 100644 --- a/app/_locales/ht/messages.json +++ b/app/_locales/ht/messages.json @@ -310,9 +310,6 @@ "noConversionRateAvailable": { "message": "Pa gen okenn Konvèsyon Disponib" }, - "noTransactions": { - "message": "Pa gen tranzaksyon" - }, "noWebcamFound": { "message": "Nou pakay jwenn webcam òdinatè ou. Tanpri eseye ankò." }, diff --git a/app/_locales/hu/messages.json b/app/_locales/hu/messages.json index ee8699a64545..9255c752b4a7 100644 --- a/app/_locales/hu/messages.json +++ b/app/_locales/hu/messages.json @@ -440,9 +440,6 @@ "noConversionRateAvailable": { "message": "Nincs elérhető átváltási díj" }, - "noTransactions": { - "message": "Nincsenek tranzakciói" - }, "noWebcamFound": { "message": "Nem található számítógéped webkamerája. Kérünk, próbáld újra." }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index c918a6cc0fb0..44b2bf804727 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "Tidak, terima kasih" }, - "noTransactions": { - "message": "Anda tidak memiliki transaksi" - }, "noWebcamFound": { "message": "Webcam komputer Anda tidak ditemukan. Harap coba lagi." }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "Uraikan kode kontrak cerdas" }, - "use4ByteResolutionDescription": { - "message": "Untuk meningkatkan pengalaman pengguna, kami menyesuaikan tab aktivitas dengan pesan berdasarkan kontrak cerdas yang berinteraksi dengan Anda. MetaMask menggunakan layanan yang disebut 4byte.directory untuk menguraikan kode data dan menampilkan versi kontrak cerdas yang lebih mudah dibaca. Ini membantu mengurangi peluang Anda untuk menyetujui tindakan kontrak cerdas yang berbahaya, tetapi dapat menyebabkan alamat IP Anda tersebar." - }, "useMultiAccountBalanceChecker": { "message": "Kelompokkan permintaan saldo akun" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index da1da37952f6..a88d710a6f81 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -1046,9 +1046,6 @@ "noConversionRateAvailable": { "message": "Tasso di conversione non disponibile" }, - "noTransactions": { - "message": "Nessuna Transazione" - }, "noWebcamFound": { "message": "La fotocamera del tuo computer non è stata trovata. Riprova." }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index ead0da87b7b8..70d6da31301e 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "結構です" }, - "noTransactions": { - "message": "トランザクションがありません" - }, "noWebcamFound": { "message": "お使いのコンピューターのWebカメラが見つかりませんでした。もう一度お試しください。" }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "スマートコントラクトのデコード" }, - "use4ByteResolutionDescription": { - "message": "ユーザーエクスペリエンスの向上のため、ユーザーがやり取りするスマートコントラクトに応じたメッセージで、アクティビティタブをカスタマイズします。MetaMaskは、4byte.directoryと呼ばれるサービスを利用してデータをデコードし、より読みやすいバージョンのスマートコントラクトを表示します。これにより、悪質なスマートコントラクトの操作を承認する可能性は減りますが、IPアドレスが公開されます。" - }, "useMultiAccountBalanceChecker": { "message": "アカウント残高の一括リクエスト" }, diff --git a/app/_locales/kn/messages.json b/app/_locales/kn/messages.json index 805de3c78fc0..148a820f5bfa 100644 --- a/app/_locales/kn/messages.json +++ b/app/_locales/kn/messages.json @@ -443,9 +443,6 @@ "noConversionRateAvailable": { "message": "ಯಾವುದೇ ಪರಿವರ್ತನೆ ದರ ಲಭ್ಯವಿಲ್ಲ" }, - "noTransactions": { - "message": "ನೀವು ಯಾವುದೇ ವಹಿವಾಟುಗಳನ್ನು ಹೊಂದಿಲ್ಲ" - }, "noWebcamFound": { "message": "ನಿಮ್ಮ ಕಂಪ್ಯೂಟರ್‌ನ ವೆಬ್‌ಕ್ಯಾಮ್ ಕಂಡುಬಂದಿಲ್ಲ. ದಯವಿಟ್ಟು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ." }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index f22491040b4f..6978164f82f4 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "괜찮습니다" }, - "noTransactions": { - "message": "트랜잭션이 없습니다." - }, "noWebcamFound": { "message": "컴퓨터의 웹캠을 찾을 수 없습니다. 다시 시도하세요." }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "스마트 계약 디코딩" }, - "use4ByteResolutionDescription": { - "message": "인터렉션하는 스마트 계약에 따라 메시지를 이용하여 활동 탭을 사용자 맞춤하여 사용자 경험을 개선합니다. MetaMask는 4byte.directory라는 서비스를 통해 데이터를 디코딩하여 스마트 계약을 읽기 쉬운 버전으로 보여 줍니다. 이는 악의적인 스마트 계약을 승인할 가능성을 줄이는 데 도움이 되지만 IP 주소가 공유될 수 있습니다." - }, "useMultiAccountBalanceChecker": { "message": "일괄 계정 잔액 요청" }, diff --git a/app/_locales/lt/messages.json b/app/_locales/lt/messages.json index cd034a303c4a..3e5586f39f50 100644 --- a/app/_locales/lt/messages.json +++ b/app/_locales/lt/messages.json @@ -443,9 +443,6 @@ "noConversionRateAvailable": { "message": "Nėra keitimo kurso" }, - "noTransactions": { - "message": "Neturite jokių operacijų" - }, "noWebcamFound": { "message": "Jūsų kompiuterio vaizdo kamera nerasta. Bandykite dar kartą." }, diff --git a/app/_locales/lv/messages.json b/app/_locales/lv/messages.json index be5d43f4afd9..a1e166937385 100644 --- a/app/_locales/lv/messages.json +++ b/app/_locales/lv/messages.json @@ -443,9 +443,6 @@ "noConversionRateAvailable": { "message": "Konversijas kurss nav pieejams" }, - "noTransactions": { - "message": "Jums nav neviena darījuma." - }, "noWebcamFound": { "message": "Jūsu datora tīmekļa kamera netika atrasta. Lūdzu, mēģiniet vēlreiz." }, diff --git a/app/_locales/ms/messages.json b/app/_locales/ms/messages.json index 20161aaf34da..3605febbc7d7 100644 --- a/app/_locales/ms/messages.json +++ b/app/_locales/ms/messages.json @@ -430,9 +430,6 @@ "noConversionRateAvailable": { "message": "Tiada Kadar Penukaran yang Tersedia" }, - "noTransactions": { - "message": "Anda tiada transaksi" - }, "noWebcamFound": { "message": "Webcam komputer anda tidak dijumpai. Sila cuba semula." }, diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json index 7ac22889df2d..acf66091845f 100644 --- a/app/_locales/nl/messages.json +++ b/app/_locales/nl/messages.json @@ -183,9 +183,6 @@ "next": { "message": "volgende" }, - "noTransactions": { - "message": "Geen transacties" - }, "pastePrivateKey": { "message": "Plak hier uw privésleutelstring:", "description": "For importing an account from a private key" diff --git a/app/_locales/no/messages.json b/app/_locales/no/messages.json index 67948544fdbf..8459d3ef3a5f 100644 --- a/app/_locales/no/messages.json +++ b/app/_locales/no/messages.json @@ -431,9 +431,6 @@ "noConversionRateAvailable": { "message": "Ingen konverteringsrate tilgjengelig " }, - "noTransactions": { - "message": "Du har ingen transaksjoner" - }, "noWebcamFound": { "message": "Datamaskinens webkamera ble ikke funnet. Vennligst prøv igjen." }, diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index af995dbf5f5f..3dd9ae28d896 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -819,9 +819,6 @@ "noConversionRateAvailable": { "message": "Hindi Available ang Rate ng Conversion" }, - "noTransactions": { - "message": "Wala kang transaksyon" - }, "noWebcamFound": { "message": "Hindi nakita ang webcam ng iyong computer. Pakisubukan ulit." }, diff --git a/app/_locales/pl/messages.json b/app/_locales/pl/messages.json index 5baeee9b8a00..f7f5111c9973 100644 --- a/app/_locales/pl/messages.json +++ b/app/_locales/pl/messages.json @@ -440,9 +440,6 @@ "noConversionRateAvailable": { "message": "Brak kursu waluty" }, - "noTransactions": { - "message": "Nie ma transakcji" - }, "noWebcamFound": { "message": "Twoja kamera nie została znaleziona. Spróbuj ponownie." }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 50bf0a7d9996..7819be595e94 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "Não, obrigado" }, - "noTransactions": { - "message": "Você não tem transações" - }, "noWebcamFound": { "message": "A webcam do seu computador não foi encontrada. Tente novamente." }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "Decodificar contratos inteligentes" }, - "use4ByteResolutionDescription": { - "message": "Para melhorar a experiência do usuário, personalizamos a guia de atividades com mensagens baseadas nos contratos inteligentes com os quais você interage. A MetaMask usa um serviço chamado 4byte.directory para decodificar os dados e exibir a você uma versão de um contrato inteligente que é mais fácil de ler. Isso ajuda a reduzir suas chances de aprovar ações de contratos inteligentes mal-intencionados, mas pode resultar no compartilhamento do seu endereço IP." - }, "useMultiAccountBalanceChecker": { "message": "Agrupar solicitações de saldo de contas" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index 3c61987263b7..3c9c30147276 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -1273,9 +1273,6 @@ "noConversionRateAvailable": { "message": "Não há uma taxa de conversão disponível" }, - "noTransactions": { - "message": "Você não tem transações" - }, "noWebcamFound": { "message": "A webcam do seu computador não foi encontrada. Tente novamente." }, diff --git a/app/_locales/ro/messages.json b/app/_locales/ro/messages.json index f149a976cf1a..db440fcfd264 100644 --- a/app/_locales/ro/messages.json +++ b/app/_locales/ro/messages.json @@ -434,9 +434,6 @@ "noConversionRateAvailable": { "message": "Nici o rată de conversie disponibilă" }, - "noTransactions": { - "message": "Nu aveți tranzacții" - }, "noWebcamFound": { "message": "Webcam-ul computerului dvs. nu a fost găsit. Vă rugăm să încercați din nou." }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index b1445befdd17..5e281f34ac05 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "Нет, спасибо" }, - "noTransactions": { - "message": "У вас нет транзакций" - }, "noWebcamFound": { "message": "Веб-камера вашего компьютера не найдена. Попробуйте еще раз." }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "Расшифровать смарт-контракты" }, - "use4ByteResolutionDescription": { - "message": "Чтобы улучшить взаимодействие с пользователем, мы настраиваем вкладку действий, добавляя сообщения на основе смарт-контрактов, с которыми вы взаимодействуете. MetaMask использует службу под названием 4byte.directory для декодирования данных и показа версии смарт-контакта, которую легче читать. Это помогает снизить шансы того, что вы одобрите вредоносные действия смарт-контракта, но может привести к раскрытию вашего IP-адреса." - }, "useMultiAccountBalanceChecker": { "message": "Пакетные запросы баланса счета" }, diff --git a/app/_locales/sk/messages.json b/app/_locales/sk/messages.json index 1616af135d1a..a65bc68593f6 100644 --- a/app/_locales/sk/messages.json +++ b/app/_locales/sk/messages.json @@ -418,9 +418,6 @@ "noConversionRateAvailable": { "message": "Nie je k dispozícii žiadna sadzba konverzie" }, - "noTransactions": { - "message": "Žádné transakce" - }, "noWebcamFound": { "message": "Webová kamera vášho počítača sa nenašla. Skúste znova." }, diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index dd73bc8e373f..ebc303e90c5c 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -431,9 +431,6 @@ "noConversionRateAvailable": { "message": "Menjalni tečaj ni na voljo" }, - "noTransactions": { - "message": "Nimate transakcij" - }, "noWebcamFound": { "message": "Spletna kamera ni najdena. Poskusite znova kasneje." }, diff --git a/app/_locales/sr/messages.json b/app/_locales/sr/messages.json index c3986a0c0b98..1bbcd0754b92 100644 --- a/app/_locales/sr/messages.json +++ b/app/_locales/sr/messages.json @@ -434,9 +434,6 @@ "noConversionRateAvailable": { "message": "Nije dostupan kurs za konverziju" }, - "noTransactions": { - "message": "Nemate transakcije" - }, "noWebcamFound": { "message": "Nije pronađena veb kamera na vašem kompjuteru. Molimo vas pokušajte ponovo." }, diff --git a/app/_locales/sv/messages.json b/app/_locales/sv/messages.json index 278515e908eb..818f1eb498d6 100644 --- a/app/_locales/sv/messages.json +++ b/app/_locales/sv/messages.json @@ -431,9 +431,6 @@ "noConversionRateAvailable": { "message": "Ingen omräkningskurs tillgänglig" }, - "noTransactions": { - "message": "Du har inga överföringar" - }, "noWebcamFound": { "message": "Din dators webbkamera hittades inte. Vänligen försök igen." }, diff --git a/app/_locales/sw/messages.json b/app/_locales/sw/messages.json index 7c4a52f733fa..c33abcb51a18 100644 --- a/app/_locales/sw/messages.json +++ b/app/_locales/sw/messages.json @@ -425,9 +425,6 @@ "noConversionRateAvailable": { "message": "Hakuna Kiwango cha Ubadilishaji" }, - "noTransactions": { - "message": "Huna miamala." - }, "noWebcamFound": { "message": "Kamera yako ya kumpyuta haikupatikana. Tafadhali jaribu tena." }, diff --git a/app/_locales/ta/messages.json b/app/_locales/ta/messages.json index c5c36501cbb9..c5905e4e0bee 100644 --- a/app/_locales/ta/messages.json +++ b/app/_locales/ta/messages.json @@ -253,9 +253,6 @@ "next": { "message": "அடுத்தது" }, - "noTransactions": { - "message": "பரிவர்த்தனைகள் இல்லை" - }, "off": { "message": "ஆஃப்" }, diff --git a/app/_locales/th/messages.json b/app/_locales/th/messages.json index 08c9cb0df6dc..96166c6fb6ba 100644 --- a/app/_locales/th/messages.json +++ b/app/_locales/th/messages.json @@ -232,9 +232,6 @@ "next": { "message": "ถัดไป" }, - "noTransactions": { - "message": "ยังไม่มีรายการธุรกรรม" - }, "on": { "message": "เปิด" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 327be7838680..5a6bce602970 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "Salamat na lang" }, - "noTransactions": { - "message": "Wala kang transaksyon" - }, "noWebcamFound": { "message": "Hindi nakita ang webcam ng iyong computer. Pakisubukan ulit." }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "I-decode ang mga smart na kontrata" }, - "use4ByteResolutionDescription": { - "message": "Upang mapabuti ang karanasan ng user, kino-customize namin ang tab ng mga aktibidad gamit ang mga mensahe ayon sa mga smart na kontrata kung saan ka nakikipag-ugnayan. Gumagamit ang MetaMask ng serbisyong tinatawag na 4byte.directory para i-decode ang datos at ipakita sa iyo ang bersyon ng smart na kontrata na mas madaling basahin. Tumutulong ito na bawasan ang pagkakataon na aprubahan mo ang mga mapaminsalang aksyon sa smart na kontrata, ngunit maaaring magresulta sa pagbabahagi ng iyong IP address." - }, "useMultiAccountBalanceChecker": { "message": "Maramihang kahilingan sa balanse ng account" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 35ec9700e7d0..046905392710 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "Hayır, istemiyorum" }, - "noTransactions": { - "message": "İşleminiz yok" - }, "noWebcamFound": { "message": "Bilgisayarınızın web kamerası bulunamadı. Lütfen tekrar deneyin." }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "Akıllı sözleşmelerin şifresini çöz" }, - "use4ByteResolutionDescription": { - "message": "Kullanıcı deneyiminizi iyileştirmek amacıyla aktivite sekmesini etkileşimde bulunduğunuz akıllı sözleşmelere bağlı mesajlarla kişiselleştiririz. MetaMask, verileri çözmek ve size okunması daha kolay olan bir akıllı sözleşme sürümü göstermek için 4byte.directory adlı bir hizmet kullanır. Böylece kötü amaçlı akıllı sözleşme eylemlerini onaylama ihtimalinizi düşürmeye yardımcı olur ancak IP adresinizin paylaşılmasına neden olabilir." - }, "useMultiAccountBalanceChecker": { "message": "Toplu hesap bakiyesi talepleri" }, diff --git a/app/_locales/uk/messages.json b/app/_locales/uk/messages.json index 36a181b03ab2..58787e9a2238 100644 --- a/app/_locales/uk/messages.json +++ b/app/_locales/uk/messages.json @@ -443,9 +443,6 @@ "noConversionRateAvailable": { "message": "Немає доступного обмінного курсу" }, - "noTransactions": { - "message": "У вас немає транзакцій" - }, "noWebcamFound": { "message": "Веб-камеру комп’ютера не знайдено. Повторіть спробу." }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 21b187998966..a6baaff586aa 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "Không, cảm ơn" }, - "noTransactions": { - "message": "Bạn không có giao dịch nào" - }, "noWebcamFound": { "message": "Không tìm thấy webcam trên máy tính của bạn. Vui lòng thử lại." }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "Giải mã hợp đồng thông minh" }, - "use4ByteResolutionDescription": { - "message": "Để cải thiện trải nghiệm người dùng, chúng tôi sẽ tùy chỉnh thẻ hoạt động bằng các thông báo dựa trên các hợp đồng thông minh mà bạn tương tác. MetaMask sử dụng một dịch vụ có tên là 4byte.directory để giải mã dữ liệu và cho bạn thấy một phiên bản hợp đồng thông minh dễ đọc hơn. Điều này giúp giảm nguy cơ chấp thuận các hành động hợp đồng thông minh độc hại, nhưng có thể khiến địa chỉ IP của bạn bị chia sẻ." - }, "useMultiAccountBalanceChecker": { "message": "Xử lý hàng loạt yêu cầu số dư tài khoản" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 635753750343..5472a92a5660 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -3304,9 +3304,6 @@ "noThanks": { "message": "不,谢谢" }, - "noTransactions": { - "message": "您没有任何交易" - }, "noWebcamFound": { "message": "未找到您电脑的网络摄像头。请重试。" }, @@ -6338,9 +6335,6 @@ "use4ByteResolution": { "message": "对智能合约进行解码" }, - "use4ByteResolutionDescription": { - "message": "为了改善用户体验,我们根据与您交互的智能合约消息,自定义活动选项卡。MetaMask 使用名为 4byte.directory 的服务来对数据进行解码,并向您显示更方便阅读的智能合约版本。这有助于减少您批准恶意智能合约操作的机会,但可能导致您的 IP 地址被共享。" - }, "useMultiAccountBalanceChecker": { "message": "账户余额分批请求" }, diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index 87d8ebfb5520..7b4a425a03ad 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -812,9 +812,6 @@ "noConversionRateAvailable": { "message": "尚未有匯率比較值" }, - "noTransactions": { - "message": "尚未有交易" - }, "noWebcamFound": { "message": "無法搜尋到攝影鏡頭裝置。請再試一次" }, diff --git a/app/scripts/lib/createMainFrameOriginMiddleware.ts b/app/scripts/lib/createMainFrameOriginMiddleware.ts new file mode 100644 index 000000000000..bcbc2cb7d6fd --- /dev/null +++ b/app/scripts/lib/createMainFrameOriginMiddleware.ts @@ -0,0 +1,24 @@ +// Request and responses are currently untyped. +/* eslint-disable @typescript-eslint/no-explicit-any */ + +/** + * Returns a middleware that appends the mainFrameOrigin to request + * + * @param {{ mainFrameOrigin: string }} opts - The middleware options + * @returns {Function} + */ + +export default function createMainFrameOriginMiddleware({ + mainFrameOrigin, +}: { + mainFrameOrigin: string; +}) { + return function mainFrameOriginMiddleware( + req: any, + _res: any, + next: () => void, + ) { + req.mainFrameOrigin = mainFrameOrigin; + next(); + }; +} diff --git a/app/scripts/lib/ppom/ppom-util.test.ts b/app/scripts/lib/ppom/ppom-util.test.ts index d3ff3015ca41..c143f3dfe11b 100644 --- a/app/scripts/lib/ppom/ppom-util.test.ts +++ b/app/scripts/lib/ppom/ppom-util.test.ts @@ -10,7 +10,7 @@ import { SignatureController, SignatureRequest, } from '@metamask/signature-controller'; -import { Hex } from '@metamask/utils'; +import { Hex, JsonRpcRequest } from '@metamask/utils'; import { BlockaidReason, BlockaidResultType, @@ -22,6 +22,8 @@ import { AppStateController } from '../../controllers/app-state-controller'; import { generateSecurityAlertId, isChainSupported, + METHOD_SIGN_TYPED_DATA_V3, + METHOD_SIGN_TYPED_DATA_V4, updateSecurityAlertResponse, validateRequestWithPPOM, } from './ppom-util'; @@ -57,6 +59,10 @@ const TRANSACTION_PARAMS_MOCK_1: TransactionParams = { value: '0x123', }; +const SIGN_TYPED_DATA_PARAMS_MOCK_1 = '0x123'; +const SIGN_TYPED_DATA_PARAMS_MOCK_2 = + '{"primaryType":"Permit","domain":{},"types":{}}'; + const TRANSACTION_PARAMS_MOCK_2: TransactionParams = { ...TRANSACTION_PARAMS_MOCK_1, to: '0x456', @@ -261,6 +267,48 @@ describe('PPOM Utils', () => { ); }); + // @ts-expect-error This is missing from the Mocha type definitions + it.each([METHOD_SIGN_TYPED_DATA_V3, METHOD_SIGN_TYPED_DATA_V4])( + 'sanitizes request params if method is %s', + async (method: string) => { + const ppom = createPPOMMock(); + const ppomController = createPPOMControllerMock(); + + ppomController.usePPOM.mockImplementation( + (callback) => + // eslint-disable-next-line @typescript-eslint/no-explicit-any + callback(ppom as any) as any, + ); + + const firstTwoParams = [ + SIGN_TYPED_DATA_PARAMS_MOCK_1, + SIGN_TYPED_DATA_PARAMS_MOCK_2, + ]; + + const unwantedParams = [{}, undefined, 1, null]; + + const params = [...firstTwoParams, ...unwantedParams]; + + const request = { + ...REQUEST_MOCK, + method, + params, + } as unknown as JsonRpcRequest; + + await validateRequestWithPPOM({ + ...validateRequestWithPPOMOptionsBase, + ppomController, + request, + }); + + expect(ppom.validateJsonRpc).toHaveBeenCalledTimes(1); + expect(ppom.validateJsonRpc).toHaveBeenCalledWith({ + ...request, + params: firstTwoParams, + }); + }, + ); + it('updates response indicating chain is not supported', async () => { const ppomController = {} as PPOMController; const CHAIN_ID_UNSUPPORTED_MOCK = '0x2'; diff --git a/app/scripts/lib/ppom/ppom-util.ts b/app/scripts/lib/ppom/ppom-util.ts index 7dbf8c92ec5f..0407c0604a69 100644 --- a/app/scripts/lib/ppom/ppom-util.ts +++ b/app/scripts/lib/ppom/ppom-util.ts @@ -29,6 +29,8 @@ import { const { sentry } = global; const METHOD_SEND_TRANSACTION = 'eth_sendTransaction'; +export const METHOD_SIGN_TYPED_DATA_V3 = 'eth_signTypedData_v3'; +export const METHOD_SIGN_TYPED_DATA_V4 = 'eth_signTypedData_v4'; const SECURITY_ALERT_RESPONSE_ERROR = { result_type: BlockaidResultType.Errored, @@ -171,7 +173,7 @@ function normalizePPOMRequest( request, ) ) { - return request; + return sanitizeRequest(request); } const transactionParams = request.params[0]; @@ -183,6 +185,22 @@ function normalizePPOMRequest( }; } +function sanitizeRequest(request: JsonRpcRequest): JsonRpcRequest { + // This is a temporary fix to prevent a PPOM bypass + if ( + request.method === METHOD_SIGN_TYPED_DATA_V4 || + request.method === METHOD_SIGN_TYPED_DATA_V3 + ) { + if (Array.isArray(request.params)) { + return { + ...request, + params: request.params.slice(0, 2), + }; + } + } + return request; +} + function getErrorMessage(error: unknown) { if (error instanceof Error) { return `${error.name}: ${error.message}`; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 933522c449f6..62fe3c942589 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -305,6 +305,7 @@ import { createUnsupportedMethodMiddleware, } from './lib/rpc-method-middleware'; import createOriginMiddleware from './lib/createOriginMiddleware'; +import createMainFrameOriginMiddleware from './lib/createMainFrameOriginMiddleware'; import createTabIdMiddleware from './lib/createTabIdMiddleware'; import { NetworkOrderController } from './controllers/network-order'; import { AccountOrderController } from './controllers/account-order'; @@ -5804,11 +5805,18 @@ export default class MetamaskController extends EventEmitter { tabId = sender.tab.id; } + let mainFrameOrigin = origin; + if (sender.tab && sender.tab.url) { + // If sender origin is an iframe, then get the top-level frame's origin + mainFrameOrigin = new URL(sender.tab.url).origin; + } + const engine = this.setupProviderEngineEip1193({ origin, sender, subjectType, tabId, + mainFrameOrigin, }); const dupeReqFilterStream = createDupeReqFilterStream(); @@ -5929,13 +5937,25 @@ export default class MetamaskController extends EventEmitter { * @param {MessageSender | SnapSender} options.sender - The sender object. * @param {string} options.subjectType - The type of the sender subject. * @param {tabId} [options.tabId] - The tab ID of the sender - if the sender is within a tab + * @param {mainFrameOrigin} [options.mainFrameOrigin] - The origin of the main frame if the sender is an iframe */ - setupProviderEngineEip1193({ origin, subjectType, sender, tabId }) { + setupProviderEngineEip1193({ + origin, + subjectType, + sender, + tabId, + mainFrameOrigin, + }) { const engine = new JsonRpcEngine(); // Append origin to each request engine.push(createOriginMiddleware({ origin })); + // Append mainFrameOrigin to each request if present + if (mainFrameOrigin) { + engine.push(createMainFrameOriginMiddleware({ mainFrameOrigin })); + } + // Append selectedNetworkClientId to each request engine.push(createSelectedNetworkMiddleware(this.controllerMessenger)); diff --git a/builds.yml b/builds.yml index e8c7f5a28d93..43cf02d3c8e5 100644 --- a/builds.yml +++ b/builds.yml @@ -27,7 +27,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - REJECT_INVALID_SNAPS_PLATFORM_VERSION: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.10.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.11.0/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management # Main build uses the default browser manifest manifestOverrides: false @@ -48,7 +48,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - REJECT_INVALID_SNAPS_PLATFORM_VERSION: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.10.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.11.0/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management # Modifies how the version is displayed. # eg. instead of 10.25.0 -> 10.25.0-beta.2 @@ -71,7 +71,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - REJECT_INVALID_SNAPS_PLATFORM_VERSION: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.10.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.11.0/index.html - SUPPORT_LINK: https://support.metamask.io/ - SUPPORT_REQUEST_LINK: https://support.metamask.io/ - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -95,7 +95,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - REJECT_INVALID_SNAPS_PLATFORM_VERSION: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.10.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.11.0/index.html - MMI_CONFIGURATION_SERVICE_URL: https://configuration.metamask-institutional.io/v2/configuration/default - SUPPORT_LINK: https://support.metamask-institutional.io - SUPPORT_REQUEST_LINK: https://support.metamask-institutional.io diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 05a22743204c..e55d57f5ec0f 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -803,6 +803,30 @@ "@metamask/abi-utils>@metamask/utils": true } }, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true + } + }, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true + } + }, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true + } + }, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true + } + }, "@metamask/accounts-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1022,7 +1046,7 @@ "setTimeout": true }, "packages": { - "@metamask/eth-sig-util": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": true, "@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/eth-json-rpc-middleware>@metamask/utils": true, @@ -1075,11 +1099,22 @@ "@metamask/eth-sig-util>tweetnacl": true } }, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true + } + }, "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/abi-utils": true, - "@metamask/eth-snap-keyring>@metamask/utils": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/utils": true, "@metamask/utils>@scure/base": true, "browserify>buffer": true, "@ethereumjs/tx>ethereum-cryptography": true, @@ -1089,7 +1124,7 @@ "@metamask/keyring-controller>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/abi-utils": true, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": true, "@metamask/utils>@scure/base": true, "browserify>buffer": true, @@ -1100,7 +1135,7 @@ "@metamask/signature-controller>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/abi-utils": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": true, "@metamask/utils>@scure/base": true, "browserify>buffer": true, @@ -1272,7 +1307,7 @@ "packages": { "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/json-rpc-engine>@metamask/utils": true } }, "@metamask/json-rpc-middleware-stream": { @@ -1595,7 +1630,7 @@ }, "@metamask/rpc-errors": { "packages": { - "@metamask/utils": true, + "@metamask/rpc-errors>@metamask/utils": true, "@metamask/rpc-errors>fast-safe-stringify": true } }, @@ -1800,7 +1835,7 @@ "@metamask/network-controller": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, "@metamask/rpc-errors": true, - "@metamask/utils": true, + "@metamask/transaction-controller>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, "bn.js": true, "browserify>buffer": true, @@ -1861,6 +1896,51 @@ "semver": true } }, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/browser-passworder>@metamask/utils": { "globals": { "TextDecoder": true, @@ -1966,6 +2046,21 @@ "semver": true } }, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2026,6 +2121,21 @@ "semver": true } }, + "@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/keyring-api>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2116,6 +2226,21 @@ "semver": true } }, + "@metamask/rpc-errors>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2131,6 +2256,21 @@ "semver": true } }, + "@metamask/transaction-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@ngraveio/bc-ur": { "packages": { "@ngraveio/bc-ur>@keystonehq/alias-sampling": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 05a22743204c..e55d57f5ec0f 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -803,6 +803,30 @@ "@metamask/abi-utils>@metamask/utils": true } }, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true + } + }, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true + } + }, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true + } + }, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true + } + }, "@metamask/accounts-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1022,7 +1046,7 @@ "setTimeout": true }, "packages": { - "@metamask/eth-sig-util": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": true, "@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/eth-json-rpc-middleware>@metamask/utils": true, @@ -1075,11 +1099,22 @@ "@metamask/eth-sig-util>tweetnacl": true } }, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true + } + }, "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/abi-utils": true, - "@metamask/eth-snap-keyring>@metamask/utils": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/utils": true, "@metamask/utils>@scure/base": true, "browserify>buffer": true, "@ethereumjs/tx>ethereum-cryptography": true, @@ -1089,7 +1124,7 @@ "@metamask/keyring-controller>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/abi-utils": true, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": true, "@metamask/utils>@scure/base": true, "browserify>buffer": true, @@ -1100,7 +1135,7 @@ "@metamask/signature-controller>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/abi-utils": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": true, "@metamask/utils>@scure/base": true, "browserify>buffer": true, @@ -1272,7 +1307,7 @@ "packages": { "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/json-rpc-engine>@metamask/utils": true } }, "@metamask/json-rpc-middleware-stream": { @@ -1595,7 +1630,7 @@ }, "@metamask/rpc-errors": { "packages": { - "@metamask/utils": true, + "@metamask/rpc-errors>@metamask/utils": true, "@metamask/rpc-errors>fast-safe-stringify": true } }, @@ -1800,7 +1835,7 @@ "@metamask/network-controller": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, "@metamask/rpc-errors": true, - "@metamask/utils": true, + "@metamask/transaction-controller>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, "bn.js": true, "browserify>buffer": true, @@ -1861,6 +1896,51 @@ "semver": true } }, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/browser-passworder>@metamask/utils": { "globals": { "TextDecoder": true, @@ -1966,6 +2046,21 @@ "semver": true } }, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2026,6 +2121,21 @@ "semver": true } }, + "@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/keyring-api>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2116,6 +2226,21 @@ "semver": true } }, + "@metamask/rpc-errors>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2131,6 +2256,21 @@ "semver": true } }, + "@metamask/transaction-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@ngraveio/bc-ur": { "packages": { "@ngraveio/bc-ur>@keystonehq/alias-sampling": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 05a22743204c..e55d57f5ec0f 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -803,6 +803,30 @@ "@metamask/abi-utils>@metamask/utils": true } }, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true + } + }, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true + } + }, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true + } + }, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true + } + }, "@metamask/accounts-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1022,7 +1046,7 @@ "setTimeout": true }, "packages": { - "@metamask/eth-sig-util": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": true, "@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/eth-json-rpc-middleware>@metamask/utils": true, @@ -1075,11 +1099,22 @@ "@metamask/eth-sig-util>tweetnacl": true } }, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true + } + }, "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/abi-utils": true, - "@metamask/eth-snap-keyring>@metamask/utils": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/utils": true, "@metamask/utils>@scure/base": true, "browserify>buffer": true, "@ethereumjs/tx>ethereum-cryptography": true, @@ -1089,7 +1124,7 @@ "@metamask/keyring-controller>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/abi-utils": true, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": true, "@metamask/utils>@scure/base": true, "browserify>buffer": true, @@ -1100,7 +1135,7 @@ "@metamask/signature-controller>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/abi-utils": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": true, "@metamask/utils>@scure/base": true, "browserify>buffer": true, @@ -1272,7 +1307,7 @@ "packages": { "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/json-rpc-engine>@metamask/utils": true } }, "@metamask/json-rpc-middleware-stream": { @@ -1595,7 +1630,7 @@ }, "@metamask/rpc-errors": { "packages": { - "@metamask/utils": true, + "@metamask/rpc-errors>@metamask/utils": true, "@metamask/rpc-errors>fast-safe-stringify": true } }, @@ -1800,7 +1835,7 @@ "@metamask/network-controller": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, "@metamask/rpc-errors": true, - "@metamask/utils": true, + "@metamask/transaction-controller>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, "bn.js": true, "browserify>buffer": true, @@ -1861,6 +1896,51 @@ "semver": true } }, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/browser-passworder>@metamask/utils": { "globals": { "TextDecoder": true, @@ -1966,6 +2046,21 @@ "semver": true } }, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2026,6 +2121,21 @@ "semver": true } }, + "@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/keyring-api>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2116,6 +2226,21 @@ "semver": true } }, + "@metamask/rpc-errors>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2131,6 +2256,21 @@ "semver": true } }, + "@metamask/transaction-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@ngraveio/bc-ur": { "packages": { "@ngraveio/bc-ur>@keystonehq/alias-sampling": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 27ddfc86ff15..5658498ad3a7 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -895,6 +895,30 @@ "@metamask/abi-utils>@metamask/utils": true } }, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true + } + }, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true + } + }, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true + } + }, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": { + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true + } + }, "@metamask/accounts-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1114,7 +1138,7 @@ "setTimeout": true }, "packages": { - "@metamask/eth-sig-util": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": true, "@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/eth-json-rpc-middleware>@metamask/utils": true, @@ -1167,11 +1191,22 @@ "@metamask/eth-sig-util>tweetnacl": true } }, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-json-rpc-middleware>@metamask/utils": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-sig-util>tweetnacl": true + } + }, "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/abi-utils": true, - "@metamask/eth-snap-keyring>@metamask/utils": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/utils": true, "@metamask/utils>@scure/base": true, "browserify>buffer": true, "@ethereumjs/tx>ethereum-cryptography": true, @@ -1181,7 +1216,7 @@ "@metamask/keyring-controller>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/abi-utils": true, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": true, "@metamask/utils>@scure/base": true, "browserify>buffer": true, @@ -1192,7 +1227,7 @@ "@metamask/signature-controller>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/abi-utils": true, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils": true, "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/utils": true, "@metamask/utils>@scure/base": true, "browserify>buffer": true, @@ -1364,7 +1399,7 @@ "packages": { "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/json-rpc-engine>@metamask/utils": true } }, "@metamask/json-rpc-middleware-stream": { @@ -1687,7 +1722,7 @@ }, "@metamask/rpc-errors": { "packages": { - "@metamask/utils": true, + "@metamask/rpc-errors>@metamask/utils": true, "@metamask/rpc-errors>fast-safe-stringify": true } }, @@ -1892,7 +1927,7 @@ "@metamask/network-controller": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, "@metamask/rpc-errors": true, - "@metamask/utils": true, + "@metamask/transaction-controller>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, "bn.js": true, "browserify>buffer": true, @@ -1953,6 +1988,51 @@ "semver": true } }, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, + "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, + "@metamask/signature-controller>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/browser-passworder>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2058,6 +2138,21 @@ "semver": true } }, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/keyring-controller>@metamask/eth-sig-util>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2118,6 +2213,21 @@ "semver": true } }, + "@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/keyring-api>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2208,6 +2318,21 @@ "semver": true } }, + "@metamask/rpc-errors>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/rate-limit-controller>@metamask/rpc-errors>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2223,6 +2348,21 @@ "semver": true } }, + "@metamask/transaction-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@ngraveio/bc-ur": { "packages": { "@ngraveio/bc-ur>@keystonehq/alias-sampling": true, diff --git a/package.json b/package.json index 4534c2c15c2f..da93c6c75761 100644 --- a/package.json +++ b/package.json @@ -225,7 +225,7 @@ "semver@7.3.8": "^7.5.4", "@trezor/schema-utils@npm:1.0.2": "patch:@trezor/schema-utils@npm%3A1.0.2#~/.yarn/patches/@trezor-schema-utils-npm-1.0.2-7dd48689b2.patch", "lavamoat-core@npm:^15.1.1": "patch:lavamoat-core@npm%3A15.1.1#~/.yarn/patches/lavamoat-core-npm-15.1.1-51fbe39988.patch", - "@metamask/snaps-sdk": "^6.13.0", + "@metamask/snaps-sdk": "^6.14.0", "@swc/types@0.1.5": "^0.1.6", "@babel/core": "patch:@babel/core@npm%3A7.25.9#~/.yarn/patches/@babel-core-npm-7.25.9-4ae3bff7f3.patch", "@babel/runtime": "patch:@babel/runtime@npm%3A7.25.9#~/.yarn/patches/@babel-runtime-npm-7.25.9-fe8c62510a.patch", @@ -296,7 +296,7 @@ "@metamask/ens-controller": "^15.0.0", "@metamask/ens-resolver-snap": "^0.1.2", "@metamask/eth-json-rpc-filters": "^9.0.0", - "@metamask/eth-json-rpc-middleware": "^15.0.1", + "@metamask/eth-json-rpc-middleware": "^15.1.2", "@metamask/eth-ledger-bridge-keyring": "^5.0.1", "@metamask/eth-query": "^4.0.0", "@metamask/eth-sig-util": "^7.0.1", @@ -331,7 +331,7 @@ "@metamask/polling-controller": "^12.0.1", "@metamask/post-message-stream": "^8.0.0", "@metamask/ppom-validator": "0.36.0", - "@metamask/preinstalled-example-snap": "^0.2.0", + "@metamask/preinstalled-example-snap": "^0.3.0", "@metamask/profile-sync-controller": "^3.1.1", "@metamask/providers": "^18.2.0", "@metamask/queued-request-controller": "^7.0.1", @@ -343,13 +343,13 @@ "@metamask/selected-network-controller": "^19.0.0", "@metamask/signature-controller": "^23.1.0", "@metamask/smart-transactions-controller": "^16.0.0", - "@metamask/snaps-controllers": "^9.15.0", - "@metamask/snaps-execution-environments": "^6.10.0", - "@metamask/snaps-rpc-methods": "^11.7.0", - "@metamask/snaps-sdk": "^6.13.0", - "@metamask/snaps-utils": "^8.6.1", + "@metamask/snaps-controllers": "^9.16.0", + "@metamask/snaps-execution-environments": "^6.11.0", + "@metamask/snaps-rpc-methods": "^11.8.0", + "@metamask/snaps-sdk": "^6.14.0", + "@metamask/snaps-utils": "^8.7.0", "@metamask/solana-wallet-snap": "^1.0.4", - "@metamask/transaction-controller": "^42.0.0", + "@metamask/transaction-controller": "^42.1.0", "@metamask/user-operation-controller": "^21.0.0", "@metamask/utils": "^10.0.1", "@ngraveio/bc-ur": "^1.1.12", diff --git a/shared/constants/snaps/permissions.ts b/shared/constants/snaps/permissions.ts index 837beea4d9ff..e08afdf46421 100644 --- a/shared/constants/snaps/permissions.ts +++ b/shared/constants/snaps/permissions.ts @@ -7,6 +7,7 @@ export const EndowmentPermissions = Object.freeze({ 'endowment:webassembly': 'endowment:webassembly', 'endowment:lifecycle-hooks': 'endowment:lifecycle-hooks', 'endowment:page-home': 'endowment:page-home', + 'endowment:page-settings': 'endowment:page-settings', 'endowment:signature-insight': 'endowment:signature-insight', 'endowment:name-lookup': 'endowment:name-lookup', ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) diff --git a/test/data/confirmations/set-approval-for-all.ts b/test/data/confirmations/set-approval-for-all.ts index ca997f6212af..ec1889a7b16b 100644 --- a/test/data/confirmations/set-approval-for-all.ts +++ b/test/data/confirmations/set-approval-for-all.ts @@ -6,6 +6,9 @@ import { genUnapprovedContractInteractionConfirmation, } from './contract-interaction'; +export const INCREASE_ALLOWANCE_TRANSACTION_DATA = + '0x395093510000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b0000000000000000000000000000000000000000000000000000000000000123'; + export const genUnapprovedSetApprovalForAllConfirmation = ({ address = CONTRACT_INTERACTION_SENDER_ADDRESS, chainId = CHAIN_ID, @@ -16,7 +19,7 @@ export const genUnapprovedSetApprovalForAllConfirmation = ({ ...genUnapprovedContractInteractionConfirmation({ chainId }), txParams: { from: address, - data: '0x095ea7b30000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b0000000000000000000000000000000000000000000000000000000000000001', + data: '0xa22cb4650000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b0000000000000000000000000000000000000000000000000000000000000001', gas: '0x16a92', to: '0x076146c765189d51be3160a2140cf80bfc73ad68', value: '0x0', diff --git a/test/data/confirmations/token-approve.ts b/test/data/confirmations/token-approve.ts index c77d59101a99..f10a2c9c92d3 100644 --- a/test/data/confirmations/token-approve.ts +++ b/test/data/confirmations/token-approve.ts @@ -9,14 +9,16 @@ import { export const genUnapprovedApproveConfirmation = ({ address = CONTRACT_INTERACTION_SENDER_ADDRESS, chainId = CHAIN_ID, + amountHex = '0000000000000000000000000000000000000000000000000000000000000001', }: { address?: Hex; chainId?: string; + amountHex?: string; } = {}) => ({ ...genUnapprovedContractInteractionConfirmation({ chainId }), txParams: { from: address, - data: '0x095ea7b30000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b0000000000000000000000000000000000000000000000000000000000000001', + data: `0x095ea7b30000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b${amountHex}`, gas: '0x16a92', to: '0x076146c765189d51be3160a2140cf80bfc73ad68', value: '0x0', diff --git a/test/data/confirmations/token-transfer.ts b/test/data/confirmations/token-transfer.ts index 22d0cb2d00b4..9228373bdf3e 100644 --- a/test/data/confirmations/token-transfer.ts +++ b/test/data/confirmations/token-transfer.ts @@ -6,19 +6,24 @@ import { genUnapprovedContractInteractionConfirmation, } from './contract-interaction'; +export const TRANSFER_FROM_TRANSACTION_DATA = + '0x23b872dd0000000000000000000000002e0D7E8c45221FcA00d74a3609A0f7097035d09B0000000000000000000000002e0D7E8c45221FcA00d74a3609A0f7097035d09C0000000000000000000000000000000000000000000000000000000000000123'; + export const genUnapprovedTokenTransferConfirmation = ({ address = CONTRACT_INTERACTION_SENDER_ADDRESS, chainId = CHAIN_ID, isWalletInitiatedConfirmation = false, + amountHex = '0000000000000000000000000000000000000000000000000000000000000001', }: { address?: Hex; chainId?: string; isWalletInitiatedConfirmation?: boolean; + amountHex?: string; } = {}) => ({ ...genUnapprovedContractInteractionConfirmation({ chainId }), txParams: { from: address, - data: '0x095ea7b30000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b0000000000000000000000000000000000000000000000000000000000000001', + data: `0xa9059cbb0000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b${amountHex}`, gas: '0x16a92', to: '0x076146c765189d51be3160a2140cf80bfc73ad68', value: '0x0', diff --git a/test/e2e/constants.ts b/test/e2e/constants.ts index 9d4d5bbf3c8d..6af1056d7232 100644 --- a/test/e2e/constants.ts +++ b/test/e2e/constants.ts @@ -76,3 +76,10 @@ export const DEFAULT_SOLANA_BALANCE = 1; // SOL /* Title of the mocked E2E test empty HTML page */ export const EMPTY_E2E_TEST_PAGE_TITLE = 'E2E Test Page'; + +/* Account types */ +export enum ACCOUNT_TYPE { + Ethereum, + Bitcoin, + Solana, +} diff --git a/test/e2e/flask/btc/common-btc.ts b/test/e2e/flask/btc/common-btc.ts index db2db85eb554..ea9cb6b1f2e6 100644 --- a/test/e2e/flask/btc/common-btc.ts +++ b/test/e2e/flask/btc/common-btc.ts @@ -2,6 +2,7 @@ import { Mockttp } from 'mockttp'; import FixtureBuilder from '../../fixture-builder'; import { withFixtures } from '../../helpers'; import { + ACCOUNT_TYPE, DEFAULT_BTC_ACCOUNT, DEFAULT_BTC_BALANCE, DEFAULT_BTC_FEES_RATE, @@ -14,7 +15,6 @@ import { Driver } from '../../webdriver/driver'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; import AccountListPage from '../../page-objects/pages/account-list-page'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; -import { ACCOUNT_TYPE } from '../../page-objects/common'; const QUICKNODE_URL_REGEX = /^https:\/\/.*\.btc.*\.quiknode\.pro(\/|$)/u; @@ -218,7 +218,7 @@ export async function withBtcAccountSnap( await new HeaderNavbar(driver).openAccountMenu(); const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.addAccount(ACCOUNT_TYPE.Bitcoin, ''); + await accountListPage.addAccount({ accountType: ACCOUNT_TYPE.Bitcoin }); await test(driver, mockServer); }, ); diff --git a/test/e2e/flask/btc/create-btc-account.spec.ts b/test/e2e/flask/btc/create-btc-account.spec.ts index f563454ad1e6..68cdcd82caf5 100644 --- a/test/e2e/flask/btc/create-btc-account.spec.ts +++ b/test/e2e/flask/btc/create-btc-account.spec.ts @@ -1,13 +1,14 @@ import { strict as assert } from 'assert'; import { Suite } from 'mocha'; import { WALLET_PASSWORD } from '../../helpers'; +import AccountDetailsModal from '../../page-objects/pages/dialog/account-details-modal'; import AccountListPage from '../../page-objects/pages/account-list-page'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; import LoginPage from '../../page-objects/pages/login-page'; import PrivacySettings from '../../page-objects/pages/settings/privacy-settings'; import ResetPasswordPage from '../../page-objects/pages/reset-password-page'; import SettingsPage from '../../page-objects/pages/settings/settings-page'; -import { ACCOUNT_TYPE } from '../../page-objects/common'; +import { ACCOUNT_TYPE } from '../../constants'; import { withBtcAccountSnap } from './common-btc'; describe('Create BTC Account', function (this: Suite) { @@ -82,9 +83,11 @@ describe('Create BTC Account', function (this: Suite) { await headerNavbar.openAccountMenu(); const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - const accountAddress = await accountListPage.getAccountAddress( - 'Bitcoin Account', - ); + await accountListPage.openAccountDetailsModal('Bitcoin Account'); + + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + const accountAddress = await accountDetailsModal.getAccountAddress(); await headerNavbar.openAccountMenu(); await accountListPage.removeAccount('Bitcoin Account'); @@ -97,14 +100,15 @@ describe('Create BTC Account', function (this: Suite) { ); await accountListPage.closeAccountModal(); await headerNavbar.openAccountMenu(); - await accountListPage.addAccount(ACCOUNT_TYPE.Bitcoin, ''); + await accountListPage.addAccount({ accountType: ACCOUNT_TYPE.Bitcoin }); await headerNavbar.check_accountLabel('Bitcoin Account'); await headerNavbar.openAccountMenu(); await accountListPage.check_pageIsLoaded(); - const recreatedAccountAddress = await accountListPage.getAccountAddress( - 'Bitcoin Account', - ); + await accountListPage.openAccountDetailsModal('Bitcoin Account'); + await accountDetailsModal.check_pageIsLoaded(); + const recreatedAccountAddress = + await accountDetailsModal.getAccountAddress(); assert(accountAddress === recreatedAccountAddress); }, @@ -123,9 +127,10 @@ describe('Create BTC Account', function (this: Suite) { await headerNavbar.openAccountMenu(); const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - const accountAddress = await accountListPage.getAccountAddress( - 'Bitcoin Account', - ); + await accountListPage.openAccountDetailsModal('Bitcoin Account'); + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + const accountAddress = await accountDetailsModal.getAccountAddress(); // go to privacy settings page and get the SRP await headerNavbar.openSettingsPage(); @@ -151,14 +156,16 @@ describe('Create BTC Account', function (this: Suite) { await headerNavbar.check_pageIsLoaded(); await headerNavbar.openAccountMenu(); await accountListPage.check_pageIsLoaded(); - await accountListPage.addAccount(ACCOUNT_TYPE.Bitcoin, ''); + await accountListPage.addAccount({ accountType: ACCOUNT_TYPE.Bitcoin }); await headerNavbar.check_accountLabel('Bitcoin Account'); await headerNavbar.openAccountMenu(); await accountListPage.check_pageIsLoaded(); - const recreatedAccountAddress = await accountListPage.getAccountAddress( - 'Bitcoin Account', - ); + await accountListPage.openAccountDetailsModal('Bitcoin Account'); + await accountDetailsModal.check_pageIsLoaded(); + const recreatedAccountAddress = + await accountDetailsModal.getAccountAddress(); + assert(accountAddress === recreatedAccountAddress); }, ); diff --git a/test/e2e/flask/create-watch-account.spec.ts b/test/e2e/flask/create-watch-account.spec.ts index f25f38f0b2ce..5cfca1dda5ef 100644 --- a/test/e2e/flask/create-watch-account.spec.ts +++ b/test/e2e/flask/create-watch-account.spec.ts @@ -1,70 +1,22 @@ import { strict as assert } from 'assert'; import { Suite } from 'mocha'; -import messages from '../../../app/_locales/en/messages.json'; import FixtureBuilder from '../fixture-builder'; -import { defaultGanacheOptions, unlockWallet, withFixtures } from '../helpers'; +import { withFixtures } from '../helpers'; import { Driver } from '../webdriver/driver'; +import AccountDetailsModal from '../page-objects/pages/dialog/account-details-modal'; +import AccountListPage from '../page-objects/pages/account-list-page'; +import ExperimentalSettings from '../page-objects/pages/settings/experimental-settings'; +import HeaderNavbar from '../page-objects/pages/header-navbar'; +import HomePage from '../page-objects/pages/home/homepage'; +import SettingsPage from '../page-objects/pages/settings/settings-page'; +import { loginWithBalanceValidation } from '../page-objects/flows/login.flow'; +import { watchEoaAddress } from '../page-objects/flows/watch-account.flow'; const ACCOUNT_1 = '0x5CfE73b6021E818B776b421B1c4Db2474086a7e1'; const EOA_ADDRESS = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'; const SHORTENED_EOA_ADDRESS = '0xd8dA6...96045'; const DEFAULT_WATCHED_ACCOUNT_NAME = 'Watched Account 1'; -/** - * Start the flow to create a watch account by clicking the account menu and selecting the option to add a watch account. - * - * @param driver - The WebDriver instance used to control the browser. - * @param unlockWalletFirst - Whether to unlock the wallet before starting the flow. - */ -async function startCreateWatchAccountFlow( - driver: Driver, - unlockWalletFirst: boolean = true, -): Promise { - if (unlockWalletFirst) { - await unlockWallet(driver); - } - - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-add-watch-only-account"]', - ); -} - -/** - * Watches an EOA address. - * - * @param driver - The WebDriver instance used to control the browser. - * @param unlockWalletFirst - Whether to unlock the wallet before watching the address. - * @param address - The EOA address to watch. - */ -async function watchEoaAddress( - driver: Driver, - unlockWalletFirst: boolean = true, - address: string = EOA_ADDRESS, -): Promise { - await startCreateWatchAccountFlow(driver, unlockWalletFirst); - await driver.fill('input#address-input[type="text"]', address); - await driver.clickElement({ text: 'Watch account', tag: 'button' }); - await driver.clickElement('[data-testid="submit-add-account-with-name"]'); -} - -/** - * Removes the selected account. - * - * @param driver - The WebDriver instance used to control the browser. - */ -async function removeSelectedAccount(driver: Driver): Promise { - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '.multichain-account-list-item--selected [data-testid="account-list-item-menu-button"]', - ); - await driver.clickElement('[data-testid="account-list-menu-remove"]'); - await driver.clickElement({ text: 'Remove', tag: 'button' }); -} - describe('Account-watcher snap', function (this: Suite) { describe('Adding watched accounts', function () { it('adds watch account with valid EOA address', async function () { @@ -76,22 +28,17 @@ describe('Account-watcher snap', function (this: Suite) { }) .withNetworkControllerOnMainnet() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { // watch an EOA address - await watchEoaAddress(driver); + await loginWithBalanceValidation(driver); + await watchEoaAddress(driver, EOA_ADDRESS); // new account should be displayed in the account list - await driver.findElement({ - css: '[data-testid="account-menu-icon"]', - text: DEFAULT_WATCHED_ACCOUNT_NAME, - }); - await driver.findElement({ - css: '.mm-text--ellipsis', - text: SHORTENED_EOA_ADDRESS, - }); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel(DEFAULT_WATCHED_ACCOUNT_NAME); + await headerNavbar.check_accountAddress(SHORTENED_EOA_ADDRESS); }, ); }); @@ -105,40 +52,29 @@ describe('Account-watcher snap', function (this: Suite) { }) .withNetworkControllerOnMainnet() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { // watch an EOA address - await watchEoaAddress(driver); + await loginWithBalanceValidation(driver); + await watchEoaAddress(driver, EOA_ADDRESS); + const homePage = new HomePage(driver); + await homePage.headerNavbar.check_accountLabel( + DEFAULT_WATCHED_ACCOUNT_NAME, + ); // 'Send' button should be disabled - await driver.findElement( - '[data-testid="eth-overview-send"][disabled]', - ); - await driver.findElement( - '[data-testid="eth-overview-send"].icon-button--disabled', - ); + assert.equal(await homePage.check_ifSendButtonIsClickable(), false); // 'Swap' button should be disabled - await driver.findElement( - '[data-testid="token-overview-button-swap"][disabled]', - ); - await driver.findElement( - '[data-testid="token-overview-button-swap"].icon-button--disabled', - ); + assert.equal(await homePage.check_ifSwapButtonIsClickable(), false); // 'Bridge' button should be disabled - await driver.findElement( - '[data-testid="eth-overview-bridge"][disabled]', - ); - await driver.findElement( - '[data-testid="eth-overview-bridge"].icon-button--disabled', - ); + assert.equal(await homePage.check_ifBridgeButtonIsClickable(), false); // check tooltips for disabled buttons - await driver.findElement( - '.icon-button--disabled [data-tooltipped][data-original-title="Not supported with this account."]', + await homePage.check_disabledButtonTooltip( + 'Not supported with this account.', ); }, ); @@ -182,20 +118,19 @@ describe('Account-watcher snap', function (this: Suite) { }) .withNetworkControllerOnMainnet() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { - await startCreateWatchAccountFlow(driver); - - await driver.fill('input#address-input[type="text"]', input); - await driver.clickElement({ text: 'Watch account', tag: 'button' }); - - // error message should be displayed by the snap - await driver.findElement({ - css: '.snap-ui-renderer__text', - text: message, - }); + await loginWithBalanceValidation(driver); + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + + // error message should be displayed by snap when try to watch an EOA with invalid input + await homePage.headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.addEoaAccount(input, message); }, ); }); @@ -216,30 +151,23 @@ describe('Account-watcher snap', function (this: Suite) { }) .withNetworkControllerOnMainnet() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { // watch an EOA address for ACCOUNT_2 - await watchEoaAddress(driver, true, ACCOUNT_2); - - // try to import private key of watched ACCOUNT_2 address - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - await driver.clickElement({ text: 'Import account', tag: 'button' }); - await driver.findClickableElement('#private-key-box'); - await driver.fill('#private-key-box', PRIVATE_KEY_TWO); - await driver.clickElement( - '[data-testid="import-account-confirm-button"]', + await loginWithBalanceValidation(driver); + await watchEoaAddress(driver, ACCOUNT_2); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel(DEFAULT_WATCHED_ACCOUNT_NAME); + + // try to import private key of watched ACCOUNT_2 address and check error message + await headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.addNewImportedAccount( + PRIVATE_KEY_TWO, + 'KeyringController - The account you are trying to import is a duplicate', ); - - // error message should be displayed - await driver.findElement({ - css: '.mm-box--color-error-default', - text: 'KeyringController - The account you are trying to import is a duplicate', - }); }, ); }); @@ -253,62 +181,27 @@ describe('Account-watcher snap', function (this: Suite) { }) .withNetworkControllerOnMainnet() .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test?.fullTitle(), - }, - async ({ driver }: { driver: Driver }) => { - // watch an EOA address - await watchEoaAddress(driver); - - // click to view account details - await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - await driver.clickElement( - '[data-testid="account-list-menu-details"]', - ); - // 'Show private key' button should not be displayed - await driver.assertElementNotPresent({ - css: 'button', - text: 'Show private key', - }); - }, - ); - }); - - it('removes a watched account', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder() - .withPreferencesControllerAndFeatureFlag({ - watchEthereumAccountEnabled: true, - }) - .withNetworkControllerOnMainnet() - .build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { // watch an EOA address - await watchEoaAddress(driver); - - // remove the selected watched account - await removeSelectedAccount(driver); - - // account should be removed from the account list - await driver.assertElementNotPresent({ - css: '[data-testid="account-menu-icon"]', - text: DEFAULT_WATCHED_ACCOUNT_NAME, - }); - await driver.assertElementNotPresent({ - css: '.mm-text--ellipsis', - text: SHORTENED_EOA_ADDRESS, - }); + await loginWithBalanceValidation(driver); + await watchEoaAddress(driver, EOA_ADDRESS); + + // open account details modal in header navbar + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel(DEFAULT_WATCHED_ACCOUNT_NAME); + await headerNavbar.openAccountDetailsModal(); + + // check 'Show private key' button should not be displayed + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + await accountDetailsModal.check_showPrivateKeyButtonIsNotDisplayed(); }, ); }); - it('can remove and recreate a watched account', async function () { + it('removes a watched account and recreate a watched account', async function () { await withFixtures( { fixtures: new FixtureBuilder() @@ -317,113 +210,83 @@ describe('Account-watcher snap', function (this: Suite) { }) .withNetworkControllerOnMainnet() .build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { // watch an EOA address - await watchEoaAddress(driver); + await loginWithBalanceValidation(driver); + await watchEoaAddress(driver, EOA_ADDRESS); + const homePage = new HomePage(driver); + await homePage.headerNavbar.check_accountLabel( + DEFAULT_WATCHED_ACCOUNT_NAME, + ); // remove the selected watched account - await removeSelectedAccount(driver); + await homePage.headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.removeAccount(DEFAULT_WATCHED_ACCOUNT_NAME); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); // account should be removed from the account list - await driver.assertElementNotPresent({ - css: '[data-testid="account-menu-icon"]', - text: DEFAULT_WATCHED_ACCOUNT_NAME, - }); - await driver.assertElementNotPresent({ - css: '.mm-text--ellipsis', - text: SHORTENED_EOA_ADDRESS, - }); - - // watch the same EOA address again - await watchEoaAddress(driver, false); - - // same account should be displayed in the account list - await driver.findElement({ - css: '[data-testid="account-menu-icon"]', - text: DEFAULT_WATCHED_ACCOUNT_NAME, - }); - await driver.findElement({ - css: '.mm-text--ellipsis', - text: SHORTENED_EOA_ADDRESS, - }); + await homePage.headerNavbar.openAccountMenu(); + await accountListPage.check_accountIsNotDisplayedInAccountList( + DEFAULT_WATCHED_ACCOUNT_NAME, + ); + await accountListPage.closeAccountModal(); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + + // watch the same EOA address again and check the account is recreated + await watchEoaAddress(driver, EOA_ADDRESS); + await homePage.headerNavbar.check_accountLabel( + DEFAULT_WATCHED_ACCOUNT_NAME, + ); + await homePage.headerNavbar.check_accountAddress( + SHORTENED_EOA_ADDRESS, + ); }, ); }); }); describe('Experimental toggle', function () { - const navigateToExperimentalSettings = async (driver: Driver) => { - await driver.clickElement('[data-testid="account-options-menu-button"]'); - await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.clickElement({ text: 'Experimental', tag: 'div' }); - await driver.waitForSelector({ - text: messages.watchEthereumAccountsToggle.message, - tag: 'span', - }); - }; - - const getToggleState = async (driver: Driver): Promise => { - const toggleInput = await driver.findElement( - '[data-testid="watch-account-toggle"]', - ); - return toggleInput.isSelected(); - }; - - const toggleWatchAccountOptionAndCloseSettings = async (driver: Driver) => { - await driver.clickElement('[data-testid="watch-account-toggle-div"]'); - await driver.clickElement( - '.settings-page__header__title-container__close-button', - ); - }; - - const verifyWatchAccountOptionAndCloseMenu = async ( - driver: Driver, - shouldBePresent: boolean, - ) => { - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - if (shouldBePresent) { - await driver.waitForSelector({ - text: messages.addEthereumWatchOnlyAccount.message, - tag: 'button', - }); - } else { - await driver.assertElementNotPresent({ - text: messages.addEthereumWatchOnlyAccount.message, - tag: 'button', - }); - } - await driver.clickElement('header button[aria-label="Close"]'); - }; - it("will show the 'Watch an Ethereum account (Beta)' option when setting is enabled", async function () { await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }) => { - await unlockWallet(driver); - await navigateToExperimentalSettings(driver); - - // verify toggle is off by default + await loginWithBalanceValidation(driver); + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + + // navigate to experimental settings + await homePage.headerNavbar.openSettingsPage(); + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToExperimentalSettings(); + const experimentalSettings = new ExperimentalSettings(driver); + await experimentalSettings.check_pageIsLoaded(); + + // verify watch account toggle is off by default and enable the toggle assert.equal( - await getToggleState(driver), + await experimentalSettings.getWatchAccountToggleState(), false, 'Toggle should be off by default', ); - - // enable the toggle - await toggleWatchAccountOptionAndCloseSettings(driver); + await experimentalSettings.toggleWatchAccount(); + await settingsPage.closeSettingsPage(); // verify the 'Watch and Ethereum account (Beta)' option is available - await verifyWatchAccountOptionAndCloseMenu(driver, true); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + await homePage.headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_addWatchAccountAvailable(true); }, ); }); @@ -432,27 +295,49 @@ describe('Account-watcher snap', function (this: Suite) { await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }) => { - await unlockWallet(driver); - await navigateToExperimentalSettings(driver); - - // enable the toggle - await toggleWatchAccountOptionAndCloseSettings(driver); + await loginWithBalanceValidation(driver); + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + + // navigate to experimental settings and enable the toggle + await homePage.headerNavbar.openSettingsPage(); + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToExperimentalSettings(); + const experimentalSettings = new ExperimentalSettings(driver); + await experimentalSettings.check_pageIsLoaded(); + await experimentalSettings.toggleWatchAccount(); + await settingsPage.closeSettingsPage(); // verify the 'Watch and Ethereum account (Beta)' option is available - await verifyWatchAccountOptionAndCloseMenu(driver, true); - - // navigate back to experimental settings - await navigateToExperimentalSettings(driver); - - // disable the toggle - await toggleWatchAccountOptionAndCloseSettings(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + await homePage.headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_addWatchAccountAvailable(true); + await accountListPage.closeAccountModal(); + + // navigate back to experimental settings and disable the toggle + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + await homePage.headerNavbar.openSettingsPage(); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToExperimentalSettings(); + await experimentalSettings.check_pageIsLoaded(); + await experimentalSettings.toggleWatchAccount(); + await settingsPage.closeSettingsPage(); // verify the 'Watch and Ethereum account (Beta)' option is not available - await verifyWatchAccountOptionAndCloseMenu(driver, false); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + await homePage.headerNavbar.openAccountMenu(); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_addWatchAccountAvailable(false); }, ); }); diff --git a/test/e2e/flask/solana/common-solana.ts b/test/e2e/flask/solana/common-solana.ts index 2ac107bb442c..ac36d5ebed86 100644 --- a/test/e2e/flask/solana/common-solana.ts +++ b/test/e2e/flask/solana/common-solana.ts @@ -4,7 +4,7 @@ import { Driver } from '../../webdriver/driver'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; import AccountListPage from '../../page-objects/pages/account-list-page'; import FixtureBuilder from '../../fixture-builder'; -import { ACCOUNT_TYPE } from '../../page-objects/common'; +import { ACCOUNT_TYPE } from '../../constants'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; const SOLANA_URL_REGEX = /^https:\/\/.*\.solana.*/u; @@ -64,7 +64,10 @@ export async function withSolanaAccountSnap( const headerComponen = new HeaderNavbar(driver); await headerComponen.openAccountMenu(); const accountListPage = new AccountListPage(driver); - await accountListPage.addAccount(ACCOUNT_TYPE.Solana, 'Solana 1'); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Solana, + accountName: 'Solana 1', + }); await test(driver, mockServer); }, ); diff --git a/test/e2e/flask/solana/create-solana-account.spec.ts b/test/e2e/flask/solana/create-solana-account.spec.ts index cca4f5222993..0cac9fab7375 100644 --- a/test/e2e/flask/solana/create-solana-account.spec.ts +++ b/test/e2e/flask/solana/create-solana-account.spec.ts @@ -1,7 +1,7 @@ import { Suite } from 'mocha'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; import AccountListPage from '../../page-objects/pages/account-list-page'; -import { ACCOUNT_TYPE } from '../../page-objects/common'; +import { ACCOUNT_TYPE } from '../../constants'; import { withSolanaAccountSnap } from './common-solana'; // Scenarios skipped due to https://consensyssoftware.atlassian.net/browse/SOL-87 @@ -17,7 +17,10 @@ describe('Create Solana Account', function (this: Suite) { await headerNavbar.openAccountMenu(); const accountListPage = new AccountListPage(driver); await accountListPage.check_accountDisplayedInAccountList('Account 1'); - await accountListPage.addAccount(ACCOUNT_TYPE.Solana, 'Solana 2'); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Solana, + accountName: 'Solana 2', + }); await headerNavbar.check_accountLabel('Solana 2'); await headerNavbar.openAccountMenu(); await accountListPage.check_numberOfAvailableAccounts(3); diff --git a/test/e2e/page-objects/common.ts b/test/e2e/page-objects/common.ts index 40eb625d94ac..5bf1a91e1859 100644 --- a/test/e2e/page-objects/common.ts +++ b/test/e2e/page-objects/common.ts @@ -2,9 +2,3 @@ export type RawLocator = | string | { css?: string; text?: string } | { tag: string; text: string }; - -export enum ACCOUNT_TYPE { - Ethereum, - Bitcoin, - Solana, -} diff --git a/test/e2e/page-objects/flows/watch-account.flow.ts b/test/e2e/page-objects/flows/watch-account.flow.ts new file mode 100644 index 000000000000..8481c71599a6 --- /dev/null +++ b/test/e2e/page-objects/flows/watch-account.flow.ts @@ -0,0 +1,22 @@ +import { Driver } from '../../webdriver/driver'; +import HomePage from '../pages/home/homepage'; +import AccountListPage from '../pages/account-list-page'; + +/** + * Initiates the flow of watching an EOA address. + * + * @param driver - The WebDriver instance. + * @param address - The EOA address that is to be watched. + */ +export async function watchEoaAddress( + driver: Driver, + address: string, +): Promise { + // watch a new EOA + const homePage = new HomePage(driver); + await homePage.headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.addEoaAccount(address); + await homePage.check_pageIsLoaded(); +} diff --git a/test/e2e/page-objects/pages/account-list-page.ts b/test/e2e/page-objects/pages/account-list-page.ts index 628d758e7e71..92eede81652b 100644 --- a/test/e2e/page-objects/pages/account-list-page.ts +++ b/test/e2e/page-objects/pages/account-list-page.ts @@ -1,13 +1,11 @@ import { Driver } from '../../webdriver/driver'; import { largeDelayMs, regularDelayMs } from '../../helpers'; import messages from '../../../../app/_locales/en/messages.json'; -import { ACCOUNT_TYPE } from '../common'; +import { ACCOUNT_TYPE } from '../../constants'; class AccountListPage { private readonly driver: Driver; - private readonly accountAddressText = '.qr-code__address-segments'; - private readonly accountListAddressItem = '[data-testid="account-list-address"]'; @@ -28,10 +26,6 @@ class AccountListPage { private readonly accountOptionsMenuButton = '[data-testid="account-list-item-menu-button"]'; - private readonly accountQrCodeImage = '.qr-code__wrapper'; - - private readonly accountQrCodeAddress = '.qr-code__address-segments'; - private readonly addAccountConfirmButton = '[data-testid="submit-add-account-with-name"]'; @@ -48,6 +42,9 @@ class AccountListPage { private readonly addEthereumAccountButton = '[data-testid="multichain-account-menu-popover-add-account"]'; + private readonly addEoaAccountButton = + '[data-testid="multichain-account-menu-popover-add-watch-only-account"]'; + private readonly addHardwareWalletButton = { text: 'Add hardware wallet', tag: 'button', @@ -70,11 +67,6 @@ class AccountListPage { private readonly currentSelectedAccount = '.multichain-account-list-item--selected'; - private readonly editableLabelButton = - '[data-testid="editable-label-button"]'; - - private readonly editableLabelInput = '[data-testid="editable-input"] input'; - private readonly hiddenAccountOptionsMenuButton = '.multichain-account-menu-popover__list--menu-item-hidden-account [data-testid="account-list-item-menu-button"]'; @@ -124,8 +116,18 @@ class AccountListPage { tag: 'button', }; - private readonly saveAccountLabelButton = - '[data-testid="save-account-label-input"]'; + private readonly watchAccountAddressInput = + 'input#address-input[type="text"]'; + + private readonly watchAccountConfirmButton = { + text: 'Watch account', + tag: 'button', + }; + + private readonly watchAccountModalTitle = { + text: 'Watch any Ethereum account', + tag: 'h4', + }; constructor(driver: Driver) { this.driver = driver; @@ -145,34 +147,36 @@ class AccountListPage { } /** - * Adds a new account with an optional custom label. + * Watch an EOA (external owned account). * - * @param customLabel - The custom label for the new account. If not provided, a default name will be used. + * @param address - The address to watch. + * @param expectedErrorMessage - Optional error message to display if the address is invalid. */ - async addNewAccount(customLabel?: string): Promise { - if (customLabel) { - console.log(`Adding new account with custom label: ${customLabel}`); - } else { - console.log(`Adding new account with default name`); - } + async addEoaAccount( + address: string, + expectedErrorMessage: string = '', + ): Promise { + console.log(`Watch EOA account with address ${address}`); await this.driver.clickElement(this.createAccountButton); - await this.driver.clickElement(this.addEthereumAccountButton); - if (customLabel) { - await this.driver.fill(this.accountNameInput, customLabel); - } - // needed to mitigate a race condition with the state update - // there is no condition we can wait for in the UI - await this.driver.delay(largeDelayMs); + await this.driver.clickElement(this.addEoaAccountButton); + await this.driver.waitForSelector(this.watchAccountModalTitle); + await this.driver.fill(this.watchAccountAddressInput, address); await this.driver.clickElementAndWaitToDisappear( - this.addAccountConfirmButton, + this.watchAccountConfirmButton, ); - } - - async isBtcAccountCreationButtonEnabled() { - const createButton = await this.driver.findElement( - this.addBtcAccountButton, - ); - return await createButton.isEnabled(); + if (expectedErrorMessage) { + console.log( + `Check if error message is displayed: ${expectedErrorMessage}`, + ); + await this.driver.waitForSelector({ + css: '.snap-ui-renderer__text', + text: expectedErrorMessage, + }); + } else { + await this.driver.clickElementAndWaitToDisappear( + this.addAccountConfirmButton, + ); + } } /** @@ -205,20 +209,27 @@ class AccountListPage { /** * Adds a new account of the specified type with an optional custom name. * - * @param accountType - The type of account to add (Ethereum, Bitcoin, or Solana) - * @param accountName - Optional custom name for the new account + * @param options - Options for adding a new account + * @param options.accountType - The type of account to add (Ethereum, Bitcoin, or Solana) + * @param [options.accountName] - Optional custom name for the new account * @throws {Error} If the specified account type is not supported * @example * // Add a new Ethereum account with default name - * await accountListPage.addAccount(ACCOUNT_TYPE.Ethereum); + * await accountListPage.addAccount({ accountType: ACCOUNT_TYPE.Ethereum }); * * // Add a new Bitcoin account with custom name - * await accountListPage.addAccount(ACCOUNT_TYPE.Bitcoin, 'My BTC Wallet'); + * await accountListPage.addAccount({ accountType: ACCOUNT_TYPE.Bitcoin, accountName: 'My BTC Wallet' }); */ - async addAccount(accountType: ACCOUNT_TYPE, accountName?: string) { + async addAccount({ + accountType, + accountName, + }: { + accountType: ACCOUNT_TYPE; + accountName?: string; + }) { + console.log(`Adding new account of type: ${ACCOUNT_TYPE[accountType]}`); await this.driver.clickElement(this.createAccountButton); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let addAccountButton: any; + let addAccountButton; switch (accountType) { case ACCOUNT_TYPE.Ethereum: addAccountButton = this.addEthereumAccountButton; @@ -235,56 +246,20 @@ class AccountListPage { await this.driver.clickElement(addAccountButton); if (accountName) { + console.log( + `Customize the new account with account name: ${accountName}`, + ); await this.driver.fill(this.accountNameInput, accountName); } - + // needed to mitigate a race condition with the state update + // there is no condition we can wait for in the UI + await this.driver.delay(largeDelayMs); await this.driver.clickElementAndWaitToDisappear( this.addAccountConfirmButton, 5000, ); } - /** - * Changes the label of the current account. - * - * @param newLabel - The new label to set for the account. - */ - async changeAccountLabel(newLabel: string): Promise { - console.log(`Changing account label to: ${newLabel}`); - await this.driver.clickElement(this.accountMenuButton); - await this.changeLabelFromAccountDetailsModal(newLabel); - } - - /** - * Changes the account label from within an already opened account details modal. - * Note: This method assumes the account details modal is already open. - * - * Recommended usage: - * ```typescript - * await accountListPage.openAccountDetailsModal('Current Account Name'); - * await accountListPage.changeLabelFromAccountDetailsModal('New Account Name'); - * ``` - * - * @param newLabel - The new label to set for the account - * @throws Will throw an error if the modal is not open when method is called - * @example - * // To rename a specific account, first open its details modal: - * await accountListPage.openAccountDetailsModal('Current Account Name'); - * await accountListPage.changeLabelFromAccountDetailsModal('New Account Name'); - * - * // Note: Using changeAccountLabel() alone will only work for the first account - */ - async changeLabelFromAccountDetailsModal(newLabel: string): Promise { - await this.driver.waitForSelector(this.editableLabelButton); - console.log( - `Account details modal opened, changing account label to: ${newLabel}`, - ); - await this.driver.clickElement(this.editableLabelButton); - await this.driver.fill(this.editableLabelInput, newLabel); - await this.driver.clickElement(this.saveAccountLabelButton); - await this.driver.clickElement(this.closeAccountModalButton); - } - async closeAccountModal(): Promise { console.log(`Close account modal in account list`); await this.driver.clickElementAndWaitToDisappear( @@ -292,23 +267,6 @@ class AccountListPage { ); } - /** - * Get the address of the specified account. - * - * @param accountLabel - The label of the account to get the address. - */ - async getAccountAddress(accountLabel: string): Promise { - console.log(`Get account address in account list`); - await this.openAccountOptionsInAccountList(accountLabel); - await this.driver.clickElement(this.accountMenuButton); - await this.driver.waitForSelector(this.accountAddressText); - const accountAddress = await ( - await this.driver.findElement(this.accountAddressText) - ).getText(); - await this.driver.clickElement(this.closeAccountModalButton); - return accountAddress; - } - async hideAccount(): Promise { console.log(`Hide account in account list`); await this.driver.clickElement(this.hideUnhideAccountButton); @@ -340,6 +298,13 @@ class AccountListPage { ); } + async isBtcAccountCreationButtonEnabled(): Promise { + const createButton = await this.driver.findElement( + this.addBtcAccountButton, + ); + return await createButton.isEnabled(); + } + /** * Open the account details modal for the specified account in account list. * @@ -556,21 +521,24 @@ class AccountListPage { } /** - * Check that the correct address is displayed in the account details modal. + * Checks that the add watch account button is displayed in the create account modal. * - * @param expectedAddress - The expected address to check. + * @param expectedAvailability - Whether the add watch account button is expected to be displayed. */ - async check_addressInAccountDetailsModal( - expectedAddress: string, + async check_addWatchAccountAvailable( + expectedAvailability: boolean, ): Promise { console.log( - `Check that address ${expectedAddress} is displayed in account details modal`, + `Check add watch account button is ${ + expectedAvailability ? 'displayed ' : 'not displayed' + }`, ); - await this.driver.waitForSelector(this.accountQrCodeImage); - await this.driver.waitForSelector({ - css: this.accountQrCodeAddress, - text: expectedAddress, - }); + await this.openAddAccountModal(); + if (expectedAvailability) { + await this.driver.waitForSelector(this.addEoaAccountButton); + } else { + await this.driver.assertElementNotPresent(this.addEoaAccountButton); + } } /** diff --git a/test/e2e/page-objects/pages/dialog/account-details-modal.ts b/test/e2e/page-objects/pages/dialog/account-details-modal.ts new file mode 100644 index 000000000000..0dbb2e0f87d7 --- /dev/null +++ b/test/e2e/page-objects/pages/dialog/account-details-modal.ts @@ -0,0 +1,106 @@ +import { Driver } from '../../../webdriver/driver'; + +class AccountDetailsModal { + private driver: Driver; + + private readonly accountAddressText = '.qr-code__address-segments'; + + private readonly accountQrCodeAddress = '.qr-code__address-segments'; + + private readonly accountQrCodeImage = '.qr-code__wrapper'; + + private readonly closeAccountModalButton = + 'header button[aria-label="Close"]'; + + private readonly copyAddressButton = + '[data-testid="address-copy-button-text"]'; + + private readonly editableLabelButton = + '[data-testid="editable-label-button"]'; + + private readonly editableLabelInput = '[data-testid="editable-input"] input'; + + private readonly saveAccountLabelButton = + '[data-testid="save-account-label-input"]'; + + private readonly showPrivateKeyButton = { + css: 'button', + text: 'Show private key', + }; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.editableLabelButton, + this.copyAddressButton, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for account details modal to be loaded', + e, + ); + throw e; + } + console.log('Account details modal is loaded'); + } + + async closeAccountDetailsModal(): Promise { + await this.driver.clickElementAndWaitToDisappear( + this.closeAccountModalButton, + ); + } + + /** + * Change the label of the account in the account details modal. + * + * @param newLabel - The new label to set for the account. + */ + async changeAccountLabel(newLabel: string): Promise { + console.log( + `Account details modal opened, changing account label to: ${newLabel}`, + ); + await this.driver.clickElement(this.editableLabelButton); + await this.driver.fill(this.editableLabelInput, newLabel); + await this.driver.clickElement(this.saveAccountLabelButton); + await this.closeAccountDetailsModal(); + } + + async getAccountAddress(): Promise { + console.log(`Get account address in account details modal`); + await this.driver.waitForSelector(this.accountAddressText); + const accountAddress = await ( + await this.driver.findElement(this.accountAddressText) + ).getText(); + await this.closeAccountDetailsModal(); + return accountAddress; + } + + /** + * Check that the correct address is displayed in the account details modal. + * + * @param expectedAddress - The expected address to check. + */ + async check_addressInAccountDetailsModal( + expectedAddress: string, + ): Promise { + console.log( + `Check that address ${expectedAddress} is displayed in account details modal`, + ); + await this.driver.waitForSelector(this.accountQrCodeImage); + await this.driver.waitForSelector({ + css: this.accountQrCodeAddress, + text: expectedAddress, + }); + } + + async check_showPrivateKeyButtonIsNotDisplayed(): Promise { + console.log('Check that show private key button is not displayed'); + await this.driver.assertElementNotPresent(this.showPrivateKeyButton); + } +} + +export default AccountDetailsModal; diff --git a/test/e2e/page-objects/pages/header-navbar.ts b/test/e2e/page-objects/pages/header-navbar.ts index 57e02e864624..e64fdc5a4cd7 100644 --- a/test/e2e/page-objects/pages/header-navbar.ts +++ b/test/e2e/page-objects/pages/header-navbar.ts @@ -9,6 +9,8 @@ class HeaderNavbar { private readonly allPermissionsButton = '[data-testid="global-menu-connected-sites"]'; + private readonly copyAddressButton = '[data-testid="app-header-copy-button"]'; + private readonly threeDotMenuButton = '[data-testid="account-options-menu-button"]'; @@ -19,6 +21,9 @@ class HeaderNavbar { private readonly mmiPortfolioButton = '[data-testid="global-menu-mmi-portfolio"]'; + private readonly openAccountDetailsButton = + '[data-testid="account-list-menu-details"]'; + private readonly settingsButton = '[data-testid="global-menu-settings"]'; private readonly switchNetworkDropDown = '[data-testid="network-display"]'; @@ -51,6 +56,12 @@ class HeaderNavbar { await this.driver.clickElement(this.accountMenuButton); } + async openAccountDetailsModal(): Promise { + console.log('Open account details modal'); + await this.openThreeDotMenu(); + await this.driver.clickElement(this.openAccountDetailsButton); + } + async openThreeDotMenu(): Promise { console.log('Open account options menu'); await this.driver.clickElement(this.threeDotMenuButton); @@ -98,6 +109,21 @@ class HeaderNavbar { ); } + /** + * Verifies that the displayed account address in header matches the expected address. + * + * @param expectedAddress - The expected address of the account. + */ + async check_accountAddress(expectedAddress: string): Promise { + console.log( + `Verify the displayed account address in header is: ${expectedAddress}`, + ); + await this.driver.waitForSelector({ + css: this.copyAddressButton, + text: expectedAddress, + }); + } + /** * Verifies that the displayed account label in header matches the expected label. * diff --git a/test/e2e/page-objects/pages/home/bitcoin-homepage.ts b/test/e2e/page-objects/pages/home/bitcoin-homepage.ts index 19b235636405..691d85763b14 100644 --- a/test/e2e/page-objects/pages/home/bitcoin-homepage.ts +++ b/test/e2e/page-objects/pages/home/bitcoin-homepage.ts @@ -4,10 +4,7 @@ class BitcoinHomepage extends HomePage { protected readonly balance = '[data-testid="coin-overview__primary-currency"]'; - private readonly bridgeButton = { - text: 'Bridge', - tag: 'button', - }; + protected readonly bridgeButton = '[data-testid="coin-overview-bridge"]'; private readonly buySellButton = '[data-testid="coin-overview-buy"]'; @@ -15,10 +12,7 @@ class BitcoinHomepage extends HomePage { protected readonly sendButton = '[data-testid="coin-overview-send"]'; - private readonly swapButton = { - text: 'Swap', - tag: 'button', - }; + protected readonly swapButton = '[data-testid="coin-overview-swap"]'; async check_pageIsLoaded(): Promise { try { diff --git a/test/e2e/page-objects/pages/home/homepage.ts b/test/e2e/page-objects/pages/home/homepage.ts index b4b79846fb06..708ae5ac0277 100644 --- a/test/e2e/page-objects/pages/home/homepage.ts +++ b/test/e2e/page-objects/pages/home/homepage.ts @@ -20,6 +20,9 @@ class HomePage { css: '.mm-banner-alert', }; + protected readonly bridgeButton: string = + '[data-testid="eth-overview-bridge"]'; + private readonly closeUseNetworkNotificationModalButton = { text: 'Got it', tag: 'h6', @@ -45,12 +48,15 @@ class HomePage { testId: 'sensitive-toggle', }; + protected readonly sendButton: string = '[data-testid="eth-overview-send"]'; + + protected readonly swapButton: string = + '[data-testid="token-overview-button-swap"]'; + private readonly refreshErc20Tokens = { testId: 'refreshList', }; - protected readonly sendButton: string = '[data-testid="eth-overview-send"]'; - private readonly tokensTab = { testId: 'account-overview__asset-tab', }; @@ -142,6 +148,13 @@ class HomePage { await this.driver.waitForSelector(this.basicFunctionalityOffWarningMessage); } + async check_disabledButtonTooltip(tooltipText: string): Promise { + console.log(`Check if disabled button tooltip is displayed on homepage`); + await this.driver.waitForSelector( + `.icon-button--disabled [data-tooltipped][data-original-title="${tooltipText}"]`, + ); + } + /** * Checks if the toaster message for editing a network is displayed on the homepage. * @@ -197,6 +210,39 @@ class HomePage { }, 10000); } + async check_ifBridgeButtonIsClickable(): Promise { + try { + await this.driver.findClickableElement(this.bridgeButton, 1000); + } catch (e) { + console.log('Bridge button not clickable', e); + return false; + } + console.log('Bridge button is clickable'); + return true; + } + + async check_ifSendButtonIsClickable(): Promise { + try { + await this.driver.findClickableElement(this.sendButton, 1000); + } catch (e) { + console.log('Send button not clickable', e); + return false; + } + console.log('Send button is clickable'); + return true; + } + + async check_ifSwapButtonIsClickable(): Promise { + try { + await this.driver.findClickableElement(this.swapButton, 1000); + } catch (e) { + console.log('Swap button not clickable', e); + return false; + } + console.log('Swap button is clickable'); + return true; + } + async check_localBlockchainBalanceIsDisplayed( localBlockchainServer?: Ganache, address = null, diff --git a/test/e2e/page-objects/pages/settings/experimental-settings.ts b/test/e2e/page-objects/pages/settings/experimental-settings.ts index f551db6d47c5..69df0525093d 100644 --- a/test/e2e/page-objects/pages/settings/experimental-settings.ts +++ b/test/e2e/page-objects/pages/settings/experimental-settings.ts @@ -22,6 +22,12 @@ class ExperimentalSettings { private readonly requestQueueToggle = '[data-testid="experimental-setting-toggle-request-queue"] label'; + private readonly watchAccountToggleState = + '[data-testid="watch-account-toggle"]'; + + private readonly watchAccountToggle = + '[data-testid="watch-account-toggle-div"]'; + constructor(driver: Driver) { this.driver = driver; } @@ -39,6 +45,15 @@ class ExperimentalSettings { console.log('Experimental Settings page is loaded'); } + // Get the state of the Watch Account Toggle, returns true if the toggle is selected + async getWatchAccountToggleState(): Promise { + console.log('Get Watch Account Toggle State'); + const toggleInput = await this.driver.findElement( + this.watchAccountToggleState, + ); + return toggleInput.isSelected(); + } + async toggleBitcoinAccount(): Promise { console.log('Toggle Add new Bitcoin account on experimental setting page'); await this.driver.waitForSelector({ @@ -62,6 +77,11 @@ class ExperimentalSettings { console.log('Toggle Request Queue on experimental setting page'); await this.driver.clickElement(this.requestQueueToggle); } + + async toggleWatchAccount(): Promise { + console.log('Toggle Watch Account on experimental setting page'); + await this.driver.clickElement(this.watchAccountToggle); + } } export default ExperimentalSettings; diff --git a/test/e2e/seeder/ganache.ts b/test/e2e/seeder/ganache.ts index 7fa3d1ab0238..d262924ab61e 100644 --- a/test/e2e/seeder/ganache.ts +++ b/test/e2e/seeder/ganache.ts @@ -76,6 +76,13 @@ export class Ganache { }); } + async mineBlock() { + return await this.getProvider()?.request({ + method: 'evm_mine', + params: [], + }); + } + async quit() { if (!this.#server) { throw new Error('Server not running yet'); diff --git a/test/e2e/tests/account/account-custom-name.spec.ts b/test/e2e/tests/account/account-custom-name.spec.ts index 4c0ecbe196f9..92302e032224 100644 --- a/test/e2e/tests/account/account-custom-name.spec.ts +++ b/test/e2e/tests/account/account-custom-name.spec.ts @@ -1,8 +1,10 @@ import { Suite } from 'mocha'; import { Driver } from '../../webdriver/driver'; import { withFixtures } from '../../helpers'; +import { ACCOUNT_TYPE } from '../../constants'; import FixtureBuilder from '../../fixture-builder'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; +import AccountDetailsModal from '../../page-objects/pages/dialog/account-details-modal'; import AccountListPage from '../../page-objects/pages/account-list-page'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; @@ -25,14 +27,20 @@ describe('Account Custom Name Persistence', function (this: Suite) { // Change account label for existing account and verify edited account label const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.openAccountOptionsMenu(); - await accountListPage.changeAccountLabel(newAccountLabel); + await accountListPage.openAccountDetailsModal('Account 1'); + + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + await accountDetailsModal.changeAccountLabel(newAccountLabel); await headerNavbar.check_accountLabel(newAccountLabel); // Add new account with custom label and verify new added account label await headerNavbar.openAccountMenu(); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewAccount(anotherAccountLabel); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + accountName: anotherAccountLabel, + }); await headerNavbar.check_accountLabel(anotherAccountLabel); // Switch back to the first account and verify first custom account persists diff --git a/test/e2e/tests/account/add-account.spec.ts b/test/e2e/tests/account/add-account.spec.ts index db62927b91d7..2df140899212 100644 --- a/test/e2e/tests/account/add-account.spec.ts +++ b/test/e2e/tests/account/add-account.spec.ts @@ -1,5 +1,6 @@ import { E2E_SRP } from '../../default-fixture'; import FixtureBuilder from '../../fixture-builder'; +import { ACCOUNT_TYPE } from '../../constants'; import { WALLET_PASSWORD, defaultGanacheOptions, @@ -43,7 +44,9 @@ describe('Add account', function () { const newAccountName = 'Account 2'; const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewAccount(); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + }); await headerNavbar.check_accountLabel(newAccountName); await homePage.check_expectedBalanceIsDisplayed(); @@ -112,7 +115,9 @@ describe('Add account', function () { const newAccountName = 'Account 2'; const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewAccount(); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + }); await headerNavbar.check_accountLabel(newAccountName); await homePage.check_expectedBalanceIsDisplayed(); @@ -177,7 +182,9 @@ describe('Add account', function () { // Create new account with default name Account 2 const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewAccount(); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + }); await headerNavbar.check_accountLabel('Account 2'); await homePage.check_expectedBalanceIsDisplayed(); diff --git a/test/e2e/tests/account/import-flow.spec.ts b/test/e2e/tests/account/import-flow.spec.ts index dbb7b2f85d49..c8ffcc334e49 100644 --- a/test/e2e/tests/account/import-flow.spec.ts +++ b/test/e2e/tests/account/import-flow.spec.ts @@ -3,6 +3,7 @@ import { DEFAULT_FIXTURE_ACCOUNT } from '../../constants'; import { withFixtures } from '../../helpers'; import FixtureBuilder from '../../fixture-builder'; import AccountListPage from '../../page-objects/pages/account-list-page'; +import AccountDetailsModal from '../../page-objects/pages/dialog/account-details-modal'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; import HomePage from '../../page-objects/pages/home/homepage'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; @@ -32,7 +33,9 @@ describe('Import flow @no-mmi', function () { const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); await accountListPage.openAccountDetailsModal('Account 1'); - await accountListPage.check_addressInAccountDetailsModal( + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + await accountDetailsModal.check_addressInAccountDetailsModal( DEFAULT_FIXTURE_ACCOUNT.toLowerCase(), ); }, diff --git a/test/e2e/tests/dapp-interactions/failing-contract.spec.js b/test/e2e/tests/dapp-interactions/failing-contract.spec.js index c05938d668e0..c02a279adb3f 100644 --- a/test/e2e/tests/dapp-interactions/failing-contract.spec.js +++ b/test/e2e/tests/dapp-interactions/failing-contract.spec.js @@ -72,6 +72,15 @@ describe('Failing contract interaction ', function () { css: '.activity-list-item .transaction-status-label', text: 'Failed', }); + // inspect transaction details + await driver.clickElement({ + css: '.activity-list-item .transaction-status-label', + text: 'Failed', + }); + await driver.waitForSelector('.transaction-list-item-details'); + await driver.waitForSelector( + '[data-testid="transaction-list-item-details-banner-error-message"]', + ); }, ); }); diff --git a/test/e2e/tests/identity/account-syncing/new-user-sync.spec.ts b/test/e2e/tests/identity/account-syncing/new-user-sync.spec.ts index e9a82b128352..64a3ddbf5fd7 100644 --- a/test/e2e/tests/identity/account-syncing/new-user-sync.spec.ts +++ b/test/e2e/tests/identity/account-syncing/new-user-sync.spec.ts @@ -2,6 +2,7 @@ import { Mockttp } from 'mockttp'; import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; +import { ACCOUNT_TYPE } from '../../../constants'; import { mockIdentityServices } from '../mocks'; import { IDENTITY_TEAM_PASSWORD } from '../constants'; import { UserStorageMockttpController } from '../../../helpers/identity/user-storage/userStorageMockttpController'; @@ -64,7 +65,10 @@ describe('Account syncing - New User @no-mmi', function () { // Add a second account await accountListPage.openAccountOptionsMenu(); - await accountListPage.addNewAccount('My Second Account'); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + accountName: 'My Second Account', + }); // Set SRP to use for retreival const headerNavbar = new HeaderNavbar(driver); diff --git a/test/e2e/tests/identity/account-syncing/onboarding-with-opt-out.spec.ts b/test/e2e/tests/identity/account-syncing/onboarding-with-opt-out.spec.ts index 19908211181b..bd063c182baf 100644 --- a/test/e2e/tests/identity/account-syncing/onboarding-with-opt-out.spec.ts +++ b/test/e2e/tests/identity/account-syncing/onboarding-with-opt-out.spec.ts @@ -7,6 +7,7 @@ import { IDENTITY_TEAM_PASSWORD, IDENTITY_TEAM_SEED_PHRASE, } from '../constants'; +import { ACCOUNT_TYPE } from '../../../constants'; import { UserStorageMockttpController } from '../../../helpers/identity/user-storage/userStorageMockttpController'; import AccountListPage from '../../../page-objects/pages/account-list-page'; import HeaderNavbar from '../../../page-objects/pages/header-navbar'; @@ -135,7 +136,11 @@ describe('Account syncing - Opt-out Profile Sync @no-mmi', function () { await accountListPage.check_accountDisplayedInAccountList( 'Account 1', ); - await accountListPage.addNewAccount('New Account'); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + accountName: 'New Account', + }); + // Set SRP to use for retreival const headerNavbar = new HeaderNavbar(driver); await headerNavbar.check_pageIsLoaded(); diff --git a/test/e2e/tests/identity/account-syncing/sync-after-adding-account.spec.ts b/test/e2e/tests/identity/account-syncing/sync-after-adding-account.spec.ts index 9c2fdb69c7e9..e02833e7c172 100644 --- a/test/e2e/tests/identity/account-syncing/sync-after-adding-account.spec.ts +++ b/test/e2e/tests/identity/account-syncing/sync-after-adding-account.spec.ts @@ -3,6 +3,7 @@ import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sd import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; import { mockIdentityServices } from '../mocks'; +import { ACCOUNT_TYPE } from '../../../constants'; import { IDENTITY_TEAM_PASSWORD, IDENTITY_TEAM_SEED_PHRASE, @@ -65,7 +66,10 @@ describe('Account syncing - Add Account @no-mmi', function () { await accountListPage.check_accountDisplayedInAccountList( 'My Second Synced Account', ); - await accountListPage.addNewAccount('My third account'); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + accountName: 'My third account', + }); }, ); @@ -164,7 +168,9 @@ describe('Account syncing - Add Account @no-mmi', function () { await accountListPage.check_accountDisplayedInAccountList( 'My Second Synced Account', ); - await accountListPage.addNewAccount(); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + }); }, ); diff --git a/test/e2e/tests/identity/account-syncing/sync-after-modifying-account-name.spec.ts b/test/e2e/tests/identity/account-syncing/sync-after-modifying-account-name.spec.ts index 07f6e4848aba..ce10b3129d58 100644 --- a/test/e2e/tests/identity/account-syncing/sync-after-modifying-account-name.spec.ts +++ b/test/e2e/tests/identity/account-syncing/sync-after-modifying-account-name.spec.ts @@ -9,6 +9,7 @@ import { } from '../constants'; import { UserStorageMockttpController } from '../../../helpers/identity/user-storage/userStorageMockttpController'; import HeaderNavbar from '../../../page-objects/pages/header-navbar'; +import AccountDetailsModal from '../../../page-objects/pages/dialog/account-details-modal'; import AccountListPage from '../../../page-objects/pages/account-list-page'; import HomePage from '../../../page-objects/pages/home/homepage'; import { completeImportSRPOnboardingFlow } from '../../../page-objects/flows/onboarding.flow'; @@ -65,8 +66,14 @@ describe('Account syncing - Rename Accounts @no-mmi', function () { await accountListPage.check_accountDisplayedInAccountList( 'My Second Synced Account', ); - await accountListPage.openAccountOptionsMenu(); - await accountListPage.changeAccountLabel('My Renamed First Account'); + await accountListPage.openAccountDetailsModal( + 'My First Synced Account', + ); + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + await accountDetailsModal.changeAccountLabel( + 'My Renamed First Account', + ); }, ); diff --git a/test/e2e/tests/identity/account-syncing/sync-with-account-balances.spec.ts b/test/e2e/tests/identity/account-syncing/sync-with-account-balances.spec.ts index 6fcc9e9cc0d0..ec52bb3124bd 100644 --- a/test/e2e/tests/identity/account-syncing/sync-with-account-balances.spec.ts +++ b/test/e2e/tests/identity/account-syncing/sync-with-account-balances.spec.ts @@ -6,8 +6,10 @@ import { IDENTITY_TEAM_PASSWORD, IDENTITY_TEAM_SEED_PHRASE, } from '../constants'; +import { ACCOUNT_TYPE } from '../../../constants'; import { UserStorageMockttpController } from '../../../helpers/identity/user-storage/userStorageMockttpController'; import HeaderNavbar from '../../../page-objects/pages/header-navbar'; +import AccountDetailsModal from '../../../page-objects/pages/dialog/account-details-modal'; import AccountListPage from '../../../page-objects/pages/account-list-page'; import HomePage from '../../../page-objects/pages/home/homepage'; import { completeImportSRPOnboardingFlow } from '../../../page-objects/flows/onboarding.flow'; @@ -108,7 +110,9 @@ describe('Account syncing - User already has balances on multple accounts @no-mm } // Create new account and prepare for additional accounts - await accountListPage.addNewAccount(); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + }); accountsToMock = [...INITIAL_ACCOUNTS, ...ADDITIONAL_ACCOUNTS]; }, ); @@ -158,11 +162,13 @@ describe('Account syncing - User already has balances on multple accounts @no-mm // Rename Account 6 to verify update to user storage await accountListPage.switchToAccount('Account 6'); + await header.check_accountLabel('Account 6'); await header.openAccountMenu(); + await accountListPage.check_pageIsLoaded(); await accountListPage.openAccountDetailsModal('Account 6'); - await accountListPage.changeLabelFromAccountDetailsModal( - 'My Renamed Account 6', - ); + const accountDetailsModal = new AccountDetailsModal(driver); + await accountDetailsModal.check_pageIsLoaded(); + await accountDetailsModal.changeAccountLabel('My Renamed Account 6'); }, ); diff --git a/test/e2e/tests/metrics/signature-approved.spec.js b/test/e2e/tests/metrics/signature-approved.spec.js index d0d1bb9c32c3..56e376f878ff 100644 --- a/test/e2e/tests/metrics/signature-approved.spec.js +++ b/test/e2e/tests/metrics/signature-approved.spec.js @@ -10,8 +10,6 @@ const { clickSignOnSignatureConfirmation, tempToggleSettingRedesignedConfirmations, validateContractDetails, - clickSignOnRedesignedSignatureConfirmation, - WINDOW_TITLES, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -60,10 +58,6 @@ const expectedEventPropertiesBase = { security_alert_response: 'loading', }; -const additionalRedesignEventProperties = { - ui_customizations: ['redesigned_confirmation'], -}; - describe('Signature Approved Event @no-mmi', function () { describe('Old confirmation screens', function () { it('Successfully tracked for signTypedData_v4', async function () { @@ -230,172 +224,4 @@ describe('Signature Approved Event @no-mmi', function () { ); }); }); - - describe('Redesigned confirmation screens', function () { - it('Successfully tracked for signTypedData_v4', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .withMetaMetricsController({ - metaMetricsId: 'fake-metrics-id', - participateInMetaMetrics: true, - }) - .build(), - defaultGanacheOptions, - title: this.test.fullTitle(), - testSpecificMock: mockSegment, - }, - async ({ driver, mockedEndpoint: mockedEndpoints }) => { - await unlockWallet(driver); - await openDapp(driver); - - // creates a sign typed data signature request - await driver.clickElement('#signTypedDataV4'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await clickSignOnRedesignedSignatureConfirmation({ driver }); - const events = await getEventPayloads(driver, mockedEndpoints); - - assert.deepStrictEqual(events[0].properties, { - ...expectedEventPropertiesBase, - ...additionalRedesignEventProperties, - signature_type: 'eth_signTypedData_v4', - eip712_primary_type: 'Mail', - }); - - assert.deepStrictEqual(events[1].properties, { - ...expectedEventPropertiesBase, - ...additionalRedesignEventProperties, - signature_type: 'eth_signTypedData_v4', - eip712_primary_type: 'Mail', - security_alert_response: 'Benign', - }); - }, - ); - }); - - it('Successfully tracked for signTypedData_v3', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .withMetaMetricsController({ - metaMetricsId: 'fake-metrics-id', - participateInMetaMetrics: true, - }) - .build(), - defaultGanacheOptions, - title: this.test.fullTitle(), - testSpecificMock: mockSegment, - }, - async ({ driver, mockedEndpoint: mockedEndpoints }) => { - await unlockWallet(driver); - await openDapp(driver); - - // creates a sign typed data signature request - await driver.clickElement('#signTypedDataV3'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await clickSignOnRedesignedSignatureConfirmation({ driver }); - const events = await getEventPayloads(driver, mockedEndpoints); - - assert.deepStrictEqual(events[0].properties, { - ...expectedEventPropertiesBase, - ...additionalRedesignEventProperties, - signature_type: 'eth_signTypedData_v3', - }); - - assert.deepStrictEqual(events[1].properties, { - ...expectedEventPropertiesBase, - ...additionalRedesignEventProperties, - signature_type: 'eth_signTypedData_v3', - security_alert_response: 'Benign', - }); - }, - ); - }); - - it('Successfully tracked for signTypedData', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .withMetaMetricsController({ - metaMetricsId: 'fake-metrics-id', - participateInMetaMetrics: true, - }) - .build(), - defaultGanacheOptions, - title: this.test.fullTitle(), - testSpecificMock: mockSegment, - }, - async ({ driver, mockedEndpoint: mockedEndpoints }) => { - await unlockWallet(driver); - await openDapp(driver); - - // creates a sign typed data signature request - await driver.clickElement('#signTypedData'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await clickSignOnRedesignedSignatureConfirmation({ driver }); - const events = await getEventPayloads(driver, mockedEndpoints); - - assert.deepStrictEqual(events[0].properties, { - ...expectedEventPropertiesBase, - ...additionalRedesignEventProperties, - signature_type: 'eth_signTypedData', - }); - - assert.deepStrictEqual(events[1].properties, { - ...expectedEventPropertiesBase, - ...additionalRedesignEventProperties, - signature_type: 'eth_signTypedData', - security_alert_response: 'Benign', - }); - }, - ); - }); - - it('Successfully tracked for personalSign', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .withMetaMetricsController({ - metaMetricsId: 'fake-metrics-id', - participateInMetaMetrics: true, - }) - .build(), - defaultGanacheOptions, - title: this.test.fullTitle(), - testSpecificMock: mockSegment, - }, - async ({ driver, mockedEndpoint: mockedEndpoints }) => { - await unlockWallet(driver); - await openDapp(driver); - - // creates a sign typed data signature request - await driver.clickElement('#personalSign'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await clickSignOnRedesignedSignatureConfirmation({ driver }); - const events = await getEventPayloads(driver, mockedEndpoints); - - assert.deepStrictEqual(events[0].properties, { - ...expectedEventPropertiesBase, - ...additionalRedesignEventProperties, - signature_type: 'personal_sign', - }); - - assert.deepStrictEqual(events[1].properties, { - ...expectedEventPropertiesBase, - ...additionalRedesignEventProperties, - signature_type: 'personal_sign', - security_alert_response: 'Benign', - }); - }, - ); - }); - }); }); diff --git a/test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.js b/test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.js deleted file mode 100644 index 53c763d8891f..000000000000 --- a/test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.js +++ /dev/null @@ -1,95 +0,0 @@ -const { strict: assert } = require('assert'); -const FixtureBuilder = require('../../fixture-builder'); -const { - withFixtures, - openDapp, - unlockWallet, - DAPP_URL, - regularDelayMs, - WINDOW_TITLES, - switchToNotificationWindow, - defaultGanacheOptions, -} = require('../../helpers'); - -describe('Request Queueing', function () { - it('should keep subscription on dapp network when switching different mm network', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - - async ({ driver }) => { - await unlockWallet(driver); - - 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 switchToNotificationWindow(driver); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // Navigate to test dapp - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - const subscribeRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'eth_subscribe', - params: ['newHeads'], - }); - - const subscribe = await driver.executeScript( - `return window.ethereum.request(${subscribeRequest})`, - ); - - const subscriptionMessage = await driver.executeAsyncScript( - `const callback = arguments[arguments.length - 1];` + - `window.ethereum.on('message', (message) => callback(message))`, - ); - - assert.strictEqual(subscribe, subscriptionMessage.data.subscription); - assert.strictEqual(subscriptionMessage.type, 'eth_subscription'); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); - - assert.strictEqual(subscribe, subscriptionMessage.data.subscription); - assert.strictEqual(subscriptionMessage.type, 'eth_subscription'); - }, - ); - }); -}); diff --git a/test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.ts b/test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.ts new file mode 100644 index 000000000000..ec607fc34936 --- /dev/null +++ b/test/e2e/tests/request-queuing/dapp1-subscribe-network-switch.spec.ts @@ -0,0 +1,98 @@ +import { strict as assert } from 'assert'; +import FixtureBuilder from '../../fixture-builder'; +import { + defaultGanacheOptions, + WINDOW_TITLES, + withFixtures, +} from '../../helpers'; +import TestDapp from '../../page-objects/pages/test-dapp'; +import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow'; +import { switchToNetworkFlow } from '../../page-objects/flows/network.flow'; + +describe('Request Queueing', function () { + it('should keep subscription on dapp network when switching different mm network', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test?.fullTitle(), + }, + + async ({ driver, ganacheServer }) => { + await loginWithoutBalanceValidation(driver); + + // Connect to dapp + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.check_pageIsLoaded(); + await testDapp.connectAccount({}); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + // Subscribe to newHeads event + const subscribeRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'eth_subscribe', + params: ['newHeads'], + }); + + await driver.executeScript( + `return window.ethereum.request(${subscribeRequest})`, + ); + + // Save event logs into the messages variable in the window context, to access it later + await driver.executeScript(` + window.messages = []; + window.ethereum.on('message', (message) => { + if (message.type === 'eth_subscription') { + console.log('New block header:', message.data.result); + window.messages.push(message.data.result); + } + }); + `); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Switch networks + await switchToNetworkFlow(driver, 'Localhost 8546'); + + // Navigate back to the test dapp + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + // Access to the window messages variable + const messagesBeforeMining = await driver.executeScript( + 'return window.messages;', + ); + + // Mine a block deterministically + await ganacheServer.mineBlock(); + + // Wait a couple of seconds for the logs to populate into the messages window variable + await driver.delay(5000); + + // Access the window messages variable and check there are more events than before mining + const messagesAfterMining = await driver.executeScript( + 'return window.messages;', + ); + + assert.ok(messagesBeforeMining.length < messagesAfterMining.length); + }, + ); + }); +}); diff --git a/test/e2e/tests/signature/personal-sign.spec.js b/test/e2e/tests/signature/personal-sign.spec.js index 092a9518ba01..908a422bfea0 100644 --- a/test/e2e/tests/signature/personal-sign.spec.js +++ b/test/e2e/tests/signature/personal-sign.spec.js @@ -116,43 +116,6 @@ describe('Personal sign', function () { }); describe('Redesigned confirmation screens', function () { - it('can initiate and confirm a personal sign', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer }) => { - const addresses = await ganacheServer.getAccounts(); - const publicAddress = addresses[0]; - await unlockWallet(driver); - - await openDapp(driver); - await driver.clickElement('#personalSign'); - - await driver.waitUntilXWindowHandles(3); - const windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Dialog, - windowHandles, - ); - - await driver.findElement({ - css: 'p', - text: 'Example `personal_sign` message', - }); - - await driver.clickElement('[data-testid="confirm-footer-button"]'); - - await verifyAndAssertPersonalMessage(driver, publicAddress); - }, - ); - }); - it('can queue multiple personal signs and confirm', async function () { await withFixtures( { diff --git a/test/e2e/tests/signature/signature-request.spec.js b/test/e2e/tests/signature/signature-request.spec.js index 716ee1f98d95..7d37da3de0db 100644 --- a/test/e2e/tests/signature/signature-request.spec.js +++ b/test/e2e/tests/signature/signature-request.spec.js @@ -337,64 +337,6 @@ describe('Sign Typed Data Signature Request', function () { }); describe('Redesigned confirmation screens', function () { - testData.forEach((data) => { - it(`can initiate and confirm a Signature Request of ${data.type}`, async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer }) => { - const addresses = await ganacheServer.getAccounts(); - const publicAddress = addresses[0]; - await unlockWallet(driver); - - await openDapp(driver); - - // creates a sign typed data signature request - await driver.clickElement(data.buttonId); - - await driver.waitUntilXWindowHandles(3); - let windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Dialog, - windowHandles, - ); - - await verifyAndAssertRedesignedSignTypedData( - driver, - data.expectedMessage, - ); - - // Approve signing typed data - await finalizeSignatureRequest( - driver, - '.confirm-scroll-to-bottom__button', - 'Confirm', - ); - await driver.waitUntilXWindowHandles(2); - windowHandles = await driver.getAllWindowHandles(); - - // switch to the Dapp and verify the signed address - await driver.switchToWindowWithTitle( - 'E2E Test Dapp', - windowHandles, - ); - await driver.clickElement(data.verifyId); - const recoveredAddress = await driver.findElement( - data.verifyResultId, - ); - - assert.equal(await recoveredAddress.getText(), publicAddress); - }, - ); - }); - }); - testData.forEach((data) => { it(`can queue multiple Signature Requests of ${data.type} and confirm`, async function () { await withFixtures( diff --git a/test/e2e/tests/tokens/nft/import-nft.spec.ts b/test/e2e/tests/tokens/nft/import-nft.spec.ts index 5983d1002035..e151e83a355f 100644 --- a/test/e2e/tests/tokens/nft/import-nft.spec.ts +++ b/test/e2e/tests/tokens/nft/import-nft.spec.ts @@ -1,4 +1,5 @@ import { defaultGanacheOptions, withFixtures } from '../../../helpers'; +import { ACCOUNT_TYPE } from '../../../constants'; import { SMART_CONTRACTS } from '../../../seeder/smart-contracts'; import FixtureBuilder from '../../../fixture-builder'; import AccountListPage from '../../../page-objects/pages/account-list-page'; @@ -67,7 +68,9 @@ describe('Import NFT', function () { await headerNavbar.openAccountMenu(); const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewAccount(); + await accountListPage.addAccount({ + accountType: ACCOUNT_TYPE.Ethereum, + }); await headerNavbar.check_accountLabel('Account 2'); await homepage.check_expectedBalanceIsDisplayed(); diff --git a/test/e2e/tests/transaction/multiple-transactions.spec.js b/test/e2e/tests/transaction/multiple-transactions.spec.js index b85937f5b6bd..72dbbe638e3b 100644 --- a/test/e2e/tests/transaction/multiple-transactions.spec.js +++ b/test/e2e/tests/transaction/multiple-transactions.spec.js @@ -122,12 +122,6 @@ describe('Multiple transactions', function () { '[data-testid="account-overview__activity-tab"]', ); - const isTransactionListEmpty = - await driver.isElementPresentAndVisible( - '.transaction-list__empty-text', - ); - assert.equal(isTransactionListEmpty, true); - // The previous isTransactionListEmpty wait already serves as the guard here for the assertElementNotPresent await driver.assertElementNotPresent( '.transaction-list__completed-transactions .activity-list-item', @@ -244,12 +238,6 @@ describe('Multiple transactions', function () { '[data-testid="account-overview__activity-tab"]', ); - const isTransactionListEmpty = - await driver.isElementPresentAndVisible( - '.transaction-list__empty-text', - ); - assert.equal(isTransactionListEmpty, true); - // The previous isTransactionListEmpty wait already serves as the guard here for the assertElementNotPresent await driver.assertElementNotPresent( '.transaction-list__completed-transactions .activity-list-item', diff --git a/test/integration/confirmations/transactions/increase-allowance.test.tsx b/test/integration/confirmations/transactions/increase-allowance.test.tsx index 49b33bf2d21b..083c8b3a72dd 100644 --- a/test/integration/confirmations/transactions/increase-allowance.test.tsx +++ b/test/integration/confirmations/transactions/increase-allowance.test.tsx @@ -213,7 +213,7 @@ describe('ERC20 increaseAllowance Confirmation', () => { 'simulation-token-value', ); expect(simulationSection).toContainElement(spendingCapValue); - expect(spendingCapValue).toHaveTextContent('1'); + expect(spendingCapValue).toHaveTextContent('3'); expect(simulationSection).toHaveTextContent('0x07614...3ad68'); }); @@ -240,7 +240,7 @@ describe('ERC20 increaseAllowance Confirmation', () => { expect(approveDetails).toContainElement(approveDetailsSpender); expect(approveDetailsSpender).toHaveTextContent(tEn('spender') as string); - expect(approveDetailsSpender).toHaveTextContent('0x2e0D7...5d09B'); + expect(approveDetailsSpender).toHaveTextContent('0x9bc5b...AfEF4'); const spenderTooltip = await screen.findByTestId( 'confirmation__approve-spender-tooltip', ); @@ -301,7 +301,7 @@ describe('ERC20 increaseAllowance Confirmation', () => { ); expect(spendingCapSection).toContainElement(spendingCapGroup); expect(spendingCapGroup).toHaveTextContent(tEn('spendingCap') as string); - expect(spendingCapGroup).toHaveTextContent('1'); + expect(spendingCapGroup).toHaveTextContent('3'); const spendingCapGroupTooltip = await screen.findByTestId( 'confirmation__approve-spending-cap-group-tooltip', diff --git a/test/integration/confirmations/transactions/set-approval-for-all.test.tsx b/test/integration/confirmations/transactions/set-approval-for-all.test.tsx index 236162c7b08f..c017f8acc9ca 100644 --- a/test/integration/confirmations/transactions/set-approval-for-all.test.tsx +++ b/test/integration/confirmations/transactions/set-approval-for-all.test.tsx @@ -246,7 +246,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => { expect(approveDetailsSpender).toHaveTextContent( tEn('permissionFor') as string, ); - expect(approveDetailsSpender).toHaveTextContent('0x2e0D7...5d09B'); + expect(approveDetailsSpender).toHaveTextContent('0x9bc5b...AfEF4'); const spenderTooltip = await screen.findByTestId( 'confirmation__approve-spender-tooltip', ); diff --git a/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap b/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap index 7fb51f212ebd..3ec0face8295 100644 --- a/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap +++ b/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap @@ -28,7 +28,7 @@ exports[`Token Cell should match snapshot 1`] = ` class="mm-box mm-badge-wrapper__badge-container mm-badge-wrapper__badge-container--rectangular-bottom-right" >
network logo } marginRight={2} diff --git a/ui/components/app/metamask-template-renderer/safe-component-list.js b/ui/components/app/metamask-template-renderer/safe-component-list.js index 962b630562d1..b15bff11247c 100644 --- a/ui/components/app/metamask-template-renderer/safe-component-list.js +++ b/ui/components/app/metamask-template-renderer/safe-component-list.js @@ -33,6 +33,7 @@ import { Copyable } from '../snaps/copyable'; import { SnapDelineator } from '../snaps/snap-delineator'; import { SnapUIAddress } from '../snaps/snap-ui-address'; import { SnapUIAvatar } from '../snaps/snap-ui-avatar'; +import { SnapUIBanner } from '../snaps/snap-ui-banner'; import { SnapUIButton } from '../snaps/snap-ui-button'; import { SnapUICard } from '../snaps/snap-ui-card'; import { SnapUICheckbox } from '../snaps/snap-ui-checkbox'; @@ -89,6 +90,7 @@ export const safeComponentList = { SnapDelineator, SnapUIAddress, SnapUIAvatar, + SnapUIBanner, SnapUIButton, SnapUICard, SnapUICheckbox, diff --git a/ui/components/app/snaps/snap-settings-page/index.ts b/ui/components/app/snaps/snap-settings-page/index.ts new file mode 100644 index 000000000000..91a594fcdeb0 --- /dev/null +++ b/ui/components/app/snaps/snap-settings-page/index.ts @@ -0,0 +1 @@ +export * from './snap-settings-renderer'; diff --git a/ui/components/app/snaps/snap-settings-page/snap-settings-renderer.tsx b/ui/components/app/snaps/snap-settings-page/snap-settings-renderer.tsx new file mode 100644 index 000000000000..fa5636ebb77f --- /dev/null +++ b/ui/components/app/snaps/snap-settings-page/snap-settings-renderer.tsx @@ -0,0 +1,81 @@ +import React, { FunctionComponent, useEffect, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; + +import { deleteInterface } from '../../../../store/actions'; +import { Box, Text } from '../../../component-library'; +import { + BackgroundColor, + BlockSize, + TextVariant, +} from '../../../../helpers/constants/design-system'; +import { SnapDelineator } from '../snap-delineator'; +import { getSnapMetadata } from '../../../../selectors'; +import { DelineatorType } from '../../../../helpers/constants/snaps'; +import { Copyable } from '../copyable'; +import { SnapUIRenderer } from '../snap-ui-renderer'; +import { useSnapSettings } from '../../../../hooks/snaps/useSnapSettings'; +import { decodeSnapIdFromPathname } from '../../../../helpers/utils/snaps'; + +type SnapSettingsRendererProps = { + snapId: string; +}; + +export const SnapSettingsRenderer: FunctionComponent< + SnapSettingsRendererProps +> = () => { + const { pathname } = useLocation(); + const dispatch = useDispatch(); + const t = useI18nContext(); + + const snapId = useMemo(() => decodeSnapIdFromPathname(pathname), [pathname]); + + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, snapId), + ); + + const { data, error, loading } = useSnapSettings({ + snapId, + }); + + const interfaceId = !loading && !error ? data?.id : undefined; + + useEffect(() => { + return () => { + interfaceId && dispatch(deleteInterface(interfaceId)); + }; + }, [interfaceId]); + + if (!snapId) { + return null; + } + + return ( + + {error && ( + + + + {t('snapsUIError', [{snapName}])} + + + + + )} + {(interfaceId || loading) && ( + + )} + + ); +}; diff --git a/ui/components/app/snaps/snap-ui-banner/index.ts b/ui/components/app/snaps/snap-ui-banner/index.ts new file mode 100644 index 000000000000..f7e12ce782b0 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-banner/index.ts @@ -0,0 +1 @@ +export * from './snap-ui-banner'; diff --git a/ui/components/app/snaps/snap-ui-banner/snap-ui-banner.stories.tsx b/ui/components/app/snaps/snap-ui-banner/snap-ui-banner.stories.tsx new file mode 100644 index 000000000000..4d1581e9d3e8 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-banner/snap-ui-banner.stories.tsx @@ -0,0 +1,33 @@ +import React, { ReactNode } from 'react'; +import { SnapUIBanner } from './snap-ui-banner'; +import { BannerAlertSeverity } from '../../../component-library'; + +export default { + title: 'Components/App/Snaps/SnapUIBanner', + component: SnapUIBanner, + argTypes: { + title: { + control: 'text', + }, + severity: { + control: 'text', + }, + children: { + control: 'text', + }, + }, +}; + +export const DefaultStory = (args: { + title: string; + severity: BannerAlertSeverity; + children: ReactNode; +}) => ; + +DefaultStory.storyName = 'Default'; + +DefaultStory.args = { + title: 'Banner title', + severity: 'info', + children: 'Banner content.', +}; diff --git a/ui/components/app/snaps/snap-ui-banner/snap-ui-banner.tsx b/ui/components/app/snaps/snap-ui-banner/snap-ui-banner.tsx new file mode 100644 index 000000000000..872eb3032993 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-banner/snap-ui-banner.tsx @@ -0,0 +1,19 @@ +import React, { FunctionComponent } from 'react'; +import { BannerAlert, BannerAlertSeverity } from '../../../component-library'; + +export type SnapUIBannerProps = { + severity: BannerAlertSeverity | undefined; + title: string; +}; + +export const SnapUIBanner: FunctionComponent = ({ + children, + severity, + title, +}) => { + return ( + + {children} + + ); +}; diff --git a/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx b/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx index 08fef2f9a6b7..49f1de8fbee1 100644 --- a/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx +++ b/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx @@ -1,7 +1,12 @@ import React, { FunctionComponent, MouseEvent as ReactMouseEvent } from 'react'; import classnames from 'classnames'; import { ButtonType, UserInputEventType } from '@metamask/snaps-sdk'; -import { ButtonLinkProps, Text } from '../../../component-library'; +import { + ButtonLinkProps, + Icon, + IconName, + Text, +} from '../../../component-library'; import { FontWeight, TextColor, @@ -10,6 +15,8 @@ import { useSnapInterfaceContext } from '../../../../contexts/snaps'; export type SnapUIButtonProps = { name?: string; + textVariant: ButtonLinkProps<'button'>['variant']; + loading?: boolean; }; const COLORS = { @@ -26,7 +33,9 @@ export const SnapUIButton: FunctionComponent< type = ButtonType.Button, variant = 'primary', disabled = false, + loading = false, className = '', + textVariant, ...props }) => { const { handleEvent } = useSnapInterfaceContext(); @@ -58,9 +67,17 @@ export const SnapUIButton: FunctionComponent< onClick={handleClick} color={color} disabled={disabled} + variant={textVariant} {...props} > - {children} + {loading ? ( + + ) : ( + children + )} ); }; diff --git a/ui/components/app/snaps/snap-ui-footer-button/snap-ui-footer-button.tsx b/ui/components/app/snaps/snap-ui-footer-button/snap-ui-footer-button.tsx index 240024bb673a..ff3f530ae1e7 100644 --- a/ui/components/app/snaps/snap-ui-footer-button/snap-ui-footer-button.tsx +++ b/ui/components/app/snaps/snap-ui-footer-button/snap-ui-footer-button.tsx @@ -10,6 +10,8 @@ import { Button, ButtonProps, ButtonSize, + Icon, + IconName, IconSize, } from '../../../component-library'; import { @@ -35,6 +37,7 @@ export const SnapUIFooterButton: FunctionComponent< name, children, disabled = false, + loading = false, isSnapAction = false, type, variant = ButtonVariant.Primary, @@ -85,10 +88,17 @@ export const SnapUIFooterButton: FunctionComponent< flexDirection: FlexDirection.Row, }} > - {isSnapAction && !hideSnapBranding && ( + {isSnapAction && !hideSnapBranding && !loading && ( )} - {children} + {loading ? ( + + ) : ( + children + )} ); }; diff --git a/ui/components/app/snaps/snap-ui-renderer/components/banner.ts b/ui/components/app/snaps/snap-ui-renderer/components/banner.ts new file mode 100644 index 000000000000..fceb03d4be5d --- /dev/null +++ b/ui/components/app/snaps/snap-ui-renderer/components/banner.ts @@ -0,0 +1,20 @@ +import { BannerElement, JSXElement } from '@metamask/snaps-sdk/jsx'; +import { getJsxChildren } from '@metamask/snaps-utils'; +import { mapToTemplate } from '../utils'; +import { UIComponentFactory } from './types'; + +export const banner: UIComponentFactory = ({ + element, + ...params +}) => { + return { + element: 'SnapUIBanner', + children: getJsxChildren(element).map((children) => + mapToTemplate({ element: children as JSXElement, ...params }), + ), + props: { + title: element.props.title, + severity: element.props.severity, + }, + }; +}; diff --git a/ui/components/app/snaps/snap-ui-renderer/components/button.ts b/ui/components/app/snaps/snap-ui-renderer/components/button.ts index f624ffb23195..4b0cdb808e79 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/button.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/button.ts @@ -2,6 +2,7 @@ import { ButtonElement, JSXElement } from '@metamask/snaps-sdk/jsx'; import { getJsxChildren } from '@metamask/snaps-utils'; import { NonEmptyArray } from '@metamask/utils'; import { mapTextToTemplate } from '../utils'; +import { TextVariant } from '../../../../../helpers/constants/design-system'; import { UIComponentFactory } from './types'; export const button: UIComponentFactory = ({ @@ -15,6 +16,9 @@ export const button: UIComponentFactory = ({ variant: element.props.variant, name: element.props.name, disabled: element.props.disabled, + loading: element.props.loading, + textVariant: + element.props.size === 'sm' ? TextVariant.bodySm : TextVariant.bodyMd, }, children: mapTextToTemplate( getJsxChildren(element) as NonEmptyArray, diff --git a/ui/components/app/snaps/snap-ui-renderer/components/index.ts b/ui/components/app/snaps/snap-ui-renderer/components/index.ts index 17a9b6aa37c1..f6173b7199b0 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/index.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/index.ts @@ -27,6 +27,7 @@ import { selector } from './selector'; import { icon } from './icon'; import { section } from './section'; import { avatar } from './avatar'; +import { banner } from './banner'; export const COMPONENT_MAPPING = { Box: box, @@ -58,4 +59,5 @@ export const COMPONENT_MAPPING = { Container: container, Selector: selector, Section: section, + Banner: banner, }; diff --git a/ui/components/app/snaps/snap-ui-renderer/components/text.ts b/ui/components/app/snaps/snap-ui-renderer/components/text.ts index fe9194817ca3..96bb6c520e48 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/text.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/text.ts @@ -6,32 +6,45 @@ import { TextVariant, OverflowWrap, TextColor, + FontWeight, } from '../../../../../helpers/constants/design-system'; import { UIComponentFactory } from './types'; +function getTextColor(color: TextElement['props']['color']) { + switch (color) { + case 'default': + return TextColor.textDefault; + case 'alternative': + return TextColor.textAlternative; + case 'muted': + return TextColor.textMuted; + case 'error': + return TextColor.errorDefault; + case 'success': + return TextColor.successDefault; + case 'warning': + return TextColor.warningDefault; + default: + return TextColor.inherit; + } +} + +function getFontWeight(color: TextElement['props']['fontWeight']) { + switch (color) { + case 'bold': + return FontWeight.Bold; + case 'medium': + return FontWeight.Medium; + case 'regular': + default: + return FontWeight.Normal; + } +} + export const text: UIComponentFactory = ({ element, ...params }) => { - const getTextColor = () => { - switch (element.props.color) { - case 'default': - return TextColor.textDefault; - case 'alternative': - return TextColor.textAlternative; - case 'muted': - return TextColor.textMuted; - case 'error': - return TextColor.errorDefault; - case 'success': - return TextColor.successDefault; - case 'warning': - return TextColor.warningDefault; - default: - return TextColor.inherit; - } - }; - return { element: 'Text', children: mapTextToTemplate( @@ -41,8 +54,9 @@ export const text: UIComponentFactory = ({ props: { variant: element.props.size === 'sm' ? TextVariant.bodySm : TextVariant.bodyMd, + fontWeight: getFontWeight(element.props.fontWeight), overflowWrap: OverflowWrap.Anywhere, - color: getTextColor(), + color: getTextColor(element.props.color), className: 'snap-ui-renderer__text', textAlign: element.props.alignment, }, diff --git a/ui/components/app/snaps/snap-ui-renderer/index.scss b/ui/components/app/snaps/snap-ui-renderer/index.scss index d32edf726479..1b027890b247 100644 --- a/ui/components/app/snaps/snap-ui-renderer/index.scss +++ b/ui/components/app/snaps/snap-ui-renderer/index.scss @@ -12,14 +12,10 @@ &__container { & > *:first-child { gap: 16px; - padding: 16px; + margin: 16px; } } - &__error { - margin-top: 0 !important; - } - &__spinner { width: 30px; } diff --git a/ui/components/app/tab-bar/index.scss b/ui/components/app/tab-bar/index.scss index ce149f922879..6efdbab6c51e 100644 --- a/ui/components/app/tab-bar/index.scss +++ b/ui/components/app/tab-bar/index.scss @@ -38,11 +38,16 @@ display: flex; align-items: center; position: relative; + overflow: hidden; width: 100%; &__title { @include design-system.H4; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + @include design-system.screen-sm-min { @include design-system.H6; } diff --git a/ui/components/app/transaction-list-item-details/index.scss b/ui/components/app/transaction-list-item-details/index.scss index 13adb780fa4d..34d4af1ea82e 100644 --- a/ui/components/app/transaction-list-item-details/index.scss +++ b/ui/components/app/transaction-list-item-details/index.scss @@ -49,6 +49,8 @@ display: flex; flex-direction: column; align-items: flex-end; + height: 42px; + justify-content: space-between; .btn-link { font-size: 12px; @@ -62,9 +64,10 @@ } &__operations { - margin: 0 0 16px 16px; + margin: 0 16px 16px 16px; display: flex; - justify-content: flex-end; + justify-content: space-between; + align-items: center; } &__header { diff --git a/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js index 226a2a9113c0..f614a4dbc250 100644 --- a/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js +++ b/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js @@ -13,9 +13,19 @@ import Tooltip from '../../ui/tooltip'; import CancelButton from '../cancel-button'; import Popover from '../../ui/popover'; import { Box } from '../../component-library/box'; +import { Text } from '../../component-library/text'; +import { + BannerAlert, + BannerAlertSeverity, +} from '../../component-library/banner-alert'; +import { + TextVariant, + ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) + IconColor, + ///: END:ONLY_INCLUDE_IF +} from '../../../helpers/constants/design-system'; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) -import { Icon, IconName, Text } from '../../component-library'; -import { IconColor } from '../../../helpers/constants/design-system'; +import { Icon, IconName } from '../../component-library'; ///: END:ONLY_INCLUDE_IF import { SECOND } from '../../../../shared/constants/time'; import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics'; @@ -55,6 +65,7 @@ export default class TransactionListItemDetails extends PureComponent { recipientNickname: PropTypes.string, transactionStatus: PropTypes.func, isCustomNetwork: PropTypes.bool, + showErrorBanner: PropTypes.bool, history: PropTypes.object, blockExplorerLinkText: PropTypes.object, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) @@ -204,6 +215,7 @@ export default class TransactionListItemDetails extends PureComponent { onClose, recipientNickname, showCancel, + showErrorBanner, transactionStatus: TransactionStatus, blockExplorerLinkText, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) @@ -220,6 +232,17 @@ export default class TransactionListItemDetails extends PureComponent {
+ {showErrorBanner && ( + + + {t('transactionFailedBannerMessage')} + + + )}
{showSpeedUp && ( )} @@ -258,22 +281,18 @@ export default class TransactionListItemDetails extends PureComponent { data-testid="transaction-list-item-details-tx-status" >
{t('status')}
-
- -
+
-
- -
+
{ +const render = (overrideProps) => { const rpcPrefs = { blockExplorerUrl: 'https://customblockexplorer.com/', }; @@ -71,7 +71,7 @@ const render = async (overrideProps) => { senderAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', tryReverseResolveAddress: jest.fn(), transactionGroup, - transactionStatus: () =>
, + transactionStatus: () =>
, blockExplorerLinkText, rpcPrefs, ...overrideProps, @@ -79,59 +79,57 @@ const render = async (overrideProps) => { const mockStore = configureMockStore([thunk])(mockState); - let result; - - await act( - async () => - (result = renderWithProvider( - , - mockStore, - )), + const result = renderWithProvider( + , + mockStore, ); return result; }; describe('TransactionListItemDetails Component', () => { - it('should render title with title prop', async () => { - const { queryByText } = await render(); + describe('matches snapshot', () => { + it('for non-error details', async () => { + const { queryByText, queryByTestId } = render(); + expect(queryByText('Test Transaction Details')).toBeInTheDocument(); + expect( + queryByTestId('transaction-list-item-details-banner-error-message'), + ).not.toBeInTheDocument(); + }); - await waitFor(() => { + it('for error details', async () => { + const { queryByText, queryByTestId } = render({ showErrorBanner: true }); expect(queryByText('Test Transaction Details')).toBeInTheDocument(); + expect( + queryByTestId('transaction-list-item-details-banner-error-message'), + ).toBeInTheDocument(); }); }); - describe('Retry button', () => { - it('should render retry button with showRetry prop', async () => { - const { queryByTestId } = await render({ showRetry: true }); - + describe('Action buttons', () => { + it('renders retry button with showRetry prop', async () => { + const { queryByTestId } = render({ showRetry: true }); expect(queryByTestId('rety-button')).toBeInTheDocument(); }); - }); - - describe('Cancel button', () => { - it('should render cancel button with showCancel prop', async () => { - const { queryByTestId } = await render({ showCancel: true }); + it('renders cancel button with showCancel prop', async () => { + const { queryByTestId } = render({ showCancel: true }); expect(queryByTestId('cancel-button')).toBeInTheDocument(); }); - }); - - describe('Speedup button', () => { - it('should render speedup button with showSpeedUp prop', async () => { - const { queryByTestId } = await render({ showSpeedUp: true }); + it('renders speedup button with showSpeedUp prop', async () => { + const { queryByTestId } = render({ showSpeedUp: true }); expect(queryByTestId('speedup-button')).toBeInTheDocument(); }); }); describe('Institutional', () => { - it('should render correctly if custodyTransactionDeepLink has a url', async () => { + it('renders correctly if custodyTransactionDeepLink has a url', async () => { mockGetCustodianTransactionDeepLink = jest .fn() .mockReturnValue({ url: 'https://url.com' }); - await render({ showCancel: true }); + render({ showCancel: true }); await waitFor(() => { const custodianViewButton = document.querySelector( @@ -143,7 +141,7 @@ describe('TransactionListItemDetails Component', () => { }); }); - it('should render correctly if transactionNote is provided', async () => { + it('renders correctly if transactionNote is provided', async () => { const newTransaction = { ...transaction, metadata: { @@ -159,13 +157,11 @@ describe('TransactionListItemDetails Component', () => { initialTransaction: newTransaction, }; - const { queryByText } = await render({ + const { queryByText } = render({ transactionGroup: newTransactionGroup, }); - await waitFor(() => { - expect(queryByText('some note')).toBeInTheDocument(); - }); + expect(queryByText('some note')).toBeInTheDocument(); }); }); }); diff --git a/ui/components/app/transaction-list-item/smart-transaction-list-item.component.js b/ui/components/app/transaction-list-item/smart-transaction-list-item.component.js index 5c1658918dbe..547f2e460497 100644 --- a/ui/components/app/transaction-list-item/smart-transaction-list-item.component.js +++ b/ui/components/app/transaction-list-item/smart-transaction-list-item.component.js @@ -125,6 +125,7 @@ export default function SmartTransactionListItem({ date={date} status={displayedStatusKey} statusOnly + shouldShowTooltip={false} /> )} /> diff --git a/ui/components/app/transaction-list-item/transaction-list-item.component.js b/ui/components/app/transaction-list-item/transaction-list-item.component.js index 09e8983b9196..5dc8cf48ef35 100644 --- a/ui/components/app/transaction-list-item/transaction-list-item.component.js +++ b/ui/components/app/transaction-list-item/transaction-list-item.component.js @@ -459,6 +459,7 @@ function TransactionListItemInner({ !hasCancelled && !isBridgeTx } + showErrorBanner={Boolean(error)} transactionStatus={() => ( )} - {completedTransactions.length > 0 ? ( - completedTransactions - .map(removeIncomingTxsButToAnotherAddress) - .map(removeTxGroupsWithNoTx) - .filter(dateGroupsWithTransactionGroups) - .slice(0, limit) - .map((dateGroup) => { - return dateGroup.transactionGroups.map( - (transactionGroup, index) => { - return ( - - {renderDateStamp(index, dateGroup)} - {transactionGroup.initialTransaction - ?.isSmartTransaction ? ( - - ) : ( - - )} - - ); - }, - ); - }) - ) : ( - - - {isChainIdMismatch - ? noTransactionsMessage - : t('noTransactions')} - - - )} + {completedTransactions.length > 0 + ? completedTransactions + .map(removeIncomingTxsButToAnotherAddress) + .map(removeTxGroupsWithNoTx) + .filter(dateGroupsWithTransactionGroups) + .slice(0, limit) + .map((dateGroup) => { + return dateGroup.transactionGroups.map( + (transactionGroup, index) => { + return ( + + {renderDateStamp(index, dateGroup)} + {transactionGroup.initialTransaction + ?.isSmartTransaction ? ( + + ) : ( + + )} + + ); + }, + ); + }) + : null} {completedTransactions.length > limit && (
@@ -1327,7 +1327,7 @@ exports[`SendPageYourAccounts render renders correctly 1`] = ` style="bottom: -1px; right: 2px;" >
@@ -1639,7 +1639,7 @@ exports[`SendPageYourAccounts render renders correctly 1`] = ` style="bottom: -1px; right: 2px;" >
diff --git a/ui/components/multichain/token-list-item/__snapshots__/token-list-item.test.tsx.snap b/ui/components/multichain/token-list-item/__snapshots__/token-list-item.test.tsx.snap index 2359a5d9a5a0..e0efd4cd3fe6 100644 --- a/ui/components/multichain/token-list-item/__snapshots__/token-list-item.test.tsx.snap +++ b/ui/components/multichain/token-list-item/__snapshots__/token-list-item.test.tsx.snap @@ -24,7 +24,7 @@ exports[`TokenListItem handles clicking staking opens tab 1`] = ` class="mm-box mm-badge-wrapper__badge-container mm-badge-wrapper__badge-container--rectangular-bottom-right" >
network logo
?
@@ -191,7 +191,7 @@ exports[`TokenListItem should display warning scam modal fallback when safechain class="mm-box mm-badge-wrapper__badge-container mm-badge-wrapper__badge-container--rectangular-bottom-right" >
?
@@ -270,7 +270,7 @@ exports[`TokenListItem should render correctly 1`] = ` class="mm-box mm-badge-wrapper__badge-container mm-badge-wrapper__badge-container--rectangular-bottom-right" >
network logo
?
@@ -403,7 +403,7 @@ exports[`TokenListItem should render crypto balance with warning scam 1`] = ` class="mm-box mm-badge-wrapper__badge-container mm-badge-wrapper__badge-container--rectangular-bottom-right" >
?
diff --git a/ui/components/multichain/token-list-item/token-list-item.tsx b/ui/components/multichain/token-list-item/token-list-item.tsx index 40b91a001f17..d5ce348e0297 100644 --- a/ui/components/multichain/token-list-item/token-list-item.tsx +++ b/ui/components/multichain/token-list-item/token-list-item.tsx @@ -40,7 +40,6 @@ import { } from '../../component-library'; import { getMetaMetricsId, - getTestNetworkBackgroundColor, getParticipateInMetaMetrics, getDataCollectionForMarketing, getMarketData, @@ -228,7 +227,6 @@ export const TokenListItem = ({ ); // Used for badge icon const allNetworks = useSelector(getNetworkConfigurationsByChainId); - const testNetworkBackgroundColor = useSelector(getTestNetworkBackgroundColor); return ( } diff --git a/ui/components/ui/unit-input/unit-input.component.js b/ui/components/ui/unit-input/unit-input.component.js index 727a33e98c4a..3aaa0c5c315c 100644 --- a/ui/components/ui/unit-input/unit-input.component.js +++ b/ui/components/ui/unit-input/unit-input.component.js @@ -95,7 +95,7 @@ export default class UnitInput extends PureComponent { } this.props.onBlur && this.props.onBlur(value); - this.unitInput.scrollTo && this.unitInput.scrollTo(0, 0); + this.unitInput?.scrollTo?.(0, 0); }; handleChange = (event) => { diff --git a/ui/helpers/constants/routes.ts b/ui/helpers/constants/routes.ts index da12a52be812..ac21c32a2b1b 100644 --- a/ui/helpers/constants/routes.ts +++ b/ui/helpers/constants/routes.ts @@ -62,6 +62,9 @@ PATH_NAME_MAP[CONTACT_ADD_ROUTE] = 'Add Contact Settings Page'; export const CONTACT_VIEW_ROUTE = '/settings/contact-list/view-contact'; PATH_NAME_MAP[`${CONTACT_VIEW_ROUTE}/:address`] = 'View Contact Settings Page'; +export const SNAP_SETTINGS_ROUTE = '/settings/snap'; +PATH_NAME_MAP[`${SNAP_SETTINGS_ROUTE}/:snapId`] = 'Snap Settings Page'; + export const REVEAL_SEED_ROUTE = '/seed'; PATH_NAME_MAP[REVEAL_SEED_ROUTE] = 'Reveal Secret Recovery Phrase Page'; diff --git a/ui/helpers/constants/settings.js b/ui/helpers/constants/settings.js index 232bcdae5aff..adb3102da571 100644 --- a/ui/helpers/constants/settings.js +++ b/ui/helpers/constants/settings.js @@ -212,7 +212,7 @@ const SETTINGS_CONSTANTS = [ { tabMessage: (t) => t('securityAndPrivacy'), sectionMessage: (t) => t('use4ByteResolution'), - descriptionMessage: (t) => t('use4ByteResolutionDescription'), + descriptionMessage: (t) => t('toggleDecodeDescription'), route: `${SECURITY_ROUTE}#decode-smart-contracts`, icon: 'fa fa-lock', }, diff --git a/ui/helpers/utils/snaps.js b/ui/helpers/utils/snaps.js deleted file mode 100644 index 8a57ce516cf2..000000000000 --- a/ui/helpers/utils/snaps.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Check if the given value is a valid snap ID. - * - * NOTE: This function is a duplicate oF a yet to be released version in @metamask/snaps-utils. - * - * @param value - The value to check. - * @returns `true` if the value is a valid snap ID, and `false` otherwise. - */ -export function isSnapId(value) { - return ( - (typeof value === 'string' || value instanceof String) && - (value.startsWith('local:') || value.startsWith('npm:')) - ); -} diff --git a/ui/helpers/utils/snaps.ts b/ui/helpers/utils/snaps.ts new file mode 100644 index 000000000000..c788393e83ab --- /dev/null +++ b/ui/helpers/utils/snaps.ts @@ -0,0 +1,40 @@ +import { SnapId } from '@metamask/snaps-sdk'; +import { isProduction } from '../../../shared/modules/environment'; + +/** + * Check if the given value is a valid snap ID. + * + * NOTE: This function is a duplicate oF a yet to be released version in @metamask/snaps-utils. + * + * @param value - The value to check. + * @returns `true` if the value is a valid snap ID, and `false` otherwise. + */ +export function isSnapId(value: unknown): value is SnapId { + return ( + (typeof value === 'string' || value instanceof String) && + (value.startsWith('local:') || value.startsWith('npm:')) + ); +} + +/** + * Decode a snap ID fron a pathname. + * + * @param pathname - The pathname to decode the snap ID from. + * @returns The decoded snap ID, or `undefined` if the snap ID could not be decoded. + */ +export const decodeSnapIdFromPathname = (pathname: string) => { + const snapIdURI = pathname?.match(/[^/]+$/u)?.[0]; + return snapIdURI && decodeURIComponent(snapIdURI); +}; + +const IGNORED_EXAMPLE_SNAPS = ['npm:@metamask/preinstalled-example-snap']; + +/** + * Check if the given snap ID is ignored in production. + * + * @param snapId - The snap ID to check. + * @returns `true` if the snap ID is ignored in production, and `false` otherwise. + */ +export const isSnapIgnoredInProd = (snapId: string) => { + return isProduction() ? IGNORED_EXAMPLE_SNAPS.includes(snapId) : false; +}; diff --git a/ui/hooks/snaps/useSnapSettings.ts b/ui/hooks/snaps/useSnapSettings.ts new file mode 100644 index 000000000000..ad2debc8cac2 --- /dev/null +++ b/ui/hooks/snaps/useSnapSettings.ts @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { + forceUpdateMetamaskState, + handleSnapRequest, +} from '../../store/actions'; + +export function useSnapSettings({ snapId }: { snapId?: string }) { + const dispatch = useDispatch(); + const [loading, setLoading] = useState(true); + const [data, setData] = useState<{ id: string } | undefined>(undefined); + const [error, setError] = useState(undefined); + + useEffect(() => { + let cancelled = false; + async function fetchPage(id: string) { + try { + setError(undefined); + setLoading(true); + + const newData = (await handleSnapRequest({ + snapId: id, + origin: '', + handler: 'onSettingsPage', + request: { + jsonrpc: '2.0', + method: ' ', + }, + })) as { id: string }; + if (!cancelled) { + setData(newData); + forceUpdateMetamaskState(dispatch); + } + } catch (err) { + if (!cancelled) { + setError(err as Error); + } + } finally { + if (!cancelled) { + setLoading(false); + } + } + } + + if (snapId) { + fetchPage(snapId); + } + + return () => { + cancelled = true; + }; + }, [snapId]); + + return { data, error, loading }; +} diff --git a/ui/pages/asset/components/__snapshots__/asset-page.test.tsx.snap b/ui/pages/asset/components/__snapshots__/asset-page.test.tsx.snap index e4bc078a8c2b..2caa7f6eb5bb 100644 --- a/ui/pages/asset/components/__snapshots__/asset-page.test.tsx.snap +++ b/ui/pages/asset/components/__snapshots__/asset-page.test.tsx.snap @@ -198,7 +198,7 @@ exports[`AssetPage should render a native asset 1`] = ` class="mm-box mm-badge-wrapper__badge-container mm-badge-wrapper__badge-container--rectangular-bottom-right" >
static-logo
-
-
- You have no transactions -
-
-
+ />
@@ -539,7 +529,7 @@ exports[`AssetPage should render an ERC20 asset without prices 1`] = ` class="mm-box mm-badge-wrapper__badge-container mm-badge-wrapper__badge-container--rectangular-bottom-right" >
static-logo
-
-
- You have no transactions -
-
-
+ />
@@ -1068,7 +1048,7 @@ exports[`AssetPage should render an ERC20 token with prices 1`] = ` class="mm-box mm-badge-wrapper__badge-container mm-badge-wrapper__badge-container--rectangular-bottom-right" >
static-logo
-
-
- You have no transactions -
-
-
+ />
diff --git a/ui/pages/asset/components/asset-page.tsx b/ui/pages/asset/components/asset-page.tsx index ad3f54761798..e65e263d088c 100644 --- a/ui/pages/asset/components/asset-page.tsx +++ b/ui/pages/asset/components/asset-page.tsx @@ -467,9 +467,9 @@ const AssetPage = ({ {t('yourActivity')} {type === AssetType.native ? ( - + ) : ( - + )} diff --git a/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap index 04a9f5951226..8dfed1275f0a 100644 --- a/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap @@ -2,10 +2,208 @@ exports[`Info renders info section for approve request 1`] = `
+
+
+
+
+

+ Estimated changes +

+
+
+ +
+
+
+
+
+

+ You're giving someone else permission to withdraw NFTs from your account. +

+
+
+
+
+
+

+ Withdraw +

+
+
+
+
+
+

+ #0.0001 +

+
+
+
+ +

+ 0x07614...3ad68 +

+
+
+
+
+
+
+
+
+
+

+ Spender +

+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + +
+
+
+

+ 0x2e0D7...5d09B +

+
+
+
+
+
+
+
+

+ Permission for +

+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + +
+
+
+

+ 0x2e0D7...5d09B +

+
+
+
+
renders component for approve details 1`] = ` data-testid="confirmation__approve-details" >
-
-

+

+
- Data -

+ +
+
+
- - - - - - + + + + + +
+
+
+

+ 0x2e0D7...5d09B +

+
+
+
+
+
+
+
+

+ Request from +

+
+
- - - +
+
+
+

+ metamask.github.io +

+
@@ -83,70 +157,144 @@ exports[` renders component for approve details for setApprova data-testid="confirmation__approve-details" >
-
-

+

+
- Data -

+ +
+
+
- - - - - - + + + + + +
+
+
+

+ 0x2e0D7...5d09B +

+
+
+
+
+
+
+
+

+ Request from +

+
+
- - - +
+
+
+

+ metamask.github.io +

+
diff --git a/ui/pages/confirmations/components/confirm/info/approve/approve-details/approve-details.test.tsx b/ui/pages/confirmations/components/confirm/info/approve/approve-details/approve-details.test.tsx index daec14c166af..df6147914304 100644 --- a/ui/pages/confirmations/components/confirm/info/approve/approve-details/approve-details.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/approve/approve-details/approve-details.test.tsx @@ -1,15 +1,29 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import mockState from '../../../../../../../../test/data/mock-state.json'; import { renderWithConfirmContextProvider } from '../../../../../../../../test/lib/confirmations/render-helpers'; +import { genUnapprovedApproveConfirmation } from '../../../../../../../../test/data/confirmations/token-approve'; +import { getMockConfirmStateForTransaction } from '../../../../../../../../test/data/confirmations/helper'; import { ApproveDetails } from './approve-details'; +jest.mock( + '../../../../../../../components/app/alert-system/contexts/alertMetricsContext.tsx', + () => ({ + useAlertMetrics: () => ({ + trackInlineAlertClicked: jest.fn(), + trackAlertRender: jest.fn(), + trackAlertActionClicked: jest.fn(), + }), + }), +); + describe('', () => { const middleware = [thunk]; it('renders component for approve details', () => { - const state = mockState; + const state = getMockConfirmStateForTransaction( + genUnapprovedApproveConfirmation(), + ); const mockStore = configureMockStore(middleware)(state); const { container } = renderWithConfirmContextProvider( , @@ -19,7 +33,9 @@ describe('', () => { }); it('renders component for approve details for setApprovalForAll', () => { - const state = mockState; + const state = getMockConfirmStateForTransaction( + genUnapprovedApproveConfirmation(), + ); const mockStore = configureMockStore(middleware)(state); const { container } = renderWithConfirmContextProvider( , diff --git a/ui/pages/confirmations/components/confirm/info/approve/approve-details/approve-details.tsx b/ui/pages/confirmations/components/confirm/info/approve/approve-details/approve-details.tsx index e46b581220a4..1d552db45930 100644 --- a/ui/pages/confirmations/components/confirm/info/approve/approve-details/approve-details.tsx +++ b/ui/pages/confirmations/components/confirm/info/approve/approve-details/approve-details.tsx @@ -11,8 +11,6 @@ import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; import { useConfirmContext } from '../../../../../context/confirm'; import { selectConfirmationAdvancedDetailsOpen } from '../../../../../selectors/preferences'; import { SigningInWithRow } from '../../shared/sign-in-with-row/sign-in-with-row'; -import { useDecodedTransactionData } from '../../hooks/useDecodedTransactionData'; -import { Container } from '../../shared/transaction-data/transaction-data'; import { MethodDataRow, OriginRow, @@ -20,6 +18,7 @@ import { } from '../../shared/transaction-details/transaction-details'; import { getIsRevokeSetApprovalForAll } from '../../utils'; import { useIsNFT } from '../hooks/use-is-nft'; +import { useTokenTransactionData } from '../../hooks/useTokenTransactionData'; const Spender = ({ isSetApprovalForAll = false, @@ -32,23 +31,20 @@ const Spender = ({ useConfirmContext(); const { isNFT } = useIsNFT(transactionMeta); + const parsedTransactionData = useTokenTransactionData(); - const decodedResponse = useDecodedTransactionData(); - - const { value, pending } = decodedResponse; - - if (pending) { - return ; - } - - if (!value) { + if (!parsedTransactionData) { return null; } - const spender = value.data[0].params[0].value; + const spender = + parsedTransactionData.args?._spender ?? // ERC-20 - approve + parsedTransactionData.args?._operator ?? // ERC-721 - setApprovalForAll + parsedTransactionData.args?.spender; // Fiat Token V2 - increaseAllowance + const { chainId } = transactionMeta; - if (getIsRevokeSetApprovalForAll(value)) { + if (getIsRevokeSetApprovalForAll(parsedTransactionData)) { return null; } diff --git a/ui/pages/confirmations/components/confirm/info/approve/approve.test.tsx b/ui/pages/confirmations/components/confirm/info/approve/approve.test.tsx index f69671dccf9f..749f70a9ed08 100644 --- a/ui/pages/confirmations/components/confirm/info/approve/approve.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/approve/approve.test.tsx @@ -2,9 +2,10 @@ import { screen, waitFor } from '@testing-library/dom'; import React from 'react'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { getMockApproveConfirmState } from '../../../../../../../test/data/confirmations/helper'; +import { getMockConfirmStateForTransaction } from '../../../../../../../test/data/confirmations/helper'; import { renderWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers'; import { useAssetDetails } from '../../../../hooks/useAssetDetails'; +import { genUnapprovedApproveConfirmation } from '../../../../../../../test/data/confirmations/token-approve'; import ApproveInfo from './approve'; jest.mock('../../../../../../store/actions', () => ({ @@ -82,7 +83,9 @@ describe('', () => { }); it('renders component for approve request', async () => { - const state = getMockApproveConfirmState(); + const state = getMockConfirmStateForTransaction( + genUnapprovedApproveConfirmation(), + ); const mockStore = configureMockStore(middleware)(state); diff --git a/ui/pages/confirmations/components/confirm/info/approve/hooks/use-approve-token-simulation.test.ts b/ui/pages/confirmations/components/confirm/info/approve/hooks/use-approve-token-simulation.test.ts index e0a5a8165dfb..26ca44587ce4 100644 --- a/ui/pages/confirmations/components/confirm/info/approve/hooks/use-approve-token-simulation.test.ts +++ b/ui/pages/confirmations/components/confirm/info/approve/hooks/use-approve-token-simulation.test.ts @@ -1,11 +1,7 @@ import { TransactionMeta } from '@metamask/transaction-controller'; -import { - CONTRACT_INTERACTION_SENDER_ADDRESS, - genUnapprovedContractInteractionConfirmation, -} from '../../../../../../../../test/data/confirmations/contract-interaction'; -import mockState from '../../../../../../../../test/data/mock-state.json'; -import { renderHookWithProvider } from '../../../../../../../../test/lib/render-helpers'; -import { useDecodedTransactionData } from '../../hooks/useDecodedTransactionData'; +import { renderHookWithConfirmContextProvider } from '../../../../../../../../test/lib/confirmations/render-helpers'; +import { genUnapprovedApproveConfirmation } from '../../../../../../../../test/data/confirmations/token-approve'; +import { getMockConfirmStateForTransaction } from '../../../../../../../../test/data/confirmations/helper'; import { useApproveTokenSimulation } from './use-approve-token-simulation'; import { useIsNFT } from './use-is-nft'; @@ -14,11 +10,6 @@ jest.mock('./use-is-nft', () => ({ useIsNFT: jest.fn(), })); -jest.mock('../../hooks/useDecodedTransactionData', () => ({ - ...jest.requireActual('../../hooks/useDecodedTransactionData'), - useDecodedTransactionData: jest.fn(), -})); - describe('useApproveTokenSimulation', () => { beforeEach(() => { jest.resetAllMocks(); @@ -27,40 +18,16 @@ describe('useApproveTokenSimulation', () => { it('returns the token id for NFT', async () => { const useIsNFTMock = jest.fn().mockImplementation(() => ({ isNFT: true })); - const useDecodedTransactionDataMock = jest.fn().mockImplementation(() => ({ - pending: false, - value: { - data: [ - { - name: 'approve', - params: [ - { - type: 'address', - value: '0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4', - }, - { - type: 'uint256', - value: 70000, - }, - ], - }, - ], - source: 'FourByte', - }, - })); - (useIsNFT as jest.Mock).mockImplementation(useIsNFTMock); - (useDecodedTransactionData as jest.Mock).mockImplementation( - useDecodedTransactionDataMock, - ); - const transactionMeta = genUnapprovedContractInteractionConfirmation({ - address: CONTRACT_INTERACTION_SENDER_ADDRESS, + const transactionMeta = genUnapprovedApproveConfirmation({ + amountHex: + '0000000000000000000000000000000000000000000000000000000000011170', }) as TransactionMeta; - const { result } = renderHookWithProvider( + const { result } = renderHookWithConfirmContextProvider( () => useApproveTokenSimulation(transactionMeta, '4'), - mockState, + getMockConfirmStateForTransaction(transactionMeta), ); expect(result.current).toMatchInlineSnapshot(` @@ -70,22 +37,8 @@ describe('useApproveTokenSimulation', () => { "pending": undefined, "spendingCap": "#7", "value": { - "data": [ - { - "name": "approve", - "params": [ - { - "type": "address", - "value": "0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4", - }, - { - "type": "uint256", - "value": 70000, - }, - ], - }, - ], - "source": "FourByte", + "hex": "0x011170", + "type": "BigNumber", }, } `); @@ -94,40 +47,16 @@ describe('useApproveTokenSimulation', () => { it('returns "UNLIMITED MESSAGE" token amount for fungible tokens approvals equal or over the total number of tokens in circulation', async () => { const useIsNFTMock = jest.fn().mockImplementation(() => ({ isNFT: false })); - const useDecodedTransactionDataMock = jest.fn().mockImplementation(() => ({ - pending: false, - value: { - data: [ - { - name: 'approve', - params: [ - { - type: 'address', - value: '0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4', - }, - { - type: 'uint256', - value: 10 ** 15, - }, - ], - }, - ], - source: 'FourByte', - }, - })); - (useIsNFT as jest.Mock).mockImplementation(useIsNFTMock); - (useDecodedTransactionData as jest.Mock).mockImplementation( - useDecodedTransactionDataMock, - ); - const transactionMeta = genUnapprovedContractInteractionConfirmation({ - address: CONTRACT_INTERACTION_SENDER_ADDRESS, + const transactionMeta = genUnapprovedApproveConfirmation({ + amountHex: + '00000000000000000000000000000000000000000000000000038D7EA4C68000', }) as TransactionMeta; - const { result } = renderHookWithProvider( + const { result } = renderHookWithConfirmContextProvider( () => useApproveTokenSimulation(transactionMeta, '0'), - mockState, + getMockConfirmStateForTransaction(transactionMeta), ); expect(result.current).toMatchInlineSnapshot(` @@ -137,22 +66,8 @@ describe('useApproveTokenSimulation', () => { "pending": undefined, "spendingCap": "1000000000000000", "value": { - "data": [ - { - "name": "approve", - "params": [ - { - "type": "address", - "value": "0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4", - }, - { - "type": "uint256", - "value": 1000000000000000, - }, - ], - }, - ], - "source": "FourByte", + "hex": "0x038d7ea4c68000", + "type": "BigNumber", }, } `); @@ -161,40 +76,14 @@ describe('useApproveTokenSimulation', () => { it('returns correct small decimal number token amount for fungible tokens', async () => { const useIsNFTMock = jest.fn().mockImplementation(() => ({ isNFT: false })); - const useDecodedTransactionDataMock = jest.fn().mockImplementation(() => ({ - pending: false, - value: { - data: [ - { - name: 'approve', - params: [ - { - type: 'address', - value: '0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4', - }, - { - type: 'uint256', - value: 10 ** 5, - }, - ], - }, - ], - source: 'FourByte', - }, - })); - (useIsNFT as jest.Mock).mockImplementation(useIsNFTMock); - (useDecodedTransactionData as jest.Mock).mockImplementation( - useDecodedTransactionDataMock, - ); - const transactionMeta = genUnapprovedContractInteractionConfirmation({ - address: CONTRACT_INTERACTION_SENDER_ADDRESS, - }) as TransactionMeta; + const transactionMeta = + genUnapprovedApproveConfirmation() as TransactionMeta; - const { result } = renderHookWithProvider( + const { result } = renderHookWithConfirmContextProvider( () => useApproveTokenSimulation(transactionMeta, '18'), - mockState, + getMockConfirmStateForTransaction(transactionMeta), ); expect(result.current).toMatchInlineSnapshot(` @@ -202,24 +91,10 @@ describe('useApproveTokenSimulation', () => { "formattedSpendingCap": "<0.000001", "isUnlimitedSpendingCap": false, "pending": undefined, - "spendingCap": "0.0000000000001", + "spendingCap": "0.000000000000000001", "value": { - "data": [ - { - "name": "approve", - "params": [ - { - "type": "address", - "value": "0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4", - }, - { - "type": "uint256", - "value": 100000, - }, - ], - }, - ], - "source": "FourByte", + "hex": "0x01", + "type": "BigNumber", }, } `); diff --git a/ui/pages/confirmations/components/confirm/info/approve/hooks/use-approve-token-simulation.ts b/ui/pages/confirmations/components/confirm/info/approve/hooks/use-approve-token-simulation.ts index b8605da19f54..7548919daeaf 100644 --- a/ui/pages/confirmations/components/confirm/info/approve/hooks/use-approve-token-simulation.ts +++ b/ui/pages/confirmations/components/confirm/info/approve/hooks/use-approve-token-simulation.ts @@ -1,14 +1,12 @@ import { TransactionMeta } from '@metamask/transaction-controller'; -import { isHexString } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; -import { isBoolean } from 'lodash'; import { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { calcTokenAmount } from '../../../../../../../../shared/lib/transactions-controller-utils'; import { getIntlLocale } from '../../../../../../../ducks/locale/locale'; import { formatAmount } from '../../../../simulation-details/formatAmount'; -import { useDecodedTransactionData } from '../../hooks/useDecodedTransactionData'; import { TOKEN_VALUE_UNLIMITED_THRESHOLD } from '../../shared/constants'; +import { useTokenTransactionData } from '../../hooks/useTokenTransactionData'; import { useIsNFT } from './use-is-nft'; function isSpendingCapUnlimited(decodedSpendingCap: number) { @@ -21,30 +19,18 @@ export const useApproveTokenSimulation = ( ) => { const locale = useSelector(getIntlLocale); const { isNFT, pending: isNFTPending } = useIsNFT(transactionMeta); - const decodedResponse = useDecodedTransactionData(); - const { value, pending } = decodedResponse; + const { args: parsedArgs } = useTokenTransactionData() ?? {}; - const decodedSpendingCap = useMemo(() => { - if (!value) { - return '0'; - } + const parsedValue = + parsedArgs?._value ?? // ERC-20 - approve + parsedArgs?.increment; // Fiat Token V2 - increaseAllowance - const paramIndex = value.data[0].params.findIndex( - (param) => - param.value !== undefined && - !isHexString(param.value) && - param.value.length === undefined && - !isBoolean(param.value), - ); - if (paramIndex === -1) { - return '0'; - } + const value = parsedValue ?? new BigNumber(0); - return calcTokenAmount( - value.data[0].params[paramIndex].value, - Number(decimals || '0'), - ).toFixed(); - }, [value, decimals]); + const decodedSpendingCap = calcTokenAmount( + value, + Number(decimals ?? '0'), + ).toFixed(); const tokenPrefix = isNFT ? '#' : ''; @@ -69,6 +55,6 @@ export const useApproveTokenSimulation = ( spendingCap, formattedSpendingCap, value, - pending: pending || isNFTPending, + pending: isNFTPending, }; }; diff --git a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts index f7f9dd25d4b8..43be106de1f2 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.test.ts @@ -1,31 +1,23 @@ import { TransactionMeta } from '@metamask/transaction-controller'; import { Numeric } from '../../../../../../../shared/modules/Numeric'; -import { genUnapprovedTokenTransferConfirmation } from '../../../../../../../test/data/confirmations/token-transfer'; -import mockState from '../../../../../../../test/data/mock-state.json'; import { renderHookWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers'; import useTokenExchangeRate from '../../../../../../components/app/currency-input/hooks/useTokenExchangeRate'; import { useAssetDetails } from '../../../../hooks/useAssetDetails'; +import { getMockConfirmStateForTransaction } from '../../../../../../../test/data/confirmations/helper'; +import { genUnapprovedTokenTransferConfirmation } from '../../../../../../../test/data/confirmations/token-transfer'; import { useTokenValues } from './use-token-values'; -import { useDecodedTransactionData } from './useDecodedTransactionData'; jest.mock('../../../../hooks/useAssetDetails', () => ({ ...jest.requireActual('../../../../hooks/useAssetDetails'), useAssetDetails: jest.fn(), })); -jest.mock('./useDecodedTransactionData', () => ({ - ...jest.requireActual('./useDecodedTransactionData'), - useDecodedTransactionData: jest.fn(), -})); - jest.mock( '../../../../../../components/app/currency-input/hooks/useTokenExchangeRate', - () => jest.fn(), ); describe('useTokenValues', () => { const useAssetDetailsMock = jest.mocked(useAssetDetails); - const useDecodedTransactionDataMock = jest.mocked(useDecodedTransactionData); const useTokenExchangeRateMock = jest.mocked(useTokenExchangeRate); beforeEach(() => { @@ -34,97 +26,51 @@ describe('useTokenValues', () => { it('returns native and fiat balances', async () => { (useAssetDetailsMock as jest.Mock).mockImplementation(() => ({ - decimals: '10', - })); - (useDecodedTransactionDataMock as jest.Mock).mockImplementation(() => ({ - pending: false, - value: { - data: [ - { - name: 'transfer', - params: [ - { - type: 'address', - value: '0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4', - }, - { - type: 'uint256', - value: 70000000000, - }, - ], - }, - ], - source: 'FourByte', - }, + decimals: '4', })); - (useTokenExchangeRateMock as jest.Mock).mockResolvedValue( - new Numeric(0.91, 10), - ); - const transactionMeta = genUnapprovedTokenTransferConfirmation( - {}, - ) as TransactionMeta; + useTokenExchangeRateMock.mockReturnValue(new Numeric(0.91, 10)); - const { result, waitForNextUpdate } = renderHookWithConfirmContextProvider( + const transactionMeta = genUnapprovedTokenTransferConfirmation({ + amountHex: + '0000000000000000000000000000000000000000000000000000000000011170', + }) as TransactionMeta; + + const { result } = renderHookWithConfirmContextProvider( () => useTokenValues(transactionMeta), - mockState, + getMockConfirmStateForTransaction(transactionMeta), ); - await waitForNextUpdate(); - expect(result.current).toEqual({ decodedTransferValue: '7', displayTransferValue: '7', fiatDisplayValue: '$6.37', fiatValue: 6.37, - pending: false, }); }); it('returns undefined fiat balance if no token rate is returned', async () => { (useAssetDetailsMock as jest.Mock).mockImplementation(() => ({ - decimals: '10', - })); - (useDecodedTransactionDataMock as jest.Mock).mockImplementation(() => ({ - pending: false, - value: { - data: [ - { - name: 'transfer', - params: [ - { - type: 'address', - value: '0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4', - }, - { - type: 'uint256', - value: 70000000000, - }, - ], - }, - ], - source: 'FourByte', - }, + decimals: '4', })); - (useTokenExchangeRateMock as jest.Mock).mockResolvedValue(null); - const transactionMeta = genUnapprovedTokenTransferConfirmation( - {}, - ) as TransactionMeta; + useTokenExchangeRateMock.mockReturnValue(undefined); - const { result, waitForNextUpdate } = renderHookWithConfirmContextProvider( + const transactionMeta = genUnapprovedTokenTransferConfirmation({ + amountHex: + '0000000000000000000000000000000000000000000000000000000000011170', + }) as TransactionMeta; + + const { result } = renderHookWithConfirmContextProvider( () => useTokenValues(transactionMeta), - mockState, + getMockConfirmStateForTransaction(transactionMeta), ); - await waitForNextUpdate(); - expect(result.current).toEqual({ decodedTransferValue: '7', displayTransferValue: '7', - fiatDisplayValue: null, - fiatValue: null, - pending: false, + fiatDisplayValue: undefined, + fiatValue: undefined, }); }); }); diff --git a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts index b53e2842e5e7..b38dfd2be8ce 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/use-token-values.ts @@ -1,19 +1,20 @@ import { TransactionMeta } from '@metamask/transaction-controller'; -import { isHexString } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; -import { isBoolean } from 'lodash'; -import { useMemo, useState } from 'react'; import { useSelector } from 'react-redux'; import { calcTokenAmount } from '../../../../../../../shared/lib/transactions-controller-utils'; -import { Numeric } from '../../../../../../../shared/modules/Numeric'; import useTokenExchangeRate from '../../../../../../components/app/currency-input/hooks/useTokenExchangeRate'; import { getIntlLocale } from '../../../../../../ducks/locale/locale'; import { useFiatFormatter } from '../../../../../../hooks/useFiatFormatter'; import { useAssetDetails } from '../../../../hooks/useAssetDetails'; import { formatAmount } from '../../../simulation-details/formatAmount'; -import { useDecodedTransactionData } from './useDecodedTransactionData'; +import { useTokenTransactionData } from './useTokenTransactionData'; export const useTokenValues = (transactionMeta: TransactionMeta) => { + const locale = useSelector(getIntlLocale); + const parsedTransactionData = useTokenTransactionData(); + const exchangeRate = useTokenExchangeRate(transactionMeta?.txParams?.to); + const fiatFormatter = useFiatFormatter(); + const { decimals } = useAssetDetails( transactionMeta.txParams.to, transactionMeta.txParams.from, @@ -21,65 +22,21 @@ export const useTokenValues = (transactionMeta: TransactionMeta) => { transactionMeta.chainId, ); - const decodedResponse = useDecodedTransactionData(); - const { value, pending } = decodedResponse; - - const { decodedTransferValue, isDecodedTransferValuePending } = - useMemo(() => { - if (!value) { - return { - decodedTransferValue: '0', - isDecodedTransferValuePending: false, - }; - } - - if (!decimals) { - return { - decodedTransferValue: '0', - isDecodedTransferValuePending: true, - }; - } - - const paramIndex = value.data[0].params.findIndex( - (param) => - param.value !== undefined && - !isHexString(param.value) && - param.value.length === undefined && - !isBoolean(param.value), - ); - if (paramIndex === -1) { - return { - decodedTransferValue: '0', - isDecodedTransferValuePending: false, - }; - } + const value = parsedTransactionData?.args?._value as BigNumber | undefined; - return { - decodedTransferValue: calcTokenAmount( - value.data[0].params[paramIndex].value, - decimals, - ).toFixed(), - isDecodedTransferValuePending: false, - }; - }, [value, decimals]); - - const [exchangeRate, setExchangeRate] = useState(); - const fetchExchangeRate = async () => { - const result = await useTokenExchangeRate(transactionMeta?.txParams?.to); - - setExchangeRate(result); - }; - fetchExchangeRate(); + const decodedTransferValue = + decimals !== undefined && value + ? calcTokenAmount(value, Number(decimals)).toFixed() + : '0'; const fiatValue = exchangeRate && decodedTransferValue && exchangeRate.times(decodedTransferValue, 10).toNumber(); - const fiatFormatter = useFiatFormatter(); + const fiatDisplayValue = fiatValue && fiatFormatter(fiatValue, { shorten: true }); - const locale = useSelector(getIntlLocale); const displayTransferValue = formatAmount( locale, new BigNumber(decodedTransferValue), @@ -90,6 +47,5 @@ export const useTokenValues = (transactionMeta: TransactionMeta) => { displayTransferValue, fiatDisplayValue, fiatValue, - pending: pending || isDecodedTransferValuePending, }; }; diff --git a/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.test.ts b/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.test.ts index 32a711abf754..c12ce7025cb5 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.test.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.test.ts @@ -76,6 +76,32 @@ describe('useDecodedTransactionData', () => { expect(result).toStrictEqual({ pending: false, value: undefined }); }); + it('returns undefined if decode disabled', async () => { + decodeTransactionDataMock.mockResolvedValue(TRANSACTION_DECODE_SOURCIFY); + + const result = await runHook( + getMockConfirmStateForTransaction( + { + id: '123', + chainId: CHAIN_ID_MOCK, + type: TransactionType.contractInteraction, + status: TransactionStatus.unapproved, + txParams: { + data: TRANSACTION_DATA_UNISWAP, + to: CONTRACT_ADDRESS_MOCK, + } as TransactionParams, + }, + { + metamask: { + use4ByteResolution: false, + }, + }, + ), + ); + + expect(result).toStrictEqual({ pending: false, value: undefined }); + }); + it('returns the decoded data', async () => { decodeTransactionDataMock.mockResolvedValue(TRANSACTION_DECODE_SOURCIFY); diff --git a/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.ts b/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.ts index 5276e02eaad1..3486f16ed864 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.ts @@ -1,6 +1,7 @@ import { Hex } from '@metamask/utils'; import { TransactionMeta } from '@metamask/transaction-controller'; +import { useSelector } from 'react-redux'; import { AsyncResult, useAsyncResult, @@ -9,11 +10,13 @@ import { decodeTransactionData } from '../../../../../../store/actions'; import { DecodedTransactionDataResponse } from '../../../../../../../shared/types/transaction-decode'; import { useConfirmContext } from '../../../../context/confirm'; import { hasTransactionData } from '../../../../../../../shared/modules/transaction.utils'; +import { use4ByteResolutionSelector } from '../../../../../../selectors'; export function useDecodedTransactionData( transactionTypeFilter?: string, ): AsyncResult { const { currentConfirmation } = useConfirmContext(); + const isDecodeEnabled = useSelector(use4ByteResolutionSelector); const currentTransactionType = currentConfirmation?.type; const chainId = currentConfirmation?.chainId as Hex; @@ -23,6 +26,7 @@ export function useDecodedTransactionData( return useAsyncResult(async () => { if ( + !isDecodeEnabled || !hasTransactionData(transactionData) || !transactionTo || (transactionTypeFilter && @@ -36,5 +40,11 @@ export function useDecodedTransactionData( chainId, contractAddress, }); - }, [transactionData, transactionTo, chainId, contractAddress]); + }, [ + isDecodeEnabled, + transactionData, + transactionTo, + chainId, + contractAddress, + ]); } diff --git a/ui/pages/confirmations/components/confirm/info/hooks/useTokenTransactionData.test.ts b/ui/pages/confirmations/components/confirm/info/hooks/useTokenTransactionData.test.ts new file mode 100644 index 000000000000..f73372d9391f --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/hooks/useTokenTransactionData.test.ts @@ -0,0 +1,94 @@ +import { Hex } from '@metamask/utils'; +import { TransactionDescription } from '@ethersproject/abi'; +import { genUnapprovedContractInteractionConfirmation } from '../../../../../../../test/data/confirmations/contract-interaction'; +import { getMockConfirmStateForTransaction } from '../../../../../../../test/data/confirmations/helper'; +import { + genUnapprovedTokenTransferConfirmation, + TRANSFER_FROM_TRANSACTION_DATA, +} from '../../../../../../../test/data/confirmations/token-transfer'; +import { renderHookWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers'; +import { genUnapprovedApproveConfirmation } from '../../../../../../../test/data/confirmations/token-approve'; +import { + genUnapprovedSetApprovalForAllConfirmation, + INCREASE_ALLOWANCE_TRANSACTION_DATA, +} from '../../../../../../../test/data/confirmations/set-approval-for-all'; +import { useTokenTransactionData } from './useTokenTransactionData'; + +function runHook(transactionData: string) { + const transaction = genUnapprovedContractInteractionConfirmation({ + txData: transactionData as Hex, + }); + + const state = getMockConfirmStateForTransaction(transaction); + + const { result } = renderHookWithConfirmContextProvider( + useTokenTransactionData, + state, + ); + + return result.current as TransactionDescription; +} + +describe('useTokenTransactionData', () => { + it('parses transfer transaction', () => { + const transactionData = + genUnapprovedTokenTransferConfirmation().txParams.data; + + const result = runHook(transactionData); + + expect(result.name).toBe('transfer'); + expect(result.args._to).toBe('0x2e0D7E8c45221FcA00d74a3609A0f7097035d09B'); + expect(result.args._value.toHexString()).toBe('0x01'); + }); + + it('parses transferFrom transaction', () => { + const result = runHook(TRANSFER_FROM_TRANSACTION_DATA); + + expect(result.name).toBe('transferFrom'); + expect(result.args._from).toBe( + '0x2e0D7E8c45221FcA00d74a3609A0f7097035d09B', + ); + expect(result.args._to).toBe('0x2e0d7E8c45221fCa00d74A3609A0F7097035D09c'); + expect(result.args._value.toHexString()).toEqual('0x0123'); + }); + + it('parses approve transaction', () => { + const transactionData = genUnapprovedApproveConfirmation().txParams.data; + + const result = runHook(transactionData); + + expect(result.name).toBe('approve'); + expect(result.args._spender).toBe( + '0x2e0D7E8c45221FcA00d74a3609A0f7097035d09B', + ); + expect(result.args._value.toHexString()).toBe('0x01'); + }); + + it('parses setApprovalForAll transaction', () => { + const transactionData = + genUnapprovedSetApprovalForAllConfirmation().txParams.data; + + const result = runHook(transactionData); + + expect(result.name).toBe('setApprovalForAll'); + expect(result.args._operator).toBe( + '0x2e0D7E8c45221FcA00d74a3609A0f7097035d09B', + ); + expect(result.args._approved).toBe(true); + }); + + it('parses increaseAllowance transaction', () => { + const result = runHook(INCREASE_ALLOWANCE_TRANSACTION_DATA); + + expect(result.name).toBe('increaseAllowance'); + expect(result.args.spender).toBe( + '0x2e0D7E8c45221FcA00d74a3609A0f7097035d09B', + ); + expect(result.args.increment.toHexString()).toBe('0x0123'); + }); + + it('returns undefined if no transaction data', () => { + const result = runHook(undefined as never); + expect(result).toBeUndefined(); + }); +}); diff --git a/ui/pages/confirmations/components/confirm/info/hooks/useTokenTransactionData.ts b/ui/pages/confirmations/components/confirm/info/hooks/useTokenTransactionData.ts new file mode 100644 index 000000000000..c7ca2af1c19c --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/hooks/useTokenTransactionData.ts @@ -0,0 +1,14 @@ +import { TransactionMeta } from '@metamask/transaction-controller'; +import { useConfirmContext } from '../../../../context/confirm'; +import { parseStandardTokenTransactionData } from '../../../../../../../shared/modules/transaction.utils'; + +export function useTokenTransactionData() { + const { currentConfirmation } = useConfirmContext(); + const transactionData = currentConfirmation?.txParams?.data; + + if (!transactionData) { + return undefined; + } + + return parseStandardTokenTransactionData(transactionData); +} diff --git a/ui/pages/confirmations/components/confirm/info/native-transfer/__snapshots__/native-transfer.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/native-transfer/__snapshots__/native-transfer.test.tsx.snap index 406fd66ae962..6ec41230cdab 100644 --- a/ui/pages/confirmations/components/confirm/info/native-transfer/__snapshots__/native-transfer.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/native-transfer/__snapshots__/native-transfer.test.tsx.snap @@ -16,6 +16,181 @@ exports[`NativeTransferInfo renders correctly 1`] = ` 0 ETH
+
+
+
+
+
+

+ From +

+
+
+
+
+
+
+
+
+ + + + + +
+
+
+

+ 0x2e0D7...5d09B +

+
+
+
+
+ +
+
+
+

+ To +

+
+
+
+
+
+
+
+
+ + + + + +
+
+
+

+ 0x2e0D7...5d09B +

+
+
+
+
+
+
diff --git a/ui/pages/confirmations/components/confirm/info/nft-token-transfer/__snapshots__/nft-token-transfer.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/nft-token-transfer/__snapshots__/nft-token-transfer.test.tsx.snap index 5ce090406606..3e9233e5c742 100644 --- a/ui/pages/confirmations/components/confirm/info/nft-token-transfer/__snapshots__/nft-token-transfer.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/nft-token-transfer/__snapshots__/nft-token-transfer.test.tsx.snap @@ -44,6 +44,181 @@ exports[`NFTTokenTransferInfo renders correctly 1`] = ` #undefined

+
+
+
+
+
+

+ From +

+
+
+
+
+
+
+
+
+ + + + + +
+
+
+

+ 0x2e0D7...5d09B +

+
+
+
+
+ +
+
+
+

+ To +

+
+
+
+
+
+
+
+
+ + + + + +
+
+
+

+ 0x2e0D7...5d09B +

+
+
+
+
+
+
diff --git a/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx b/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx index 3b5d1dfc3e62..336ef3f9f92f 100644 --- a/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx @@ -149,7 +149,7 @@ describe('PersonalSignInfo', () => { getMockPersonalSignConfirmStateForRequest(signatureRequestSIWE); (utils.isSIWESignatureRequest as jest.Mock).mockReturnValue(false); - (snapUtils.isSnapId as jest.Mock).mockReturnValue(true); + (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(true); const mockStore = configureMockStore([])(state); const { queryByText, getByText } = renderWithConfirmContextProvider( @@ -171,7 +171,7 @@ describe('PersonalSignInfo', () => { const state = getMockPersonalSignConfirmStateForRequest(signatureRequestSIWE); (utils.isSIWESignatureRequest as jest.Mock).mockReturnValue(false); - (snapUtils.isSnapId as jest.Mock).mockReturnValue(true); + (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(true); const mockStore = configureMockStore([])(state); const { getByText, queryByText } = renderWithConfirmContextProvider( diff --git a/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/__snapshots__/set-approval-for-all-info.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/__snapshots__/set-approval-for-all-info.test.tsx.snap index a3f8724e7561..034bb47c38cb 100644 --- a/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/__snapshots__/set-approval-for-all-info.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/__snapshots__/set-approval-for-all-info.test.tsx.snap @@ -110,6 +110,102 @@ exports[` renders component for approve request 1`] = ` class="mm-box mm-box--margin-bottom-4 mm-box--padding-2 mm-box--background-color-background-default mm-box--rounded-md" data-testid="confirmation__approve-details" > +
+
+
+

+ Permission for +

+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + +
+
+
+

+ 0x2e0D7...5d09B +

+
+
+
+
renders component for approve request 1`] = `

renders component for approve request 1`] = `

', () => { mockStore, ); - await waitFor(() => { - expect(screen.getByText('Data')).toBeInTheDocument(); - }); - expect(container).toMatchSnapshot(); }); diff --git a/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/set-approval-for-all-info.tsx b/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/set-approval-for-all-info.tsx index 6902a6da9b1f..e2371d09454d 100644 --- a/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/set-approval-for-all-info.tsx +++ b/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/set-approval-for-all-info.tsx @@ -2,11 +2,10 @@ import { TransactionMeta } from '@metamask/transaction-controller'; import React from 'react'; import { useConfirmContext } from '../../../../context/confirm'; import { ApproveDetails } from '../approve/approve-details/approve-details'; -import { useDecodedTransactionData } from '../hooks/useDecodedTransactionData'; import { AdvancedDetails } from '../shared/advanced-details/advanced-details'; -import { ConfirmLoader } from '../shared/confirm-loader/confirm-loader'; import { GasFeesSection } from '../shared/gas-fees-section/gas-fees-section'; import { getIsRevokeSetApprovalForAll } from '../utils'; +import { useTokenTransactionData } from '../hooks/useTokenTransactionData'; import { RevokeSetApprovalForAllStaticSimulation } from './revoke-set-approval-for-all-static-simulation/revoke-set-approval-for-all-static-simulation'; import { SetApprovalForAllStaticSimulation } from './set-approval-for-all-static-simulation/set-approval-for-all-static-simulation'; @@ -14,22 +13,18 @@ const SetApprovalForAllInfo = () => { const { currentConfirmation: transactionMeta } = useConfirmContext(); - const decodedResponse = useDecodedTransactionData(); + const parsedTransactionData = useTokenTransactionData(); - const { value, pending } = decodedResponse; + const spender = parsedTransactionData?.args?._operator; - const isRevokeSetApprovalForAll = getIsRevokeSetApprovalForAll(value); - - const spender = value?.data[0].params[0].value; + const isRevokeSetApprovalForAll = getIsRevokeSetApprovalForAll( + parsedTransactionData, + ); if (!transactionMeta?.txParams) { return null; } - if (pending) { - return ; - } - return ( <> {isRevokeSetApprovalForAll ? ( diff --git a/ui/pages/confirmations/components/confirm/info/shared/send-heading/__snapshots__/send-heading.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/shared/send-heading/__snapshots__/send-heading.test.tsx.snap index fdc069a399dc..a2dd153305ed 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/send-heading/__snapshots__/send-heading.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/shared/send-heading/__snapshots__/send-heading.test.tsx.snap @@ -3,47 +3,29 @@ exports[` renders component 1`] = `
- - - +
+
- - - - - - +

+ <0.000001 Unknown +

+
+
`; diff --git a/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.test.tsx b/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.test.tsx index 613930f9901d..40a9842c9491 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.test.tsx @@ -5,6 +5,12 @@ import { getMockTokenTransferConfirmState } from '../../../../../../../../test/d import { renderWithConfirmContextProvider } from '../../../../../../../../test/lib/confirmations/render-helpers'; import SendHeading from './send-heading'; +jest.mock('../../../../../hooks/useAssetDetails', () => ({ + useAssetDetails: jest.fn(() => ({ + decimals: 18, + })), +})); + describe('', () => { const middleware = [thunk]; const state = getMockTokenTransferConfirmState({}); diff --git a/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx b/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx index cbba12835073..4e7f86e0ac8b 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/send-heading/send-heading.tsx @@ -24,7 +24,6 @@ import { useConfirmContext } from '../../../../../context/confirm'; import { useTokenValues } from '../../hooks/use-token-values'; import { useSendingValueMetric } from '../../hooks/useSendingValueMetric'; import { useTokenDetails } from '../../hooks/useTokenDetails'; -import { ConfirmLoader } from '../confirm-loader/confirm-loader'; const SendHeading = () => { const t = useI18nContext(); @@ -36,7 +35,6 @@ const SendHeading = () => { displayTransferValue, fiatDisplayValue, fiatValue, - pending, } = useTokenValues(transactionMeta); type TestNetChainId = (typeof TEST_CHAINS)[number]; @@ -89,10 +87,6 @@ const SendHeading = () => { useSendingValueMetric({ transactionMeta, fiatValue }); - if (pending) { - return ; - } - return (
- - - +
+
- - - + <0.000001 Unknown + +
+
+
+
+
+
+
+
+

+ From +

+
+
+
+
+
+
+
+
+ + + + + +
+
+
+

+ 0x2e0D7...5d09B +

+
+
+
+
+ +
- - - +
+
+

+ To +

+
+
+
+
+
+
+
+
+ + + + + +
+
+
+

+ 0x2e0D7...5d09B +

+
+
+
+
+
({ }), })); +jest.mock('../../../../hooks/useAssetDetails', () => ({ + useAssetDetails: jest.fn(() => ({ + decimals: 18, + })), +})); + describe('TokenTransferInfo', () => { it('renders correctly', () => { const state = getMockTokenTransferConfirmState({}); - const mockStore = configureMockStore([])(state); + const mockStore = configureMockStore()(state); const { container } = renderWithConfirmContextProvider( , mockStore, diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.test.tsx b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.test.tsx index bdc6ed30678a..866dd3c2805f 100644 --- a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.test.tsx @@ -1,51 +1,38 @@ import { TransactionType } from '@metamask/transaction-controller'; import React from 'react'; import configureMockStore from 'redux-mock-store'; +import { TransactionDescription } from '@ethersproject/abi'; import { getMockTokenTransferConfirmState } from '../../../../../../../test/data/confirmations/helper'; import { renderWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers'; -import { useDecodedTransactionData } from '../hooks/useDecodedTransactionData'; +import { useTokenTransactionData } from '../hooks/useTokenTransactionData'; import { TransactionFlowSection } from './transaction-flow-section'; -jest.mock('../hooks/useDecodedTransactionData', () => ({ - ...jest.requireActual('../hooks/useDecodedTransactionData'), - useDecodedTransactionData: jest.fn(), -})); +jest.mock('../hooks/useTokenTransactionData'); jest.mock( '../../../../../../components/app/alert-system/contexts/alertMetricsContext.tsx', () => ({ - useAlertMetrics: jest.fn(() => ({ + useAlertMetrics: () => ({ trackInlineAlertClicked: jest.fn(), trackAlertRender: jest.fn(), trackAlertActionClicked: jest.fn(), - })), + }), }), ); describe('', () => { - const useDecodedTransactionDataMock = jest.fn().mockImplementation(() => ({ - pending: false, - value: { - data: [ - { - name: TransactionType.tokenMethodTransfer, - params: [ - { - name: 'dst', - type: 'address', - value: '0x6B175474E89094C44Da98b954EedeAC495271d0F', - }, - { name: 'wad', type: 'uint256', value: 0 }, - ], - }, - ], - source: 'Sourcify', - }, - })); + const useTokenTransactionDataMock = jest.mocked(useTokenTransactionData); - (useDecodedTransactionData as jest.Mock).mockImplementation( - useDecodedTransactionDataMock, - ); + beforeEach(() => { + jest.resetAllMocks(); + + useTokenTransactionDataMock.mockReturnValue({ + name: TransactionType.tokenMethodTransfer, + args: { + _to: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + }, + } as unknown as TransactionDescription); + }); it('renders correctly', () => { const state = getMockTokenTransferConfirmState({}); diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx index b874999d3956..fe9b9f319c9f 100644 --- a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx +++ b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx @@ -22,27 +22,20 @@ import { ConfirmInfoAlertRow } from '../../../../../../components/app/confirm/in import { RowAlertKey } from '../../../../../../components/app/confirm/info/row/constants'; import { useI18nContext } from '../../../../../../hooks/useI18nContext'; import { useConfirmContext } from '../../../../context/confirm'; -import { useDecodedTransactionData } from '../hooks/useDecodedTransactionData'; +import { useTokenTransactionData } from '../hooks/useTokenTransactionData'; export const TransactionFlowSection = () => { const t = useI18nContext(); + const { currentConfirmation: transactionMeta } = useConfirmContext(); - const { value, pending } = useDecodedTransactionData(); + const parsedTransactionData = useTokenTransactionData(); - const addresses = value?.data[0].params.filter( - (param) => param.type === 'address', - ); const recipientAddress = transactionMeta.type === TransactionType.simpleSend ? transactionMeta.txParams.to - : // sometimes there's more than one address, in which case we want the last one - addresses?.[addresses.length - 1].value; - - if (pending) { - return null; - } + : parsedTransactionData?.args?._to; const { chainId } = transactionMeta; diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.test.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.test.tsx index 2b1e6969ddd5..83696b69ac64 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.test.tsx @@ -65,7 +65,7 @@ describe('TypedSignInfo', () => { type: TransactionType.signTypedData, chainId: '0x5', }); - (snapUtils.isSnapId as jest.Mock).mockReturnValue(true); + (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(true); const mockStore = configureMockStore([])(mockState); const { queryByText } = renderWithConfirmContextProvider( , @@ -88,7 +88,7 @@ describe('TypedSignInfo', () => { type: TransactionType.signTypedData, chainId: '0x5', }); - (snapUtils.isSnapId as jest.Mock).mockReturnValue(false); + (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(false); const mockStore = configureMockStore([])(mockState); const { queryByText } = renderWithConfirmContextProvider( , diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx index 56421561ccd2..640b663e5cc1 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx @@ -153,7 +153,7 @@ describe('TypedSignInfo', () => { type: TransactionType.signTypedData, chainId: '0x5', }); - (snapUtils.isSnapId as jest.Mock).mockReturnValue(true); + (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(true); const mockStore = configureMockStore([])(mockState); const { queryByText } = renderWithConfirmContextProvider( , @@ -177,7 +177,7 @@ describe('TypedSignInfo', () => { type: TransactionType.signTypedData, chainId: '0x5', }); - (snapUtils.isSnapId as jest.Mock).mockReturnValue(false); + (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(false); const mockStore = configureMockStore([])(mockState); const { queryByText } = renderWithConfirmContextProvider( , diff --git a/ui/pages/confirmations/components/confirm/info/utils.test.ts b/ui/pages/confirmations/components/confirm/info/utils.test.ts index 8723cfc3f05d..154e3882af11 100644 --- a/ui/pages/confirmations/components/confirm/info/utils.test.ts +++ b/ui/pages/confirmations/components/confirm/info/utils.test.ts @@ -1,6 +1,6 @@ import { TransactionMeta } from '@metamask/transaction-controller'; import { toHex } from '@metamask/controller-utils'; -import { DecodedTransactionDataSource } from '../../../../../../shared/types/transaction-decode'; +import { TransactionDescription } from '@ethersproject/abi'; import { getIsRevokeSetApprovalForAll, hasValueAndNativeBalanceMismatch, @@ -10,33 +10,22 @@ import { describe('getIsRevokeSetApprovalForAll', () => { it('returns false if no data is passed as an argument', () => { const testValue = { - data: [], - source: DecodedTransactionDataSource.FourByte, - }; + args: {}, + } as TransactionDescription; + const actual = getIsRevokeSetApprovalForAll(testValue); expect(actual).toEqual(false); }); - it('returns true if no setApprovalForAll decoded tx is passed as an argument', () => { + it('returns true if setApprovalForAll decoded tx is passed as an argument', () => { const testValue = { - data: [ - { - name: 'setApprovalForAll', - params: [ - { - type: 'address', - value: '0x2e0D7E8c45221FcA00d74a3609A0f7097035d09B', - }, - { - type: 'boolean', - value: false, - }, - ], - }, - ], - source: DecodedTransactionDataSource.FourByte, - }; + name: 'setApprovalForAll', + args: { + _approved: false, + }, + } as unknown as TransactionDescription; + const actual = getIsRevokeSetApprovalForAll(testValue); expect(actual).toEqual(true); diff --git a/ui/pages/confirmations/components/confirm/info/utils.ts b/ui/pages/confirmations/components/confirm/info/utils.ts index 17b10e555952..2840df9052bf 100644 --- a/ui/pages/confirmations/components/confirm/info/utils.ts +++ b/ui/pages/confirmations/components/confirm/info/utils.ts @@ -2,7 +2,7 @@ import { TransactionMeta } from '@metamask/transaction-controller'; import type { Hex } from '@metamask/utils'; import { remove0x } from '@metamask/utils'; import { BN } from 'bn.js'; -import { DecodedTransactionDataResponse } from '../../../../../../shared/types/transaction-decode'; +import { TransactionDescription } from '@ethersproject/abi'; import { BackgroundColor, TextColor, @@ -11,13 +11,11 @@ import { const VALUE_COMPARISON_PERCENT_THRESHOLD = 5; export function getIsRevokeSetApprovalForAll( - value: DecodedTransactionDataResponse | undefined, + value: TransactionDescription | undefined, ): boolean { - const isRevokeSetApprovalForAll = - value?.data?.[0]?.name === 'setApprovalForAll' && - value?.data?.[0]?.params?.[1]?.value === false; - - return isRevokeSetApprovalForAll; + return ( + value?.name === 'setApprovalForAll' && value?.args?._approved === false + ); } export const getAmountColors = (credit?: boolean, debit?: boolean) => { diff --git a/ui/pages/confirmations/components/confirm/snaps/snaps-section/__snapshots__/snaps-section.test.tsx.snap b/ui/pages/confirmations/components/confirm/snaps/snaps-section/__snapshots__/snaps-section.test.tsx.snap index 12e984bc207a..656b1fc49740 100644 --- a/ui/pages/confirmations/components/confirm/snaps/snaps-section/__snapshots__/snaps-section.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/snaps/snaps-section/__snapshots__/snaps-section.test.tsx.snap @@ -46,7 +46,7 @@ exports[`SnapsSection renders section for typed sign request 1`] = ` style="overflow-y: auto;" >

Hello world again!

@@ -104,7 +104,7 @@ exports[`SnapsSection renders section personal sign request 1`] = ` style="overflow-y: auto;" >

Hello world!

diff --git a/ui/pages/confirmations/components/confirm/title/title.tsx b/ui/pages/confirmations/components/confirm/title/title.tsx index e8148912cac1..7be6c59be50d 100644 --- a/ui/pages/confirmations/components/confirm/title/title.tsx +++ b/ui/pages/confirmations/components/confirm/title/title.tsx @@ -20,8 +20,8 @@ import { Confirmation, SignatureRequestType } from '../../../types/confirm'; import { isSIWESignatureRequest } from '../../../utils'; import { useTypedSignSignatureInfo } from '../../../hooks/useTypedSignSignatureInfo'; import { useIsNFT } from '../info/approve/hooks/use-is-nft'; -import { useDecodedTransactionData } from '../info/hooks/useDecodedTransactionData'; import { getIsRevokeSetApprovalForAll } from '../info/utils'; +import { useTokenTransactionData } from '../info/hooks/useTokenTransactionData'; import { useCurrentSpendingCap } from './hooks/useCurrentSpendingCap'; function ConfirmBannerAlert({ ownerId }: { ownerId: string }) { @@ -173,19 +173,12 @@ const ConfirmTitle: React.FC = memo(() => { const { customSpendingCap, pending: spendingCapPending } = useCurrentSpendingCap(currentConfirmation); - let isRevokeSetApprovalForAll = false; - let revokePending = false; - const decodedResponse = useDecodedTransactionData( - TransactionType.tokenMethodSetApprovalForAll, - ); - if ( - currentConfirmation?.type === TransactionType.tokenMethodSetApprovalForAll - ) { - isRevokeSetApprovalForAll = getIsRevokeSetApprovalForAll( - decodedResponse.value, - ); - revokePending = decodedResponse.pending; - } + const parsedTransactionData = useTokenTransactionData(); + + const isRevokeSetApprovalForAll = + currentConfirmation?.type === + TransactionType.tokenMethodSetApprovalForAll && + getIsRevokeSetApprovalForAll(parsedTransactionData); const title = useMemo( () => @@ -195,7 +188,7 @@ const ConfirmTitle: React.FC = memo(() => { isNFT, customSpendingCap, isRevokeSetApprovalForAll, - spendingCapPending || revokePending, + spendingCapPending, primaryType, tokenStandard, ), @@ -205,7 +198,6 @@ const ConfirmTitle: React.FC = memo(() => { customSpendingCap, isRevokeSetApprovalForAll, spendingCapPending, - revokePending, primaryType, tokenStandard, ], @@ -219,7 +211,7 @@ const ConfirmTitle: React.FC = memo(() => { isNFT, customSpendingCap, isRevokeSetApprovalForAll, - spendingCapPending || revokePending, + spendingCapPending, primaryType, tokenStandard, ), @@ -229,7 +221,6 @@ const ConfirmTitle: React.FC = memo(() => { customSpendingCap, isRevokeSetApprovalForAll, spendingCapPending, - revokePending, primaryType, tokenStandard, ], diff --git a/ui/pages/confirmations/confirmation/templates/__snapshots__/remove-snap-account.test.js.snap b/ui/pages/confirmations/confirmation/templates/__snapshots__/remove-snap-account.test.js.snap index 1673efaa6631..4ec6bcfa5026 100644 --- a/ui/pages/confirmations/confirmation/templates/__snapshots__/remove-snap-account.test.js.snap +++ b/ui/pages/confirmations/confirmation/templates/__snapshots__/remove-snap-account.test.js.snap @@ -208,7 +208,7 @@ exports[`remove-snap-account confirmation should match snapshot 1`] = ` style="bottom: -1px; right: 2px;" >
diff --git a/ui/pages/confirmations/confirmation/templates/add-ethereum-chain.test.js b/ui/pages/confirmations/confirmation/templates/add-ethereum-chain.test.js index 19f51b6fa798..50fb4a04e4c7 100644 --- a/ui/pages/confirmations/confirmation/templates/add-ethereum-chain.test.js +++ b/ui/pages/confirmations/confirmation/templates/add-ethereum-chain.test.js @@ -159,6 +159,7 @@ describe('add-ethereum-chain confirmation', () => { "Attackers sometimes mimic sites by making small changes to the site address. Make sure you're interacting with the intended site before you continue. Punycode version: https://xn--ifura-dig.io/gnosis", ), ).toBeInTheDocument(); + expect(getByText('https://iոfura.io/gnosis')).toBeInTheDocument(); }); }); }); diff --git a/ui/pages/confirmations/hooks/alerts/transactions/usePendingTransactionAlerts.test.ts b/ui/pages/confirmations/hooks/alerts/transactions/usePendingTransactionAlerts.test.ts index f7be0f93e2c1..e045a648f484 100644 --- a/ui/pages/confirmations/hooks/alerts/transactions/usePendingTransactionAlerts.test.ts +++ b/ui/pages/confirmations/hooks/alerts/transactions/usePendingTransactionAlerts.test.ts @@ -4,34 +4,18 @@ import { TransactionStatus, TransactionType, } from '@metamask/transaction-controller'; -import { useSelector } from 'react-redux'; -import { useParams } from 'react-router-dom'; import { genUnapprovedContractInteractionConfirmation } from '../../../../../../test/data/confirmations/contract-interaction'; import { getMockConfirmState } from '../../../../../../test/data/confirmations/helper'; import { renderHookWithConfirmContextProvider } from '../../../../../../test/lib/confirmations/render-helpers'; import { RowAlertKey } from '../../../../../components/app/confirm/info/row/constants'; import { Severity } from '../../../../../helpers/constants/design-system'; -import { - getRedesignedTransactionsEnabled, - submittedPendingTransactionsSelector, -} from '../../../../../selectors'; import { PendingTransactionAlertMessage } from './PendingTransactionAlertMessage'; import { usePendingTransactionAlerts } from './usePendingTransactionAlerts'; -jest.mock('react-redux', () => ({ - ...jest.requireActual('react-redux'), - useSelector: jest.fn(), -})); - jest.mock('./PendingTransactionAlertMessage', () => ({ PendingTransactionAlertMessage: () => 'PendingTransactionAlertMessage', })); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: jest.fn().mockReturnValue({ id: 'mock-transaction-id' }), -})); - const ACCOUNT_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'; const TRANSACTION_ID_MOCK = '123-456'; @@ -59,6 +43,7 @@ function runHook({ transactions?: TransactionMeta[]; } = {}) { let pendingApprovals = {}; + if (currentConfirmation) { pendingApprovals = { [currentConfirmation.id as string]: { @@ -68,12 +53,14 @@ function runHook({ }; transactions.push(currentConfirmation); } + const state = getMockConfirmState({ metamask: { pendingApprovals, transactions, }, }); + const response = renderHookWithConfirmContextProvider( usePendingTransactionAlerts, state, @@ -83,19 +70,8 @@ function runHook({ } describe('usePendingTransactionAlerts', () => { - const useSelectorMock = useSelector as jest.Mock; - beforeEach(() => { jest.resetAllMocks(); - - (useParams as jest.Mock).mockReturnValue({ id: 'mock-transaction-id' }); - - useSelectorMock.mockImplementation((selector) => { - if (selector.toString().includes('pendingApprovalsSortedSelector')) { - return []; - } - return undefined; - }); }); it('returns no alerts if no confirmation', () => { @@ -152,24 +128,6 @@ describe('usePendingTransactionAlerts', () => { }); it('returns alert if submitted transaction', () => { - useSelectorMock.mockImplementation((selector) => { - if (selector === submittedPendingTransactionsSelector) { - return [ - { name: 'first transaction', id: '1' }, - { name: 'second transaction', id: '2' }, - ]; - } else if (selector === getRedesignedTransactionsEnabled) { - return true; - } else if (selector.toString().includes('getUnapprovedTransaction')) { - return { type: TransactionType.contractInteraction }; - } else if ( - selector.toString().includes('pendingApprovalsSortedSelector') - ) { - return []; - } - return undefined; - }); - const alerts = runHook({ currentConfirmation: CONFIRMATION_MOCK, transactions: [TRANSACTION_META_MOCK], diff --git a/ui/pages/confirmations/hooks/useConfirmationNavigation.test.ts b/ui/pages/confirmations/hooks/useConfirmationNavigation.test.ts index edaddc0e520f..9804e14a0e00 100644 --- a/ui/pages/confirmations/hooks/useConfirmationNavigation.test.ts +++ b/ui/pages/confirmations/hooks/useConfirmationNavigation.test.ts @@ -28,34 +28,40 @@ jest.mock('../confirmation/templates', () => ({ const APPROVAL_ID_MOCK = '123-456'; const APPROVAL_ID_2_MOCK = '456-789'; -function renderHook( - approvalType: ApprovalType, - requestData?: Json, - approvalFlows?: ApprovalFlowState[], -) { +function renderHookWithState(state: Record) { const { result } = renderHookWithProvider(() => useConfirmationNavigation(), { ...mockState, metamask: { ...mockState.metamask, - pendingApprovals: { - [APPROVAL_ID_MOCK]: { - id: APPROVAL_ID_MOCK, - type: approvalType, - requestData, - }, - [APPROVAL_ID_2_MOCK]: { - id: APPROVAL_ID_2_MOCK, - type: approvalType, - requestData, - }, - }, - approvalFlows, + ...state, }, }); return result.current; } +function renderHook( + approvalType: ApprovalType, + requestData?: Json, + approvalFlows?: ApprovalFlowState[], +) { + return renderHookWithState({ + pendingApprovals: { + [APPROVAL_ID_MOCK]: { + id: APPROVAL_ID_MOCK, + type: approvalType, + requestData, + }, + [APPROVAL_ID_2_MOCK]: { + id: APPROVAL_ID_2_MOCK, + type: approvalType, + requestData, + }, + }, + approvalFlows, + }); +} + describe('useConfirmationNavigation', () => { const useHistoryMock = jest.mocked(useHistory); const history = { replace: jest.fn() }; @@ -202,6 +208,36 @@ describe('useConfirmationNavigation', () => { const result = renderHook(ApprovalType.Transaction); expect(result.count).toBe(2); }); + + // @ts-expect-error This function is missing from the Mocha type definitions + it.each([ + ['token', undefined], + ['NFT', '123'], + ])( + 'ignores additional watch %s approvals', + (_title: string, tokenId?: string) => { + const result = renderHookWithState({ + pendingApprovals: { + [APPROVAL_ID_MOCK]: { + id: APPROVAL_ID_MOCK, + type: ApprovalType.WatchAsset, + requestData: { asset: { tokenId } }, + }, + [APPROVAL_ID_2_MOCK]: { + id: APPROVAL_ID_2_MOCK, + type: ApprovalType.Transaction, + }, + duplicate: { + id: 'duplicate', + type: ApprovalType.WatchAsset, + requestData: { asset: { tokenId } }, + }, + }, + }); + + expect(result.count).toBe(2); + }, + ); }); describe('getIndex', () => { @@ -224,5 +260,37 @@ describe('useConfirmationNavigation', () => { APPROVAL_ID_2_MOCK, ]); }); + + // @ts-expect-error This function is missing from the Mocha type definitions + it.each([ + ['token', undefined], + ['NFT', '123'], + ])( + 'ignores additional watch %s approvals', + (_title: string, tokenId?: string) => { + const result = renderHookWithState({ + pendingApprovals: { + [APPROVAL_ID_MOCK]: { + id: APPROVAL_ID_MOCK, + type: ApprovalType.WatchAsset, + requestData: { asset: { tokenId } }, + }, + [APPROVAL_ID_2_MOCK]: { + id: APPROVAL_ID_2_MOCK, + type: ApprovalType.Transaction, + }, + duplicate: { + id: 'duplicate', + type: ApprovalType.WatchAsset, + requestData: { asset: { tokenId } }, + }, + }, + }); + + expect( + result.confirmations.map(({ id }: { id: string }) => id), + ).toEqual([APPROVAL_ID_MOCK, APPROVAL_ID_2_MOCK]); + }, + ); }); }); diff --git a/ui/pages/confirmations/hooks/useConfirmationNavigation.ts b/ui/pages/confirmations/hooks/useConfirmationNavigation.ts index 9a5cdfc5cd9e..95bc1d93cd34 100644 --- a/ui/pages/confirmations/hooks/useConfirmationNavigation.ts +++ b/ui/pages/confirmations/hooks/useConfirmationNavigation.ts @@ -19,7 +19,7 @@ import { import { isSignatureTransactionType } from '../utils'; import { getApprovalFlows, - pendingApprovalsSortedSelector, + selectPendingApprovalsForNavigation, } from '../../../selectors'; const CONNECT_APPROVAL_TYPES = [ @@ -30,7 +30,7 @@ const CONNECT_APPROVAL_TYPES = [ ]; export function useConfirmationNavigation() { - const confirmations = useSelector(pendingApprovalsSortedSelector, isEqual); + const confirmations = useSelector(selectPendingApprovalsForNavigation); const approvalFlows = useSelector(getApprovalFlows, isEqual); const history = useHistory(); diff --git a/ui/pages/confirmations/hooks/useGasFeeInputs.js b/ui/pages/confirmations/hooks/useGasFeeInputs.js index 8675b726038a..129778bfcf20 100644 --- a/ui/pages/confirmations/hooks/useGasFeeInputs.js +++ b/ui/pages/confirmations/hooks/useGasFeeInputs.js @@ -164,7 +164,11 @@ export function useGasFeeInputs( }); const [gasLimit, setGasLimit] = useState(() => - Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0')), + Number( + hexToDecimal( + transaction?.txParams?.gasLimit ?? transaction?.txParams?.gas ?? '0x0', + ), + ), ); const properGasLimit = Number(hexToDecimal(transaction?.originalGasEstimate)); @@ -195,7 +199,15 @@ export function useGasFeeInputs( setEstimateUsed(transaction?.userFeeLevel); } - setGasLimit(Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0'))); + setGasLimit( + Number( + hexToDecimal( + transaction?.txParams?.gasLimit ?? + transaction?.txParams?.gas ?? + '0x0', + ), + ), + ); } }, [ setEstimateUsed, diff --git a/ui/pages/confirmations/hooks/useTransactionFunction.test.js b/ui/pages/confirmations/hooks/useTransactionFunction.test.js index 22984ba71a37..ec823d2acf70 100644 --- a/ui/pages/confirmations/hooks/useTransactionFunction.test.js +++ b/ui/pages/confirmations/hooks/useTransactionFunction.test.js @@ -164,4 +164,16 @@ describe('useMaxPriorityFeePerGasInput', () => { userFeeLevel: 'dappSuggested', }); }); + + it('returns early when gasFeeEstimates is undefined', () => { + const mockUpdateTransaction = jest + .spyOn(Actions, 'updateTransactionGasFees') + .mockImplementation(() => ({ type: '' })); + + const { result } = renderUseTransactionFunctions({ + gasFeeEstimates: undefined, + }); + result.current.updateTransactionUsingEstimate(GasRecommendations.low); + expect(mockUpdateTransaction).not.toHaveBeenCalled(); + }); }); diff --git a/ui/pages/confirmations/hooks/useTransactionFunctions.js b/ui/pages/confirmations/hooks/useTransactionFunctions.js index 17b5165da94e..96b41cd5e08e 100644 --- a/ui/pages/confirmations/hooks/useTransactionFunctions.js +++ b/ui/pages/confirmations/hooks/useTransactionFunctions.js @@ -201,9 +201,10 @@ export const useTransactionFunctions = ({ const updateTransactionUsingEstimate = useCallback( (gasFeeEstimateToUse) => { - if (!gasFeeEstimates[gasFeeEstimateToUse]) { + if (!gasFeeEstimates?.[gasFeeEstimateToUse]) { return; } + const { suggestedMaxFeePerGas, suggestedMaxPriorityFeePerGas } = gasFeeEstimates[gasFeeEstimateToUse]; updateTransaction({ diff --git a/ui/pages/confirmations/utils/confirm.test.ts b/ui/pages/confirmations/utils/confirm.test.ts index 9a8b3d1a0f8a..a81b5959a916 100644 --- a/ui/pages/confirmations/utils/confirm.test.ts +++ b/ui/pages/confirmations/utils/confirm.test.ts @@ -93,8 +93,11 @@ describe('confirm util', () => { expect(toPunycodeURL('https://iոfura.io/gnosis')).toStrictEqual( 'https://xn--ifura-dig.io/gnosis', ); - expect(toPunycodeURL('https://www.google.com')).toStrictEqual( - 'https://www.google.com/', + expect(toPunycodeURL('https://iոfura.io')).toStrictEqual( + 'https://xn--ifura-dig.io', + ); + expect(toPunycodeURL('https://iոfura.io/')).toStrictEqual( + 'https://xn--ifura-dig.io/', ); expect( toPunycodeURL('https://iոfura.io/gnosis:5050?test=iոfura&foo=bar'), @@ -102,7 +105,7 @@ describe('confirm util', () => { 'https://xn--ifura-dig.io/gnosis:5050?test=i%D5%B8fura&foo=bar', ); expect(toPunycodeURL('https://www.google.com')).toStrictEqual( - 'https://www.google.com/', + 'https://www.google.com', ); }); }); diff --git a/ui/pages/confirmations/utils/confirm.ts b/ui/pages/confirmations/utils/confirm.ts index f464f51e8159..379d98728be2 100644 --- a/ui/pages/confirmations/utils/confirm.ts +++ b/ui/pages/confirmations/utils/confirm.ts @@ -80,7 +80,12 @@ export const isValidASCIIURL = (urlString?: string) => { export const toPunycodeURL = (urlString: string) => { try { - return new URL(urlString).href; + const url = new URL(urlString); + const { protocol, hostname, port, search, hash } = url; + const pathname = + url.pathname === '/' && !urlString.endsWith('/') ? '' : url.pathname; + + return `${protocol}//${hostname}${port}${pathname}${search}${hash}`; } catch (err: unknown) { console.error(`Failed to convert URL to Punycode: ${err}`); return undefined; diff --git a/ui/pages/home/home.container.js b/ui/pages/home/home.container.js index 4fb46ccc1744..cae73b180f4d 100644 --- a/ui/pages/home/home.container.js +++ b/ui/pages/home/home.container.js @@ -40,7 +40,7 @@ import { getSelectedInternalAccount, getQueuedRequestCount, getEditedNetwork, - pendingApprovalsSortedSelector, + selectPendingApprovalsForNavigation, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) getAccountType, ///: END:ONLY_INCLUDE_IF @@ -108,7 +108,7 @@ const mapStateToProps = (state) => { const totalUnapprovedAndQueuedRequestCount = totalUnapprovedCount + queuedRequestCount; const swapsEnabled = getSwapsFeatureIsLive(state); - const pendingApprovals = pendingApprovalsSortedSelector(state); + const pendingApprovals = selectPendingApprovalsForNavigation(state); ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) const institutionalConnectRequests = getInstitutionalConnectRequests(state); diff --git a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js index 163091e41958..b7c4e07aa9d6 100644 --- a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js +++ b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js @@ -721,7 +721,7 @@ export default function PrivacySettings() { value={turnOn4ByteResolution} setValue={setTurnOn4ByteResolution} title={t('use4ByteResolution')} - description={t('use4ByteResolutionDescription')} + description={t('toggleDecodeDescription')} /> - To improve user experience, we customize the activity tab with messages based on the smart contracts you interact with. MetaMask uses a service called 4byte.directory to decode data and show you a version of a smart contract that's easier to read. This helps reduce your chances of approving malicious smart contract actions, but can result in your IP address being shared. + We use 4byte.directory and Sourcify services to decode and display more readable transaction data. This helps you understand the outcome of pending and past transactions, but can result in your IP address being shared.
{t('use4ByteResolution')}
- {t('use4ByteResolutionDescription')} + {t('toggleDecodeDescription')}
diff --git a/ui/pages/settings/settings.component.js b/ui/pages/settings/settings.component.js index 724c661c9aeb..37257e2c8fcb 100644 --- a/ui/pages/settings/settings.component.js +++ b/ui/pages/settings/settings.component.js @@ -21,6 +21,7 @@ import { ADD_POPULAR_CUSTOM_NETWORK, DEFAULT_ROUTE, NOTIFICATIONS_SETTINGS_ROUTE, + SNAP_SETTINGS_ROUTE, } from '../../helpers/constants/routes'; import { getSettingsRoutes } from '../../helpers/utils/settings-search'; @@ -31,6 +32,7 @@ import { IconName, Box, Text, + IconSize, } from '../../components/component-library'; import { AlignItems, @@ -44,6 +46,8 @@ import MetafoxLogo from '../../components/ui/metafox-logo'; // eslint-disable-next-line import/no-restricted-paths import { getEnvironmentType } from '../../../app/scripts/lib/util'; import { ENVIRONMENT_TYPE_POPUP } from '../../../shared/constants/app'; +import { SnapIcon } from '../../components/app/snaps/snap-icon'; +import { SnapSettingsRenderer } from '../../components/app/snaps/snap-settings-page'; import SettingsTab from './settings-tab'; import AdvancedTab from './advanced-tab'; import InfoTab from './info-tab'; @@ -70,6 +74,8 @@ class SettingsPage extends PureComponent { mostRecentOverviewPage: PropTypes.string.isRequired, pathnameI18nKey: PropTypes.string, remoteFeatureFlags: PropTypes.object.isRequired, + settingsPageSnaps: PropTypes.array, + snapSettingsTitle: PropTypes.string, toggleNetworkMenu: PropTypes.func.isRequired, useExternalServices: PropTypes.bool, }; @@ -210,19 +216,24 @@ class SettingsPage extends PureComponent { renderTitle() { const { t } = this.context; - const { isPopup, pathnameI18nKey, addressName } = this.props; + const { isPopup, pathnameI18nKey, addressName, snapSettingsTitle } = + this.props; let titleText; if (isPopup && addressName) { titleText = t('details'); } else if (pathnameI18nKey && isPopup) { titleText = t(pathnameI18nKey); + } else if (snapSettingsTitle) { + titleText = snapSettingsTitle; } else { titleText = t('settings'); } return (
- {titleText} + + {titleText} +
); } @@ -293,15 +304,31 @@ class SettingsPage extends PureComponent { } renderTabs() { - const { history, currentPath, useExternalServices } = this.props; + const { history, currentPath, useExternalServices, settingsPageSnaps } = + this.props; const { t } = this.context; + const snapsSettings = settingsPageSnaps.map(({ id, name }) => { + return { + content: name, + icon: ( + + ), + key: `${SNAP_SETTINGS_ROUTE}/${encodeURIComponent(id)}`, + }; + }); + const tabs = [ { content: t('general'), icon: , key: GENERAL_ROUTE, }, + ...snapsSettings, { content: t('advanced'), icon: , @@ -390,6 +417,10 @@ class SettingsPage extends PureComponent { )} /> + { metamask: { currencyRates }, } = state; const remoteFeatureFlags = getRemoteFeatureFlags(state); + + const settingsPageSnapsIds = getSettingsPageSnapsIds(state); + const snapsMetadata = getSnapsMetadata(state); const conversionDate = currencyRates[ticker]?.conversionDate; const pathNameTail = pathname.match(/[^/]+$/u)[0]; @@ -75,6 +83,7 @@ const mapStateToProps = (state, ownProps) => { const isAddPopularCustomNetwork = Boolean( pathname.match(ADD_POPULAR_CUSTOM_NETWORK), ); + const isSnapSettingsRoute = Boolean(pathname.match(SNAP_SETTINGS_ROUTE)); const isPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP; const pathnameI18nKey = ROUTES_TO_I18N_KEYS[pathname]; @@ -102,6 +111,16 @@ const mapStateToProps = (state, ownProps) => { ); const useExternalServices = getUseExternalServices(state); + const snapNameGetter = getSnapName(snapsMetadata); + + const settingsPageSnaps = settingsPageSnapsIds.map((snapId) => ({ + id: snapId, + name: snapNameGetter(snapId), + })); + + const snapSettingsTitle = + isSnapSettingsRoute && snapNameGetter(decodeSnapIdFromPathname(pathname)); + return { addNewNetwork, addressName, @@ -115,6 +134,8 @@ const mapStateToProps = (state, ownProps) => { mostRecentOverviewPage: getMostRecentOverviewPage(state), pathnameI18nKey, remoteFeatureFlags, + settingsPageSnaps, + snapSettingsTitle, useExternalServices, }; }; diff --git a/ui/pages/settings/settings.stories.js b/ui/pages/settings/settings.stories.js index 53437f4175db..b6b695a89cb1 100644 --- a/ui/pages/settings/settings.stories.js +++ b/ui/pages/settings/settings.stories.js @@ -62,6 +62,7 @@ const Settings = ({ history }) => { pathnameI18nKey={pathnameI18nKey} backRoute={SETTINGS_ROUTE} remoteFeatureFlags={{}} + settingsPageSnaps={[]} />
); diff --git a/ui/selectors/approvals.ts b/ui/selectors/approvals.ts index 55a5d052579c..f8f937bb2f34 100644 --- a/ui/selectors/approvals.ts +++ b/ui/selectors/approvals.ts @@ -1,6 +1,10 @@ -import { ApprovalControllerState } from '@metamask/approval-controller'; +import { + ApprovalControllerState, + ApprovalRequest, +} from '@metamask/approval-controller'; import { ApprovalType } from '@metamask/controller-utils'; import { createSelector } from 'reselect'; +import { Json } from '@metamask/utils'; import { createDeepEqualSelector } from '../../shared/modules/selectors/util'; export type ApprovalsMetaMaskState = { @@ -58,6 +62,32 @@ export function pendingApprovalsSortedSelector(state: ApprovalsMetaMaskState) { return getPendingApprovals(state).sort((a1, a2) => a1.time - a2.time); } +/** + * Returns pending approvals sorted by time for use in confirmation navigation. + * Excludes duplicate watch asset approvals as they are combined into a single confirmation. + */ +export const selectPendingApprovalsForNavigation = createDeepEqualSelector( + pendingApprovalsSortedSelector, + (sortedPendingApprovals) => + sortedPendingApprovals.filter((approval, index) => { + if ( + isWatchNftApproval(approval) && + sortedPendingApprovals.findIndex(isWatchNftApproval) !== index + ) { + return false; + } + + if ( + isWatchTokenApproval(approval) && + sortedPendingApprovals.findIndex(isWatchTokenApproval) !== index + ) { + return false; + } + + return true; + }), +); + const internalSelectPendingApproval = createSelector( getPendingApprovals, (_state: ApprovalsMetaMaskState, id: string) => id, @@ -68,3 +98,17 @@ export const selectPendingApproval = createDeepEqualSelector( internalSelectPendingApproval, (approval) => approval, ); + +function isWatchTokenApproval(approval: ApprovalRequest>) { + const tokenId = (approval.requestData?.asset as Record) + ?.tokenId; + + return approval.type === ApprovalType.WatchAsset && !tokenId; +} + +function isWatchNftApproval(approval: ApprovalRequest>) { + const tokenId = (approval.requestData?.asset as Record) + ?.tokenId; + + return approval.type === ApprovalType.WatchAsset && Boolean(tokenId); +} diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 61c15000baba..ce117af76262 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -1,3 +1,4 @@ +import { toUnicode } from 'punycode/punycode.js'; import { SubjectType } from '@metamask/permission-controller'; import { ApprovalType } from '@metamask/controller-utils'; import { @@ -12,6 +13,7 @@ import { NameType } from '@metamask/name-controller'; import { TransactionStatus } from '@metamask/transaction-controller'; import { isEvmAccountType } from '@metamask/keyring-api'; import { RpcEndpointType } from '@metamask/network-controller'; +import { SnapEndowments } from '@metamask/snaps-rpc-methods'; import { getCurrentChainId, getProviderConfig, @@ -110,6 +112,7 @@ import { BridgeFeatureFlagsKey } from '../../shared/types/bridge'; import { hasTransactionData } from '../../shared/modules/transaction.utils'; import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils'; import { createDeepEqualSelector } from '../../shared/modules/selectors/util'; +import { isSnapIgnoredInProd } from '../helpers/utils/snaps'; import { getAllUnapprovedTransactions, getCurrentNetworkTransactions, @@ -729,7 +732,10 @@ export function getAddressBook(state) { export function getEnsResolutionByAddress(state, address) { if (state.metamask.ensResolutionsByAddress[address]) { - return state.metamask.ensResolutionsByAddress[address]; + const ensResolution = state.metamask.ensResolutionsByAddress[address]; + // ensResolution is a punycode encoded string hence toUnicode is used to decode it from same package + const normalizedEnsResolution = toUnicode(ensResolution); + return normalizedEnsResolution; } const entry = @@ -1914,6 +1920,19 @@ export const getInsightSnaps = createDeepEqualSelector( }, ); +export const getSettingsPageSnaps = createDeepEqualSelector( + getEnabledSnaps, + getPermissionSubjects, + (snaps, subjects) => { + return Object.values(snaps).filter( + ({ id, preinstalled }) => + subjects[id]?.permissions[SnapEndowments.SettingsPage] && + preinstalled && + !isSnapIgnoredInProd(id), + ); + }, +); + export const getSignatureInsightSnaps = createDeepEqualSelector( getEnabledSnaps, getPermissionSubjects, @@ -1944,6 +1963,11 @@ export const getNameLookupSnapsIds = createDeepEqualSelector( }, ); +export const getSettingsPageSnapsIds = createDeepEqualSelector( + getSettingsPageSnaps, + (snaps) => snaps.map((snap) => snap.id), +); + export const getNotifySnaps = createDeepEqualSelector( getEnabledSnaps, getPermissionSubjects, diff --git a/yarn.lock b/yarn.lock index ed1c8a1de350..f45eb5cf233e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4873,6 +4873,16 @@ __metadata: languageName: node linkType: hard +"@metamask/abi-utils@npm:^3.0.0": + version: 3.0.0 + resolution: "@metamask/abi-utils@npm:3.0.0" + dependencies: + "@metamask/superstruct": "npm:^3.1.0" + "@metamask/utils": "npm:^11.0.1" + checksum: 10/068b98185148b9e185b4af4392c6a6f82f1d4b1ff60013c57679c618f37afe9030e3ccc940e1a8b690be6f62ea91115ab18b73f3c3c09f4eff1794e31ababb9b + languageName: node + linkType: hard + "@metamask/account-watcher@npm:^4.1.2": version: 4.1.2 resolution: "@metamask/account-watcher@npm:4.1.2" @@ -5085,13 +5095,13 @@ __metadata: languageName: node linkType: hard -"@metamask/base-controller@npm:^7.0.0, @metamask/base-controller@npm:^7.0.1, @metamask/base-controller@npm:^7.0.2": - version: 7.0.2 - resolution: "@metamask/base-controller@npm:7.0.2" +"@metamask/base-controller@npm:^7.0.0, @metamask/base-controller@npm:^7.0.1, @metamask/base-controller@npm:^7.0.2, @metamask/base-controller@npm:^7.1.0": + version: 7.1.0 + resolution: "@metamask/base-controller@npm:7.1.0" dependencies: "@metamask/utils": "npm:^10.0.0" immer: "npm:^9.0.6" - checksum: 10/6f78ec5af840c9947aa8eac6e402df6469600260d613a92196daefd5b072097a176fe5da1c386f2d36853513254b74140d667d817a12880c46f088e18ff3606a + checksum: 10/5a0b50c1e096cbf6483e308eddb3ca2e5e1865b803b5dba778bf635ec59657290895e21ada71c7508d8e34ff9695a192a414fd75e287d290346359ef8e23960a languageName: node linkType: hard @@ -5260,16 +5270,16 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-block-tracker@npm:^11.0.3": - version: 11.0.3 - resolution: "@metamask/eth-block-tracker@npm:11.0.3" +"@metamask/eth-block-tracker@npm:^11.0.3, @metamask/eth-block-tracker@npm:^11.0.4": + version: 11.0.4 + resolution: "@metamask/eth-block-tracker@npm:11.0.4" dependencies: "@metamask/eth-json-rpc-provider": "npm:^4.1.5" "@metamask/safe-event-emitter": "npm:^3.1.1" - "@metamask/utils": "npm:^9.1.0" + "@metamask/utils": "npm:^11.0.1" json-rpc-random-id: "npm:^1.0.1" pify: "npm:^5.0.0" - checksum: 10/c73a570f889c613ab309643c84a4aed1a4eeed5c101434da84b34babe2352218c65f863602e013a8a55052e3f80a538efed865cc5fb7af558d168c52c5a399a4 + checksum: 10/56b60255a3ae23a378570a49c30d0c13bd74094c0509a978cad20ef57079c80bae91fd35749acb9ac5feef2922eec45a6fef8c0ee6e754cbf3722f8e5d0d771e languageName: node linkType: hard @@ -5311,35 +5321,35 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-json-rpc-middleware@npm:^15.0.1": - version: 15.0.1 - resolution: "@metamask/eth-json-rpc-middleware@npm:15.0.1" +"@metamask/eth-json-rpc-middleware@npm:^15.0.1, @metamask/eth-json-rpc-middleware@npm:^15.1.2": + version: 15.1.2 + resolution: "@metamask/eth-json-rpc-middleware@npm:15.1.2" dependencies: - "@metamask/eth-block-tracker": "npm:^11.0.3" - "@metamask/eth-json-rpc-provider": "npm:^4.1.5" - "@metamask/eth-sig-util": "npm:^7.0.3" - "@metamask/json-rpc-engine": "npm:^10.0.0" - "@metamask/rpc-errors": "npm:^7.0.0" - "@metamask/utils": "npm:^9.1.0" + "@metamask/eth-block-tracker": "npm:^11.0.4" + "@metamask/eth-json-rpc-provider": "npm:^4.1.7" + "@metamask/eth-sig-util": "npm:^8.1.2" + "@metamask/json-rpc-engine": "npm:^10.0.2" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/utils": "npm:^11.0.1" "@types/bn.js": "npm:^5.1.5" bn.js: "npm:^5.2.1" klona: "npm:^2.0.6" pify: "npm:^5.0.0" safe-stable-stringify: "npm:^2.4.3" - checksum: 10/9777fca31440bf0076f5d2c24e2ddb4848ecd9d41b0a5d6114c27339567e60bfcb9057d6bfa81f18f5ca0ffa848ecf9603c765f606b8de206d3e34dba519c501 + checksum: 10/71e7d61cc58df250bfef73438a9e30cc2f78e0e979feb8a9c0be72bbad470a2fe068fa790194cb88ef56865e36156e525272bc3e1a2a7135d07f7bd81a752239 languageName: node linkType: hard -"@metamask/eth-json-rpc-provider@npm:^4.0.0, @metamask/eth-json-rpc-provider@npm:^4.1.5, @metamask/eth-json-rpc-provider@npm:^4.1.6": - version: 4.1.6 - resolution: "@metamask/eth-json-rpc-provider@npm:4.1.6" +"@metamask/eth-json-rpc-provider@npm:^4.0.0, @metamask/eth-json-rpc-provider@npm:^4.1.5, @metamask/eth-json-rpc-provider@npm:^4.1.6, @metamask/eth-json-rpc-provider@npm:^4.1.7": + version: 4.1.7 + resolution: "@metamask/eth-json-rpc-provider@npm:4.1.7" dependencies: - "@metamask/json-rpc-engine": "npm:^10.0.1" - "@metamask/rpc-errors": "npm:^7.0.1" + "@metamask/json-rpc-engine": "npm:^10.0.2" + "@metamask/rpc-errors": "npm:^7.0.2" "@metamask/safe-event-emitter": "npm:^3.0.0" - "@metamask/utils": "npm:^10.0.0" + "@metamask/utils": "npm:^11.0.1" uuid: "npm:^8.3.2" - checksum: 10/aeec2c362a5386357e9f8c707da9baa4326e83889633723656b6801b6461ea8ab8f020b0d9ed0bbc2d8fd6add4af4c99cc9c9a1cbedca267a033a9f19da41200 + checksum: 10/ddfa2a888c83015672a6b879bad061f1e617d6875b741aa714cbe7ac0878dbc8beb9d4ce00ec61a2672d2dfe251f04c4d3eed77b8a9877b7aeb0034eec3e7c51 languageName: node linkType: hard @@ -5381,17 +5391,17 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-sig-util@npm:^8.0.0": - version: 8.0.0 - resolution: "@metamask/eth-sig-util@npm:8.0.0" +"@metamask/eth-sig-util@npm:^8.0.0, @metamask/eth-sig-util@npm:^8.1.2": + version: 8.1.2 + resolution: "@metamask/eth-sig-util@npm:8.1.2" dependencies: "@ethereumjs/util": "npm:^8.1.0" - "@metamask/abi-utils": "npm:^2.0.4" - "@metamask/utils": "npm:^9.0.0" + "@metamask/abi-utils": "npm:^3.0.0" + "@metamask/utils": "npm:^11.0.1" "@scure/base": "npm:~1.1.3" ethereum-cryptography: "npm:^2.1.2" tweetnacl: "npm:^1.0.3" - checksum: 10/5de92bc59df31bcf417ecbdfd2b47f15c21b29454f45108513c55d9c005b7cb51373e9d254bd97533603ab7c7758fdf8fc5159612f366b05f92ebe5beb6d75d8 + checksum: 10/32b284fc8c3229e3741b1c21f44ca3f55c2215ef8ad700775cd9501bbaab56a4e861827bef24ed263734d28c899eb3b34a9646e9d21ec3fce12204b7eb58bfed languageName: node linkType: hard @@ -5626,14 +5636,14 @@ __metadata: languageName: node linkType: hard -"@metamask/json-rpc-engine@npm:^10.0.0, @metamask/json-rpc-engine@npm:^10.0.1": - version: 10.0.1 - resolution: "@metamask/json-rpc-engine@npm:10.0.1" +"@metamask/json-rpc-engine@npm:^10.0.0, @metamask/json-rpc-engine@npm:^10.0.1, @metamask/json-rpc-engine@npm:^10.0.2": + version: 10.0.2 + resolution: "@metamask/json-rpc-engine@npm:10.0.2" dependencies: - "@metamask/rpc-errors": "npm:^7.0.1" + "@metamask/rpc-errors": "npm:^7.0.2" "@metamask/safe-event-emitter": "npm:^3.0.0" - "@metamask/utils": "npm:^10.0.0" - checksum: 10/15a8eeab9af39b9ed87311da728e81169484ace733a8ef9fc469bd887654e37afa19f9e5228246dc80daad3fbf9b16067e73b2969d37d44acf5bc6ffa2c70082 + "@metamask/utils": "npm:^11.0.1" + checksum: 10/479e4c36ee10ecaa9b26bf8aaea375f7dbe68b5379fabc0f78ac087e310d0040b0e7a2d55eccebd820089404a2170f498c4e2b82eb7f0d34c5becbd811340d49 languageName: node linkType: hard @@ -6053,12 +6063,12 @@ __metadata: languageName: node linkType: hard -"@metamask/preinstalled-example-snap@npm:^0.2.0": - version: 0.2.0 - resolution: "@metamask/preinstalled-example-snap@npm:0.2.0" +"@metamask/preinstalled-example-snap@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/preinstalled-example-snap@npm:0.3.0" dependencies: - "@metamask/snaps-sdk": "npm:^6.9.0" - checksum: 10/f8ad6f42c9bd7ce3b7fc9b45eecda6191320ff762b48c482ba4944a6d7a228682b833c15e56058f26ac7bb10417dfe9de340af1c8eb9bbe5dc03c665426ccb13 + "@metamask/snaps-sdk": "npm:^6.14.0" + checksum: 10/add8f89c1b7327bc90486d868a9d4b7eff426ef98a5a96235fc6fdce4710c6d17842636ccd02db6638d061ce2b16939c6fe1f06e69cdde8bde2f6026c7b82df5 languageName: node linkType: hard @@ -6158,13 +6168,13 @@ __metadata: languageName: node linkType: hard -"@metamask/rpc-errors@npm:^7.0.0, @metamask/rpc-errors@npm:^7.0.1": - version: 7.0.1 - resolution: "@metamask/rpc-errors@npm:7.0.1" +"@metamask/rpc-errors@npm:^7.0.0, @metamask/rpc-errors@npm:^7.0.1, @metamask/rpc-errors@npm:^7.0.2": + version: 7.0.2 + resolution: "@metamask/rpc-errors@npm:7.0.2" dependencies: - "@metamask/utils": "npm:^10.0.0" + "@metamask/utils": "npm:^11.0.1" fast-safe-stringify: "npm:^2.0.6" - checksum: 10/819708b4a7d9695ee67fd867d8f94bb5a273b479a242b17bd53c83d1fceec421fc42928f0bb340f4f138ec803dd82ec9659ce7b09a86aedad6a81d5a39ec5c35 + checksum: 10/daf77a48b3f970585ef1f2efe3383d620fd4bffb550e8c6378b04a052f6948724a0b7e8a3e45b8b73298c70c4b9594b71fe0272664ea99620fe36e23443f8545 languageName: node linkType: hard @@ -6255,9 +6265,9 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-controllers@npm:^9.10.0, @metamask/snaps-controllers@npm:^9.15.0": - version: 9.15.0 - resolution: "@metamask/snaps-controllers@npm:9.15.0" +"@metamask/snaps-controllers@npm:^9.10.0, @metamask/snaps-controllers@npm:^9.16.0": + version: 9.16.0 + resolution: "@metamask/snaps-controllers@npm:9.16.0" dependencies: "@metamask/approval-controller": "npm:^7.1.1" "@metamask/base-controller": "npm:^7.0.2" @@ -6270,9 +6280,9 @@ __metadata: "@metamask/post-message-stream": "npm:^8.1.1" "@metamask/rpc-errors": "npm:^7.0.1" "@metamask/snaps-registry": "npm:^3.2.2" - "@metamask/snaps-rpc-methods": "npm:^11.7.0" - "@metamask/snaps-sdk": "npm:^6.13.0" - "@metamask/snaps-utils": "npm:^8.6.1" + "@metamask/snaps-rpc-methods": "npm:^11.8.0" + "@metamask/snaps-sdk": "npm:^6.14.0" + "@metamask/snaps-utils": "npm:^8.7.0" "@metamask/utils": "npm:^10.0.0" "@xstate/fsm": "npm:^2.0.0" browserify-zlib: "npm:^0.2.0" @@ -6286,30 +6296,30 @@ __metadata: semver: "npm:^7.5.4" tar-stream: "npm:^3.1.7" peerDependencies: - "@metamask/snaps-execution-environments": ^6.10.0 + "@metamask/snaps-execution-environments": ^6.11.0 peerDependenciesMeta: "@metamask/snaps-execution-environments": optional: true - checksum: 10/dd849398c4deefbca55b3b4a5e3fe885c45012cd8132bb83367d024c0c2dc99b13be036aa84049d5a1ba1431f9fd66897623f3961a34ebbbf70fe7bce4db322e + checksum: 10/f0a9efaad8fac2aa833edd5df6a4929a84de31f3e11457d407f39793c9ecd3b94eff543135729691b125c32f4290183375ae6416bc04e4aad31466517727af4f languageName: node linkType: hard -"@metamask/snaps-execution-environments@npm:^6.10.0": - version: 6.10.0 - resolution: "@metamask/snaps-execution-environments@npm:6.10.0" +"@metamask/snaps-execution-environments@npm:^6.11.0": + version: 6.11.0 + resolution: "@metamask/snaps-execution-environments@npm:6.11.0" dependencies: "@metamask/json-rpc-engine": "npm:^10.0.1" "@metamask/object-multiplex": "npm:^2.0.0" "@metamask/post-message-stream": "npm:^8.1.1" "@metamask/providers": "npm:^18.1.1" "@metamask/rpc-errors": "npm:^7.0.1" - "@metamask/snaps-sdk": "npm:^6.11.0" - "@metamask/snaps-utils": "npm:^8.6.0" + "@metamask/snaps-sdk": "npm:^6.14.0" + "@metamask/snaps-utils": "npm:^8.7.0" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^10.0.0" nanoid: "npm:^3.1.31" readable-stream: "npm:^3.6.2" - checksum: 10/a881696ec942f268d7485869fcb8c6bc0c278319bbfaf7e5c6099e86278c7f59049595f00ecfc27511d0106b5ad2f7621f734c7b17f088b835e38e638d80db01 + checksum: 10/3fc46e1b1d7e11996ce8c3694738d1cdab9b5d6c129a45e691b98f0d753e044869c3d0471729cba9e120bb2ff7ebd8e9aa644e608792903e74eee61213509b08 languageName: node linkType: hard @@ -6325,38 +6335,38 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-rpc-methods@npm:^11.7.0": - version: 11.7.0 - resolution: "@metamask/snaps-rpc-methods@npm:11.7.0" +"@metamask/snaps-rpc-methods@npm:^11.8.0": + version: 11.8.0 + resolution: "@metamask/snaps-rpc-methods@npm:11.8.0" dependencies: "@metamask/key-tree": "npm:^10.0.1" "@metamask/permission-controller": "npm:^11.0.3" "@metamask/rpc-errors": "npm:^7.0.1" - "@metamask/snaps-sdk": "npm:^6.13.0" - "@metamask/snaps-utils": "npm:^8.6.1" + "@metamask/snaps-sdk": "npm:^6.14.0" + "@metamask/snaps-utils": "npm:^8.7.0" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^10.0.0" "@noble/hashes": "npm:^1.3.1" - checksum: 10/92e4131d15b8dd68a29bd845e6c795aab8c3299048eaff2c3970db78a5eb476d8841f6a612b42e878812bb0757f2126287581f4e12259846851f02d6e6d836f5 + checksum: 10/a84c3648195efaeaeb021bd86c8a90e3f555236a8804cb2191778dddcf2acf7ea23ebc30f4670f6669dc881736c28f387b6ca2fc61050393411ee947a86cd47b languageName: node linkType: hard -"@metamask/snaps-sdk@npm:^6.13.0": - version: 6.13.0 - resolution: "@metamask/snaps-sdk@npm:6.13.0" +"@metamask/snaps-sdk@npm:^6.14.0": + version: 6.14.0 + resolution: "@metamask/snaps-sdk@npm:6.14.0" dependencies: "@metamask/key-tree": "npm:^10.0.1" "@metamask/providers": "npm:^18.1.1" "@metamask/rpc-errors": "npm:^7.0.1" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^10.0.0" - checksum: 10/115c738cb140810856ded055ac92a538c011adbd6a5f32a4e1fde42dcbd162c7eac182aab904de6b65af99b9520995d768627bd7f460da11d0aa359700e05b04 + checksum: 10/dafe8618418c607c5d962bbcf675324651254631791a257898f4a939c58a8b2f56a743dcff534aa7889662d5fb1a4dd1048558f4b025404e0dcd507ff5a5e89a languageName: node linkType: hard -"@metamask/snaps-utils@npm:^8.3.0, @metamask/snaps-utils@npm:^8.6.0, @metamask/snaps-utils@npm:^8.6.1": - version: 8.6.1 - resolution: "@metamask/snaps-utils@npm:8.6.1" +"@metamask/snaps-utils@npm:^8.3.0, @metamask/snaps-utils@npm:^8.7.0": + version: 8.7.0 + resolution: "@metamask/snaps-utils@npm:8.7.0" dependencies: "@babel/core": "npm:^7.23.2" "@babel/types": "npm:^7.23.0" @@ -6366,7 +6376,7 @@ __metadata: "@metamask/rpc-errors": "npm:^7.0.1" "@metamask/slip44": "npm:^4.0.0" "@metamask/snaps-registry": "npm:^3.2.2" - "@metamask/snaps-sdk": "npm:^6.13.0" + "@metamask/snaps-sdk": "npm:^6.14.0" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^10.0.0" "@noble/hashes": "npm:^1.3.1" @@ -6381,7 +6391,7 @@ __metadata: semver: "npm:^7.5.4" ses: "npm:^1.1.0" validate-npm-package-name: "npm:^5.0.0" - checksum: 10/d58e276b2849662e764a4ce5f45a03df361a5c65e9c09814e41b723407dffbd3a215626f5d9a8c4705a0cad73b702e10d76201174b44f92fbc68fdf59fb24d5d + checksum: 10/0681878e29c010853b610ed99569044feaa37b4cc92bafdba28b1eec68694d7779833fb4262a05a4d18182c6931d258531ed628e422d7c96567b61d1b710a95d languageName: node linkType: hard @@ -6436,9 +6446,9 @@ __metadata: languageName: node linkType: hard -"@metamask/transaction-controller@npm:^42.0.0": - version: 42.0.0 - resolution: "@metamask/transaction-controller@npm:42.0.0" +"@metamask/transaction-controller@npm:^42.1.0": + version: 42.1.0 + resolution: "@metamask/transaction-controller@npm:42.1.0" dependencies: "@ethereumjs/common": "npm:^3.2.0" "@ethereumjs/tx": "npm:^4.2.0" @@ -6446,13 +6456,13 @@ __metadata: "@ethersproject/abi": "npm:^5.7.0" "@ethersproject/contracts": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.0" - "@metamask/base-controller": "npm:^7.0.2" + "@metamask/base-controller": "npm:^7.1.0" "@metamask/controller-utils": "npm:^11.4.4" "@metamask/eth-query": "npm:^4.0.0" "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/nonce-tracker": "npm:^6.0.0" - "@metamask/rpc-errors": "npm:^7.0.1" - "@metamask/utils": "npm:^10.0.0" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/utils": "npm:^11.0.1" async-mutex: "npm:^0.5.0" bn.js: "npm:^5.2.1" eth-method-registry: "npm:^4.0.0" @@ -6466,7 +6476,7 @@ __metadata: "@metamask/eth-block-tracker": ">=9" "@metamask/gas-fee-controller": ^22.0.0 "@metamask/network-controller": ^22.0.0 - checksum: 10/73c510803a720b4c1da0b82f1279a404a9b11c4ab76f8e5e4378c65d5d08bbb32c52062abfe319476cc3f5e2623a8987775c4524e55aa94002af73d73721b869 + checksum: 10/9f842e2b68e84cbffdda301a0e15faab08226fd8e22eb954690ed41df60fe92c24acffdd9186b4c9f1da911a368cbe22cdb9ee046fc02d079c53f76100c66755 languageName: node linkType: hard @@ -6513,6 +6523,23 @@ __metadata: languageName: node linkType: hard +"@metamask/utils@npm:^11.0.1": + version: 11.0.1 + resolution: "@metamask/utils@npm:11.0.1" + dependencies: + "@ethereumjs/tx": "npm:^4.2.0" + "@metamask/superstruct": "npm:^3.1.0" + "@noble/hashes": "npm:^1.3.1" + "@scure/base": "npm:^1.1.3" + "@types/debug": "npm:^4.1.7" + debug: "npm:^4.3.4" + pony-cause: "npm:^2.1.10" + semver: "npm:^7.5.4" + uuid: "npm:^9.0.1" + checksum: 10/3949d16c8021bfb5f70e3b1c99f097ffaf43158116734197b039b32be6aabecb12178deb62c0b182e45295b0865618636324020059821c5b053029d8bdc90d70 + languageName: node + linkType: hard + "@metamask/utils@npm:^8.1.0, @metamask/utils@npm:^8.2.0, @metamask/utils@npm:^8.3.0": version: 8.5.0 resolution: "@metamask/utils@npm:8.5.0" @@ -26597,7 +26624,7 @@ __metadata: "@metamask/eslint-config-typescript": "npm:^9.0.1" "@metamask/eslint-plugin-design-tokens": "npm:^1.1.0" "@metamask/eth-json-rpc-filters": "npm:^9.0.0" - "@metamask/eth-json-rpc-middleware": "npm:^15.0.1" + "@metamask/eth-json-rpc-middleware": "npm:^15.1.2" "@metamask/eth-json-rpc-provider": "npm:^4.1.6" "@metamask/eth-ledger-bridge-keyring": "npm:^5.0.1" "@metamask/eth-query": "npm:^4.0.0" @@ -26636,7 +26663,7 @@ __metadata: "@metamask/post-message-stream": "npm:^8.0.0" "@metamask/ppom-validator": "npm:0.36.0" "@metamask/preferences-controller": "npm:^15.0.1" - "@metamask/preinstalled-example-snap": "npm:^0.2.0" + "@metamask/preinstalled-example-snap": "npm:^0.3.0" "@metamask/profile-sync-controller": "npm:^3.1.1" "@metamask/providers": "npm:^18.2.0" "@metamask/queued-request-controller": "npm:^7.0.1" @@ -26648,15 +26675,15 @@ __metadata: "@metamask/selected-network-controller": "npm:^19.0.0" "@metamask/signature-controller": "npm:^23.1.0" "@metamask/smart-transactions-controller": "npm:^16.0.0" - "@metamask/snaps-controllers": "npm:^9.15.0" - "@metamask/snaps-execution-environments": "npm:^6.10.0" - "@metamask/snaps-rpc-methods": "npm:^11.7.0" - "@metamask/snaps-sdk": "npm:^6.13.0" - "@metamask/snaps-utils": "npm:^8.6.1" + "@metamask/snaps-controllers": "npm:^9.16.0" + "@metamask/snaps-execution-environments": "npm:^6.11.0" + "@metamask/snaps-rpc-methods": "npm:^11.8.0" + "@metamask/snaps-sdk": "npm:^6.14.0" + "@metamask/snaps-utils": "npm:^8.7.0" "@metamask/solana-wallet-snap": "npm:^1.0.4" "@metamask/test-bundler": "npm:^1.0.0" "@metamask/test-dapp": "npm:8.13.0" - "@metamask/transaction-controller": "npm:^42.0.0" + "@metamask/transaction-controller": "npm:^42.1.0" "@metamask/user-operation-controller": "npm:^21.0.0" "@metamask/utils": "npm:^10.0.1" "@ngraveio/bc-ur": "npm:^1.1.12"