From 52fc5fc688ef0ac3105b1bb9dd0cc2a9a85cba52 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Fri, 20 Dec 2024 17:43:01 +0000 Subject: [PATCH 1/2] fix: remove reliance on transaction decode in confirmations (#29341) --- 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 a738b56b0fe0..50fd55d9ee78 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -6378,9 +6378,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 7992443a500a..34962464e64a 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -6378,9 +6378,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 12df50e257d7..f435cd4a1eea 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -6293,6 +6293,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." }, @@ -6659,9 +6662,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 2475977d0794..d32128e8ca43 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -6378,9 +6378,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 abe83f8ea900..728dd0a8bf2d 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -6378,9 +6378,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 4eaf54f8b012..863257ee1178 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -6378,9 +6378,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 c93bd496ac12..418ca4b60489 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -6378,9 +6378,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 24a2f60314cd..41e34d26dd2d 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -6379,9 +6379,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 c261f74b4b5c..8c0f26b8cad7 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -6379,9 +6379,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 5a1389a0d4d6..77312d33bb07 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -6379,9 +6379,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 37a672ec798a..89ba6f0d0bd9 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -6378,9 +6378,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 20b318456d77..d235dc123943 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -6379,9 +6379,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 470bb934f74b..cf65b516f6eb 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -6379,9 +6379,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 d331ab42b9b5..024a91858cc2 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -6379,9 +6379,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 52941dcc24f9..719d2f102faf 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -6379,9 +6379,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 810477d3a3a5..fb2ef9cf2629 100644 --- a/test/integration/confirmations/transactions/increase-allowance.test.tsx +++ b/test/integration/confirmations/transactions/increase-allowance.test.tsx @@ -198,7 +198,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'); }); @@ -225,7 +225,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', ); @@ -286,7 +286,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 ebe680983a6c..51cb991ac3b2 100644 --- a/test/integration/confirmations/transactions/set-approval-for-all.test.tsx +++ b/test/integration/confirmations/transactions/set-approval-for-all.test.tsx @@ -231,7 +231,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 2ff281c5186e..5bec49097c07 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 914e276369f5..bf26a6e1abc0 100644 --- a/ui/pages/confirmations/components/confirm/info/approve/approve.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/approve/approve.test.tsx @@ -1,8 +1,9 @@ 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 { genUnapprovedApproveConfirmation } from '../../../../../../../test/data/confirmations/token-approve'; import ApproveInfo from './approve'; jest.mock('../../../../../../store/actions', () => ({ @@ -72,7 +73,9 @@ describe('', () => { const middleware = [thunk]; 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 acd470ba8822..442bfca3881c 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,13 +1,11 @@ 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 { useTokenTransactionData } from '../../hooks/useTokenTransactionData'; import { useIsNFT } from './use-is-nft'; const UNLIMITED_THRESHOLD = 10 ** 15; @@ -22,30 +20,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), - ).toFixed(); - }, [value, decimals]); + const decodedSpendingCap = calcTokenAmount( + value, + Number(decimals ?? '0'), + ).toFixed(); const tokenPrefix = isNFT ? '#' : ''; @@ -70,6 +56,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 4fe5c3f41bbd..bd992e954422 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 9c12b9127811..b9752da7f1a8 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, @@ -9,33 +9,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 1af918aea74e..f0f2f6fbeade 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')}
From 7af9f6083e89c03e50e2c3094ab413317bed935a Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Fri, 20 Dec 2024 18:13:08 +0000 Subject: [PATCH 2/2] Fix unit test --- .../components/confirm/info/__snapshots__/info.test.tsx.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5bec49097c07..480ef57d7097 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 @@ -80,7 +80,7 @@ exports[`Info renders info section for approve request 1`] = ` class="mm-box mm-text mm-text--body-md mm-text--text-align-center mm-box--padding-inline-2 mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-xl" data-testid="simulation-token-value" > - #0.0001 + #1