1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-21 17:37:01 +01:00

Token detection V2 Flag Removal and Re-introducing the use of legacy token list when token detection is OFF (#15138)

* addding the legacy tokenlist, tuning token detection OFF by default, adding new message while importing tokens

updating the controller version and calling detectNewToken on network change

fixing rebase error

Run yarn lavamoat:auto for updating policies

updating lavamoat

Deleted node modules and run again lavamoat auto

fixing rebase issues

updating lavamoat policies

updating lavamoat after rebasing

policies

updating custom token warning and blocking detectedtoken link when tpken detection is off for supported networks

to update the token in fetchTosync

updating the contract map object

Revert build-system lavamoat policy changes

Move token list selection logic from components to getTokenList selector

updating the tokenList

Update lavamoat

Fix error

updating lavamoat

lint fix

fix unit test fail

fix unit test fail

lint fix

fixing rebase locale error

rebase fix

Revert build-system policy changes

temp

addressing review comments

* rebase fix
This commit is contained in:
Niranjana Binoy 2022-08-09 21:26:25 -04:00 committed by GitHub
parent d21a8a1cfb
commit 6e5c2f03bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 681 additions and 958 deletions

View File

@ -1203,9 +1203,6 @@
"failureMessage": {
"message": "Etwas ist schief gelaufen und wir konnten die Aktion nicht abschließen"
},
"fakeTokenWarning": {
"message": "Jeder kann ein Token erstellen, einschließlich der Erstellung gefälschter Versionen bestehender Token. Erfahren Sie mehr über $1"
},
"fast": {
"message": "Schnell"
},
@ -3683,12 +3680,6 @@
"tokenDetectionAlertMessage": {
"message": "Die Token-Erkennung ist derzeit für $1 verfügbar. $2"
},
"tokenDetectionAnnouncement": {
"message": "Neu! Verbesserte Token Erkennung ist im Ethereum Mainnet als experimentelle Funktion verfügbar. $1"
},
"tokenDetectionToggleDescription": {
"message": "Die Token-API von ConsenSys sammelt eine Liste von Token aus verschiedenen Token-Listen von Drittanbietern. Wenn Sie diese Funktion deaktivieren, werden keine neuen Token mehr erkannt, die zu Ihrer Wallet hinzugefügt werden, aber die Option zur Suche nach Token für den Import bleibt erhalten."
},
"tokenId": {
"message": "Token-ID"
},
@ -3923,12 +3914,6 @@
"usePhishingDetectionDescription": {
"message": "Zeigt eine Warnung für Phishing-Domänen, die Ethereum Benutzer ansprechen"
},
"useTokenDetection": {
"message": "Token-Erkennung verwenden"
},
"useTokenDetectionDescription": {
"message": "Wir verwenden Drittanbieter-API, um neue an Ihre Wallet gesendete Token zu erkennen und anzuzeigen. Deaktivieren Sie diese, wenn Sie nicht möchten, dass MetaMask Daten von diesen Diensten abruft."
},
"useTokenDetectionPrivacyDesc": {
"message": "Die automatische Anzeige der an Ihr Konto gesendeten Token erfordert die Kommunikation mit Servern von Drittanbietern, um die Bilder der Token abzurufen. Diese Server haben Zugriff auf Ihre IP-Adresse."
},

View File

@ -1211,9 +1211,6 @@
"failureMessage": {
"message": "Κάτι πήγε λάθος και δεν μπορέσαμε να ολοκληρώσουμε την ενέργεια"
},
"fakeTokenWarning": {
"message": "Οποιοσδήποτε μπορεί να δημιουργήσει ένα token, συμπεριλαμβανομένης της δημιουργίας ψεύτικων εκδόσεων των υφιστάμενων tokens. Μάθετε περισσότερα γι'αυτό $1"
},
"fast": {
"message": "Γρήγορα"
},
@ -3739,12 +3736,6 @@
"tokenDetectionAlertMessage": {
"message": "Ο εντοπισμός token είναι επί του παρόντος διαθέσιμος στο $1. $2"
},
"tokenDetectionAnnouncement": {
"message": "Νέο! Η βελτιωμένη ανίχνευση token είναι διαθέσιμη στο Ethereum Mainnet ως πειραματικό χαρακτηριστικό. $1"
},
"tokenDetectionToggleDescription": {
"message": "Το token API της ConsenSys δημιουργεί μια λίστα με token από διάφορες λίστες token τρίτων. Εάν το απενεργοποιήσετε, θα σταματήσει ο εντοπισμός νέων token που προστίθενται στο πορτοφόλι σας, αλλά θα διατηρηθεί η επιλογή αναζήτησης token για εισαγωγή."
},
"tokenId": {
"message": "Αναγνωριστικό token"
},
@ -3979,12 +3970,6 @@
"usePhishingDetectionDescription": {
"message": "Εμφάνιση μιας προειδοποίησης για τομείς Απάτης Ηλεκτρονικού Ψαρέματος που στοχεύουν χρήστες του Ethereum"
},
"useTokenDetection": {
"message": "Χρήση Ανίχνευσης Token"
},
"useTokenDetectionDescription": {
"message": "Χρησιμοποιούμε API τρίτων για να εντοπίσουμε και να εμφανίσουμε νέα tokens που αποστέλλονται στο πορτοφόλι σας. Απενεργοποιήστε αν δεν θέλετε το MetaMask να τραβήξει δεδομένα από αυτές τις υπηρεσίες."
},
"useTokenDetectionPrivacyDesc": {
"message": "Η αυτόματη εμφάνιση των token που αποστέλλονται στον λογαριασμό σας συνεπάγεται επικοινωνία με διακομιστές τρίτων για τη λήψη εικόνων των token. Αυτοί οι διακομιστές θα έχουν πρόσβαση στη διεύθυνση IP σας."
},

View File

@ -837,6 +837,9 @@
"customTokenWarningInTokenDetectionNetwork": {
"message": "Before manually importing a token, make sure you trust it. Learn about $1."
},
"customTokenWarningInTokenDetectionNetworkWithTDOFF": {
"message": "Make sure you trust a token before you import it. Learn how to avoid $1. You can also enable token detection $2."
},
"customerSupport": {
"message": "customer support"
},
@ -1284,9 +1287,6 @@
"failureMessage": {
"message": "Something went wrong, and we were unable to complete the action"
},
"fakeTokenWarning": {
"message": "Anyone can create a token, including creating fake versions of existing tokens. Learn more about $1"
},
"fast": {
"message": "Fast"
},
@ -1602,6 +1602,12 @@
"importNFTs": {
"message": "Import NFTs"
},
"importSelectedTokens": {
"message": "Import selected tokens?"
},
"importSelectedTokensDescription": {
"message": "Only the tokens you've selected will appear in your wallet. You can always import hidden tokens later by searching for them."
},
"importTokenQuestion": {
"message": "Import token?"
},
@ -1628,6 +1634,9 @@
"message": "Imported",
"description": "status showing that an account has been fully loaded into the keyring"
},
"inYourSettings": {
"message": "in your Settings"
},
"infuraBlockedNotification": {
"message": "MetaMask is unable to connect to the blockchain host. Review possible reasons $1.",
"description": "$1 is a clickable link with with text defined by the 'here' key"
@ -3847,11 +3856,8 @@
"tokenDetectionAlertMessage": {
"message": "Token detection is currently available on $1. $2"
},
"tokenDetectionAnnouncement": {
"message": "New! Improved token detection is available on Ethereum Mainnet as an experimental feature. $1"
},
"tokenDetectionToggleDescription": {
"message": "ConsenSys token API aggregates a list of tokens from various third party token lists. Turning it off will stop detecting new tokens added to your wallet, but will keep the option to search for tokens to import."
"tokenDetectionDescription": {
"message": "ConsenSys' token API aggregates a list of tokens from various third party token lists. When turned on, tokens will be automatically detected, and searchable, on Ethereum mainnet, Binance, Polygon and Avalanche. When turned off, you will still be able to search for tokens on Ethereum mainnet using MetaMask's legacy token list."
},
"tokenId": {
"message": "Token ID"
@ -3859,6 +3865,9 @@
"tokenList": {
"message": "Token lists:"
},
"tokenScamSecurityRisk": {
"message": "token scams and security risks"
},
"tokenSymbol": {
"message": "Token symbol"
},
@ -4091,12 +4100,6 @@
"usePhishingDetectionDescription": {
"message": "Display a warning for phishing domains targeting Ethereum users"
},
"useTokenDetection": {
"message": "Use token detection"
},
"useTokenDetectionDescription": {
"message": "We use third-party APIs to detect and display new tokens sent to your wallet. Turn off if you dont want MetaMask to pull data from those services."
},
"useTokenDetectionPrivacyDesc": {
"message": "Automatically displaying tokens sent to your account involves communication with third party servers to fetch tokens images. Those serves will have access to your IP address."
},

View File

@ -1211,9 +1211,6 @@
"failureMessage": {
"message": "Se produjo un error y no pudimos completar la acción"
},
"fakeTokenWarning": {
"message": "Cualquiera puede crear un token, incluso crear versiones falsas de tokens existentes. Aprenda más sobre $1"
},
"fast": {
"message": "Rápido"
},
@ -3739,12 +3736,6 @@
"tokenDetectionAlertMessage": {
"message": "La detección de tókens está actualmente disponible en $1. $2"
},
"tokenDetectionAnnouncement": {
"message": "¡Nuevo! La detección de tokens mejorada está disponible en la Mainnet de Ethereum como funcionalidad experimental. $1"
},
"tokenDetectionToggleDescription": {
"message": "La API de tokens de ConsenSys agrega una lista de tokens de varias listas de tokens de terceros. Al desactivarla se dejará de detectar nuevos tokens agregados a su billetera, pero se mantendrá la opción de buscar tokens para importar."
},
"tokenId": {
"message": "Identificador de Token"
},
@ -3979,12 +3970,6 @@
"usePhishingDetectionDescription": {
"message": "Mostrar una advertencia respecto de los dominios de phishing dirigidos a los usuarios de Ethereum"
},
"useTokenDetection": {
"message": "Usar detección de token"
},
"useTokenDetectionDescription": {
"message": "Utilizamos API de terceros para detectar y mostrar nuevos tokens enviados a su cartera. Desactive si no desea que MetaMask extraiga datos de esos servicios."
},
"useTokenDetectionPrivacyDesc": {
"message": "La visualización automática de tokens enviados a su cuenta implica la comunicación con servidores de terceros para obtener imágenes de tokens. Esos servicios tendrán acceso a su dirección IP."
},

View File

@ -1047,9 +1047,6 @@
"failureMessage": {
"message": "Se produjo un error y no pudimos finalizar la acción"
},
"fakeTokenWarning": {
"message": "Cualquiera puede crear un token, incluso crear versiones falsas de tokens existentes. Aprenda más sobre $1"
},
"fast": {
"message": "Rápido"
},
@ -3021,9 +3018,6 @@
"tokenDecimalFetchFailed": {
"message": "Se requieren los decimales del token."
},
"tokenDetectionAnnouncement": {
"message": "¡Nuevo! La detección de tokens mejorada está disponible en la Mainnet de Ethereum como funcionalidad experimental. $1"
},
"tokenId": {
"message": "ID del token"
},
@ -3236,12 +3230,6 @@
"usePhishingDetectionDescription": {
"message": "Mostrar una advertencia respecto de los dominios de phishing dirigidos a los usuarios de Ethereum"
},
"useTokenDetection": {
"message": "Usar detección de token"
},
"useTokenDetectionDescription": {
"message": "Utilizamos API de terceros para detectar y mostrar nuevos tokens enviados a su cartera. Desactive si no desea que MetaMask extraiga datos de esos servicios."
},
"usedByClients": {
"message": "Usado por una variedad de clientes distintos"
},

View File

@ -1211,9 +1211,6 @@
"failureMessage": {
"message": "Un problème est survenu et nous navons pas pu mener à bien laction"
},
"fakeTokenWarning": {
"message": "Tout un chacun peut créer un jeton, y compris créer de fausses copies de jetons existants. En savoir plus sur $1"
},
"fast": {
"message": "Rapide"
},
@ -3739,12 +3736,6 @@
"tokenDetectionAlertMessage": {
"message": "La détection du token est actuellement disponible sur $1. $2"
},
"tokenDetectionAnnouncement": {
"message": "Nouveau ! Une détection améliorée des jetons est disponible sur le Mainnet dEthereum en tant que fonctionnalité expérimentale. $1"
},
"tokenDetectionToggleDescription": {
"message": "LAPI des tokens de ConsenSys regroupe une liste de tokens provenant de diverses listes de tokens externes. Sa désactivation arrêtera la détection de nouveaux tokens ajoutés à votre portefeuille, mais conservera loption de recherche de tokens à importer."
},
"tokenId": {
"message": "ID de token"
},
@ -3979,12 +3970,6 @@
"usePhishingDetectionDescription": {
"message": "Cela permet dafficher un avertissement pour les domaines dhameçonnage ciblant les utilisateurs dEthereum"
},
"useTokenDetection": {
"message": "Utiliser la détection des jetons"
},
"useTokenDetectionDescription": {
"message": "Nous utilisons des API tierces pour détecter et afficher les nouveaux jetons envoyés à votre portefeuille. Désactivez cette option si vous ne souhaitez pas que MetaMask récupère les données de ces services."
},
"useTokenDetectionPrivacyDesc": {
"message": "Laffichage automatique des tokens envoyés sur votre compte implique une communication avec des serveurs externes afin de récupérer les images des tokens. Ces serveurs auront accès à votre adresse IP."
},

View File

@ -1211,9 +1211,6 @@
"failureMessage": {
"message": "कुछ गलत हुआ और हम कार्रवाई को पूरा करने में असमर्थ रहे"
},
"fakeTokenWarning": {
"message": "कोई भी टोकन बना सकता है, जिसमें मौजूदा टोकन के नकली संस्करण को बनाना शामिल है। $1 के बारे में और अधिक जानें"
},
"fast": {
"message": "तेज"
},
@ -3739,12 +3736,6 @@
"tokenDetectionAlertMessage": {
"message": "फिलहाल टोकन डिटेक्शन $1 पर उपलब्ध है। $2"
},
"tokenDetectionAnnouncement": {
"message": "नया! प्रायोगिक फीचर के रूप में Ethereum Mainnet पर बेहतर टोकन डिटेक्शन उपलब्ध है। $1"
},
"tokenDetectionToggleDescription": {
"message": "ConsenSys के टोकन का एपीआई विभिन्न थर्ड पार्टी टोकन सूचियों में से टोकन की एक सूची एकत्र करता है। इसे बंद करने से आपके वॉलेट में जोड़े गए नए टोकन का पता चलना बंद हो जाएगा, लेकिन इंपोर्ट करने के लिए टोकन खोजने का विकल्प बना रहेगा।"
},
"tokenId": {
"message": "टोकन आइडी"
},
@ -3979,12 +3970,6 @@
"usePhishingDetectionDescription": {
"message": "Ethereum उपयोगकर्ताओं को लक्षित करने वाले फिशिंग डोमेन के लिए एक चेतावनी प्रदर्शित करें"
},
"useTokenDetection": {
"message": "टोकन डिटेक्शन का उपयोग करें"
},
"useTokenDetectionDescription": {
"message": "हम आपके वॉलेट में भेजे गए नए टोकन का पता लगाने और प्रदर्शित करने के लिए तीसरे-पक्ष API का उपयोग करते हैं। बंद करें यदि आप नहीं चाहते कि MetaMask उन सेवाओं से डेटा पुल करे।"
},
"useTokenDetectionPrivacyDesc": {
"message": "आपके खाते में भेजे गए टोकन को स्वचालित रूप से प्रदर्शित करने में थर्ड पार्टी के सर्वर्स के साथ संचार शामिल रहेगा, जो टोकन के चित्रों को लाने का काम करते हैं। वे सर्वर्स आपके IP एड्रेस को एक्सेस कर पाएंगे।"
},

View File

@ -1211,9 +1211,6 @@
"failureMessage": {
"message": "Ada yang salah, dan kami tidak dapat menyelesaikan tindakan"
},
"fakeTokenWarning": {
"message": "Siapa pun dapat membuat token, termasuk membuat versi palsu dari token yang ada. Pelajari selengkapnya seputar $1"
},
"fast": {
"message": "Cepat"
},
@ -3739,12 +3736,6 @@
"tokenDetectionAlertMessage": {
"message": "Saat ini deteksi token tersedia di $1. $2"
},
"tokenDetectionAnnouncement": {
"message": "Baru! Deteksi token yang ditingkatkan tersedia di Ethereum Mainnet sebagai fitur eksperimental. $1"
},
"tokenDetectionToggleDescription": {
"message": "API token ConsenSys mengumpulkan daftar token dari berbagai daftar token pihak ketiga. Menonaktifkannya akan menghentikan deteksi token baru yang ditambahkan ke dompet Anda, tetapi Anda akan tetap memiliki opsi untuk mencari token yang akan diimpor."
},
"tokenId": {
"message": "ID token"
},
@ -3979,12 +3970,6 @@
"usePhishingDetectionDescription": {
"message": "Menampilkan peringatan untuk domain pengelabuan yang menargetkan pengguna Ethereum"
},
"useTokenDetection": {
"message": "Gunakan Deteksi Token"
},
"useTokenDetectionDescription": {
"message": "Kami menggunakan API pihak ketiga untuk mendeteksi dan menampilkan token baru yang dikirim ke dompet Anda. Matikan jika Anda tidak ingin MetaMask memakai data dari layanan tersebut."
},
"useTokenDetectionPrivacyDesc": {
"message": "Menampilkan token yang dikirim ke akun Anda secara otomatis yang melibatkan komunikasi dengan server pihak ketiga untuk mengambil gambar token. Server tersebut akan memiliki akses ke alamat IP Anda."
},

View File

@ -1211,9 +1211,6 @@
"failureMessage": {
"message": "問題が発生しました。アクションを完了させることができません"
},
"fakeTokenWarning": {
"message": "既存のトークンの偽のバージョンの作成を含め、誰でもトークンを作成できます。$1に関する詳細をご覧ください"
},
"fast": {
"message": "高速"
},
@ -3739,12 +3736,6 @@
"tokenDetectionAlertMessage": {
"message": "トークン検出は現在 $1 で利用可能です。$2"
},
"tokenDetectionAnnouncement": {
"message": "新機能! 実験的な機能として、Ethereum Mainnetでのトークン検出が改善されました。$1"
},
"tokenDetectionToggleDescription": {
"message": "ConsenSys のトークン API は、さまざまなサードパーティのトークンリストからトークンのリストを集積します。これをオフにすると、ウォレットに追加された新しいトークンは検出されなくなりますが、引き続きトークンを検索してインポートすることは可能です。"
},
"tokenId": {
"message": "トークン ID"
},
@ -3979,12 +3970,6 @@
"usePhishingDetectionDescription": {
"message": "イーサリアムユーザーを対象としたドメインのフィッシングに対して警告を表示します"
},
"useTokenDetection": {
"message": "トークン検出を使用"
},
"useTokenDetectionDescription": {
"message": "弊社はユーザーのウォレットに送信された新しいトークンを検出して表示するために、サードパーティーAPIを使用します。MetaMaskにこれらのサービスからデータを取得させたくない場合は、この機能をオフにしてください。"
},
"useTokenDetectionPrivacyDesc": {
"message": "アカウントに送られたトークンを自動的に表示するには、サードパーティーサーバーと通信し、トークンの画像を取得する必要があります。これらのサーバーはユーザーの IP アドレスにアクセスできます。"
},

View File

@ -1211,9 +1211,6 @@
"failureMessage": {
"message": "문제가 발생했습니다. 작업을 완료할 수 없습니다."
},
"fakeTokenWarning": {
"message": "기존 토큰의 가짜 버전 생성을 포함하여 누구나 토큰을 생성할 수 있습니다. $1에 대해 자세히 알아보기"
},
"fast": {
"message": "빠름"
},
@ -3739,12 +3736,6 @@
"tokenDetectionAlertMessage": {
"message": "토큰 감지 기능은 현재 $1. $2에서 사용할 수 있습니다."
},
"tokenDetectionAnnouncement": {
"message": "신규! 개선된 토큰 감지는 실험적 기능으로 이더리움 메인넷에서 사용할 수 있습니다. $1"
},
"tokenDetectionToggleDescription": {
"message": "ConsenSys의 토큰 API는 타사의 다양한 목록을 모아 하나의 토큰 목록을 작성합니다. 이 API를 끄면 신규 토큰을 감지해서 지갑에 추가하는 기능은 중단되지만 토큰을 검색해서 가져오는 기능은 계속 사용할 수 있습니다."
},
"tokenId": {
"message": "토큰 ID"
},
@ -3979,12 +3970,6 @@
"usePhishingDetectionDescription": {
"message": "이더리움 사용자를 노리는 피싱 도메인에 대한 경고를 표시합니다"
},
"useTokenDetection": {
"message": "토큰 감지 사용"
},
"useTokenDetectionDescription": {
"message": "당사는 타사 API를 사용하여 지갑으로 전송된 새 토큰을 감지하고 표시합니다. MetaMask가 해당 서비스에서 데이터를 가져오는 것을 원하지 않으면 이 기능을 사용하지 마세요."
},
"useTokenDetectionPrivacyDesc": {
"message": "계정으로 전송된 토큰이 자동으로 표시되도록 하려면 타사 서버와의 통신을 통해 토큰 이미지를 불러와야 합니다. 이를 위해 타사 서버는 사용자의 IP 주소에 액세스하게 됩니다."
},

View File

@ -1211,9 +1211,6 @@
"failureMessage": {
"message": "Ocorreu algum erro e não conseguimos concluir a ação"
},
"fakeTokenWarning": {
"message": "Qualquer um pode criar um token, incluindo versões falsas de tokens existentes. Saiba mais sobre $1"
},
"fast": {
"message": "Rápido"
},
@ -3739,12 +3736,6 @@
"tokenDetectionAlertMessage": {
"message": "A detecção de tokens está atualmente disponível em $1. $2"
},
"tokenDetectionAnnouncement": {
"message": "Novidade! A detecção aprimorada de token está disponível na Mainnet do Ethereum como uma funcionalidade experimental. $1"
},
"tokenDetectionToggleDescription": {
"message": "A API de token da ConsenSys agrega uma lista de tokens de várias listas de tokens de terceiros. Se você desativá-la, não haverá detecção de novos tokens adicionados à sua carteira, mas continuará com a opção de procurar tokens para importar."
},
"tokenId": {
"message": "ID do token"
},
@ -3979,12 +3970,6 @@
"usePhishingDetectionDescription": {
"message": "Exibir uma advertência para os domínios de phishing destinados a usuários do Ethereum"
},
"useTokenDetection": {
"message": "Usar detecção de tokens"
},
"useTokenDetectionDescription": {
"message": "Utilizamos APIs terceirizadas para detectar e exibir novos tokens enviados à sua carteira. Desative essa opção se não deseja que a MetaMask extraia dados desses serviços."
},
"useTokenDetectionPrivacyDesc": {
"message": "A exibição automática de tokens enviados para a sua conta envolve a comunicação com servidores de terceiros para buscar as imagens dos tokens. Esses servidores terão acesso ao seu endereço IP."
},

View File

@ -1031,9 +1031,6 @@
"failureMessage": {
"message": "Ocorreu algum erro e não conseguimos concluir a ação"
},
"fakeTokenWarning": {
"message": "Qualquer um pode criar um token, incluindo versões falsas de tokens existentes. Saiba mais sobre $1"
},
"fast": {
"message": "Rápido"
},
@ -3005,9 +3002,6 @@
"tokenDecimalFetchFailed": {
"message": "A casa decimal do token é necessária."
},
"tokenDetectionAnnouncement": {
"message": "Novidade! A detecção aprimorada de token está disponível na Mainnet do Ethereum como uma funcionalidade experimental. $1"
},
"tokenId": {
"message": "ID do token"
},
@ -3220,12 +3214,6 @@
"usePhishingDetectionDescription": {
"message": "Exibir uma advertência para os domínios de phishing destinados a usuários do Ethereum"
},
"useTokenDetection": {
"message": "Usar detecção de tokens"
},
"useTokenDetectionDescription": {
"message": "Utilizamos APIs terceirizadas para detectar e exibir novos tokens enviados à sua carteira. Desative essa opção se não deseja que a MetaMask extraia dados desses serviços."
},
"usedByClients": {
"message": "Usado por diversos clientes diferentes"
},

View File

@ -1211,9 +1211,6 @@
"failureMessage": {
"message": "Что-то пошло не так, и мы не смогли завершить действие"
},
"fakeTokenWarning": {
"message": "Кто угодно может создать токен, в том числе создать поддельные версии существующих токенов. Узнайте подробнее о $1"
},
"fast": {
"message": "Быстрый"
},
@ -3739,12 +3736,6 @@
"tokenDetectionAlertMessage": {
"message": "Обнаружение токена в настоящее время доступно на $1. $2"
},
"tokenDetectionAnnouncement": {
"message": "Новинка! Улучшенное обнаружение токенов доступно в сети Ethereum Mainnet в качестве экспериментальной функции. $1"
},
"tokenDetectionToggleDescription": {
"message": "API токенов ConsenSys объединяет список токенов из различных списков сторонних токенов. Отключение этого параметра прекратит обнаружение новых токенов, добавляемых в ваш кошелек, но сохранит возможность поиска токенов для импорта."
},
"tokenId": {
"message": "Ид. токена"
},
@ -3979,12 +3970,6 @@
"usePhishingDetectionDescription": {
"message": "Показывать предупреждение для фишинговых доменов, нацеленных на пользователей Ethereum"
},
"useTokenDetection": {
"message": "Использовать обнаружение токенов"
},
"useTokenDetectionDescription": {
"message": "Мы используем сторонние API для обнаружения и отображения новых токенов, отправленных в ваш кошелек. Отключите, если не хотите, чтобы MetaMask получал данные от этих служб."
},
"useTokenDetectionPrivacyDesc": {
"message": "Автоматическое отображение токенов, отправленных на ваш счет, требует обмена данными со сторонними серверами для получения изображений токенов. Эти серверы получат доступ к вашему IP-адресу."
},

View File

@ -1211,9 +1211,6 @@
"failureMessage": {
"message": "Nagkaproblema, at hindi namin makumpleto ang aksyon"
},
"fakeTokenWarning": {
"message": "Sinuman ay maaaring gumawa ng token, kabilang ang paggawa ng mga pekeng bersyon ng mga umiiral na token. Alamin pa ang tungkol sa $1"
},
"fast": {
"message": "Mabilis"
},
@ -3739,12 +3736,6 @@
"tokenDetectionAlertMessage": {
"message": "Ang pagtukoy ng token ay kasalukuyang magagamit sa $1. $2"
},
"tokenDetectionAnnouncement": {
"message": "Bago! Ang pinahusay na pagtukoy ng token ay magagamit sa Ethereum Mainnet bilang isang pang-eksperimentong feature. $1"
},
"tokenDetectionToggleDescription": {
"message": "Pinagsasama-sama ng ConsenSys token API ang listahan ng mga token mula sa maraming listahan ng token ng third party. Ang pag-off dito ay magpapahinto sa pagtuklas ng mga bagong token na madadagdag sa iyong wallet, ngunit pananatilihin ang opsyon sa paghahanap ng mga token para i-import."
},
"tokenId": {
"message": "Token ID"
},
@ -3979,12 +3970,6 @@
"usePhishingDetectionDescription": {
"message": "Magpakita ng babala para sa mga phishing domain na nagta-target sa mga user ng Ethereum"
},
"useTokenDetection": {
"message": "Gamitin ang Pag-detect ng Token"
},
"useTokenDetectionDescription": {
"message": "Gumagamit kami ng mga third-party na API para makita at magpakita ng mga bagong token na ipinadala sa iyong wallet. I-off kung ayaw mong makuha ng MetaMask ang data mula sa mga serbisyong iyon."
},
"useTokenDetectionPrivacyDesc": {
"message": "Awtomatikong ipinapakita ang mga token na ipinadala sa iyong account na nakapaloob sa komunikasyon ng mga server ng third party para makuha ang mga larawan ng token. Ang mga server na iyon ay magkakaroon ng access sa iyong IP address."
},

View File

@ -1211,9 +1211,6 @@
"failureMessage": {
"message": "Bir şeyler ters gitti ve işlemi tamamlayamadık"
},
"fakeTokenWarning": {
"message": "Mevcut tokenlerin sahteleri de dahil olmak üzere herkes bir token oluşturabilir. $1 hakkında daha fazla bilgi edinin"
},
"fast": {
"message": "Hızlı"
},
@ -3739,12 +3736,6 @@
"tokenDetectionAlertMessage": {
"message": "Token algılama şu anda $1 üzerinden kullanılabilir. $2"
},
"tokenDetectionAnnouncement": {
"message": "Yeni! Deney aşamasında olan bir özellik olarak Ethereum Mainnet'te gelişmiş token algılama mevcut. $1"
},
"tokenDetectionToggleDescription": {
"message": "ConsenSys'in token API'si, çeşitli üçüncü taraf token listelerinden token'ların bir listesini toplar. Kapatmak, cüzdanına eklenen yeni token'ları algılamayı durduracak, ancak içe aktarılacak token'ları arama seçeneğini koruyacaktır."
},
"tokenId": {
"message": "Token Kimliği"
},
@ -3979,12 +3970,6 @@
"usePhishingDetectionDescription": {
"message": "Ethereum kullanıcılarını hedefleyen kimlik avı alanları için bir uyarı görüntüler"
},
"useTokenDetection": {
"message": "Token Algılama Kullan"
},
"useTokenDetectionDescription": {
"message": "Cüzdanınıza gönderilen yeni tokenleri algılamak ve görüntülemek için üçüncü taraf API'leri kullanıyoruz. MetaMask tarafından bu hizmetlerden veri çekilmesini istemiyorsanız bunu kapatın."
},
"useTokenDetectionPrivacyDesc": {
"message": "Hesabına gönderilen token'ların otomatik olarak görüntülenmesi, token'ın görüntülerini almak için üçüncü taraf sunucularla iletişimi içerir. Bu servislerin IP adresine erişimi olacaktır."
},

View File

@ -1211,9 +1211,6 @@
"failureMessage": {
"message": "Đã xảy ra sự cố và chúng tôi không thể hoàn tất hành động"
},
"fakeTokenWarning": {
"message": "Bất kỳ ai cũng có thể tạo token, bao gồm cả phiên bản giả mạo của các token hiện tại. Tìm hiểu thêm về $1"
},
"fast": {
"message": "Nhanh"
},
@ -3739,12 +3736,6 @@
"tokenDetectionAlertMessage": {
"message": "Tính năng phát hiện token hiện có sẵn trên $1. $2"
},
"tokenDetectionAnnouncement": {
"message": "Mới! Tính năng phát hiện token được cải tiến hiện đã có sẵn trên Mạng chính thức của Ethereum dưới dạng một tính năng thử nghiệm. $1"
},
"tokenDetectionToggleDescription": {
"message": "API token của ConsenSys sẽ tổng hợp danh sách token từ các danh sách token của nhiều bên thứ ba khác nhau. Tắt tính năng này sẽ ngừng phát hiện token mới được thêm vào ví của bạn, nhưng sẽ giữ lại tùy chọn tìm kiếm token để nhập."
},
"tokenId": {
"message": "ID Token"
},
@ -3979,12 +3970,6 @@
"usePhishingDetectionDescription": {
"message": "Hiển thị cảnh báo đối với các tên miền lừa đảo nhắm đến người dùng Ethereum"
},
"useTokenDetection": {
"message": "Sử Dụng Phát Hiện Token"
},
"useTokenDetectionDescription": {
"message": "Chúng tôi sử dụng API của bên thứ ba để phát hiện và hiển thị các token mới được gửi vào ví của bạn. Hãy tắt tính năng này nếu bạn không muốn MetaMask lấy dữ liệu từ các dịch vụ đó."
},
"useTokenDetectionPrivacyDesc": {
"message": "Tự động hiển thị các token được gửi vào tài khoản của bạn có liên quan đến hoạt động trao đổi thông tin với các máy chủ bên thứ ba để tìm nạp hình ảnh của token. Các máy chủ đó sẽ có quyền truy cập vào địa chỉ IP của bạn."
},

View File

@ -1214,9 +1214,6 @@
"failureMessage": {
"message": "出了点问题,我们无法完成此操作"
},
"fakeTokenWarning": {
"message": "任何人都可以创建代币,包括创建现有代币的虚假版本。了解关于 $ 的更多详情"
},
"fast": {
"message": "快"
},
@ -3745,9 +3742,6 @@
"tokenDetectionAlertMessage": {
"message": "代币检测目前适用于 $1. $2"
},
"tokenDetectionAnnouncement": {
"message": "新功能!以太坊主网上提供了经过改进的代币检测作为实验功能。$1"
},
"tokenDetectionToggleDescription": {
"message": "ConsenSys的代币API使用来自各种第三方的代币列表汇总成一个代币列表。关闭它将会停止检测添加到您钱包中的新代币但会保留搜索代币以导入的选项。"
},
@ -3985,12 +3979,6 @@
"usePhishingDetectionDescription": {
"message": "显示针对 Ethereum 用户的网络钓鱼域名警告"
},
"useTokenDetection": {
"message": "使用代币检测"
},
"useTokenDetectionDescription": {
"message": "我们使用第三方 API 来检测和显示发送到您钱包的新代币。如果您不希望 MetaMask 从这些服务中提取数据,请关闭。"
},
"useTokenDetectionPrivacyDesc": {
"message": "要自动显示发送到您账户的代币需要与第三方服务器通信以获取代币的图像。这些服务器将拥有您的IP地址的访问权限。"
},

View File

@ -1017,9 +1017,6 @@
"failureMessage": {
"message": "出了点问题,我们无法完成这个操作。"
},
"fakeTokenWarning": {
"message": "任何人都可以创建代币,包括创建现有代币的假版本。了解更多关于 $1"
},
"fast": {
"message": "快"
},
@ -2966,9 +2963,6 @@
"tokenDecimalFetchFailed": {
"message": "需要代币十进制。"
},
"tokenDetectionAnnouncement": {
"message": "新功能改进的代币检测可以作为实验功能在Ethereum Mainnet上进行。$1"
},
"tokenSymbol": {
"message": "代币符号"
},
@ -3178,12 +3172,6 @@
"usePhishingDetectionDescription": {
"message": "显示针对 Ethereum 用户钓鱼域名的警告。"
},
"useTokenDetection": {
"message": "使用代币检测"
},
"useTokenDetectionDescription": {
"message": "我们使用第三方API来检测和显示发送到您钱包的新代币。 如果您不想从这些服务中拉取数据,请关闭"
},
"usedByClients": {
"message": "可用于各种不同的客户端"
},

View File

@ -1,9 +1,8 @@
import Web3 from 'web3';
import { warn } from 'loglevel';
import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi';
import { SINGLE_CALL_BALANCES_ADDRESS } from '../constants/contracts';
import { MINUTE } from '../../../shared/constants/time';
import { MAINNET_CHAIN_ID } from '../../../shared/constants/network';
import { STATIC_MAINNET_TOKEN_LIST } from '../../../shared/constants/tokens';
import { isTokenDetectionEnabledForNetwork } from '../../../shared/modules/network.utils';
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
import {
@ -50,14 +49,15 @@ export default class DetectTokensController {
this.network = network;
this.keyringMemStore = keyringMemStore;
this.tokenList = tokenList;
this.useTokenDetection =
this.preferences?.store.getState().useTokenDetection;
this.selectedAddress = this.preferences?.store.getState().selectedAddress;
this.tokenAddresses = this.tokensController?.state.tokens.map((token) => {
return token.address;
});
this.hiddenTokens = this.tokensController?.state.ignoredTokens;
this.detectedTokens = process.env.TOKEN_DETECTION_V2
? this.tokensController?.state.detectedTokens
: [];
this.detectedTokens = this.tokensController?.state.detectedTokens;
this.chainId = this.getChainIdFromNetworkStore(network);
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
preferences?.store.subscribe(({ selectedAddress, useTokenDetection }) => {
@ -76,32 +76,11 @@ export default class DetectTokensController {
return token.address;
});
this.hiddenTokens = ignoredTokens;
this.detectedTokens = process.env.TOKEN_DETECTION_V2
? detectedTokens
: [];
this.detectedTokens = detectedTokens;
},
);
}
/**
* TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up
*
* @param tokens
*/
async _getTokenBalances(tokens) {
const ethContract = this.web3.eth
.contract(SINGLE_CALL_BALANCES_ABI)
.at(SINGLE_CALL_BALANCES_ADDRESS);
return new Promise((resolve, reject) => {
ethContract.balances([this.selectedAddress], tokens, (error, result) => {
if (error) {
return reject(error);
}
return resolve(result);
});
});
}
/**
* For each token in the tokenlist provided by the TokenListController, check selectedAddress balance.
*/
@ -110,31 +89,33 @@ export default class DetectTokensController {
return;
}
if (
process.env.TOKEN_DETECTION_V2 &&
(!this.useTokenDetection ||
!isTokenDetectionEnabledForNetwork(
this._network.store.getState().provider.chainId,
))
!isTokenDetectionEnabledForNetwork(
this.getChainIdFromNetworkStore(this._network),
)
) {
return;
}
const { tokenList } = this._tokenList.state;
// since the token detection is currently enabled only on Mainnet
// we can use the chainId check to ensure token detection is not triggered for any other network
// but once the balance check contract for other networks are deploayed and ready to use, we need to update this check.
if (
!process.env.TOKEN_DETECTION_V2 &&
(this._network.store.getState().provider.chainId !== MAINNET_CHAIN_ID ||
Object.keys(tokenList).length === 0)
!this.useTokenDetection &&
this.getChainIdFromNetworkStore(this._network) !== MAINNET_CHAIN_ID
) {
return;
}
const isTokenDetectionInactiveInMainnet =
!this.useTokenDetection &&
this.getChainIdFromNetworkStore(this._network) === MAINNET_CHAIN_ID;
const { tokenList } = this._tokenList.state;
const tokenListUsed = isTokenDetectionInactiveInMainnet
? STATIC_MAINNET_TOKEN_LIST
: tokenList;
const tokensToDetect = [];
this.web3.setProvider(this._network._provider);
for (const tokenAddress in tokenList) {
for (const tokenAddress in tokenListUsed) {
if (
!this.tokenAddresses.find((address) =>
!this.tokenAddresses.find(({ address }) =>
isEqualCaseInsensitive(address, tokenAddress),
) &&
!this.hiddenTokens.find((address) =>
@ -154,12 +135,10 @@ export default class DetectTokensController {
for (const tokensSlice of sliceOfTokensToDetect) {
let result;
try {
result = process.env.TOKEN_DETECTION_V2
? await this.assetsContractController.getBalancesInSingleCall(
this.selectedAddress,
tokensSlice,
)
: await this._getTokenBalances(tokensSlice);
result = await this.assetsContractController.getBalancesInSingleCall(
this.selectedAddress,
tokensSlice,
);
} catch (error) {
warn(
`MetaMask - DetectTokensController single call balance fetch failed`,
@ -168,53 +147,36 @@ export default class DetectTokensController {
return;
}
let tokensWithBalance = [];
if (process.env.TOKEN_DETECTION_V2) {
const eventTokensDetails = [];
if (result) {
const nonZeroTokenAddresses = Object.keys(result);
for (const nonZeroTokenAddress of nonZeroTokenAddresses) {
const { address, symbol, decimals, iconUrl, aggregators } =
tokenList[nonZeroTokenAddress];
const tokensWithBalance = [];
const eventTokensDetails = [];
if (result) {
const nonZeroTokenAddresses = Object.keys(result);
for (const nonZeroTokenAddress of nonZeroTokenAddresses) {
const { address, symbol, decimals, aggregators } =
tokenListUsed[nonZeroTokenAddress];
eventTokensDetails.push(`${symbol} - ${address}`);
eventTokensDetails.push(`${symbol} - ${address}`);
tokensWithBalance.push({
address,
symbol,
decimals,
image: iconUrl,
aggregators,
});
}
if (tokensWithBalance.length > 0) {
this._trackMetaMetricsEvent({
event: EVENT_NAMES.TOKEN_DETECTED,
category: EVENT.CATEGORIES.WALLET,
properties: {
tokens: eventTokensDetails,
token_standard: TOKEN_STANDARDS.ERC20,
asset_type: ASSET_TYPES.TOKEN,
},
});
await this.tokensController.addDetectedTokens(tokensWithBalance);
}
tokensWithBalance.push({
address,
symbol,
decimals,
aggregators,
});
}
if (tokensWithBalance.length > 0) {
this._trackMetaMetricsEvent({
event: EVENT_NAMES.TOKEN_DETECTED,
category: EVENT.CATEGORIES.WALLET,
properties: {
tokens: eventTokensDetails,
token_standard: TOKEN_STANDARDS.ERC20,
asset_type: ASSET_TYPES.TOKEN,
},
});
await this.tokensController.addDetectedTokens(tokensWithBalance);
}
} else {
tokensWithBalance = tokensSlice.filter((_, index) => {
const balance = result[index];
return balance && !balance.isZero();
});
await Promise.all(
tokensWithBalance.map((tokenAddress) => {
return this.tokensController.addToken(
tokenAddress,
tokenList[tokenAddress].symbol,
tokenList[tokenAddress].decimals,
);
}),
);
}
}
}
@ -232,6 +194,10 @@ export default class DetectTokensController {
this.interval = DEFAULT_INTERVAL;
}
getChainIdFromNetworkStore(network) {
return network?.store.getState().provider.chainId;
}
/* eslint-disable accessor-pairs */
/**
* @type {number}
@ -255,6 +221,12 @@ export default class DetectTokensController {
}
this._network = network;
this.web3 = new Web3(network._provider);
this._network.store.subscribe(() => {
if (this.chainId !== this.getChainIdFromNetworkStore(network)) {
this.restartTokenDetection();
this.chainId = this.getChainIdFromNetworkStore(network);
}
});
}
/**

View File

@ -7,24 +7,23 @@ import {
ControllerMessenger,
TokenListController,
TokensController,
AssetsContractController,
} from '@metamask/controllers';
import {
MAINNET,
MAINNET_NETWORK_ID,
ROPSTEN,
} from '../../../shared/constants/network';
import { MAINNET, ROPSTEN } from '../../../shared/constants/network';
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
import DetectTokensController from './detect-tokens';
import NetworkController from './network';
import PreferencesController from './preferences';
const tokenIconsApiBaseUrl =
'https://static.metaswap.codefi.network/api/v1/tokenIcons';
describe('DetectTokensController', function () {
let tokenListController;
const sandbox = sinon.createSandbox();
let keyringMemStore, network, preferences, provider, tokensController;
let assetsContractController,
keyringMemStore,
network,
preferences,
provider,
tokensController,
tokenListController;
const noop = () => undefined;
@ -38,18 +37,44 @@ describe('DetectTokensController', function () {
network.setInfuraProjectId('foo');
network.initializeProvider(networkControllerProviderConfig);
provider = network.getProviderAndBlockTracker().provider;
preferences = new PreferencesController({ network, provider });
tokensController = new TokensController({
onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store,
),
onNetworkStateChange: network.store.subscribe.bind(network.store),
const tokenListMessenger = new ControllerMessenger().getRestricted({
name: 'TokenListController',
});
tokenListController = new TokenListController({
chainId: '1',
preventPollingOnNetworkRestart: false,
onNetworkStateChange: sinon.spy(),
onPreferencesStateChange: sinon.spy(),
messenger: tokenListMessenger,
});
await tokenListController.start();
preferences = new PreferencesController({
network,
provider,
tokenListController,
});
preferences.setAddresses([
'0x7e57e2',
'0xbc86727e770de68b1060c91f6bb6945c73e10388',
]);
preferences.setUseTokenDetection(true);
tokensController = new TokensController({
onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store,
),
onNetworkStateChange: network.store.subscribe.bind(network.store),
});
assetsContractController = new AssetsContractController({
onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store,
),
onNetworkStateChange: network.store.subscribe.bind(network.store),
});
sandbox
.stub(network, 'getLatestBlock')
.callsFake(() => Promise.resolve({}));
@ -129,16 +154,6 @@ describe('DetectTokensController', function () {
.get(`/tokens/3`)
.reply(200, { error: 'ChainId 3 is not supported' })
.persist();
const tokenListMessenger = new ControllerMessenger().getRestricted({
name: 'TokenListController',
});
tokenListController = new TokenListController({
chainId: '1',
onNetworkStateChange: sinon.spy(),
onPreferencesStateChange: sinon.spy(),
messenger: tokenListMessenger,
});
await tokenListController.start();
});
after(function () {
@ -161,6 +176,7 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
assetsContractController,
});
controller.isOpen = true;
controller.isUnlocked = true;
@ -177,7 +193,7 @@ describe('DetectTokensController', function () {
sandbox.assert.calledThrice(stub);
});
it('should not check tokens while on test network', async function () {
it('should not check and add tokens while on unsupported networks', async function () {
sandbox.useFakeTimers();
network.setProviderType(ROPSTEN);
const tokenListMessengerRopsten = new ControllerMessenger().getRestricted({
@ -196,17 +212,21 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
assetsContractController,
});
controller.isOpen = true;
controller.isUnlocked = true;
const stub = sandbox.stub(controller, '_getTokenBalances');
const stub = sandbox.stub(
assetsContractController,
'getBalancesInSingleCall',
);
await controller.detectNewTokens();
sandbox.assert.notCalled(stub);
});
it('should skip adding tokens listed in hiddenTokens array', async function () {
it('should skip adding tokens listed in ignoredTokens array', async function () {
sandbox.useFakeTimers();
network.setProviderType(MAINNET);
const controller = new DetectTokensController({
@ -215,53 +235,49 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
assetsContractController,
trackMetaMetricsEvent: noop,
});
controller.isOpen = true;
controller.isUnlocked = true;
const { tokenList } = tokenListController.state;
const erc20ContractAddresses = Object.keys(tokenList);
const tokenValues = Object.values(tokenList);
const existingTokenAddress = erc20ContractAddresses[0];
const existingToken = tokenList[existingTokenAddress];
await tokensController.addToken(
existingTokenAddress,
existingToken.symbol,
existingToken.decimals,
);
const tokenAddressToSkip = erc20ContractAddresses[1];
const tokenToSkip = tokenList[tokenAddressToSkip];
await tokensController.addToken(
tokenAddressToSkip,
tokenToSkip.symbol,
tokenToSkip.decimals,
);
await tokensController.addDetectedTokens([
{
address: tokenValues[0].address,
symbol: tokenValues[0].symbol,
decimals: tokenValues[0].decimals,
aggregators: tokenValues[0].aggregators,
image: undefined,
isERC721: undefined,
},
]);
sandbox
.stub(controller, '_getTokenBalances')
.stub(assetsContractController, 'getBalancesInSingleCall')
.callsFake((tokensToDetect) =>
tokensToDetect.map((token) =>
token === tokenAddressToSkip ? new BigNumber(10) : 0,
token.address === tokenValues[1].address ? new BigNumber(10) : 0,
),
);
await tokensController.ignoreTokens([tokenValues[1].address]);
await tokensController.ignoreTokens([tokenAddressToSkip]);
await controller.detectNewTokens();
assert.deepEqual(tokensController.state.tokens, [
assert.deepEqual(tokensController.state.detectedTokens, [
{
address: toChecksumHexAddress(existingTokenAddress),
decimals: existingToken.decimals,
symbol: existingToken.symbol,
aggregators: [],
image: `${tokenIconsApiBaseUrl}/${MAINNET_NETWORK_ID}/${existingTokenAddress}.png`,
isERC721: false,
address: toChecksumHexAddress(tokenValues[0].address),
decimals: tokenValues[0].decimals,
symbol: tokenValues[0].symbol,
aggregators: tokenValues[0].aggregators,
image: undefined,
isERC721: undefined,
},
]);
});
it('should check and add tokens while on main network', async function () {
it('should check and add tokens while on supported networks', async function () {
sandbox.useFakeTimers();
network.setProviderType(MAINNET);
const controller = new DetectTokensController({
@ -270,6 +286,8 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
assetsContractController,
trackMetaMetricsEvent: noop,
});
controller.isOpen = true;
controller.isUnlocked = true;
@ -279,107 +297,41 @@ describe('DetectTokensController', function () {
const existingTokenAddress = erc20ContractAddresses[0];
const existingToken = tokenList[existingTokenAddress];
await tokensController.addToken(
existingTokenAddress,
existingToken.symbol,
existingToken.decimals,
);
const tokenAddressToAdd = erc20ContractAddresses[1];
const tokenToAdd = tokenList[tokenAddressToAdd];
const contractAddressesToDetect = erc20ContractAddresses.filter(
(address) => address !== existingTokenAddress,
);
const indexOfTokenToAdd =
contractAddressesToDetect.indexOf(tokenAddressToAdd);
const balances = new Array(contractAddressesToDetect.length);
balances[indexOfTokenToAdd] = new BigNumber(10);
sandbox
.stub(controller, '_getTokenBalances')
.returns(Promise.resolve(balances));
await controller.detectNewTokens();
assert.deepEqual(tokensController.state.tokens, [
await tokensController.addDetectedTokens([
{
address: toChecksumHexAddress(existingTokenAddress),
decimals: existingToken.decimals,
address: existingToken.address,
symbol: existingToken.symbol,
isERC721: false,
aggregators: [],
image: `${tokenIconsApiBaseUrl}/${MAINNET_NETWORK_ID}/${existingTokenAddress}.png`,
},
{
address: toChecksumHexAddress(tokenAddressToAdd),
decimals: tokenToAdd.decimals,
symbol: tokenToAdd.symbol,
isERC721: false,
aggregators: [],
image: `${tokenIconsApiBaseUrl}/${MAINNET_NETWORK_ID}/${tokenAddressToAdd}.png`,
decimals: existingToken.decimals,
aggregators: existingToken.aggregators,
image: undefined,
isERC721: undefined,
},
]);
});
it('should check and add tokens while on non-default Mainnet', async function () {
sandbox.useFakeTimers();
network.setRpcTarget('https://some-fake-RPC-endpoint.metamask.io', '0x1');
const controller = new DetectTokensController({
preferences,
network,
keyringMemStore,
tokenList: tokenListController,
tokensController,
});
controller.isOpen = true;
controller.isUnlocked = true;
const { tokenList } = tokenListController.state;
const erc20ContractAddresses = Object.keys(tokenList);
const existingTokenAddress = erc20ContractAddresses[0];
const existingToken = tokenList[existingTokenAddress];
await tokensController.addToken(
existingTokenAddress,
existingToken.symbol,
existingToken.decimals,
);
const tokenAddressToAdd = erc20ContractAddresses[1];
const tokenToAdd = tokenList[tokenAddressToAdd];
const contractAddressesToDetect = erc20ContractAddresses.filter(
(address) => address !== existingTokenAddress,
);
const indexOfTokenToAdd =
contractAddressesToDetect.indexOf(tokenAddressToAdd);
const balances = new Array(contractAddressesToDetect.length);
balances[indexOfTokenToAdd] = new BigNumber(10);
sandbox
.stub(controller, '_getTokenBalances')
.returns(Promise.resolve(balances));
.stub(assetsContractController, 'getBalancesInSingleCall')
.callsFake(() =>
Promise.resolve({ [tokenAddressToAdd]: new BigNumber(10) }),
);
await controller.detectNewTokens();
assert.deepEqual(tokensController.state.tokens, [
assert.deepEqual(tokensController.state.detectedTokens, [
{
address: toChecksumHexAddress(existingTokenAddress),
decimals: existingToken.decimals,
symbol: existingToken.symbol,
image: `${tokenIconsApiBaseUrl}/${MAINNET_NETWORK_ID}/${existingTokenAddress}.png`,
isERC721: false,
aggregators: [],
aggregators: existingToken.aggregators,
image: undefined,
isERC721: undefined,
},
{
address: toChecksumHexAddress(tokenAddressToAdd),
decimals: tokenToAdd.decimals,
symbol: tokenToAdd.symbol,
image: `${tokenIconsApiBaseUrl}/${MAINNET_NETWORK_ID}/${tokenAddressToAdd}.png`,
isERC721: false,
aggregators: [],
aggregators: tokenToAdd.aggregators,
image: undefined,
isERC721: undefined,
},
]);
});
@ -392,6 +344,7 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
assetsContractController,
});
controller.isOpen = true;
controller.isUnlocked = true;
@ -410,6 +363,7 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
assetsContractController,
});
controller.isOpen = true;
controller.selectedAddress = '0x0';
@ -427,10 +381,14 @@ describe('DetectTokensController', function () {
keyringMemStore,
tokenList: tokenListController,
tokensController,
assetsContractController,
});
controller.isOpen = true;
controller.isUnlocked = false;
const stub = sandbox.stub(controller, '_getTokenBalances');
const stub = sandbox.stub(
assetsContractController,
'getBalancesInSingleCall',
);
clock.tick(180000);
sandbox.assert.notCalled(stub);
});
@ -443,6 +401,7 @@ describe('DetectTokensController', function () {
network,
keyringMemStore,
tokensController,
assetsContractController,
});
// trigger state update from preferences controller
await preferences.setSelectedAddress(
@ -450,7 +409,10 @@ describe('DetectTokensController', function () {
);
controller.isOpen = false;
controller.isUnlocked = true;
const stub = sandbox.stub(controller, '_getTokenBalances');
const stub = sandbox.stub(
assetsContractController,
'getBalancesInSingleCall',
);
clock.tick(180000);
sandbox.assert.notCalled(stub);
});

View File

@ -38,7 +38,7 @@ export default class PreferencesController {
// set to true means the dynamic list from the API is being used
// set to false will be using the static list from contract-metadata
useTokenDetection: Boolean(process.env.TOKEN_DETECTION_V2),
useTokenDetection: false,
useCollectibleDetection: false,
openSeaEnabled: false,
advancedGasFee: null,
@ -79,6 +79,7 @@ export default class PreferencesController {
this.store.setMaxListeners(12);
this.openPopup = opts.openPopup;
this.migrateAddressBookState = opts.migrateAddressBookState;
this.tokenListController = opts.tokenListController;
this._subscribeToInfuraAvailability();
@ -131,6 +132,13 @@ export default class PreferencesController {
*/
setUseTokenDetection(val) {
this.store.updateState({ useTokenDetection: val });
this.tokenListController.updatePreventPollingOnNetworkRestart(!val);
if (val) {
this.tokenListController.start();
} else {
this.tokenListController.clearingTokenListData();
this.tokenListController.stop();
}
}
/**

View File

@ -1,5 +1,9 @@
import { strict as assert } from 'assert';
import sinon from 'sinon';
import {
ControllerMessenger,
TokenListController,
} from '@metamask/controllers';
import { MAINNET_CHAIN_ID } from '../../../shared/constants/network';
import PreferencesController from './preferences';
import NetworkController from './network';
@ -9,6 +13,7 @@ describe('preferences controller', function () {
let network;
let currentChainId;
let provider;
let tokenListController;
const migrateAddressBookState = sinon.stub();
beforeEach(function () {
@ -21,6 +26,16 @@ describe('preferences controller', function () {
network.setInfuraProjectId('foo');
network.initializeProvider(networkControllerProviderConfig);
provider = network.getProviderAndBlockTracker().provider;
const tokenListMessenger = new ControllerMessenger().getRestricted({
name: 'TokenListController',
});
tokenListController = new TokenListController({
chainId: '1',
preventPollingOnNetworkRestart: false,
onNetworkStateChange: sinon.spy(),
onPreferencesStateChange: sinon.spy(),
messenger: tokenListMessenger,
});
sandbox
.stub(network, 'getLatestBlock')
@ -35,6 +50,7 @@ describe('preferences controller', function () {
migrateAddressBookState,
network,
provider,
tokenListController,
});
});

View File

@ -97,6 +97,7 @@ import {
} from '../../ui/helpers/utils/token-util';
import { isEqualCaseInsensitive } from '../../shared/modules/string-utils';
import { parseStandardTokenTransactionData } from '../../shared/modules/transaction.utils';
import { STATIC_MAINNET_TOKEN_LIST } from '../../shared/constants/tokens';
import {
onMessageReceived,
checkForMultipleVersionsRunning,
@ -235,11 +236,35 @@ export default class MetamaskController extends EventEmitter {
this.blockTracker =
this.networkController.getProviderAndBlockTracker().blockTracker;
const tokenListMessenger = this.controllerMessenger.getRestricted({
name: 'TokenListController',
});
this.tokenListController = new TokenListController({
chainId: hexToDecimal(this.networkController.getCurrentChainId()),
preventPollingOnNetworkRestart: true,
onNetworkStateChange: (cb) => {
this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
provider: {
...networkState.provider,
chainId: hexToDecimal(networkState.provider.chainId),
},
};
return cb(modifiedNetworkState);
});
},
messenger: tokenListMessenger,
state: initState.TokenListController,
});
this.preferencesController = new PreferencesController({
initState: initState.PreferencesController,
initLangCode: opts.initLangCode,
openPopup: opts.openPopup,
network: this.networkController,
tokenListController: this.tokenListController,
provider: this.provider,
migrateAddressBookState: this.migrateAddressBookState.bind(this),
});
@ -438,27 +463,6 @@ export default class MetamaskController extends EventEmitter {
},
});
const tokenListMessenger = this.controllerMessenger.getRestricted({
name: 'TokenListController',
});
this.tokenListController = new TokenListController({
chainId: hexToDecimal(this.networkController.getCurrentChainId()),
onNetworkStateChange: (cb) =>
this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
provider: {
...networkState.provider,
chainId: hexToDecimal(networkState.provider.chainId),
},
};
return cb(modifiedNetworkState);
}),
messenger: tokenListMessenger,
state: initState.TokenListController,
});
this.phishingController = new PhishingController();
this.announcementController = new AnnouncementController(
@ -527,12 +531,16 @@ export default class MetamaskController extends EventEmitter {
this.accountTracker.start();
this.incomingTransactionsController.start();
this.currencyRateController.start();
this.tokenListController.start();
if (this.preferencesController.store.getState().useTokenDetection) {
this.tokenListController.start();
}
} else {
this.accountTracker.stop();
this.incomingTransactionsController.stop();
this.currencyRateController.stop();
this.tokenListController.stop();
if (this.preferencesController.store.getState().useTokenDetection) {
this.tokenListController.stop();
}
}
});
@ -753,26 +761,17 @@ export default class MetamaskController extends EventEmitter {
},
});
///: END:ONLY_INCLUDE_IN
process.env.TOKEN_DETECTION_V2
? (this.detectTokensController = new DetectTokensController({
preferences: this.preferencesController,
tokensController: this.tokensController,
assetsContractController: this.assetsContractController,
network: this.networkController,
keyringMemStore: this.keyringController.memStore,
tokenList: this.tokenListController,
trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind(
this.metaMetricsController,
),
}))
: (this.detectTokensController = new DetectTokensController({
preferences: this.preferencesController,
tokensController: this.tokensController,
network: this.networkController,
keyringMemStore: this.keyringController.memStore,
tokenList: this.tokenListController,
}));
this.detectTokensController = new DetectTokensController({
preferences: this.preferencesController,
tokensController: this.tokensController,
assetsContractController: this.assetsContractController,
network: this.networkController,
keyringMemStore: this.keyringController.memStore,
tokenList: this.tokenListController,
trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind(
this.metaMetricsController,
),
});
this.addressBookController = new AddressBookController(
undefined,
@ -2244,7 +2243,14 @@ export default class MetamaskController extends EventEmitter {
useTokenDetection,
} = this.preferencesController.store.getState();
const isTokenDetectionInactiveInMainnet =
!useTokenDetection &&
this.networkController.store.getState().provider.chainId ===
MAINNET_CHAIN_ID;
const { tokenList } = this.tokenListController.state;
const caseInSensitiveTokenList = isTokenDetectionInactiveInMainnet
? STATIC_MAINNET_TOKEN_LIST
: tokenList;
const preferences = {
currentLocale,
@ -2267,13 +2273,11 @@ export default class MetamaskController extends EventEmitter {
checksummedAccountAddress
].filter((asset) => {
if (asset.isERC721 === undefined) {
// since the token.address from allTokens is checksumaddress
// asset.address have to be changed to lowercase when we are using dynamic list
const address = useTokenDetection
? asset.address.toLowerCase()
: asset.address;
// the tokenList will be holding only erc20 tokens
if (tokenList[address] !== undefined) {
if (
caseInSensitiveTokenList[asset.address?.toLowerCase()] !==
undefined
) {
return true;
}
} else if (asset.isERC721 === false) {

View File

@ -681,6 +681,7 @@
"3box>ipfs>ipfs-unixfs": true,
"3box>ipfs>ipfs-unixfs-importer>async-iterator-batch": true,
"3box>ipfs>ipfs-unixfs-importer>async-iterator-first": true,
"3box>ipfs>ipfs-unixfs-importer>deep-extend": true,
"3box>ipfs>ipfs-unixfs-importer>rabin-wasm": true,
"3box>ipfs>ipld-dag-pb": true,
"3box>ipfs>ipld-raw>multihashing-async": true,
@ -691,6 +692,11 @@
"madge>rc>deep-extend": true
}
},
"3box>ipfs>ipfs-unixfs-importer>deep-extend": {
"packages": {
"browserify>buffer": true
}
},
"3box>ipfs>ipfs-unixfs-importer>rabin-wasm": {
"globals": {
"Blob": true,

View File

@ -681,6 +681,7 @@
"3box>ipfs>ipfs-unixfs": true,
"3box>ipfs>ipfs-unixfs-importer>async-iterator-batch": true,
"3box>ipfs>ipfs-unixfs-importer>async-iterator-first": true,
"3box>ipfs>ipfs-unixfs-importer>deep-extend": true,
"3box>ipfs>ipfs-unixfs-importer>rabin-wasm": true,
"3box>ipfs>ipld-dag-pb": true,
"3box>ipfs>ipld-raw>multihashing-async": true,
@ -691,6 +692,11 @@
"madge>rc>deep-extend": true
}
},
"3box>ipfs>ipfs-unixfs-importer>deep-extend": {
"packages": {
"browserify>buffer": true
}
},
"3box>ipfs>ipfs-unixfs-importer>rabin-wasm": {
"globals": {
"Blob": true,

View File

@ -681,6 +681,7 @@
"3box>ipfs>ipfs-unixfs": true,
"3box>ipfs>ipfs-unixfs-importer>async-iterator-batch": true,
"3box>ipfs>ipfs-unixfs-importer>async-iterator-first": true,
"3box>ipfs>ipfs-unixfs-importer>deep-extend": true,
"3box>ipfs>ipfs-unixfs-importer>rabin-wasm": true,
"3box>ipfs>ipld-dag-pb": true,
"3box>ipfs>ipld-raw>multihashing-async": true,
@ -691,6 +692,11 @@
"madge>rc>deep-extend": true
}
},
"3box>ipfs>ipfs-unixfs-importer>deep-extend": {
"packages": {
"browserify>buffer": true
}
},
"3box>ipfs>ipfs-unixfs-importer>rabin-wasm": {
"globals": {
"Blob": true,

View File

@ -121,7 +121,7 @@
"@keystonehq/metamask-airgapped-keyring": "0.2.1",
"@material-ui/core": "^4.11.0",
"@metamask/contract-metadata": "^1.31.0",
"@metamask/controllers": "^30.0.2",
"@metamask/controllers": "^30.1.0",
"@metamask/design-tokens": "^1.8.0",
"@metamask/eth-ledger-bridge-keyring": "^0.13.0",
"@metamask/eth-token-tracker": "^4.0.0",

View File

@ -21,3 +21,18 @@ export const LISTED_CONTRACT_ADDRESSES = Object.keys(contractMap).map(
* asset.
* @property {boolean} [isERC721] - True when the asset is a ERC721 token.
*/
export const STATIC_MAINNET_TOKEN_LIST = Object.keys(contractMap).reduce(
(acc, base) => {
const { logo, ...tokenMetadata } = contractMap[base];
return {
...acc,
[base.toLowerCase()]: {
...tokenMetadata,
address: base.toLowerCase(),
iconUrl: `images/contract/${logo}`,
aggregators: [],
},
};
},
{},
);

View File

@ -67,6 +67,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -57,6 +57,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -53,6 +53,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -71,6 +71,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -54,6 +54,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -54,6 +54,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -108,6 +108,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -53,6 +53,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -53,6 +53,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -57,6 +57,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -53,6 +53,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -28,6 +28,12 @@
},
"NotificationController": {
"notifications": {
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -54,6 +54,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -54,6 +54,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -135,7 +135,7 @@
"useBlockie": false,
"useNonceField": false,
"usePhishDetect": true,
"useTokenDetection": true
"useTokenDetection": false
},
"config": {},
"firstTimeInfo": {

View File

@ -64,6 +64,12 @@
"8": {
"isShown": true
},
"10": {
"isShown": true
},
"11": {
"isShown": true
},
"12": {
"isShown": true
},

View File

@ -18,8 +18,8 @@ describe('Settings Search', function () {
security: 'Reveal Secret',
alerts: 'Browsing a website',
networks: 'Ethereum Mainnet',
experimental: 'Token Detection',
about: 'Terms of use',
experimental: 'Enable Enhanced Gas Fee UI',
about: 'Terms of Use',
};
it('should find element inside the General tab', async function () {

View File

@ -14,7 +14,7 @@ describe('Swap Eth for another Token', function () {
it('Completes a Swap between Eth and Matic', async function () {
await withFixtures(
{
fixtures: 'imported-account',
fixtures: 'special-settings',
ganacheOptions,
title: this.test.title,
failOnConsoleError: false,

View File

@ -11,6 +11,7 @@ import {
getShouldShowFiat,
getNativeCurrencyImage,
getDetectedTokensInCurrentNetwork,
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork,
} from '../../../selectors';
import { getNativeCurrency } from '../../../ducks/metamask/metamask';
import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay';
@ -65,6 +66,9 @@ const AssetList = ({ onClickAsset }) => {
const primaryTokenImage = useSelector(getNativeCurrencyImage);
const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork) || [];
const istokenDetectionInactiveOnNonMainnetSupportedNetwork = useSelector(
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork,
);
return (
<>
@ -92,11 +96,10 @@ const AssetList = ({ onClickAsset }) => {
});
}}
/>
{process.env.TOKEN_DETECTION_V2
? detectedTokens.length > 0 && (
<DetectedTokensLink setShowDetectedTokens={setShowDetectedTokens} />
)
: null}
{detectedTokens.length > 0 &&
!istokenDetectionInactiveOnNonMainnetSupportedNetwork && (
<DetectedTokensLink setShowDetectedTokens={setShowDetectedTokens} />
)}
<Box marginTop={detectedTokens.length > 0 ? 0 : 4}>
<Box justifyContent={JUSTIFY_CONTENT.CENTER}>
<Typography

View File

@ -5,7 +5,9 @@ import { renderWithProvider } from '../../../../test/jest/rendering';
import ContactList from '.';
describe('Contact List', () => {
const store = configureMockStore([])({ metamask: {} });
const store = configureMockStore([])({
metamask: { provider: { chainId: '0x0' } },
});
describe('given searchForContacts', () => {
const selectRecipient = () => null;

View File

@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import Popover from '../../../ui/popover';
@ -8,6 +9,7 @@ import Typography from '../../../ui/typography/typography';
import { TYPOGRAPHY } from '../../../../helpers/constants/design-system';
const DetectedTokenIgnoredPopover = ({
partiallyIgnoreDetectedTokens,
onCancelIgnore,
handleClearTokensSelection,
}) => {
@ -34,8 +36,16 @@ const DetectedTokenIgnoredPopover = ({
return (
<Popover
title={t('areYouSure')}
className="detected-token-ignored-popover"
title={
partiallyIgnoreDetectedTokens
? t('importSelectedTokens')
: t('areYouSure')
}
className={classNames('detected-token-ignored-popover', {
'detected-token-ignored-popover--import': partiallyIgnoreDetectedTokens,
'detected-token-ignored-popover--ignore':
!partiallyIgnoreDetectedTokens,
})}
footer={footer}
>
<Typography
@ -46,13 +56,16 @@ const DetectedTokenIgnoredPopover = ({
marginBottom={7}
marginLeft={5}
>
{t('ignoreTokenWarning')}
{partiallyIgnoreDetectedTokens
? t('importSelectedTokensDescription')
: t('ignoreTokenWarning')}
</Typography>
</Popover>
);
};
DetectedTokenIgnoredPopover.propTypes = {
partiallyIgnoreDetectedTokens: PropTypes.bool.isRequired,
onCancelIgnore: PropTypes.func.isRequired,
handleClearTokensSelection: PropTypes.func.isRequired,
};

View File

@ -7,7 +7,15 @@
margin-inline-start: 8px;
}
.popover-header {
margin-inline-start: 85px;
&--ignore {
.popover-header {
margin-inline-start: 85px;
}
}
&--import {
.popover-header {
margin-inline-start: 50px;
}
}
}

View File

@ -27,7 +27,12 @@ const sortingBasedOnTokenSelection = (tokensDetected) => {
// create a new object with keys 'selected', 'deselected' and group the tokens
.groupBy((token) => (token.selected ? 'selected' : 'deselected'))
// ditch the 'selected' property and get just the tokens'
.mapValues((group) => group.map(({ token }) => token))
.mapValues((group) =>
group.map(({ token }) => {
const { address, symbol, decimals, aggregators } = token;
return { address, symbol, decimals, aggregators };
}),
)
// Exit the chain and get the underlying value, an object.
.value()
);
@ -47,6 +52,8 @@ const DetectedToken = ({ setShowDetectedTokens }) => {
);
const [showDetectedTokenIgnoredPopover, setShowDetectedTokenIgnoredPopover] =
useState(false);
const [partiallyIgnoreDetectedTokens, setPartiallyIgnoreDetectedTokens] =
useState(false);
const importSelectedTokens = async (selectedTokens) => {
selectedTokens.forEach((importedToken) => {
@ -98,6 +105,7 @@ const DetectedToken = ({ setShowDetectedTokens }) => {
}),
);
setShowDetectedTokens(false);
setPartiallyIgnoreDetectedTokens(false);
};
const handleTokenSelection = (token) => {
@ -116,6 +124,7 @@ const DetectedToken = ({ setShowDetectedTokens }) => {
if (selectedTokens.length < detectedTokens.length) {
setShowDetectedTokenIgnoredPopover(true);
setPartiallyIgnoreDetectedTokens(true);
} else {
await importSelectedTokens(selectedTokens);
setShowDetectedTokens(false);
@ -134,6 +143,7 @@ const DetectedToken = ({ setShowDetectedTokens }) => {
const onCancelIgnore = () => {
setShowDetectedTokenIgnoredPopover(false);
setPartiallyIgnoreDetectedTokens(false);
};
return (
@ -142,6 +152,7 @@ const DetectedToken = ({ setShowDetectedTokens }) => {
<DetectedTokenIgnoredPopover
onCancelIgnore={onCancelIgnore}
handleClearTokensSelection={handleClearTokensSelection}
partiallyIgnoreDetectedTokens={partiallyIgnoreDetectedTokens}
/>
)}
<DetectedTokenSelectionPopover

View File

@ -9,24 +9,29 @@ import { TEXT_ALIGN } from '../../../helpers/constants/design-system';
import { detectNewTokens } from '../../../store/actions';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import { EVENT } from '../../../../shared/constants/metametrics';
import { getIsMainnet, getIsTokenDetectionSupported } from '../../../selectors';
import {
getIsTokenDetectionSupported,
getIsTokenDetectionInactiveOnMainnet,
} from '../../../selectors';
export default function ImportTokenLink() {
const trackEvent = useContext(MetaMetricsContext);
const t = useI18nContext();
const history = useHistory();
const isMainnet = useSelector(getIsMainnet);
const isTokenDetectionSupported = useSelector(getIsTokenDetectionSupported);
const isTokenDetectionInactiveOnMainnet = useSelector(
getIsTokenDetectionInactiveOnMainnet,
);
const isTokenDetectionsupported =
isMainnet ||
(process.env.TOKEN_DETECTION_V2 && isTokenDetectionSupported) ||
const isTokenDetectionAvailable =
isTokenDetectionSupported ||
isTokenDetectionInactiveOnMainnet ||
Boolean(process.env.IN_TEST);
return (
<Box className="import-token-link" textAlign={TEXT_ALIGN.CENTER}>
{isTokenDetectionsupported && (
{isTokenDetectionAvailable && (
<>
<Button
className="import-token-link__link"
@ -53,7 +58,7 @@ export default function ImportTokenLink() {
});
}}
>
{isTokenDetectionsupported
{isTokenDetectionAvailable
? t('importTokens')
: t('importTokens').charAt(0).toUpperCase() +
t('importTokens').slice(1)}

View File

@ -10,7 +10,11 @@ describe('Confirm Remove Account', () => {
let wrapper;
const state = {
metamask: {},
metamask: {
provider: {
chainId: '0x0',
},
},
};
const props = {

View File

@ -13,7 +13,6 @@ export default function TokenCell({
balanceError,
symbol,
string,
image,
onClick,
isERC721,
}) {
@ -44,7 +43,6 @@ export default function TokenCell({
iconClassName="token-cell__icon"
onClick={onClick.bind(null, address)}
tokenAddress={address}
tokenImage={image}
tokenSymbol={symbol}
tokenDecimals={decimals}
warning={warning}
@ -61,7 +59,6 @@ TokenCell.propTypes = {
symbol: PropTypes.string,
decimals: PropTypes.number,
string: PropTypes.string,
image: PropTypes.string,
onClick: PropTypes.func.isRequired,
isERC721: PropTypes.bool,
};

View File

@ -45,7 +45,6 @@ describe('Token Cell', () => {
symbol="TEST"
string="5.000"
currentCurrency="usd"
image="./test-image"
onClick={onClick}
/>
</MemoryRouter>
@ -61,7 +60,6 @@ describe('Token Cell', () => {
expect(wrapper.find(Identicon).prop('address')).toStrictEqual(
'0xAnotherToken',
);
expect(wrapper.find(Identicon).prop('image')).toStrictEqual('./test-image');
});
it('renders token balance', () => {

View File

@ -49,10 +49,6 @@ export default class Identicon extends Component {
* Check if show image border
*/
imageBorder: PropTypes.bool,
/**
* Check if use token detection
*/
useTokenDetection: PropTypes.bool,
/**
* Add list of token in object
*/
@ -102,8 +98,7 @@ export default class Identicon extends Component {
}
renderJazzicon() {
const { address, className, diameter, alt, useTokenDetection, tokenList } =
this.props;
const { address, className, diameter, alt, tokenList } = this.props;
return (
<Jazzicon
address={address}
@ -111,7 +106,6 @@ export default class Identicon extends Component {
className={classnames('identicon', className)}
style={getStyles(diameter)}
alt={alt}
useTokenDetection={useTokenDetection}
tokenList={tokenList}
/>
);
@ -136,15 +130,8 @@ export default class Identicon extends Component {
}
render() {
const {
address,
image,
useBlockie,
addBorder,
diameter,
useTokenDetection,
tokenList,
} = this.props;
const { address, image, useBlockie, addBorder, diameter, tokenList } =
this.props;
const size = diameter + 8;
if (image) {
@ -152,22 +139,10 @@ export default class Identicon extends Component {
}
if (address) {
if (process.env.TOKEN_DETECTION_V2) {
if (tokenList[address.toLowerCase()]?.iconUrl) {
return this.renderJazzicon();
}
} else {
/** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */
// token from dynamic api list is fetched when useTokenDetection is true
// And since the token.address from allTokens is checksumaddress
// tokenAddress have to be changed to lowercase when we are using dynamic list
const tokenAddress = useTokenDetection
? address.toLowerCase()
: address;
if (tokenAddress && tokenList[tokenAddress]?.iconUrl) {
return this.renderJazzicon();
}
if (tokenList[address.toLowerCase()]?.iconUrl) {
return this.renderJazzicon();
}
return (
<div
className={classnames({ 'identicon__address-wrapper': addBorder })}

View File

@ -1,15 +1,15 @@
import { connect } from 'react-redux';
import { getTokenList } from '../../../selectors';
import Identicon from './identicon.component';
const mapStateToProps = (state) => {
const {
metamask: { useBlockie, useTokenDetection, tokenList, ipfsGateway },
metamask: { useBlockie, ipfsGateway },
} = state;
return {
useBlockie,
useTokenDetection,
tokenList,
tokenList: getTokenList(state),
ipfsGateway,
};
};

View File

@ -48,7 +48,11 @@ export default class Jazzicon extends PureComponent {
appendJazzicon() {
const { address, diameter, tokenList } = this.props;
const image = iconFactory.iconForAddress(address, diameter, tokenList);
const image = iconFactory.iconForAddress(
address,
diameter,
tokenList[address.toLowerCase()],
);
this.container.current.appendChild(image);
}

View File

@ -10,12 +10,7 @@ import Identicon from '../identicon/identicon.component';
import { shortenAddress } from '../../../helpers/utils/util';
import CopyIcon from '../icon/copy-icon.component';
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
import {
getUseTokenDetection,
getTokenList,
getBlockExplorerLinkText,
} from '../../../selectors';
import { getTokenList, getBlockExplorerLinkText } from '../../../selectors';
import { NETWORKS_ROUTE } from '../../../helpers/constants/routes';
const NicknamePopover = ({
@ -33,7 +28,6 @@ const NicknamePopover = ({
}, [onAdd]);
const [copied, handleCopy] = useCopyToClipboard();
const useTokenDetection = useSelector(getUseTokenDetection);
const tokenList = useSelector(getTokenList);
const blockExplorerLinkText = useSelector(getBlockExplorerLinkText);
@ -54,8 +48,7 @@ const NicknamePopover = ({
address={address}
diameter={36}
className="nickname-popover__identicon"
useTokenDetection={useTokenDetection}
tokenList={tokenList}
image={tokenList[address.toLowerCase()]?.iconUrl}
/>
<div className="nickname-popover__address">
{nickname || shortenAddress(address)}

View File

@ -9,7 +9,7 @@ import TextField from '../text-field';
import { I18nContext } from '../../../contexts/i18n';
import Identicon from '../identicon/identicon.component';
import { getUseTokenDetection, getTokenList } from '../../../selectors';
import { getTokenList } from '../../../selectors';
export default function UpdateNicknamePopover({
address,
@ -46,7 +46,6 @@ export default function UpdateNicknamePopover({
onClose();
};
const useTokenDetection = useSelector(getUseTokenDetection);
const tokenList = useSelector(getTokenList);
return (
@ -79,8 +78,7 @@ export default function UpdateNicknamePopover({
className="update-nickname__content__indenticon"
address={address}
diameter={36}
useTokenDetection={useTokenDetection}
tokenList={tokenList}
image={tokenList[address.toLowerCase()]?.iconUrl}
/>
<label className="update-nickname__content__label--capitalized">
{t('address')}

View File

@ -314,21 +314,11 @@ export const SETTINGS_CONSTANTS = [
icon: 'fa fa-flask',
},
{
// TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up
tabMessage: (t) => t('advanced'),
sectionMessage: (t) => t('tokenDetection'),
descriptionMessage: (t) => t('tokenDetectionToggleDescription'),
descriptionMessage: (t) => t('tokenDetectionDescription'),
route: `${ADVANCED_ROUTE}#token-description`,
icon: 'fas fa-sliders-h',
featureFlag: 'TOKEN_DETECTION_V2',
},
{
// TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up
tabMessage: (t) => t('experimental'),
sectionMessage: (t) => t('useTokenDetection'),
descriptionMessage: (t) => t('useTokenDetectionDescription'),
route: `${EXPERIMENTAL_ROUTE}#token-description`,
icon: 'fa fa-flask',
},
{
tabMessage: (t) => t('experimental'),

View File

@ -14,9 +14,13 @@ function IconFactory(jazzicon) {
this.cache = {};
}
IconFactory.prototype.iconForAddress = function (address, diameter, tokenList) {
if (iconExistsFor(address.toLowerCase(), tokenList)) {
return imageElFor(address.toLowerCase(), tokenList);
IconFactory.prototype.iconForAddress = function (
address,
diameter,
tokenMetadata,
) {
if (iconExistsFor(address, tokenMetadata)) {
return imageElFor(tokenMetadata);
}
return this.generateIdenticonSvg(address, diameter);
@ -43,16 +47,15 @@ IconFactory.prototype.generateNewIdenticon = function (address, diameter) {
// util
function iconExistsFor(address, tokenList) {
function iconExistsFor(address, tokenMetadata) {
return (
tokenList[address] &&
isValidHexAddress(address, { allowNonPrefixed: false }) &&
tokenList[address].iconUrl
tokenMetadata &&
tokenMetadata.iconUrl
);
}
function imageElFor(address, tokenList) {
const tokenMetadata = tokenList[address];
function imageElFor(tokenMetadata = {}) {
const img = document.createElement('img');
img.src = tokenMetadata?.iconUrl;
img.style.width = '100%';

View File

@ -111,15 +111,10 @@ const t = (key) => {
return 'Localhost 8545';
case 'experimental':
return 'Experimental';
/** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */
case 'useTokenDetection':
return 'Use token detection';
case 'useTokenDetectionDescription':
return 'We use third-party APIs to detect and display new tokens sent to your wallet. Turn off if you dont want MetaMask to pull data from those services.';
case 'tokenDetection':
return 'Token detection';
case 'tokenDetectionToggleDescription':
return 'ConsenSys token API aggregates a list of tokens from various third party token lists. Turning it off will stop detecting new tokens added to your wallet, but will keep the option to search for tokens to import.';
case 'tokenDetectionDescription':
return "ConsenSys' token API aggregates a list of tokens from various third party token lists. When turned on, tokens will be automatically detected, and searchable, on Ethereum mainnet, Binance, Polygon and Avalanche. When turned off, you will still be able to search for tokens on Ethereum mainnet using MetaMask's legacy token list.";
case 'enableEIP1559V2':
return 'Enable enhanced gas fee UI';
case 'enableEIP1559V2Description':
@ -172,7 +167,7 @@ describe('Settings Search Utils', () => {
});
it('should get good advanced section number', () => {
expect(getNumberOfSettingsInSection(t, t('advanced'))).toStrictEqual(15);
expect(getNumberOfSettingsInSection(t, t('advanced'))).toStrictEqual(16);
});
it('should get good contact section number', () => {
@ -195,7 +190,7 @@ describe('Settings Search Utils', () => {
it('should get good experimental section number', () => {
expect(getNumberOfSettingsInSection(t, t('experimental'))).toStrictEqual(
4,
3,
);
});

View File

@ -44,13 +44,7 @@ async function getDecimalsFromContract(tokenAddress) {
}
export function getTokenMetadata(tokenAddress, tokenList) {
const casedTokenList = Object.keys(tokenList).reduce((acc, base) => {
return {
...acc,
[base.toLowerCase()]: tokenList[base],
};
}, {});
return tokenAddress && casedTokenList[tokenAddress.toLowerCase()];
return tokenAddress && tokenList[tokenAddress.toLowerCase()];
}
async function getSymbol(tokenAddress, tokenList) {

View File

@ -5,7 +5,6 @@ import {
getAddressBook,
getMetaMaskIdentities,
getTokenList,
getUseTokenDetection,
} from '../selectors';
import { shortenAddress } from '../helpers/utils/util';
@ -13,7 +12,6 @@ const useAddressDetails = (toAddress) => {
const addressBook = useSelector(getAddressBook);
const identities = useSelector(getMetaMaskIdentities);
const tokenList = useSelector(getTokenList);
const useTokenDetection = useSelector(getUseTokenDetection);
const checksummedAddress = toChecksumHexAddress(toAddress);
if (!toAddress) {
@ -28,22 +26,11 @@ const useAddressDetails = (toAddress) => {
if (identities[toAddress]?.name) {
return { toName: identities[toAddress].name, isTrusted: true };
}
if (process.env.TOKEN_DETECTION_V2) {
if (tokenList[toAddress]?.name) {
return { toName: tokenList[toAddress].name, isTrusted: true };
}
} else {
const casedTokenList = useTokenDetection
? tokenList
: Object.keys(tokenList).reduce((acc, base) => {
return {
...acc,
[base.toLowerCase()]: tokenList[base],
};
}, {});
if (casedTokenList[toAddress]?.name) {
return { toName: casedTokenList[toAddress].name, isTrusted: true };
}
if (tokenList[toAddress?.toLowerCase()]?.name) {
return {
toName: tokenList[toAddress?.toLowerCase()].name,
isTrusted: true,
};
}
return {
toName: shortenAddress(checksummedAddress),

View File

@ -77,8 +77,8 @@ describe('useAddressDetails', () => {
{
useTokenDetection: true,
tokenList: {
'0x06195827297c7A80a443b6894d3BDB8824b43896': {
address: '0x06195827297c7A80a443b6894d3BDB8824b43896',
'0x06195827297c7a80a443b6894d3bdb8824b43896': {
address: '0x06195827297c7a80a443b6894d3bdb8824b43896',
symbol: 'LINK',
decimals: 18,
name: 'TOKEN-ABC',

View File

@ -1,16 +1,13 @@
import { useMemo } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import contractMap from '@metamask/contract-metadata';
import BigNumber from 'bignumber.js';
import { isEqual, shuffle, uniqBy } from 'lodash';
import { isEqual, uniqBy } from 'lodash';
import { getTokenFiatAmount } from '../helpers/utils/token-util';
import {
getTokenExchangeRates,
getCurrentCurrency,
getSwapsDefaultToken,
getCurrentChainId,
getUseTokenDetection,
getTokenList,
} from '../selectors';
import { getConversionRate } from '../ducks/metamask/metamask';
@ -20,24 +17,13 @@ import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
import { TOKEN_BUCKET_PRIORITY } from '../../shared/constants/swaps';
import { useEqualityCheck } from './useEqualityCheck';
/** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */
const shuffledContractMap = shuffle(
Object.entries(contractMap)
.map(([address, tokenData]) => ({
...tokenData,
address: address.toLowerCase(),
}))
.filter((tokenData) => Boolean(tokenData.erc20)),
);
export function getRenderableTokenData(
token,
contractExchangeRates,
conversionRate,
currentCurrency,
chainId,
tokenList,
useTokenDetection,
shuffledTokenList,
) {
const { symbol, name, address, iconUrl, string, balance, decimals } = token;
let contractExchangeRate;
@ -67,24 +53,15 @@ export function getRenderableTokenData(
)
: '';
// token from dynamic api list is fetched when useTokenDetection is true
// And since the token.address from allTokens is checksumaddress
// token Address have to be changed to lowercase when we are using dynamic list
const tokenAddress =
useTokenDetection || process.env.TOKEN_DETECTION_V2
? address?.toLowerCase()
: address;
const tokenMetadata = shuffledTokenList.find(
(tokenData) => tokenData.address === address?.toLowerCase(),
);
let tokenIconUrl = tokenList[tokenAddress]?.iconUrl;
if (!process.env.TOKEN_DETECTION_V2 && !useTokenDetection && tokenIconUrl) {
tokenIconUrl = `images/contract/${tokenIconUrl}`;
}
const usedIconUrl = iconUrl || tokenIconUrl || token?.image;
const usedIconUrl = iconUrl || tokenMetadata?.iconUrl || token?.image;
return {
...token,
primaryLabel: symbol,
secondaryLabel: name || tokenList[tokenAddress]?.name,
secondaryLabel: name || tokenMetadata?.name,
rightPrimaryLabel:
string && `${new BigNumber(string).round(6).toString()} ${symbol}`,
rightSecondaryLabel: formattedFiat,
@ -92,7 +69,7 @@ export function getRenderableTokenData(
identiconAddress: usedIconUrl ? null : address,
balance,
decimals,
name: name || tokenList[tokenAddress]?.name,
name: name || tokenMetadata?.name,
rawFiat,
};
}
@ -108,15 +85,7 @@ export function useTokensToSearch({
const conversionRate = useSelector(getConversionRate);
const currentCurrency = useSelector(getCurrentCurrency);
const defaultSwapsToken = useSelector(getSwapsDefaultToken, shallowEqual);
const tokenList = useSelector(getTokenList, isEqual);
const useTokenDetection = useSelector(getUseTokenDetection);
let shuffledTokenList = shuffledTokensList;
if (!process.env.TOKEN_DETECTION_V2) {
// token from dynamic api list is fetched when useTokenDetection is true
shuffledTokenList = useTokenDetection
? shuffledTokensList
: shuffledContractMap;
}
const memoizedTopTokens = useEqualityCheck(topTokens);
const memoizedUsersToken = useEqualityCheck(usersTokens);
@ -126,8 +95,7 @@ export function useTokensToSearch({
conversionRate,
currentCurrency,
chainId,
tokenList,
useTokenDetection,
shuffledTokensList,
);
const memoizedDefaultToken = useEqualityCheck(defaultToken);
@ -137,7 +105,7 @@ export function useTokensToSearch({
? swapsTokens
: [
memoizedDefaultToken,
...shuffledTokenList.filter(
...shuffledTokensList.filter(
(token) => token.symbol !== memoizedDefaultToken.symbol,
),
];
@ -167,8 +135,7 @@ export function useTokensToSearch({
conversionRate,
currentCurrency,
chainId,
tokenList,
useTokenDetection,
shuffledTokensList,
);
if (tokenBucketPriority === TOKEN_BUCKET_PRIORITY.OWNED) {
if (
@ -224,8 +191,7 @@ export function useTokensToSearch({
currentCurrency,
memoizedDefaultToken,
chainId,
tokenList,
useTokenDetection,
shuffledTokensList,
tokenBucketPriority,
]);
}

View File

@ -6,7 +6,9 @@ import { ERC20 } from '../../../../shared/constants/transaction';
import ConfirmApproveContent from '.';
const renderComponent = (props) => {
const store = configureMockStore([])({ metamask: {} });
const store = configureMockStore([])({
metamask: { provider: { chainId: '0x0' } },
});
return renderWithProvider(<ConfirmApproveContent {...props} />, store);
};

View File

@ -1,6 +1,7 @@
import { connect } from 'react-redux';
import { compose } from 'redux';
import { withRouter } from 'react-router-dom';
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck';
import {
@ -29,7 +30,6 @@ import {
checkNetworkAndAccountSupports1559,
getPreferences,
doesAddressRequireLedgerHidConnection,
getUseTokenDetection,
getTokenList,
getIsMultiLayerFeeNetwork,
getEIP1559V2Enabled,
@ -119,21 +119,10 @@ const mapStateToProps = (state, ownProps) => {
}
const tokenList = getTokenList(state);
const useTokenDetection = getUseTokenDetection(state);
let casedTokenList = tokenList;
if (!process.env.TOKEN_DETECTION_V2) {
casedTokenList = useTokenDetection
? tokenList
: Object.keys(tokenList).reduce((acc, base) => {
return {
...acc,
[base.toLowerCase()]: tokenList[base],
};
}, {});
}
const toName =
identities[toAddress]?.name ||
casedTokenList[toAddress]?.name ||
tokenList[toAddress?.toLowerCase()]?.name ||
shortenAddress(toChecksumHexAddress(toAddress));
const checksummedAddress = toChecksumHexAddress(toAddress);

View File

@ -1,7 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { getTokenTrackerLink } from '@metamask/etherscan-link';
import contractMap from '@metamask/contract-metadata';
import ZENDESK_URLS from '../../helpers/constants/zendesk-url';
import {
checkExistingAddresses,
@ -11,7 +10,6 @@ import { tokenInfoGetter } from '../../helpers/utils/token-util';
import {
ADD_COLLECTIBLE_ROUTE,
CONFIRM_IMPORT_TOKEN_ROUTE,
EXPERIMENTAL_ROUTE,
ADVANCED_ROUTE,
} from '../../helpers/constants/routes';
import TextField from '../../components/ui/text-field';
@ -24,12 +22,10 @@ import Typography from '../../components/ui/typography';
import { TYPOGRAPHY, FONT_WEIGHT } from '../../helpers/constants/design-system';
import Button from '../../components/ui/button';
import { TOKEN_STANDARDS } from '../../../shared/constants/transaction';
import { STATIC_MAINNET_TOKEN_LIST } from '../../../shared/constants/tokens';
import TokenSearch from './token-search';
import TokenList from './token-list';
/*eslint-disable prefer-destructuring*/
const TOKEN_DETECTION_V2 = process.env.TOKEN_DETECTION_V2;
const emptyAddr = '0x0000000000000000000000000000000000000000';
const MIN_DECIMAL_VALUE = 0;
@ -113,7 +109,9 @@ class ImportToken extends Component {
* The currently selected active address.
*/
selectedAddress: PropTypes.string,
isTokenDetectionSupported: PropTypes.bool.isRequired,
isDynamicTokenListAvailable: PropTypes.bool.isRequired,
tokenDetectionInactiveOnNonMainnetSupportedNetwork:
PropTypes.bool.isRequired,
networkName: PropTypes.string.isRequired,
};
@ -225,9 +223,7 @@ class ImportToken extends Component {
}
const { setPendingTokens, history, tokenList } = this.props;
const tokenAddressList = Object.keys(tokenList).map((address) =>
address.toLowerCase(),
);
const tokenAddressList = Object.keys(tokenList);
const {
customAddress: address,
customSymbol: symbol,
@ -278,7 +274,7 @@ class ImportToken extends Component {
});
const standardAddress = addHexPrefix(customAddress).toLowerCase();
const isMainnetToken = Object.keys(contractMap).some(
const isMainnetToken = Object.keys(STATIC_MAINNET_TOKEN_LIST).some(
(key) => key.toLowerCase() === customAddress.toLowerCase(),
);
@ -410,7 +406,13 @@ class ImportToken extends Component {
collectibleAddressError,
} = this.state;
const { chainId, rpcPrefs, isTokenDetectionSupported } = this.props;
const {
chainId,
rpcPrefs,
isDynamicTokenListAvailable,
tokenDetectionInactiveOnNonMainnetSupportedNetwork,
history,
} = this.props;
const blockExplorerTokenLink = getTokenTrackerLink(
customAddress,
chainId,
@ -424,11 +426,40 @@ class ImportToken extends Component {
return (
<div className="import-token__custom-token-form">
{TOKEN_DETECTION_V2 ? (
{tokenDetectionInactiveOnNonMainnetSupportedNetwork ? (
<ActionableMessage
type={isTokenDetectionSupported ? 'warning' : 'default'}
type="warning"
message={t('customTokenWarningInTokenDetectionNetworkWithTDOFF', [
<Button
type="link"
key="import-token-security-risk"
className="import-token__link"
rel="noopener noreferrer"
target="_blank"
href={ZENDESK_URLS.TOKEN_SAFETY_PRACTICES}
>
{t('tokenScamSecurityRisk')}
</Button>,
<Button
type="link"
key="import-token-token-detection-announcement"
className="import-token__link"
onClick={() =>
history.push(`${ADVANCED_ROUTE}#token-description`)
}
>
{t('inYourSettings')}
</Button>,
])}
withRightButton
useIcon
iconFillColor="var(--color-warning-default)"
/>
) : (
<ActionableMessage
type={isDynamicTokenListAvailable ? 'warning' : 'default'}
message={t(
isTokenDetectionSupported
isDynamicTokenListAvailable
? 'customTokenWarningInTokenDetectionNetwork'
: 'customTokenWarningInNonTokenDetectionNetwork',
[
@ -447,30 +478,11 @@ class ImportToken extends Component {
withRightButton
useIcon
iconFillColor={
isTokenDetectionSupported
isDynamicTokenListAvailable
? 'var(--color-warning-default)'
: 'var(--color-info-default)'
}
/>
) : (
<ActionableMessage
message={this.context.t('fakeTokenWarning', [
<Button
type="link"
key="import-token-fake-token-warning"
className="import-token__link"
rel="noopener noreferrer"
target="_blank"
href={ZENDESK_URLS.TOKEN_SAFETY_PRACTICES}
>
{this.context.t('learnScamRisk')}
</Button>,
])}
type="warning"
withRightButton
useIcon
iconFillColor="var(--color-warning-default)"
/>
)}
<TextField
id="custom-address"
@ -569,32 +581,19 @@ class ImportToken extends Component {
<div className="import-token__search-token">
{!useTokenDetection && (
<ActionableMessage
message={
TOKEN_DETECTION_V2
? t('tokenDetectionAlertMessage', [
networkName,
<Button
type="link"
key="token-detection-announcement"
className="import-token__link"
onClick={() =>
history.push(`${ADVANCED_ROUTE}#token-description`)
}
>
{t('enableFromSettings')}
</Button>,
])
: this.context.t('tokenDetectionAnnouncement', [
<Button
type="link"
key="token-detection-announcement"
className="import-token__link"
onClick={() => history.push(`${EXPERIMENTAL_ROUTE}`)}
>
{t('enableFromSettings')}
</Button>,
])
}
message={t('tokenDetectionAlertMessage', [
networkName,
<Button
type="link"
key="token-detection-announcement"
className="import-token__link"
onClick={() =>
history.push(`${ADVANCED_ROUTE}#token-description`)
}
>
{t('enableFromSettings')}
</Button>,
])}
withRightButton
useIcon
iconFillColor="var(--color-primary-default)"

View File

@ -10,7 +10,10 @@ import {
getRpcPrefsForCurrentProvider,
getIsTokenDetectionSupported,
getTokenDetectionSupportNetworkByChainId,
getIsMainnet,
getIsTokenDetectionInactiveOnMainnet,
getIsDynamicTokenListAvailable,
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork,
getTokenList,
} from '../../selectors/selectors';
import ImportToken from './import-token.component';
@ -22,16 +25,15 @@ const mapStateToProps = (state) => {
pendingTokens,
provider: { chainId },
useTokenDetection,
tokenList,
selectedAddress,
},
} = state;
const tokenDetectionV2Supported =
process.env.TOKEN_DETECTION_V2 && getIsTokenDetectionSupported(state);
const isTokenDetectionInactiveOnMainnet =
getIsTokenDetectionInactiveOnMainnet(state);
const showSearchTab =
getIsMainnet(state) ||
tokenDetectionV2Supported ||
getIsTokenDetectionSupported(state) ||
isTokenDetectionInactiveOnMainnet ||
Boolean(process.env.IN_TEST);
return {
@ -42,11 +44,13 @@ const mapStateToProps = (state) => {
showSearchTab,
chainId,
rpcPrefs: getRpcPrefsForCurrentProvider(state),
tokenList,
tokenList: getTokenList(state),
useTokenDetection,
selectedAddress,
isTokenDetectionSupported: getIsTokenDetectionSupported(state),
isDynamicTokenListAvailable: getIsDynamicTokenListAvailable(state),
networkName: getTokenDetectionSupportNetworkByChainId(state),
tokenDetectionInactiveOnNonMainnetSupportedNetwork:
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork(state),
};
};
const mapDispatchToProps = (dispatch) => {

View File

@ -39,6 +39,7 @@ describe('Import Token', () => {
frequentRpcListDetail: [],
identities: {},
selectedAddress: '0x1231231',
useTokenDetection: true,
},
history: {
mostRecentOverviewPage: '/',

View File

@ -35,14 +35,13 @@ export default class TokenList extends Component {
{Array(6)
.fill(undefined)
.map((_, i) => {
const { iconUrl, symbol, name, address } = results[i] || {};
const iconPath = iconUrl;
const { symbol, name, address } = results[i] || {};
const tokenAlreadyAdded = checkExistingAddresses(address, tokens);
const onClick = () =>
!tokenAlreadyAdded && onToggleToken(results[i]);
return (
Boolean(iconUrl || symbol || name) && (
Boolean(results[i]?.iconUrl || symbol || name) && (
<div
className={classnames('token-list__token', {
'token-list__token--selected': selectedTokens[address],
@ -56,7 +55,8 @@ export default class TokenList extends Component {
<div
className="token-list__token-icon"
style={{
backgroundImage: iconUrl && `url(${iconPath})`,
backgroundImage:
results[i]?.iconUrl && `url(${results[i]?.iconUrl})`,
}}
/>
<div className="token-list__token-data">

View File

@ -1,11 +1,10 @@
import { connect } from 'react-redux';
import TokenList from './token-list.component';
const mapStateToProps = ({ metamask }) => {
const { tokens, useTokenDetection } = metamask;
const mapStateToProps = (state) => {
const { tokens } = state.metamask;
return {
tokens,
useTokenDetection,
};
};

View File

@ -791,10 +791,6 @@ export default class AdvancedTab extends PureComponent {
}
renderTokenDetectionToggle() {
if (!process.env.TOKEN_DETECTION_V2) {
return null;
}
const { t } = this.context;
const { useTokenDetection, setUseTokenDetection } = this.props;
@ -807,7 +803,7 @@ export default class AdvancedTab extends PureComponent {
<div className="settings-page__content-item">
<span>{t('tokenDetection')}</span>
<div className="settings-page__content-description">
{t('tokenDetectionToggleDescription')}
{t('tokenDetectionDescription')}
</div>
</div>
<div className="settings-page__content-item">

View File

@ -43,23 +43,23 @@ describe('AdvancedTab Component', () => {
});
it('should render correctly when threeBoxFeatureFlag', () => {
expect(component.find('.settings-page__content-row')).toHaveLength(15);
expect(component.find('.settings-page__content-row')).toHaveLength(16);
});
it('should render backup button', () => {
expect(component.find('.settings-page__content-row')).toHaveLength(15);
expect(component.find('.settings-page__content-row')).toHaveLength(16);
expect(
component
.find('.settings-page__content-row')
.at(9)
.at(10)
.find('.settings-page__content-item'),
).toHaveLength(2);
expect(
component
.find('.settings-page__content-row')
.at(9)
.at(10)
.find('.settings-page__content-item')
.at(0)
.find('.settings-page__content-description')
@ -69,7 +69,7 @@ describe('AdvancedTab Component', () => {
expect(
component
.find('.settings-page__content-row')
.at(9)
.at(10)
.find('.settings-page__content-item')
.at(1)
.find('Button')
@ -78,19 +78,19 @@ describe('AdvancedTab Component', () => {
});
it('should render restore button', () => {
expect(component.find('.settings-page__content-row')).toHaveLength(15);
expect(component.find('.settings-page__content-row')).toHaveLength(16);
expect(
component
.find('.settings-page__content-row')
.at(10)
.at(11)
.find('.settings-page__content-item'),
).toHaveLength(2);
expect(
component
.find('.settings-page__content-row')
.at(10)
.at(11)
.find('.settings-page__content-item')
.at(0)
.find('.settings-page__content-description')
@ -100,7 +100,7 @@ describe('AdvancedTab Component', () => {
expect(
component
.find('.settings-page__content-row')
.at(10)
.at(11)
.find('.settings-page__content-item')
.at(1)
.find('label')
@ -137,7 +137,7 @@ describe('AdvancedTab Component', () => {
},
);
const autoTimeout = component.find('.settings-page__content-row').at(8);
const autoTimeout = component.find('.settings-page__content-row').at(9);
const textField = autoTimeout.find(TextField);
textField.props().onChange({ target: { value: 1440 } });
@ -148,14 +148,13 @@ describe('AdvancedTab Component', () => {
});
it('should toggle show test networks', () => {
const testNetworks = component.find('.settings-page__content-row').at(6);
const testNetworks = component.find('.settings-page__content-row').at(7);
const toggleButton = testNetworks.find(ToggleButton);
toggleButton.first().simulate('toggle');
expect(toggleTestnet.calledOnce).toStrictEqual(true);
});
it('should toggle token detection', () => {
process.env.TOKEN_DETECTION_V2 = true;
component = shallow(
<AdvancedTab
ipfsGateway=""

View File

@ -16,8 +16,6 @@ export default class ExperimentalTab extends PureComponent {
};
static propTypes = {
useTokenDetection: PropTypes.bool,
setUseTokenDetection: PropTypes.func,
useCollectibleDetection: PropTypes.bool,
setUseCollectibleDetection: PropTypes.func,
setOpenSeaEnabled: PropTypes.func,
@ -51,42 +49,6 @@ export default class ExperimentalTab extends PureComponent {
handleSettingsRefs(t, t('experimental'), this.settingsRefs);
}
renderTokenDetectionToggle() {
const { t } = this.context;
const { useTokenDetection, setUseTokenDetection } = this.props;
return (
<div ref={this.settingsRefs[0]} className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{t('useTokenDetection')}</span>
<div className="settings-page__content-description">
{t('useTokenDetectionDescription')}
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={useTokenDetection}
onToggle={(value) => {
this.context.trackEvent({
category: EVENT.CATEGORIES.SETTINGS,
event: 'Token Detection',
properties: {
action: 'Token Detection',
legacy_event: true,
},
});
setUseTokenDetection(!value);
}}
offLabel={t('off')}
onLabel={t('on')}
/>
</div>
</div>
</div>
);
}
renderCollectibleDetectionToggle() {
if (!process.env.COLLECTIBLES_V1) {
return null;
@ -326,10 +288,6 @@ export default class ExperimentalTab extends PureComponent {
render() {
return (
<div className="settings-page__body">
{/* TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */}
{process.env.TOKEN_DETECTION_V2
? null
: this.renderTokenDetectionToggle()}
{this.renderOpenSeaEnabledToggle()}
{this.renderCollectibleDetectionToggle()}
{this.renderEIP1559V2EnabledToggle()}

View File

@ -1,38 +0,0 @@
import React from 'react';
import sinon from 'sinon';
import { mount } from 'enzyme';
import ExperimentalTab from './experimental-tab.container';
describe('Experimental Tab', () => {
let wrapper;
const props = {
useTokenDetection: true,
setUseTokenDetection: sinon.spy(),
};
it('toggles Use token detection', () => {
wrapper = mount(<ExperimentalTab.WrappedComponent {...props} />, {
context: {
t: (str) => str,
trackEvent: () => undefined,
},
});
const useTokenDetection = wrapper.find({ type: 'checkbox' }).at(0);
useTokenDetection.simulate('click');
expect(props.setUseTokenDetection.calledOnce).toStrictEqual(true);
});
/** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */
it('should not show use token detection toggle', () => {
process.env.TOKEN_DETECTION_V2 = true;
wrapper = mount(<ExperimentalTab.WrappedComponent {...props} />, {
context: {
t: (str) => str,
trackEvent: () => undefined,
},
});
const useTokenDetectionText = wrapper.find({ text: 'Use token detection' });
expect(useTokenDetectionText).toHaveLength(0);
});
});

View File

@ -2,7 +2,6 @@ import { compose } from 'redux';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import {
setUseTokenDetection,
setUseCollectibleDetection,
setOpenSeaEnabled,
setEIP1559V2Enabled,
@ -10,7 +9,6 @@ import {
setCustomNetworkListEnabled,
} from '../../../store/actions';
import {
getUseTokenDetection,
getUseCollectibleDetection,
getOpenSeaEnabled,
getEIP1559V2Enabled,
@ -21,10 +19,6 @@ import ExperimentalTab from './experimental-tab.component';
const mapStateToProps = (state) => {
return {
useTokenDetection:
getUseTokenDetection(
state,
) /** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */,
useCollectibleDetection: getUseCollectibleDetection(state),
openSeaEnabled: getOpenSeaEnabled(state),
eip1559V2Enabled: getEIP1559V2Enabled(state),
@ -35,10 +29,6 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = (dispatch) => {
return {
setUseTokenDetection: (val) =>
dispatch(
setUseTokenDetection(val),
) /** TODO: Remove during TOKEN_DETECTION_V2 feature flag clean up */,
setUseCollectibleDetection: (val) =>
dispatch(setUseCollectibleDetection(val)),
setOpenSeaEnabled: (val) => dispatch(setOpenSeaEnabled(val)),

View File

@ -54,7 +54,6 @@ export default class SettingsTab extends PureComponent {
setHideZeroBalanceTokens: PropTypes.func,
lastFetchedConversionDate: PropTypes.number,
selectedAddress: PropTypes.string,
useTokenDetection: PropTypes.bool,
tokenList: PropTypes.object,
};
@ -168,13 +167,8 @@ export default class SettingsTab extends PureComponent {
renderBlockieOptIn() {
const { t } = this.context;
const {
useBlockie,
setUseBlockie,
selectedAddress,
useTokenDetection,
tokenList,
} = this.props;
const { useBlockie, setUseBlockie, selectedAddress, tokenList } =
this.props;
const getIconStyles = () => ({
display: 'block',
@ -215,7 +209,6 @@ export default class SettingsTab extends PureComponent {
id="jazzicon"
address={selectedAddress}
diameter={32}
useTokenDetection={useTokenDetection}
tokenList={tokenList}
style={getIconStyles()}
/>

View File

@ -7,7 +7,7 @@ import {
setHideZeroBalanceTokens,
setParticipateInMetaMetrics,
} from '../../../store/actions';
import { getPreferences } from '../../../selectors';
import { getTokenList, getPreferences } from '../../../selectors';
import SettingsTab from './settings-tab.component';
const mapStateToProps = (state, ownProps) => {
@ -21,13 +21,12 @@ const mapStateToProps = (state, ownProps) => {
useBlockie,
currentLocale,
selectedAddress,
useTokenDetection,
tokenList,
} = metamask;
const { useNativeCurrencyAsPrimaryCurrency, hideZeroBalanceTokens } =
getPreferences(state);
const { lastFetchedConversionDate } = ownProps;
const tokenList = getTokenList(state);
return {
warning,
@ -39,7 +38,6 @@ const mapStateToProps = (state, ownProps) => {
hideZeroBalanceTokens,
lastFetchedConversionDate,
selectedAddress,
useTokenDetection,
tokenList,
};
};

View File

@ -67,7 +67,6 @@ import {
getCurrentChainId,
getRpcPrefsForCurrentProvider,
getUseTokenDetection,
getTokenList,
isHardwareWallet,
getHardwareWalletType,
} from '../../../selectors';
@ -151,7 +150,6 @@ export default function BuildQuote({
const defaultSwapsToken = useSelector(getSwapsDefaultToken, isEqual);
const chainId = useSelector(getCurrentChainId);
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider, shallowEqual);
const tokenList = useSelector(getTokenList, isEqual);
const useTokenDetection = useSelector(getUseTokenDetection);
const quotes = useSelector(getQuotes, isEqual);
const areQuotesPresent = Object.keys(quotes).length > 0;
@ -214,7 +212,7 @@ export default function BuildQuote({
conversionRate,
currentCurrency,
chainId,
tokenList,
shuffledTokensList,
useTokenDetection,
);

View File

@ -136,8 +136,8 @@ export default function Swap() {
checkNetworkAndAccountSupports1559,
);
const defaultSwapsToken = useSelector(getSwapsDefaultToken, isEqual);
const tokenList = useSelector(getTokenList, isEqual);
const listTokenValues = shuffle(Object.values(tokenList));
const tokenList = useSelector(getTokenList);
const shuffledTokensList = shuffle(Object.entries(tokenList));
const reviewSwapClickedTimestamp = useSelector(getReviewSwapClickedTimestamp);
const pendingSmartTransactions = useSelector(getPendingSmartTransactions);
const reviewSwapClicked = Boolean(reviewSwapClickedTimestamp);
@ -474,7 +474,7 @@ export default function Swap() {
<BuildQuote
ethBalance={ethBalance}
selectedAccountAddress={selectedAccountAddress}
shuffledTokensList={listTokenValues}
shuffledTokensList={shuffledTokensList}
/>
);
}}

View File

@ -34,12 +34,8 @@ export default function TokenDetailsPage() {
const tokenList = useSelector(getTokenList);
const { address: tokenAddress } = useParams();
const tokenMetadata = Object.values(tokenList).find((token) =>
isEqualCaseInsensitive(token.address, tokenAddress),
);
const tokenMetadata = tokenList[tokenAddress.toLowerCase()];
const aggregators = tokenMetadata?.aggregators?.join(', ');
const fileName = tokenMetadata?.iconUrl;
const imagePath = fileName;
const token = tokens.find(({ address }) =>
isEqualCaseInsensitive(address, tokenAddress),
@ -99,7 +95,7 @@ export default function TokenDetailsPage() {
<Identicon
diameter={32}
address={token.address}
image={tokenMetadata ? imagePath : token.image}
image={tokenMetadata ? tokenMetadata.iconUrl : token.image}
/>
</Box>
</Box>
@ -183,7 +179,7 @@ export default function TokenDetailsPage() {
? networkNickname ?? t('privateNetwork')
: t(networkType)}
</Typography>
{process.env.TOKEN_DETECTION_V2 && aggregators && (
{aggregators && (
<>
<Typography
variant={TYPOGRAPHY.H9}

View File

@ -318,15 +318,12 @@ describe('TokenDetailsPage', () => {
});
it('should render token list title in token details page', () => {
process.env.TOKEN_DETECTION_V2 = true;
const store = configureMockStore()(state);
const { getByText } = renderWithProvider(<TokenDetailsPage />, store);
expect(getByText('Token lists:')).toBeInTheDocument();
process.env.TOKEN_DETECTION_V2 = false;
});
it('should render token list for the token in token details page', () => {
process.env.TOKEN_DETECTION_V2 = true;
const store = configureMockStore()(state);
const { getByText } = renderWithProvider(<TokenDetailsPage />, store);
expect(
@ -334,7 +331,6 @@ describe('TokenDetailsPage', () => {
'Aave, Bancor, CMC, Crypto.com, CoinGecko, 1inch, Paraswap, PMM, Synthetix, Zapper, Zerion, 0x.',
),
).toBeInTheDocument();
process.env.TOKEN_DETECTION_V2 = false;
});
it('should call hide token button when button is clicked in token details page', () => {

View File

@ -53,7 +53,7 @@ import {
} from '../helpers/utils/conversions.util';
import { TEMPLATED_CONFIRMATION_MESSAGE_TYPES } from '../pages/confirmation/templates';
import { STATIC_MAINNET_TOKEN_LIST } from '../../shared/constants/tokens';
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
import { DAY } from '../../shared/constants/time';
import {
@ -828,8 +828,8 @@ function getAllowedAnnouncementIds(state) {
7: false,
8: supportsWebHid && currentKeyringIsLedger && currentlyUsingLedgerLive,
9: false,
10: Boolean(process.env.TOKEN_DETECTION_V2) && !process.env.IN_TEST,
11: Boolean(process.env.TOKEN_DETECTION_V2) && !process.env.IN_TEST,
10: true,
11: true,
12: false,
13: true,
};
@ -919,13 +919,19 @@ export function getTheme(state) {
}
/**
* To retrieve the tokenList produced by TokenListcontroller
* To retrieve the token list for use throughout the UI. Will return the remotely fetched list
* from the tokens controller if token detection is enabled, or the static list if not.
*
* @param {*} state
* @returns {object}
*/
export function getTokenList(state) {
return state.metamask.tokenList;
const isTokenDetectionInactiveOnMainnet =
getIsTokenDetectionInactiveOnMainnet(state);
const caseInSensitiveTokenList = isTokenDetectionInactiveOnMainnet
? STATIC_MAINNET_TOKEN_LIST
: state.metamask.tokenList;
return caseInSensitiveTokenList;
}
export function doesAddressRequireLedgerHidConnection(state, address) {
@ -1014,6 +1020,8 @@ export function getIsAdvancedGasFeeDefault(state) {
}
/**
* To get the name of the network that support token detection based in chainId.
*
* @param state
* @returns string e.g. ethereum, bsc or polygon
*/
@ -1033,13 +1041,13 @@ export const getTokenDetectionSupportNetworkByChainId = (state) => {
}
};
/**
* To check for the chainId that supports token detection ,
* To check if teh chainId supports token detection ,
* currently it returns true for Ethereum Mainnet, Polygon, BSC and Avalanche
*
* @param {*} state
* @returns Boolean
*/
export function getIsTokenDetectionSupported(state) {
export function getIsDynamicTokenListAvailable(state) {
const chainId = getCurrentChainId(state);
return [
MAINNET_CHAIN_ID,
@ -1069,6 +1077,50 @@ export function getNewTokensImported(state) {
return state.appState.newTokensImported;
}
/**
* To check if the token detection is OFF and the network is Mainnet
* so that the user can skip third party token api fetch
* and use the static tokenlist from contract-metadata
*
* @param {*} state
* @returns Boolean
*/
export function getIsTokenDetectionInactiveOnMainnet(state) {
const isMainnet = getIsMainnet(state);
const useTokenDetection = getUseTokenDetection(state);
return !useTokenDetection && isMainnet;
}
/**
* To check for the chainId that supports token detection ,
* currently it returns true for Ethereum Mainnet, Polygon, BSC and Avalanche
*
* @param {*} state
* @returns Boolean
*/
export function getIsTokenDetectionSupported(state) {
const useTokenDetection = getUseTokenDetection(state);
const isDynamicTokenListAvailable = getIsDynamicTokenListAvailable(state);
return useTokenDetection && isDynamicTokenListAvailable;
}
/**
* To check if the token detection is OFF for the token detection supported networks
* and the network is not Mainnet
*
* @param {*} state
* @returns Boolean
*/
export function getIstokenDetectionInactiveOnNonMainnetSupportedNetwork(state) {
const useTokenDetection = getUseTokenDetection(state);
const isMainnet = getIsMainnet(state);
const isDynamicTokenListAvailable = getIsDynamicTokenListAvailable(state);
return isDynamicTokenListAvailable && !useTokenDetection && !isMainnet;
}
/**
* To get the `customNetworkListEnabled` value which determines whether we use the custom network list
*

View File

@ -2867,10 +2867,10 @@
resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.35.0.tgz#2bf2b8f2b6fdbd5132f0bcfa594b6c02dc71c42e"
integrity sha512-zfZKwLFOVrQS8vTFoeoNCG9JhqmK4oyembGiGVVpUAYD9BHVZnd9WpicGoUC07ROXLEyQuAK9AJZNBtqwwzfEQ==
"@metamask/controllers@^30.0.0", "@metamask/controllers@^30.0.2":
version "30.0.2"
resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-30.0.2.tgz#0a5512598d2997e34d3542889cae7088fe1fa4b8"
integrity sha512-nIUQaaGPzy9whcAvzwHCRsMtw3YWdBv6mUiN9EA2CKdYUesnVj4bpXSSMEso1oEhsNRa2/XTZSb6i+Odo/raJw==
"@metamask/controllers@^30.0.0", "@metamask/controllers@^30.1.0":
version "30.1.0"
resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-30.1.0.tgz#157d0afca156f1f37a89fbb864c4ee5c64d23af0"
integrity sha512-480mQafsYKbl0q7YgV820mrPCUtWgLLVH/s8ozNT6/ZVX3sBU0FBhNKeCalewhn0HRfMRnLe8pvHCKIH30k/0w==
dependencies:
"@ethereumjs/common" "^2.3.1"
"@ethereumjs/tx" "^3.2.1"