From 1c6391ce0f5a7ff8777b2b2e9a13d988e2296d87 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Fri, 20 Dec 2024 13:18:11 +0000 Subject: [PATCH] fix: remove reliance on transaction decode in confirmations (#29341) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Disable all advanced transaction data decoding using Sourcify, 4Byte and Uniswap, if the `Decode smart contracts` toggle is disabled. Remove all reliance on the advanced decoding excluding the `Data` section. Specifically: - Create `useTokenTransactionData` hook to decode all token transactions locally using the ABIs. - Replace all usages of `useDecodedTransactionData` with the new hook, except for the `TransactionData` component. - Update related unit and integration tests to decode valid test data rather than rely on mocking hooks. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29341?quickstart=1) ## **Related issues** ## **Manual testing steps** Regression of redesigned transaction confirmations. Specifically: - Token Transfer Recipient - Token Transfer Amount - Approval Spender - Approval Spending Cap ## **Screenshots/Recordings** ### **Before** ### **After** New Toggle Description ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- app/_locales/de/messages.json | 3 - app/_locales/el/messages.json | 3 - app/_locales/en/messages.json | 6 +- app/_locales/es/messages.json | 3 - app/_locales/fr/messages.json | 3 - app/_locales/hi/messages.json | 3 - app/_locales/id/messages.json | 3 - app/_locales/ja/messages.json | 3 - app/_locales/ko/messages.json | 3 - app/_locales/pt/messages.json | 3 - app/_locales/ru/messages.json | 3 - app/_locales/tl/messages.json | 3 - app/_locales/tr/messages.json | 3 - app/_locales/vi/messages.json | 3 - app/_locales/zh_CN/messages.json | 3 - .../confirmations/set-approval-for-all.ts | 5 +- test/data/confirmations/token-approve.ts | 4 +- test/data/confirmations/token-transfer.ts | 7 +- .../transactions/increase-allowance.test.tsx | 6 +- .../set-approval-for-all.test.tsx | 2 +- ui/helpers/constants/settings.js | 2 +- .../info/__snapshots__/info.test.tsx.snap | 304 +++++++++++++++- .../approve-details.test.tsx.snap | 336 +++++++++++++----- .../approve-details/approve-details.test.tsx | 22 +- .../approve-details/approve-details.tsx | 22 +- .../confirm/info/approve/approve.test.tsx | 7 +- .../use-approve-token-simulation.test.ts | 173 ++------- .../hooks/use-approve-token-simulation.ts | 36 +- .../info/hooks/use-token-values.test.ts | 98 ++--- .../confirm/info/hooks/use-token-values.ts | 68 +--- .../hooks/useDecodedTransactionData.test.ts | 26 ++ .../info/hooks/useDecodedTransactionData.ts | 12 +- .../hooks/useTokenTransactionData.test.ts | 94 +++++ .../info/hooks/useTokenTransactionData.ts | 14 + .../native-transfer.test.tsx.snap | 175 +++++++++ .../nft-token-transfer.test.tsx.snap | 175 +++++++++ .../set-approval-for-all-info.test.tsx.snap | 100 +++++- .../set-approval-for-all-info.test.tsx | 5 - .../set-approval-for-all-info.tsx | 17 +- .../__snapshots__/send-heading.test.tsx.snap | 58 ++- .../shared/send-heading/send-heading.test.tsx | 6 + .../info/shared/send-heading/send-heading.tsx | 6 - .../token-transfer.test.tsx.snap | 239 ++++++++++--- .../token-transfer/token-transfer.test.tsx | 8 +- .../transaction-flow-section.test.tsx | 45 +-- .../transaction-flow-section.tsx | 15 +- .../components/confirm/info/utils.test.ts | 33 +- .../components/confirm/info/utils.ts | 12 +- .../components/confirm/title/title.tsx | 27 +- .../privacy-settings/privacy-settings.js | 2 +- .../__snapshots__/security-tab.test.js.snap | 2 +- .../security-tab/security-tab.component.js | 2 +- 52 files changed, 1542 insertions(+), 671 deletions(-) create mode 100644 ui/pages/confirmations/components/confirm/info/hooks/useTokenTransactionData.test.ts create mode 100644 ui/pages/confirmations/components/confirm/info/hooks/useTokenTransactionData.ts diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 9ad22ea297e0..5a499ce7dc6d 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -6335,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 aa5a819d532b..d930bafaa3d7 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -6335,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 e8d8fe2779f9..30475784e64a 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -6259,6 +6259,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." }, @@ -6622,9 +6625,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/es/messages.json b/app/_locales/es/messages.json index 2c6fa0a29885..bfb53061f73b 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -6335,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/fr/messages.json b/app/_locales/fr/messages.json index 1c8436635797..a86d465b786d 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -6335,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/hi/messages.json b/app/_locales/hi/messages.json index 8d115d214652..144db40e41a3 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -6335,9 +6335,6 @@ "use4ByteResolution": { "message": "स्मार्ट कॉन्ट्रैक्ट्स को डीकोड करें" }, - "use4ByteResolutionDescription": { - "message": "यूज़र के अनुभव को बेहतर बनाने के लिए, आपके द्वारा इंटरैक्ट किए गए स्मार्ट कॉन्ट्रैक्ट्स के आधार पर हम एक्टिविटी टैब को मैसेज के साथ कस्टमाइज़ करते हैं। डेटा को डीकोड करने और आसानी से पढ़े जा सकने वाले स्मार्ट कॉन्ट्रैक्ट्स का एक वर्शन आपको दिखाने के लिए MetaMask एक सर्विस इस्तेमाल करता है जिसका नाम 4byte.directory है। इससे आपके द्वारा बुरी नीयत वाले स्मार्ट कॉन्ट्रैक्ट एक्शन को मंजूरी देने की संभावनाओं को कम करने में मदद मिलती है। हालांकि, इसमें आपका IP एड्रेस शेयर होने का खतरा हो सकता है।" - }, "useMultiAccountBalanceChecker": { "message": "अकाउंट के बैलेंस के रिक्वेस्ट्स को बैच करें" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index fb488c423c61..44b2bf804727 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -6335,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/ja/messages.json b/app/_locales/ja/messages.json index 35c812fa53f0..70d6da31301e 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -6335,9 +6335,6 @@ "use4ByteResolution": { "message": "スマートコントラクトのデコード" }, - "use4ByteResolutionDescription": { - "message": "ユーザーエクスペリエンスの向上のため、ユーザーがやり取りするスマートコントラクトに応じたメッセージで、アクティビティタブをカスタマイズします。MetaMaskは、4byte.directoryと呼ばれるサービスを利用してデータをデコードし、より読みやすいバージョンのスマートコントラクトを表示します。これにより、悪質なスマートコントラクトの操作を承認する可能性は減りますが、IPアドレスが公開されます。" - }, "useMultiAccountBalanceChecker": { "message": "アカウント残高の一括リクエスト" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 158a8a333c15..6978164f82f4 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -6335,9 +6335,6 @@ "use4ByteResolution": { "message": "스마트 계약 디코딩" }, - "use4ByteResolutionDescription": { - "message": "인터렉션하는 스마트 계약에 따라 메시지를 이용하여 활동 탭을 사용자 맞춤하여 사용자 경험을 개선합니다. MetaMask는 4byte.directory라는 서비스를 통해 데이터를 디코딩하여 스마트 계약을 읽기 쉬운 버전으로 보여 줍니다. 이는 악의적인 스마트 계약을 승인할 가능성을 줄이는 데 도움이 되지만 IP 주소가 공유될 수 있습니다." - }, "useMultiAccountBalanceChecker": { "message": "일괄 계정 잔액 요청" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index e4fe18f9bab9..7819be595e94 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -6335,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/ru/messages.json b/app/_locales/ru/messages.json index 984223a22503..5e281f34ac05 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -6335,9 +6335,6 @@ "use4ByteResolution": { "message": "Расшифровать смарт-контракты" }, - "use4ByteResolutionDescription": { - "message": "Чтобы улучшить взаимодействие с пользователем, мы настраиваем вкладку действий, добавляя сообщения на основе смарт-контрактов, с которыми вы взаимодействуете. MetaMask использует службу под названием 4byte.directory для декодирования данных и показа версии смарт-контакта, которую легче читать. Это помогает снизить шансы того, что вы одобрите вредоносные действия смарт-контракта, но может привести к раскрытию вашего IP-адреса." - }, "useMultiAccountBalanceChecker": { "message": "Пакетные запросы баланса счета" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 04650491ac24..5a6bce602970 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -6335,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 06cba108c1e0..046905392710 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -6335,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/vi/messages.json b/app/_locales/vi/messages.json index 6f9b8264e82a..a6baaff586aa 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -6335,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 2f011db8e804..5472a92a5660 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -6335,9 +6335,6 @@ "use4ByteResolution": { "message": "对智能合约进行解码" }, - "use4ByteResolutionDescription": { - "message": "为了改善用户体验,我们根据与您交互的智能合约消息,自定义活动选项卡。MetaMask 使用名为 4byte.directory 的服务来对数据进行解码,并向您显示更方便阅读的智能合约版本。这有助于减少您批准恶意智能合约操作的机会,但可能导致您的 IP 地址被共享。" - }, "useMultiAccountBalanceChecker": { "message": "账户余额分批请求" }, 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/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/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/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/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/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/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/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')}