mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #11439 from MetaMask/Version-v9.8.0
Version v9.8.0 RC
This commit is contained in:
commit
c87dce9ce5
11
CHANGELOG.md
11
CHANGELOG.md
@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [9.8.0]
|
||||
### Added
|
||||
- [#11435](https://github.com/MetaMask/metamask-extension/pull/11435): Add gas limit buffers for optimism network
|
||||
|
||||
### Changed
|
||||
- [#11210](https://github.com/MetaMask/metamask-extension/pull/11210): Disable sending ERC-721 assets (NFTs)
|
||||
- [#11418](https://github.com/MetaMask/metamask-extension/pull/11418): Use network gas estimate for gas limits of simple sends on custom networks
|
||||
|
||||
## [9.7.1]
|
||||
### Fixed
|
||||
- [#11426](https://github.com/MetaMask/metamask-extension/pull/11426): Fixed bug that broke transaction speed up and cancel, when attempting those actions immediately after opening MetaMask
|
||||
@ -2321,7 +2329,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Uncategorized
|
||||
- Added the ability to restore accounts from seed words.
|
||||
|
||||
[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v9.7.1...HEAD
|
||||
[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v9.8.0...HEAD
|
||||
[9.8.0]: https://github.com/MetaMask/metamask-extension/compare/v9.7.1...v9.8.0
|
||||
[9.7.1]: https://github.com/MetaMask/metamask-extension/compare/v9.7.0...v9.7.1
|
||||
[9.7.0]: https://github.com/MetaMask/metamask-extension/compare/v9.6.1...v9.7.0
|
||||
[9.6.1]: https://github.com/MetaMask/metamask-extension/compare/v9.6.0...v9.6.1
|
||||
|
@ -751,9 +751,6 @@
|
||||
"recents": {
|
||||
"message": "የቅርብ ጊዜያት"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "የተቀባይ አድራሻ"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "ፍለጋ፣ ለሕዝብ ክፍት የሆነ አድራሻ (0x), ወይም ENS"
|
||||
},
|
||||
|
@ -747,9 +747,6 @@
|
||||
"recents": {
|
||||
"message": "الحديث"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "عنوان المستلم"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "البحث، العنوان العام (0x)، أو ENS"
|
||||
},
|
||||
|
@ -750,9 +750,6 @@
|
||||
"recents": {
|
||||
"message": "Скорошни"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Адрес на получателя"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Търсене, публичен адрес (0x) или ENS"
|
||||
},
|
||||
|
@ -754,9 +754,6 @@
|
||||
"recents": {
|
||||
"message": "সাম্প্রতিকগুলি"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "প্রাপকের ঠিকানা"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "অনুসন্ধান, সার্বজনীন ঠিকানা (0x), বা ENS"
|
||||
},
|
||||
|
@ -732,9 +732,6 @@
|
||||
"readdToken": {
|
||||
"message": "Pots tornar a afegir aquesta fitxa en el futur anant a \"Afegir fitxa\" al menu d'opcions dels teus comptes."
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Adreça del destinatari"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Cerca, adreça pública (0x), o ENS"
|
||||
},
|
||||
|
@ -308,9 +308,6 @@
|
||||
"readdToken": {
|
||||
"message": "Tento token můžete v budoucnu přidat zpět s „Přidat token“ v nastavení účtu."
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Adresa příjemce"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Odmítnout"
|
||||
},
|
||||
|
@ -735,9 +735,6 @@
|
||||
"recents": {
|
||||
"message": "Seneste"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Modtagerens adresse"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Søg, offentlig adresse (0x) eller ENS"
|
||||
},
|
||||
|
@ -723,9 +723,6 @@
|
||||
"recents": {
|
||||
"message": "Letzte"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Empfängeradresse"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Suchen, öffentliche Adresse (0x) oder ENS"
|
||||
},
|
||||
|
@ -751,9 +751,6 @@
|
||||
"recents": {
|
||||
"message": "Πρόσφατα"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Διεύθυνση Παραλήπτη"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Αναζήτηση, δημόσια διεύθυνση (0x) ή ENS"
|
||||
},
|
||||
|
@ -650,12 +650,21 @@
|
||||
"message": "The endpoint returned a different chain ID: $1",
|
||||
"description": "$1 is the return value of eth_chainId from an RPC endpoint"
|
||||
},
|
||||
"ensIllegalCharacter": {
|
||||
"message": "Illegal Character for ENS."
|
||||
},
|
||||
"ensNotFoundOnCurrentNetwork": {
|
||||
"message": "ENS name not found on the current network. Try switching to Ethereum Mainnet."
|
||||
},
|
||||
"ensNotSupportedOnNetwork": {
|
||||
"message": "Network does not support ENS"
|
||||
},
|
||||
"ensRegistrationError": {
|
||||
"message": "Error in ENS name registration"
|
||||
},
|
||||
"ensUnknownError": {
|
||||
"message": "ENS Lookup failed."
|
||||
},
|
||||
"enterAnAlias": {
|
||||
"message": "Enter an alias"
|
||||
},
|
||||
@ -1174,6 +1183,9 @@
|
||||
"networkNameEthereum": {
|
||||
"message": "Ethereum"
|
||||
},
|
||||
"networkNamePolygon": {
|
||||
"message": "Polygon"
|
||||
},
|
||||
"networkNameTestnet": {
|
||||
"message": "Testnet"
|
||||
},
|
||||
@ -1451,9 +1463,6 @@
|
||||
"recents": {
|
||||
"message": "Recents"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Recipient Address"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Search, public address (0x), or ENS"
|
||||
},
|
||||
@ -2369,6 +2378,10 @@
|
||||
"message": "verify the network details",
|
||||
"description": "Serves as link text for the 'unrecognizedChain' key. This text will be embedded inside the translation for that key."
|
||||
},
|
||||
"unsendableAsset": {
|
||||
"message": "Sending collectible (ERC-721) tokens is not currently supported",
|
||||
"description": "This is an error message we show the user if they attempt to send a collectible asset type, for which currently don't support sending"
|
||||
},
|
||||
"updatedWithDate": {
|
||||
"message": "Updated $1"
|
||||
},
|
||||
|
@ -1411,9 +1411,6 @@
|
||||
"recents": {
|
||||
"message": "Recientes"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Dirección del destinatario"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Búsqueda, dirección pública (0x) o ENS"
|
||||
},
|
||||
|
@ -1419,9 +1419,6 @@
|
||||
"recents": {
|
||||
"message": "Recientes"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Dirección del destinatario"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Búsqueda, dirección pública (0x) o ENS"
|
||||
},
|
||||
|
@ -744,9 +744,6 @@
|
||||
"recents": {
|
||||
"message": "Hiljutised"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Saaja aadress"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Otsing, avalik aadress (0x) või ENS"
|
||||
},
|
||||
|
@ -754,9 +754,6 @@
|
||||
"recents": {
|
||||
"message": "واپسین"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "آدرس دریافت کننده"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "جستجو، آدرس عمومی (0x)، یا ENS"
|
||||
},
|
||||
|
@ -751,9 +751,6 @@
|
||||
"recents": {
|
||||
"message": "Viimeaikaiset"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Vastaanottajan osoite"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Haku, julkinen osoite (0x) tai ENS"
|
||||
},
|
||||
|
@ -678,9 +678,6 @@
|
||||
"recents": {
|
||||
"message": "Kamakailan"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Address ng Recipient"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Maghanap, pampublikong address (0x), o ENS"
|
||||
},
|
||||
|
@ -736,9 +736,6 @@
|
||||
"recents": {
|
||||
"message": "Récents"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Adresse du destinataire"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Recherche, adresse publique (0x) ou ENS"
|
||||
},
|
||||
|
@ -751,9 +751,6 @@
|
||||
"recents": {
|
||||
"message": "אחרונים"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "כתובת הנמען"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "חיפוש, כתובת ציבורית (0x), או ENS"
|
||||
},
|
||||
|
@ -1411,9 +1411,6 @@
|
||||
"recents": {
|
||||
"message": "हाल ही के"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "प्राप्तकर्ता का पता"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "खोज, सार्वजनिक पता (0x) या ENS"
|
||||
},
|
||||
|
@ -285,9 +285,6 @@
|
||||
"readdToken": {
|
||||
"message": "आप अपने खाता विकल्प मेनू में .टोकन जोड़ें. पर जाकर भविष्य में इस टोकन को वापस जोड़ सकते हैं।"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "प्राप्तकर्ता पता"
|
||||
},
|
||||
"reject": {
|
||||
"message": "अस्वीकार"
|
||||
},
|
||||
|
@ -747,9 +747,6 @@
|
||||
"recents": {
|
||||
"message": "Nedavno"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Adresa primatelja"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Pretraži, javne adrese (0x) ili ENS"
|
||||
},
|
||||
|
@ -450,9 +450,6 @@
|
||||
"readdToken": {
|
||||
"message": "Ou ka ajoute token sa aprè sa ankò ou prale nan \"Ajoute token\" nan opsyon meni kont ou an."
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Adrès pou resevwa"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Rejte"
|
||||
},
|
||||
|
@ -747,9 +747,6 @@
|
||||
"recents": {
|
||||
"message": "Legutóbbiak"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Címzett címe"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Keresés, nyilvános cím (0x) vagy ENS"
|
||||
},
|
||||
|
@ -1411,9 +1411,6 @@
|
||||
"recents": {
|
||||
"message": "Terkini"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Alamat Penerima"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Cari, alamat publik (0x), atau ENS"
|
||||
},
|
||||
|
@ -1201,9 +1201,6 @@
|
||||
"recents": {
|
||||
"message": "Recenti"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Indirizzo Destinatario"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Ricerca, indirizzo pubblico (0x) o ENS"
|
||||
},
|
||||
|
@ -1411,9 +1411,6 @@
|
||||
"recents": {
|
||||
"message": "最近"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "受信者のアドレス"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "検索、パブリック アドレス (0x)、または ENS"
|
||||
},
|
||||
|
@ -754,9 +754,6 @@
|
||||
"recents": {
|
||||
"message": "ಇತ್ತೀಚಿನವುಗಳು"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "ಸ್ವೀಕರಿಸುವವರ ವಿಳಾಸ"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "ಸಾರ್ವಜನಿಕ ವಿಳಾಸ (0x) ಅಥವಾ ENS ಹುಡುಕಿ"
|
||||
},
|
||||
|
@ -1415,9 +1415,6 @@
|
||||
"recents": {
|
||||
"message": "최근"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "수신인 주소"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "검색, 공개 주소(0x) 또는 ENS"
|
||||
},
|
||||
|
@ -754,9 +754,6 @@
|
||||
"recents": {
|
||||
"message": "Naujausi"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Gavėjo adresas"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Ieška, viešieji adresai (0x) arba ENS"
|
||||
},
|
||||
|
@ -750,9 +750,6 @@
|
||||
"recents": {
|
||||
"message": "Nesenie"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Saņēmēja adrese"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Meklēšana, publiskā adrese (0x) vai ENS"
|
||||
},
|
||||
|
@ -731,9 +731,6 @@
|
||||
"recents": {
|
||||
"message": "Baru-baru ini"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Alamat Penerima"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Cari, alamat awam (0x), atau ENS"
|
||||
},
|
||||
|
@ -272,9 +272,6 @@
|
||||
"readdToken": {
|
||||
"message": "U kunt dit token in de toekomst weer toevoegen door naar \"Token toevoegen\" te gaan in het menu met accountopties."
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Geadresseerde adres"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Afwijzen"
|
||||
},
|
||||
|
@ -741,9 +741,6 @@
|
||||
"recents": {
|
||||
"message": "Nylige"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Mottakeradresse"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Søk, offentlig adresse (0x) eller ENS"
|
||||
},
|
||||
|
@ -1419,9 +1419,6 @@
|
||||
"recents": {
|
||||
"message": "Mga Kamakailan"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Address ng Tatanggap"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Maghanap, pampublikong address (0x), o ENS"
|
||||
},
|
||||
|
@ -748,9 +748,6 @@
|
||||
"recents": {
|
||||
"message": "Ostatnie"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Adres odbiorcy"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Szukaj, adres publiczny (0x) lub ENS"
|
||||
},
|
||||
|
@ -282,9 +282,6 @@
|
||||
"readdToken": {
|
||||
"message": "Pode adicionar este token de novo clicando na opção “Adicionar token” no menu de opções da sua conta."
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Endereço do Destinatário"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Rejeitar"
|
||||
},
|
||||
|
@ -1405,9 +1405,6 @@
|
||||
"recents": {
|
||||
"message": "Recentes"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Endereço do destinatário"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Busca, endereço público (0x) ou ENS"
|
||||
},
|
||||
|
@ -741,9 +741,6 @@
|
||||
"recents": {
|
||||
"message": "Recente"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Adresă destinatar"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Căutare, adresa publică (0x) sau ENS"
|
||||
},
|
||||
|
@ -1411,9 +1411,6 @@
|
||||
"recents": {
|
||||
"message": "Недавние"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Адрес получателя"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Поиск, публичный адрес (0x) или ENS"
|
||||
},
|
||||
|
@ -723,9 +723,6 @@
|
||||
"recents": {
|
||||
"message": "Posledné"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Adresa příjemce"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Vyhľadávať verejnú adresu (0x) alebo ENS"
|
||||
},
|
||||
|
@ -742,9 +742,6 @@
|
||||
"recents": {
|
||||
"message": "Nedavno"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Prejemnikov naslov"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Iskanje, javni naslov (0x) ali ENS"
|
||||
},
|
||||
|
@ -745,9 +745,6 @@
|
||||
"recents": {
|
||||
"message": "Skorašnje"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Adresa primaoca"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Pretraga, javna adresa (0x) ili ENS"
|
||||
},
|
||||
|
@ -738,9 +738,6 @@
|
||||
"recents": {
|
||||
"message": "Senaste"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Mottagaradress"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Sök, allmän adress (0x) eller ENS"
|
||||
},
|
||||
|
@ -732,9 +732,6 @@
|
||||
"recents": {
|
||||
"message": "Za hivi karibuni"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Anwani ya Mpokeaji"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Tafuta, anwani za umma (0x), au ENS"
|
||||
},
|
||||
|
@ -372,9 +372,6 @@
|
||||
"readdToken": {
|
||||
"message": "உங்கள் கணக்கு விருப்பங்கள் மெனுவில் \"டோக்கனைச் சேர்\" என்பதன் மூலம் நீங்கள் எதிர்காலத்தில் இந்த டோக்கனை மீண்டும் சேர்க்கலாம்."
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "பெறுநர் முகவரி"
|
||||
},
|
||||
"reject": {
|
||||
"message": "நிராகரி"
|
||||
},
|
||||
|
@ -375,9 +375,6 @@
|
||||
"readdToken": {
|
||||
"message": "คุณสามารถเพิ่มโทเค็นนี้ในอนาคตได้โดยไปที่ “เพิ่มโทเค็น” ในเมนูตัวเลือกบัญชีของคุณ"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "แอดแดรสผู้รับ"
|
||||
},
|
||||
"reject": {
|
||||
"message": "ปฏิเสธ"
|
||||
},
|
||||
|
@ -1192,9 +1192,6 @@
|
||||
"recents": {
|
||||
"message": "Mga Kamakailan"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Address ng Tatanggap"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Maghanap, pampublikong address (0x), o ENS"
|
||||
},
|
||||
|
@ -324,9 +324,6 @@
|
||||
"readdToken": {
|
||||
"message": "Gelecekte Bu jetonu hesap seçenekleri menüsünde “Jeton ekle”'ye giderek geri ekleyebilirsiniz."
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Alıcı adresi"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Reddetmek"
|
||||
},
|
||||
|
@ -754,9 +754,6 @@
|
||||
"recents": {
|
||||
"message": "Останні"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Адреса отримувача"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Пошук, публічна адреса (0x), або ENS"
|
||||
},
|
||||
|
@ -1411,9 +1411,6 @@
|
||||
"recents": {
|
||||
"message": "Gần đây"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Địa chỉ người nhận"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Tìm kiếm, địa chỉ công khai (0x) hoặc ENS"
|
||||
},
|
||||
|
@ -1195,9 +1195,6 @@
|
||||
"recents": {
|
||||
"message": "最近记录"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "接收地址"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "查找、公用地址 (0x) 或 ENS"
|
||||
},
|
||||
|
@ -751,9 +751,6 @@
|
||||
"recents": {
|
||||
"message": "最近"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "接收位址"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "搜尋,公開地址 (0x),或 ENS"
|
||||
},
|
||||
|
@ -11,7 +11,7 @@ import PreferencesController from './preferences';
|
||||
|
||||
describe('DetectTokensController', function () {
|
||||
const sandbox = sinon.createSandbox();
|
||||
let keyringMemStore, network, preferences;
|
||||
let keyringMemStore, network, preferences, provider;
|
||||
|
||||
const noop = () => undefined;
|
||||
|
||||
@ -23,12 +23,16 @@ describe('DetectTokensController', function () {
|
||||
keyringMemStore = new ObservableStore({ isUnlocked: false });
|
||||
network = new NetworkController();
|
||||
network.setInfuraProjectId('foo');
|
||||
preferences = new PreferencesController({ network });
|
||||
network.initializeProvider(networkControllerProviderConfig);
|
||||
provider = network.getProviderAndBlockTracker().provider;
|
||||
preferences = new PreferencesController({ network, provider });
|
||||
preferences.setAddresses([
|
||||
'0x7e57e2',
|
||||
'0xbc86727e770de68b1060c91f6bb6945c73e10388',
|
||||
]);
|
||||
network.initializeProvider(networkControllerProviderConfig);
|
||||
sandbox
|
||||
.stub(preferences, '_detectIsERC721')
|
||||
.returns(Promise.resolve(false));
|
||||
});
|
||||
|
||||
after(function () {
|
||||
@ -125,6 +129,7 @@ describe('DetectTokensController', function () {
|
||||
address: existingTokenAddress.toLowerCase(),
|
||||
decimals: existingToken.decimals,
|
||||
symbol: existingToken.symbol,
|
||||
isERC721: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
@ -177,11 +182,13 @@ describe('DetectTokensController', function () {
|
||||
address: existingTokenAddress.toLowerCase(),
|
||||
decimals: existingToken.decimals,
|
||||
symbol: existingToken.symbol,
|
||||
isERC721: false,
|
||||
},
|
||||
{
|
||||
address: tokenAddressToAdd.toLowerCase(),
|
||||
decimals: tokenToAdd.decimals,
|
||||
symbol: tokenToAdd.symbol,
|
||||
isERC721: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
@ -234,11 +241,13 @@ describe('DetectTokensController', function () {
|
||||
address: existingTokenAddress.toLowerCase(),
|
||||
decimals: existingToken.decimals,
|
||||
symbol: existingToken.symbol,
|
||||
isERC721: false,
|
||||
},
|
||||
{
|
||||
address: tokenAddressToAdd.toLowerCase(),
|
||||
decimals: tokenToAdd.decimals,
|
||||
symbol: tokenToAdd.symbol,
|
||||
isERC721: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -2,14 +2,21 @@ import { strict as assert } from 'assert';
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import { ethErrors } from 'eth-rpc-errors';
|
||||
import { normalize as normalizeAddress } from 'eth-sig-util';
|
||||
import ethers from 'ethers';
|
||||
import { ethers } from 'ethers';
|
||||
import log from 'loglevel';
|
||||
import abiERC721 from 'human-standard-collectible-abi';
|
||||
import contractsMap from '@metamask/contract-metadata';
|
||||
import { LISTED_CONTRACT_ADDRESSES } from '../../../shared/constants/tokens';
|
||||
import { NETWORK_TYPE_TO_ID_MAP } from '../../../shared/constants/network';
|
||||
import { isPrefixedFormattedHexString } from '../../../shared/modules/network.utils';
|
||||
import { isValidHexAddress } from '../../../shared/modules/hexstring-utils';
|
||||
import {
|
||||
isValidHexAddress,
|
||||
toChecksumHexAddress,
|
||||
} from '../../../shared/modules/hexstring-utils';
|
||||
import { NETWORK_EVENTS } from './network';
|
||||
|
||||
const ERC721METADATA_INTERFACE_ID = '0x5b5e139f';
|
||||
|
||||
export default class PreferencesController {
|
||||
/**
|
||||
*
|
||||
@ -73,11 +80,18 @@ export default class PreferencesController {
|
||||
};
|
||||
|
||||
this.network = opts.network;
|
||||
this.ethersProvider = new ethers.providers.Web3Provider(opts.provider);
|
||||
this.store = new ObservableStore(initState);
|
||||
this.store.setMaxListeners(12);
|
||||
this.openPopup = opts.openPopup;
|
||||
this.migrateAddressBookState = opts.migrateAddressBookState;
|
||||
this._subscribeToNetworkDidChange();
|
||||
|
||||
this.network.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
|
||||
const { tokens, hiddenTokens } = this._getTokenRelatedStates();
|
||||
this.ethersProvider = new ethers.providers.Web3Provider(opts.provider);
|
||||
this._updateAccountTokens(tokens, this.getAssetImages(), hiddenTokens);
|
||||
});
|
||||
|
||||
this._subscribeToInfuraAvailability();
|
||||
|
||||
global.setPreference = (key, value) => {
|
||||
@ -393,6 +407,8 @@ export default class PreferencesController {
|
||||
});
|
||||
const previousIndex = tokens.indexOf(previousEntry);
|
||||
|
||||
newEntry.isERC721 = await this._detectIsERC721(newEntry.address);
|
||||
|
||||
if (previousEntry) {
|
||||
tokens[previousIndex] = newEntry;
|
||||
} else {
|
||||
@ -403,6 +419,24 @@ export default class PreferencesController {
|
||||
return Promise.resolve(tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds isERC721 field to token object
|
||||
* (Called when a user attempts to add tokens that were previously added which do not yet had isERC721 field)
|
||||
*
|
||||
* @param {string} tokenAddress - The contract address of the token requiring the isERC721 field added.
|
||||
* @returns {Promise<object>} The new token object with the added isERC721 field.
|
||||
*
|
||||
*/
|
||||
async updateTokenType(tokenAddress) {
|
||||
const { tokens } = this.store.getState();
|
||||
const tokenIndex = tokens.findIndex((token) => {
|
||||
return token.address === tokenAddress;
|
||||
});
|
||||
tokens[tokenIndex].isERC721 = await this._detectIsERC721(tokenAddress);
|
||||
this.store.updateState({ tokens });
|
||||
return Promise.resolve(tokens[tokenIndex]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a specified token from the tokens array and adds it to hiddenTokens array
|
||||
*
|
||||
@ -480,11 +514,8 @@ export default class PreferencesController {
|
||||
let addressBookKey = rpcDetail.chainId;
|
||||
if (!addressBookKey) {
|
||||
// We need to find the networkId to determine what these addresses were keyed by
|
||||
const provider = new ethers.providers.JsonRpcProvider(
|
||||
rpcDetail.rpcUrl,
|
||||
);
|
||||
try {
|
||||
addressBookKey = await provider.send('net_version');
|
||||
addressBookKey = await this.ethersProvider.send('net_version');
|
||||
assert(typeof addressBookKey === 'string');
|
||||
} catch (error) {
|
||||
log.debug(error);
|
||||
@ -701,17 +732,6 @@ export default class PreferencesController {
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
||||
/**
|
||||
* Handle updating token list to reflect current network by listening for the
|
||||
* NETWORK_DID_CHANGE event.
|
||||
*/
|
||||
_subscribeToNetworkDidChange() {
|
||||
this.network.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
|
||||
const { tokens, hiddenTokens } = this._getTokenRelatedStates();
|
||||
this._updateAccountTokens(tokens, this.getAssetImages(), hiddenTokens);
|
||||
});
|
||||
}
|
||||
|
||||
_subscribeToInfuraAvailability() {
|
||||
this.network.on(NETWORK_EVENTS.INFURA_IS_BLOCKED, () => {
|
||||
this._setInfuraBlocked(true);
|
||||
@ -763,6 +783,43 @@ export default class PreferencesController {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether or not a token is ERC-721 compatible.
|
||||
*
|
||||
* @param {string} tokensAddress - the token contract address.
|
||||
*
|
||||
*/
|
||||
async _detectIsERC721(tokenAddress) {
|
||||
const checksumAddress = toChecksumHexAddress(tokenAddress);
|
||||
// if this token is already in our contract metadata map we don't need
|
||||
// to check against the contract
|
||||
if (contractsMap[checksumAddress]?.erc721 === true) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
const tokenContract = await this._createEthersContract(
|
||||
tokenAddress,
|
||||
abiERC721,
|
||||
this.ethersProvider,
|
||||
);
|
||||
|
||||
return await tokenContract
|
||||
.supportsInterface(ERC721METADATA_INTERFACE_ID)
|
||||
.catch((error) => {
|
||||
console.log('error', error);
|
||||
log.debug(error);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
async _createEthersContract(tokenAddress, abi, ethersProvider) {
|
||||
const tokenContract = await new ethers.Contract(
|
||||
tokenAddress,
|
||||
abi,
|
||||
ethersProvider,
|
||||
);
|
||||
return tokenContract;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates `tokens` and `hiddenTokens` of current account and network.
|
||||
*
|
||||
|
@ -1,10 +1,13 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import sinon from 'sinon';
|
||||
import contractMaps from '@metamask/contract-metadata';
|
||||
import abiERC721 from 'human-standard-collectible-abi';
|
||||
import {
|
||||
MAINNET_CHAIN_ID,
|
||||
RINKEBY_CHAIN_ID,
|
||||
} from '../../../shared/constants/network';
|
||||
import PreferencesController from './preferences';
|
||||
import NetworkController from './network';
|
||||
|
||||
describe('preferences controller', function () {
|
||||
let preferencesController;
|
||||
@ -13,19 +16,32 @@ describe('preferences controller', function () {
|
||||
let triggerNetworkChange;
|
||||
let switchToMainnet;
|
||||
let switchToRinkeby;
|
||||
let provider;
|
||||
const migrateAddressBookState = sinon.stub();
|
||||
|
||||
beforeEach(function () {
|
||||
const sandbox = sinon.createSandbox();
|
||||
currentChainId = MAINNET_CHAIN_ID;
|
||||
network = {
|
||||
getCurrentChainId: () => currentChainId,
|
||||
on: sinon.spy(),
|
||||
const networkControllerProviderConfig = {
|
||||
getAccounts: () => undefined,
|
||||
};
|
||||
network = new NetworkController();
|
||||
network.setInfuraProjectId('foo');
|
||||
network.initializeProvider(networkControllerProviderConfig);
|
||||
provider = network.getProviderAndBlockTracker().provider;
|
||||
|
||||
sandbox.stub(network, 'getCurrentChainId').callsFake(() => currentChainId);
|
||||
sandbox
|
||||
.stub(network, 'getProviderConfig')
|
||||
.callsFake(() => ({ type: 'mainnet' }));
|
||||
const spy = sandbox.spy(network, 'on');
|
||||
|
||||
preferencesController = new PreferencesController({
|
||||
migrateAddressBookState,
|
||||
network,
|
||||
provider,
|
||||
});
|
||||
triggerNetworkChange = network.on.firstCall.args[1];
|
||||
triggerNetworkChange = spy.firstCall.args[1];
|
||||
switchToMainnet = () => {
|
||||
currentChainId = MAINNET_CHAIN_ID;
|
||||
triggerNetworkChange();
|
||||
@ -86,6 +102,104 @@ describe('preferences controller', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateTokenType', function () {
|
||||
it('should add isERC721 = true to token object in state when token is collectible and in our contract-metadata repo', async function () {
|
||||
const contractAddresses = Object.keys(contractMaps);
|
||||
const erc721ContractAddresses = contractAddresses.filter(
|
||||
(contractAddress) => contractMaps[contractAddress].erc721 === true,
|
||||
);
|
||||
const address = erc721ContractAddresses[0];
|
||||
const { symbol, decimals } = contractMaps[address];
|
||||
preferencesController.store.updateState({
|
||||
tokens: [{ address, symbol, decimals }],
|
||||
});
|
||||
const result = await preferencesController.updateTokenType(address);
|
||||
assert.equal(result.isERC721, true);
|
||||
});
|
||||
|
||||
it('should add isERC721 = true to token object in state when token is collectible and not in our contract-metadata repo', async function () {
|
||||
const tokenAddress = '0xda5584cc586d07c7141aa427224a4bd58e64af7d';
|
||||
preferencesController.store.updateState({
|
||||
tokens: [
|
||||
{
|
||||
address: tokenAddress,
|
||||
symbol: 'TESTNFT',
|
||||
decimals: '0',
|
||||
},
|
||||
],
|
||||
});
|
||||
sinon
|
||||
.stub(preferencesController, '_detectIsERC721')
|
||||
.callsFake(() => true);
|
||||
|
||||
const result = await preferencesController.updateTokenType(tokenAddress);
|
||||
assert.equal(
|
||||
preferencesController._detectIsERC721.getCall(0).args[0],
|
||||
tokenAddress,
|
||||
);
|
||||
assert.equal(result.isERC721, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_detectIsERC721', function () {
|
||||
it('should return true when token is in our contract-metadata repo', async function () {
|
||||
const tokenAddress = '0x06012c8cf97BEaD5deAe237070F9587f8E7A266d';
|
||||
|
||||
const result = await preferencesController._detectIsERC721(tokenAddress);
|
||||
assert.equal(result, true);
|
||||
});
|
||||
|
||||
it('should return true when the token is not in our contract-metadata repo but tokenContract.supportsInterface returns true', async function () {
|
||||
const tokenAddress = '0xda5584cc586d07c7141aa427224a4bd58e64af7d';
|
||||
|
||||
const supportsInterfaceStub = sinon.stub().returns(Promise.resolve(true));
|
||||
sinon
|
||||
.stub(preferencesController, '_createEthersContract')
|
||||
.callsFake(() => ({ supportsInterface: supportsInterfaceStub }));
|
||||
|
||||
const result = await preferencesController._detectIsERC721(tokenAddress);
|
||||
assert.equal(
|
||||
preferencesController._createEthersContract.getCall(0).args[0],
|
||||
tokenAddress,
|
||||
);
|
||||
assert.deepEqual(
|
||||
preferencesController._createEthersContract.getCall(0).args[1],
|
||||
abiERC721,
|
||||
);
|
||||
assert.equal(
|
||||
preferencesController._createEthersContract.getCall(0).args[2],
|
||||
preferencesController.ethersProvider,
|
||||
);
|
||||
assert.equal(result, true);
|
||||
});
|
||||
|
||||
it('should return false when the token is not in our contract-metadata repo and tokenContract.supportsInterface returns false', async function () {
|
||||
const tokenAddress = '0xda5584cc586d07c7141aa427224a4bd58e64af7d';
|
||||
|
||||
const supportsInterfaceStub = sinon
|
||||
.stub()
|
||||
.returns(Promise.resolve(false));
|
||||
sinon
|
||||
.stub(preferencesController, '_createEthersContract')
|
||||
.callsFake(() => ({ supportsInterface: supportsInterfaceStub }));
|
||||
|
||||
const result = await preferencesController._detectIsERC721(tokenAddress);
|
||||
assert.equal(
|
||||
preferencesController._createEthersContract.getCall(0).args[0],
|
||||
tokenAddress,
|
||||
);
|
||||
assert.deepEqual(
|
||||
preferencesController._createEthersContract.getCall(0).args[1],
|
||||
abiERC721,
|
||||
);
|
||||
assert.equal(
|
||||
preferencesController._createEthersContract.getCall(0).args[2],
|
||||
preferencesController.ethersProvider,
|
||||
);
|
||||
assert.equal(result, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeAddress', function () {
|
||||
it('should remove an address from state', function () {
|
||||
preferencesController.setAddresses(['0xda22le', '0x7e57e2']);
|
||||
@ -291,7 +405,12 @@ describe('preferences controller', function () {
|
||||
assert.equal(tokens.length, 1, 'one token removed');
|
||||
|
||||
const [token1] = tokens;
|
||||
assert.deepEqual(token1, { address: '0xb', symbol: 'B', decimals: 5 });
|
||||
assert.deepEqual(token1, {
|
||||
address: '0xb',
|
||||
symbol: 'B',
|
||||
decimals: 5,
|
||||
isERC721: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove a token from its state on corresponding address', async function () {
|
||||
@ -310,7 +429,12 @@ describe('preferences controller', function () {
|
||||
assert.equal(tokensFirst.length, 1, 'one token removed in account');
|
||||
|
||||
const [token1] = tokensFirst;
|
||||
assert.deepEqual(token1, { address: '0xb', symbol: 'B', decimals: 5 });
|
||||
assert.deepEqual(token1, {
|
||||
address: '0xb',
|
||||
symbol: 'B',
|
||||
decimals: 5,
|
||||
isERC721: false,
|
||||
});
|
||||
|
||||
await preferencesController.setSelectedAddress('0x7e57e3');
|
||||
const tokensSecond = preferencesController.getTokens();
|
||||
@ -335,7 +459,12 @@ describe('preferences controller', function () {
|
||||
assert.equal(tokensFirst.length, 1, 'one token removed in network');
|
||||
|
||||
const [token1] = tokensFirst;
|
||||
assert.deepEqual(token1, { address: '0xb', symbol: 'B', decimals: 5 });
|
||||
assert.deepEqual(token1, {
|
||||
address: '0xb',
|
||||
symbol: 'B',
|
||||
decimals: 5,
|
||||
isERC721: false,
|
||||
});
|
||||
|
||||
switchToRinkeby();
|
||||
const tokensSecond = preferencesController.getTokens();
|
||||
|
@ -19,7 +19,6 @@ import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils'
|
||||
|
||||
import {
|
||||
fetchTradesInfo as defaultFetchTradesInfo,
|
||||
fetchSwapsFeatureLiveness as defaultFetchSwapsFeatureLiveness,
|
||||
fetchSwapsQuoteRefreshTime as defaultFetchSwapsQuoteRefreshTime,
|
||||
} from '../../../ui/pages/swaps/swaps.util';
|
||||
import { MINUTE, SECOND } from '../../../shared/constants/time';
|
||||
@ -73,6 +72,7 @@ const initialState = {
|
||||
topAggId: null,
|
||||
routeState: '',
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: false,
|
||||
swapsQuoteRefreshTime: FALLBACK_QUOTE_REFRESH_TIME,
|
||||
},
|
||||
};
|
||||
@ -85,7 +85,6 @@ export default class SwapsController {
|
||||
getProviderConfig,
|
||||
tokenRatesStore,
|
||||
fetchTradesInfo = defaultFetchTradesInfo,
|
||||
fetchSwapsFeatureLiveness = defaultFetchSwapsFeatureLiveness,
|
||||
fetchSwapsQuoteRefreshTime = defaultFetchSwapsQuoteRefreshTime,
|
||||
getCurrentChainId,
|
||||
}) {
|
||||
@ -94,7 +93,6 @@ export default class SwapsController {
|
||||
});
|
||||
|
||||
this._fetchTradesInfo = fetchTradesInfo;
|
||||
this._fetchSwapsFeatureLiveness = fetchSwapsFeatureLiveness;
|
||||
this._fetchSwapsQuoteRefreshTime = fetchSwapsQuoteRefreshTime;
|
||||
this._getCurrentChainId = getCurrentChainId;
|
||||
|
||||
@ -119,15 +117,19 @@ export default class SwapsController {
|
||||
// Sets the refresh rate for quote updates from the MetaSwap API
|
||||
async _setSwapsQuoteRefreshTime() {
|
||||
const chainId = this._getCurrentChainId();
|
||||
const { swapsState } = this.store.getState();
|
||||
|
||||
// Default to fallback time unless API returns valid response
|
||||
let swapsQuoteRefreshTime = FALLBACK_QUOTE_REFRESH_TIME;
|
||||
try {
|
||||
swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime(chainId);
|
||||
swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime(
|
||||
chainId,
|
||||
swapsState.useNewSwapsApi,
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Request for swaps quote refresh time failed: ', e);
|
||||
}
|
||||
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, swapsQuoteRefreshTime },
|
||||
});
|
||||
@ -162,6 +164,9 @@ export default class SwapsController {
|
||||
isPolledRequest,
|
||||
) {
|
||||
const { chainId } = fetchParamsMetaData;
|
||||
const {
|
||||
swapsState: { useNewSwapsApi },
|
||||
} = this.store.getState();
|
||||
|
||||
if (!fetchParams) {
|
||||
return null;
|
||||
@ -182,7 +187,10 @@ export default class SwapsController {
|
||||
this.indexOfNewestCallInFlight = indexOfCurrentCall;
|
||||
|
||||
let [newQuotes] = await Promise.all([
|
||||
this._fetchTradesInfo(fetchParams, fetchParamsMetaData),
|
||||
this._fetchTradesInfo(fetchParams, {
|
||||
...fetchParamsMetaData,
|
||||
useNewSwapsApi,
|
||||
}),
|
||||
this._setSwapsQuoteRefreshTime(),
|
||||
]);
|
||||
|
||||
@ -449,22 +457,23 @@ export default class SwapsController {
|
||||
this.store.updateState({ swapsState: { ...swapsState, routeState } });
|
||||
}
|
||||
|
||||
setSwapsLiveness(swapsFeatureIsLive) {
|
||||
setSwapsLiveness(swapsLiveness) {
|
||||
const { swapsState } = this.store.getState();
|
||||
const { swapsFeatureIsLive, useNewSwapsApi } = swapsLiveness;
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, swapsFeatureIsLive },
|
||||
swapsState: { ...swapsState, swapsFeatureIsLive, useNewSwapsApi },
|
||||
});
|
||||
}
|
||||
|
||||
resetPostFetchState() {
|
||||
const { swapsState } = this.store.getState();
|
||||
|
||||
this.store.updateState({
|
||||
swapsState: {
|
||||
...initialState.swapsState,
|
||||
tokens: swapsState.tokens,
|
||||
fetchParams: swapsState.fetchParams,
|
||||
swapsFeatureIsLive: swapsState.swapsFeatureIsLive,
|
||||
useNewSwapsApi: swapsState.useNewSwapsApi,
|
||||
swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime,
|
||||
},
|
||||
});
|
||||
@ -473,7 +482,6 @@ export default class SwapsController {
|
||||
|
||||
resetSwapsState() {
|
||||
const { swapsState } = this.store.getState();
|
||||
|
||||
this.store.updateState({
|
||||
swapsState: {
|
||||
...initialState.swapsState,
|
||||
|
@ -128,13 +128,13 @@ const EMPTY_INIT_STATE = {
|
||||
topAggId: null,
|
||||
routeState: '',
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: false,
|
||||
swapsQuoteRefreshTime: 60000,
|
||||
},
|
||||
};
|
||||
|
||||
const sandbox = sinon.createSandbox();
|
||||
const fetchTradesInfoStub = sandbox.stub();
|
||||
const fetchSwapsFeatureLivenessStub = sandbox.stub();
|
||||
const fetchSwapsQuoteRefreshTimeStub = sandbox.stub();
|
||||
const getCurrentChainIdStub = sandbox.stub();
|
||||
getCurrentChainIdStub.returns(MAINNET_CHAIN_ID);
|
||||
@ -150,7 +150,6 @@ describe('SwapsController', function () {
|
||||
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
|
||||
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||||
fetchSwapsQuoteRefreshTime: fetchSwapsQuoteRefreshTimeStub,
|
||||
getCurrentChainId: getCurrentChainIdStub,
|
||||
});
|
||||
@ -201,7 +200,6 @@ describe('SwapsController', function () {
|
||||
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
|
||||
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||||
getCurrentChainId: getCurrentChainIdStub,
|
||||
});
|
||||
const currentEthersInstance = swapsController.ethersProvider;
|
||||
@ -226,7 +224,6 @@ describe('SwapsController', function () {
|
||||
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
|
||||
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||||
getCurrentChainId: getCurrentChainIdStub,
|
||||
});
|
||||
const currentEthersInstance = swapsController.ethersProvider;
|
||||
@ -251,7 +248,6 @@ describe('SwapsController', function () {
|
||||
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
|
||||
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||||
getCurrentChainId: getCurrentChainIdStub,
|
||||
});
|
||||
const currentEthersInstance = swapsController.ethersProvider;
|
||||
@ -658,6 +654,7 @@ describe('SwapsController', function () {
|
||||
const quotes = await swapsController.fetchAndSetQuotes(undefined);
|
||||
assert.strictEqual(quotes, null);
|
||||
});
|
||||
|
||||
it('calls fetchTradesInfo with the given fetchParams and returns the correct quotes', async function () {
|
||||
fetchTradesInfoStub.resolves(getMockQuotes());
|
||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
||||
@ -695,15 +692,15 @@ describe('SwapsController', function () {
|
||||
metaMaskFeeInEth: '0.5050505050505050505',
|
||||
ethValueOfTokens: '50',
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
fetchTradesInfoStub.calledOnceWithExactly(
|
||||
MOCK_FETCH_PARAMS,
|
||||
MOCK_FETCH_METADATA,
|
||||
),
|
||||
fetchTradesInfoStub.calledOnceWithExactly(MOCK_FETCH_PARAMS, {
|
||||
...MOCK_FETCH_METADATA,
|
||||
useNewSwapsApi: false,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('performs the allowance check', async function () {
|
||||
fetchTradesInfoStub.resolves(getMockQuotes());
|
||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
||||
@ -878,12 +875,14 @@ describe('SwapsController', function () {
|
||||
const tokens = 'test';
|
||||
const fetchParams = 'test';
|
||||
const swapsFeatureIsLive = false;
|
||||
const useNewSwapsApi = false;
|
||||
const swapsQuoteRefreshTime = 0;
|
||||
swapsController.store.updateState({
|
||||
swapsState: {
|
||||
tokens,
|
||||
fetchParams,
|
||||
swapsFeatureIsLive,
|
||||
useNewSwapsApi,
|
||||
swapsQuoteRefreshTime,
|
||||
},
|
||||
});
|
||||
|
@ -25,6 +25,7 @@ export default class ThreeBoxController {
|
||||
addressBookController,
|
||||
version,
|
||||
getKeyringControllerState,
|
||||
trackMetaMetricsEvent,
|
||||
} = opts;
|
||||
|
||||
this.preferencesController = preferencesController;
|
||||
@ -59,6 +60,7 @@ export default class ThreeBoxController {
|
||||
);
|
||||
},
|
||||
});
|
||||
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
|
||||
|
||||
const initState = {
|
||||
threeBoxSyncingAllowed: false,
|
||||
@ -83,6 +85,12 @@ export default class ThreeBoxController {
|
||||
async init() {
|
||||
const accounts = await this.keyringController.getAccounts();
|
||||
this.address = accounts[0];
|
||||
|
||||
this._trackMetaMetricsEvent({
|
||||
event: '3Box Initiated',
|
||||
category: '3Box',
|
||||
});
|
||||
|
||||
if (this.address && !(this.box && this.store.getState().threeBoxSynced)) {
|
||||
await this.new3Box();
|
||||
}
|
||||
@ -140,8 +148,18 @@ export default class ThreeBoxController {
|
||||
backupExists = threeBoxConfig.spaces && threeBoxConfig.spaces.metamask;
|
||||
} catch (e) {
|
||||
if (e.message.match(/^Error: Invalid response \(404\)/u)) {
|
||||
this._trackMetaMetricsEvent({
|
||||
event: '3Box Backup does not exist',
|
||||
category: '3Box',
|
||||
});
|
||||
|
||||
backupExists = false;
|
||||
} else {
|
||||
this._trackMetaMetricsEvent({
|
||||
event: '3Box Config Error',
|
||||
category: '3Box',
|
||||
});
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@ -175,9 +193,19 @@ export default class ThreeBoxController {
|
||||
this.store.updateState(stateUpdate);
|
||||
|
||||
log.debug('3Box space sync done');
|
||||
|
||||
this._trackMetaMetricsEvent({
|
||||
event: '3Box Synced',
|
||||
category: '3Box',
|
||||
});
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
this._trackMetaMetricsEvent({
|
||||
event: '3Box Initiation Error',
|
||||
category: '3Box',
|
||||
});
|
||||
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
@ -216,13 +244,28 @@ export default class ThreeBoxController {
|
||||
preferences && this.preferencesController.store.updateState(preferences);
|
||||
addressBook && this.addressBookController.update(addressBook, true);
|
||||
this.setShowRestorePromptToFalse();
|
||||
|
||||
this._trackMetaMetricsEvent({
|
||||
event: '3Box Restored Data',
|
||||
category: '3Box',
|
||||
});
|
||||
}
|
||||
|
||||
turnThreeBoxSyncingOn() {
|
||||
this._trackMetaMetricsEvent({
|
||||
event: '3Box Sync Turned On',
|
||||
category: '3Box',
|
||||
});
|
||||
|
||||
this._registerUpdates();
|
||||
}
|
||||
|
||||
turnThreeBoxSyncingOff() {
|
||||
this._trackMetaMetricsEvent({
|
||||
event: '3Box Sync Turned Off',
|
||||
category: '3Box',
|
||||
});
|
||||
|
||||
this.box.logout();
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
bnToHex,
|
||||
BnMultiplyByFraction,
|
||||
addHexPrefix,
|
||||
getChainType,
|
||||
} from '../../lib/util';
|
||||
import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/helpers/constants/error-keys';
|
||||
import { getSwapsTokensReceivedFromTxMeta } from '../../../../ui/pages/swaps/swaps.util';
|
||||
@ -24,6 +25,7 @@ import {
|
||||
} from '../../../../shared/constants/transaction';
|
||||
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
|
||||
import { GAS_LIMITS } from '../../../../shared/constants/gas';
|
||||
import { CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP } from '../../../../shared/constants/network';
|
||||
import { isEIP1559Transaction } from '../../../../shared/modules/transaction.utils';
|
||||
import TransactionStateManager from './tx-state-manager';
|
||||
import TxGasUtil from './tx-gas-utils';
|
||||
@ -356,11 +358,16 @@ export default class TransactionController extends EventEmitter {
|
||||
* @returns {Promise<Object>} Object containing the default gas limit, or the simulation failure object
|
||||
*/
|
||||
async _getDefaultGasLimit(txMeta, getCodeResponse) {
|
||||
const chainId = this._getCurrentChainId();
|
||||
const customNetworkGasBuffer = CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP[chainId];
|
||||
const chainType = getChainType(chainId);
|
||||
|
||||
if (txMeta.txParams.gas) {
|
||||
return {};
|
||||
} else if (
|
||||
txMeta.txParams.to &&
|
||||
txMeta.type === TRANSACTION_TYPES.SENT_ETHER
|
||||
txMeta.type === TRANSACTION_TYPES.SENT_ETHER &&
|
||||
chainType !== 'custom'
|
||||
) {
|
||||
// if there's data in the params, but there's no contract code, it's not a valid transaction
|
||||
if (txMeta.txParams.data) {
|
||||
@ -389,6 +396,7 @@ export default class TransactionController extends EventEmitter {
|
||||
const gasLimit = this.txGasUtil.addGasBuffer(
|
||||
addHexPrefix(estimatedGasHex),
|
||||
blockGasLimit,
|
||||
customNetworkGasBuffer,
|
||||
);
|
||||
return { gasLimit, simulationFails };
|
||||
}
|
||||
|
@ -3,6 +3,10 @@ import extension from 'extensionizer';
|
||||
import { stripHexPrefix } from 'ethereumjs-util';
|
||||
import BN from 'bn.js';
|
||||
import { memoize } from 'lodash';
|
||||
import {
|
||||
MAINNET_CHAIN_ID,
|
||||
TEST_CHAINS,
|
||||
} from '../../../shared/constants/network';
|
||||
|
||||
import {
|
||||
ENVIRONMENT_TYPE_POPUP,
|
||||
@ -180,6 +184,15 @@ function bnToHex(inputBn) {
|
||||
return addHexPrefix(inputBn.toString(16));
|
||||
}
|
||||
|
||||
function getChainType(chainId) {
|
||||
if (chainId === MAINNET_CHAIN_ID) {
|
||||
return 'mainnet';
|
||||
} else if (TEST_CHAINS.includes(chainId)) {
|
||||
return 'testnet';
|
||||
}
|
||||
return 'custom';
|
||||
}
|
||||
|
||||
export {
|
||||
getPlatform,
|
||||
getEnvironmentType,
|
||||
@ -189,4 +202,5 @@ export {
|
||||
checkForError,
|
||||
addHexPrefix,
|
||||
bnToHex,
|
||||
getChainType,
|
||||
};
|
||||
|
@ -132,11 +132,17 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.networkController = new NetworkController(initState.NetworkController);
|
||||
this.networkController.setInfuraProjectId(opts.infuraProjectId);
|
||||
|
||||
// now we can initialize the RPC provider, which other controllers require
|
||||
this.initializeProvider();
|
||||
this.provider = this.networkController.getProviderAndBlockTracker().provider;
|
||||
this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker;
|
||||
|
||||
this.preferencesController = new PreferencesController({
|
||||
initState: initState.PreferencesController,
|
||||
initLangCode: opts.initLangCode,
|
||||
openPopup: opts.openPopup,
|
||||
network: this.networkController,
|
||||
provider: this.provider,
|
||||
migrateAddressBookState: this.migrateAddressBookState.bind(this),
|
||||
});
|
||||
|
||||
@ -183,11 +189,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
initState.NotificationController,
|
||||
);
|
||||
|
||||
// now we can initialize the RPC provider, which other controllers require
|
||||
this.initializeProvider();
|
||||
this.provider = this.networkController.getProviderAndBlockTracker().provider;
|
||||
this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker;
|
||||
|
||||
// token exchange rate tracker
|
||||
this.tokenRatesController = new TokenRatesController({
|
||||
preferences: this.preferencesController.store,
|
||||
@ -314,6 +315,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.keyringController.memStore,
|
||||
),
|
||||
version,
|
||||
trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind(
|
||||
this.metaMetricsController,
|
||||
),
|
||||
});
|
||||
|
||||
this.txController = new TransactionController({
|
||||
@ -727,6 +731,10 @@ export default class MetamaskController extends EventEmitter {
|
||||
preferencesController,
|
||||
),
|
||||
addToken: nodeify(preferencesController.addToken, preferencesController),
|
||||
updateTokenType: nodeify(
|
||||
preferencesController.updateTokenType,
|
||||
preferencesController,
|
||||
),
|
||||
removeToken: nodeify(
|
||||
preferencesController.removeToken,
|
||||
preferencesController,
|
||||
|
@ -6,9 +6,9 @@ module.exports = {
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 32.75,
|
||||
functions: 42.9,
|
||||
lines: 43.12,
|
||||
statements: 43.67,
|
||||
functions: 40,
|
||||
lines: 42.29,
|
||||
statements: 42.83,
|
||||
},
|
||||
},
|
||||
setupFiles: ['./test/setup.js', './test/env.js'],
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "metamask-crx",
|
||||
"version": "9.7.1",
|
||||
"version": "9.8.0",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -97,7 +97,7 @@
|
||||
"@lavamoat/preinstall-always-fail": "^1.0.0",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@metamask/contract-metadata": "^1.26.0",
|
||||
"@metamask/controllers": "^9.0.0",
|
||||
"@metamask/controllers": "^10.0.0",
|
||||
"@metamask/eth-ledger-bridge-keyring": "^0.5.0",
|
||||
"@metamask/eth-token-tracker": "^3.0.1",
|
||||
"@metamask/etherscan-link": "^2.1.0",
|
||||
@ -151,6 +151,7 @@
|
||||
"fast-safe-stringify": "^2.0.7",
|
||||
"fuse.js": "^3.2.0",
|
||||
"globalthis": "^1.0.1",
|
||||
"human-standard-collectible-abi": "^1.0.2",
|
||||
"human-standard-token-abi": "^2.0.0",
|
||||
"immer": "^8.0.1",
|
||||
"json-rpc-engine": "^6.1.0",
|
||||
|
@ -19,6 +19,9 @@ export const GOERLI_CHAIN_ID = '0x5';
|
||||
export const KOVAN_CHAIN_ID = '0x2a';
|
||||
export const LOCALHOST_CHAIN_ID = '0x539';
|
||||
export const BSC_CHAIN_ID = '0x38';
|
||||
export const OPTIMISM_CHAIN_ID = '0xa';
|
||||
export const OPTIMISM_TESTNET_CHAIN_ID = '0x45';
|
||||
export const POLYGON_CHAIN_ID = '0x89';
|
||||
|
||||
/**
|
||||
* The largest possible chain ID we can handle.
|
||||
@ -120,3 +123,8 @@ export const NATIVE_CURRENCY_TOKEN_IMAGE_MAP = {
|
||||
};
|
||||
|
||||
export const INFURA_BLOCKED_KEY = 'countryBlocked';
|
||||
|
||||
export const CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP = {
|
||||
[OPTIMISM_CHAIN_ID]: 1,
|
||||
[OPTIMISM_TESTNET_CHAIN_ID]: 1,
|
||||
};
|
||||
|
@ -93,3 +93,7 @@ export const SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP = {
|
||||
[BSC_CHAIN_ID]: BSC_DEFAULT_BLOCK_EXPLORER_URL,
|
||||
[MAINNET_CHAIN_ID]: MAINNET_DEFAULT_BLOCK_EXPLORER_URL,
|
||||
};
|
||||
|
||||
export const ETHEREUM = 'ethereum';
|
||||
export const POLYGON = 'polygon';
|
||||
export const BSC = 'bsc';
|
||||
|
@ -8,9 +8,21 @@
|
||||
"mockMetaMetricsResponse": true
|
||||
},
|
||||
"swaps": {
|
||||
"featureFlag": {
|
||||
"status": {
|
||||
"active": true
|
||||
"featureFlags": {
|
||||
"bsc": {
|
||||
"mobile_active": false,
|
||||
"extension_active": true,
|
||||
"fallback_to_v1": true
|
||||
},
|
||||
"ethereum": {
|
||||
"mobile_active": false,
|
||||
"extension_active": true,
|
||||
"fallback_to_v1": true
|
||||
},
|
||||
"polygon": {
|
||||
"mobile_active": false,
|
||||
"extension_active": true,
|
||||
"fallback_to_v1": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
219
test/e2e/tests/send-eth.spec.js
Normal file
219
test/e2e/tests/send-eth.spec.js
Normal file
@ -0,0 +1,219 @@
|
||||
const { strict: assert } = require('assert');
|
||||
const { withFixtures, regularDelayMs } = require('../helpers');
|
||||
|
||||
describe('Send ETH from inside MetaMask using default gas', function () {
|
||||
const ganacheOptions = {
|
||||
accounts: [
|
||||
{
|
||||
secretKey:
|
||||
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
|
||||
balance: 25000000000000000000,
|
||||
},
|
||||
],
|
||||
};
|
||||
it('finds the transaction in the transactions list', async function () {
|
||||
await withFixtures(
|
||||
{
|
||||
fixtures: 'imported-account',
|
||||
ganacheOptions,
|
||||
title: this.test.title,
|
||||
},
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
await driver.fill('#password', 'correct horse battery staple');
|
||||
await driver.press('#password', driver.Key.ENTER);
|
||||
|
||||
await driver.clickElement('[data-testid="eth-overview-send"]');
|
||||
|
||||
await driver.fill(
|
||||
'input[placeholder="Search, public address (0x), or ENS"]',
|
||||
'0x2f318C334780961FB129D2a6c30D0763d9a5C970',
|
||||
);
|
||||
|
||||
const inputAmount = await driver.findElement('.unit-input__input');
|
||||
await inputAmount.fill('1000');
|
||||
|
||||
const errorAmount = await driver.findElement('.send-v2__error-amount');
|
||||
assert.equal(
|
||||
await errorAmount.getText(),
|
||||
'Insufficient funds.',
|
||||
'send screen should render an insufficient fund error message',
|
||||
);
|
||||
|
||||
await inputAmount.press(driver.Key.BACK_SPACE);
|
||||
await inputAmount.press(driver.Key.BACK_SPACE);
|
||||
await inputAmount.press(driver.Key.BACK_SPACE);
|
||||
await driver.delay(regularDelayMs);
|
||||
|
||||
await driver.assertElementNotPresent('.send-v2__error-amount');
|
||||
|
||||
const amountMax = await driver.findClickableElement(
|
||||
'.send-v2__amount-max',
|
||||
);
|
||||
await amountMax.click();
|
||||
|
||||
let inputValue = await inputAmount.getAttribute('value');
|
||||
|
||||
assert(Number(inputValue) > 24);
|
||||
|
||||
await amountMax.click();
|
||||
|
||||
assert.equal(await inputAmount.isEnabled(), true);
|
||||
|
||||
await inputAmount.fill('1');
|
||||
|
||||
inputValue = await inputAmount.getAttribute('value');
|
||||
assert.equal(inputValue, '1');
|
||||
|
||||
// Continue to next screen
|
||||
await driver.clickElement({ text: 'Next', tag: 'button' });
|
||||
|
||||
await driver.clickElement({ text: 'Confirm', tag: 'button' });
|
||||
|
||||
await driver.clickElement('[data-testid="home__activity-tab"]');
|
||||
await driver.wait(async () => {
|
||||
const confirmedTxes = await driver.findElements(
|
||||
'.transaction-list__completed-transactions .transaction-list-item',
|
||||
);
|
||||
return confirmedTxes.length === 1;
|
||||
}, 10000);
|
||||
|
||||
await driver.waitForSelector({
|
||||
css: '.transaction-list-item__primary-currency',
|
||||
text: '-1 ETH',
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Send ETH from inside MetaMask using fast gas option', function () {
|
||||
const ganacheOptions = {
|
||||
accounts: [
|
||||
{
|
||||
secretKey:
|
||||
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
|
||||
balance: 25000000000000000000,
|
||||
},
|
||||
],
|
||||
};
|
||||
it('finds the transaction in the transactions list', async function () {
|
||||
await withFixtures(
|
||||
{
|
||||
fixtures: 'imported-account',
|
||||
ganacheOptions,
|
||||
title: this.test.title,
|
||||
},
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
await driver.fill('#password', 'correct horse battery staple');
|
||||
await driver.press('#password', driver.Key.ENTER);
|
||||
|
||||
await driver.clickElement('[data-testid="eth-overview-send"]');
|
||||
|
||||
await driver.fill(
|
||||
'input[placeholder="Search, public address (0x), or ENS"]',
|
||||
'0x2f318C334780961FB129D2a6c30D0763d9a5C970',
|
||||
);
|
||||
|
||||
const inputAmount = await driver.findElement('.unit-input__input');
|
||||
await inputAmount.fill('1');
|
||||
|
||||
const inputValue = await inputAmount.getAttribute('value');
|
||||
assert.equal(inputValue, '1');
|
||||
|
||||
// Set the gas price
|
||||
await driver.clickElement({ text: 'Fast', tag: 'button/div/div' });
|
||||
|
||||
// Continue to next screen
|
||||
await driver.clickElement({ text: 'Next', tag: 'button' });
|
||||
|
||||
await driver.clickElement({ text: 'Confirm', tag: 'button' });
|
||||
|
||||
await driver.waitForSelector(
|
||||
'.transaction-list__completed-transactions .transaction-list-item',
|
||||
);
|
||||
await driver.waitForSelector({
|
||||
css: '.transaction-list-item__primary-currency',
|
||||
text: '-1 ETH',
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Send ETH from inside MetaMask using advanced gas modal', function () {
|
||||
const ganacheOptions = {
|
||||
accounts: [
|
||||
{
|
||||
secretKey:
|
||||
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
|
||||
balance: 25000000000000000000,
|
||||
},
|
||||
],
|
||||
};
|
||||
it('finds the transaction in the transactions list', async function () {
|
||||
await withFixtures(
|
||||
{
|
||||
fixtures: 'imported-account',
|
||||
ganacheOptions,
|
||||
title: this.test.title,
|
||||
},
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
await driver.fill('#password', 'correct horse battery staple');
|
||||
await driver.press('#password', driver.Key.ENTER);
|
||||
|
||||
await driver.clickElement('[data-testid="eth-overview-send"]');
|
||||
|
||||
await driver.fill(
|
||||
'input[placeholder="Search, public address (0x), or ENS"]',
|
||||
'0x2f318C334780961FB129D2a6c30D0763d9a5C970',
|
||||
);
|
||||
|
||||
const inputAmount = await driver.findElement('.unit-input__input');
|
||||
await inputAmount.fill('1');
|
||||
|
||||
const inputValue = await inputAmount.getAttribute('value');
|
||||
assert.equal(inputValue, '1');
|
||||
|
||||
// Set the gas limit
|
||||
await driver.clickElement('.advanced-gas-options-btn');
|
||||
|
||||
// wait for gas modal to be visible
|
||||
const gasModal = await driver.findVisibleElement('span .modal');
|
||||
|
||||
await driver.clickElement({ text: 'Save', tag: 'button' });
|
||||
|
||||
// Wait for gas modal to be removed from DOM
|
||||
await gasModal.waitForElementState('hidden');
|
||||
|
||||
// Continue to next screen
|
||||
await driver.clickElement({ text: 'Next', tag: 'button' });
|
||||
|
||||
const transactionAmounts = await driver.findElements(
|
||||
'.currency-display-component__text',
|
||||
);
|
||||
const transactionAmount = transactionAmounts[0];
|
||||
assert.equal(await transactionAmount.getText(), '1');
|
||||
|
||||
await driver.clickElement({ text: 'Confirm', tag: 'button' });
|
||||
|
||||
await driver.wait(async () => {
|
||||
const confirmedTxes = await driver.findElements(
|
||||
'.transaction-list__completed-transactions .transaction-list-item',
|
||||
);
|
||||
return confirmedTxes.length === 1;
|
||||
}, 10000);
|
||||
|
||||
await driver.waitForSelector(
|
||||
{
|
||||
css: '.transaction-list-item__primary-currency',
|
||||
text: '-1 ETH',
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
@ -48,9 +48,9 @@ async function setupFetchMocking(driver) {
|
||||
return { json: async () => clone(mockResponses.gasPricesBasic) };
|
||||
} else if (url.match(/chromeextensionmm/u)) {
|
||||
return { json: async () => clone(mockResponses.metametrics) };
|
||||
} else if (url.match(/^https:\/\/(api\.metaswap|.*airswap-dev)/u)) {
|
||||
if (url.match(/featureFlag$/u)) {
|
||||
return { json: async () => clone(mockResponses.swaps.featureFlag) };
|
||||
} else if (url.match(/^https:\/\/(api2\.metaswap\.codefi\.network)/u)) {
|
||||
if (url.match(/featureFlags$/u)) {
|
||||
return { json: async () => clone(mockResponses.swaps.featureFlags) };
|
||||
}
|
||||
}
|
||||
return window.origFetch(...args);
|
||||
|
@ -1 +1,2 @@
|
||||
export const METASWAP_BASE_URL = 'https://api.metaswap.codefi.network';
|
||||
export const METASWAP_API_V2_BASE_URL = 'https://api2.metaswap.codefi.network';
|
||||
|
@ -106,6 +106,7 @@ export const createSwapsMockStore = () => {
|
||||
topAggId: null,
|
||||
routeState: '',
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -59,3 +59,23 @@ export const TOKENS_GET_RESPONSE = [
|
||||
address: '0x0D8775F648430679A709E98d2b0Cb6250d2887EF',
|
||||
},
|
||||
];
|
||||
|
||||
export const createFeatureFlagsResponse = () => {
|
||||
return {
|
||||
bsc: {
|
||||
mobile_active: false,
|
||||
extension_active: true,
|
||||
fallback_to_v1: true,
|
||||
},
|
||||
ethereum: {
|
||||
mobile_active: false,
|
||||
extension_active: true,
|
||||
fallback_to_v1: true,
|
||||
},
|
||||
polygon: {
|
||||
mobile_active: false,
|
||||
extension_active: true,
|
||||
fallback_to_v1: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -10,7 +10,7 @@ import InfoIcon from '../../ui/icon/info-icon.component';
|
||||
import Button from '../../ui/button';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { useMetricEvent } from '../../../hooks/useMetricEvent';
|
||||
import { updateSendToken } from '../../../ducks/send/send.duck';
|
||||
import { ASSET_TYPES, updateSendAsset } from '../../../ducks/send';
|
||||
import { SEND_ROUTE } from '../../../helpers/constants/routes';
|
||||
import { SEVERITIES } from '../../../helpers/constants/design-system';
|
||||
|
||||
@ -27,6 +27,7 @@ const AssetListItem = ({
|
||||
primary,
|
||||
secondary,
|
||||
identiconBorder,
|
||||
isERC721,
|
||||
}) => {
|
||||
const t = useI18nContext();
|
||||
const dispatch = useDispatch();
|
||||
@ -68,13 +69,17 @@ const AssetListItem = ({
|
||||
e.stopPropagation();
|
||||
sendTokenEvent();
|
||||
dispatch(
|
||||
updateSendToken({
|
||||
address: tokenAddress,
|
||||
decimals: tokenDecimals,
|
||||
symbol: tokenSymbol,
|
||||
updateSendAsset({
|
||||
type: ASSET_TYPES.TOKEN,
|
||||
details: {
|
||||
address: tokenAddress,
|
||||
decimals: tokenDecimals,
|
||||
symbol: tokenSymbol,
|
||||
},
|
||||
}),
|
||||
);
|
||||
history.push(SEND_ROUTE);
|
||||
).then(() => {
|
||||
history.push(SEND_ROUTE);
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('sendSpecifiedTokens', [tokenSymbol])}
|
||||
@ -107,7 +112,7 @@ const AssetListItem = ({
|
||||
</button>
|
||||
}
|
||||
titleIcon={titleIcon}
|
||||
subtitle={<h3 title={secondary}>{secondary}</h3>}
|
||||
subtitle={secondary ? <h3 title={secondary}>{secondary}</h3> : null}
|
||||
onClick={onClick}
|
||||
icon={
|
||||
<Identicon
|
||||
@ -121,10 +126,12 @@ const AssetListItem = ({
|
||||
}
|
||||
midContent={midContent}
|
||||
rightContent={
|
||||
<>
|
||||
<i className="fas fa-chevron-right asset-list-item__chevron-right" />
|
||||
{sendTokenButton}
|
||||
</>
|
||||
!isERC721 && (
|
||||
<>
|
||||
<i className="fas fa-chevron-right asset-list-item__chevron-right" />
|
||||
{sendTokenButton}
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
@ -143,6 +150,7 @@ AssetListItem.propTypes = {
|
||||
'primary': PropTypes.string,
|
||||
'secondary': PropTypes.string,
|
||||
'identiconBorder': PropTypes.bool,
|
||||
'isERC721': PropTypes.bool,
|
||||
};
|
||||
|
||||
AssetListItem.defaultProps = {
|
||||
|
@ -56,13 +56,13 @@ const AssetList = ({ onClickAsset }) => {
|
||||
},
|
||||
);
|
||||
|
||||
const [secondaryCurrencyDisplay] = useCurrencyDisplay(
|
||||
selectedAccountBalance,
|
||||
{
|
||||
numberOfDecimals: secondaryNumberOfDecimals,
|
||||
currency: secondaryCurrency,
|
||||
},
|
||||
);
|
||||
const [
|
||||
secondaryCurrencyDisplay,
|
||||
secondaryCurrencyProperties,
|
||||
] = useCurrencyDisplay(selectedAccountBalance, {
|
||||
numberOfDecimals: secondaryNumberOfDecimals,
|
||||
currency: secondaryCurrency,
|
||||
});
|
||||
|
||||
const primaryTokenImage = useSelector(getNativeCurrencyImage);
|
||||
|
||||
@ -71,7 +71,9 @@ const AssetList = ({ onClickAsset }) => {
|
||||
<AssetListItem
|
||||
onClick={() => onClickAsset(nativeCurrency)}
|
||||
data-testid="wallet-balance"
|
||||
primary={primaryCurrencyProperties.value}
|
||||
primary={
|
||||
primaryCurrencyProperties.value ?? secondaryCurrencyProperties.value
|
||||
}
|
||||
tokenSymbol={primaryCurrencyProperties.suffix}
|
||||
secondary={showFiat ? secondaryCurrencyDisplay : undefined}
|
||||
tokenImage={primaryTokenImage}
|
||||
|
@ -9,10 +9,10 @@ import {
|
||||
} from '../../../../ducks/gas/gas.duck';
|
||||
|
||||
import {
|
||||
hideGasButtonGroup,
|
||||
setGasLimit,
|
||||
setGasPrice,
|
||||
} from '../../../../ducks/send/send.duck';
|
||||
useCustomGas,
|
||||
updateGasLimit,
|
||||
updateGasPrice,
|
||||
} from '../../../../ducks/send';
|
||||
|
||||
let mapDispatchToProps;
|
||||
let mergeProps;
|
||||
@ -32,8 +32,6 @@ jest.mock('../../../../selectors', () => ({
|
||||
`mockRenderableBasicEstimateData:${Object.keys(s).length}`,
|
||||
getDefaultActiveButtonIndex: (a, b) => a + b,
|
||||
getCurrentEthBalance: (state) => state.metamask.balance || '0x0',
|
||||
getSendToken: () => null,
|
||||
getTokenBalance: (state) => state.send.tokenBalance || '0x0',
|
||||
getCustomGasPrice: (state) => state.gas.customData.price || '0x0',
|
||||
getCustomGasLimit: (state) => state.gas.customData.limit || '0x0',
|
||||
getCurrentCurrency: jest.fn().mockReturnValue('usd'),
|
||||
@ -57,11 +55,15 @@ jest.mock('../../../../ducks/gas/gas.duck', () => ({
|
||||
resetCustomData: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../ducks/send/send.duck', () => ({
|
||||
hideGasButtonGroup: jest.fn(),
|
||||
setGasLimit: jest.fn(),
|
||||
setGasPrice: jest.fn(),
|
||||
}));
|
||||
jest.mock('../../../../ducks/send', () => {
|
||||
const { ASSET_TYPES } = jest.requireActual('../../../../ducks/send');
|
||||
return {
|
||||
useCustomGas: jest.fn(),
|
||||
updateGasLimit: jest.fn(),
|
||||
updateGasPrice: jest.fn(),
|
||||
getSendAsset: jest.fn(() => ({ type: ASSET_TYPES.NATIVE })),
|
||||
};
|
||||
});
|
||||
|
||||
require('./gas-modal-page-container.container');
|
||||
|
||||
@ -79,11 +81,11 @@ describe('gas-modal-page-container container', () => {
|
||||
dispatchSpy.resetHistory();
|
||||
});
|
||||
|
||||
describe('hideGasButtonGroup()', () => {
|
||||
it('should dispatch a hideGasButtonGroup action', () => {
|
||||
mapDispatchToPropsObject.hideGasButtonGroup();
|
||||
describe('useCustomGas()', () => {
|
||||
it('should dispatch a useCustomGas action', () => {
|
||||
mapDispatchToPropsObject.useCustomGas();
|
||||
expect(dispatchSpy.calledOnce).toStrictEqual(true);
|
||||
expect(hideGasButtonGroup).toHaveBeenCalled();
|
||||
expect(useCustomGas).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@ -126,13 +128,13 @@ describe('gas-modal-page-container container', () => {
|
||||
});
|
||||
|
||||
describe('setGasData()', () => {
|
||||
it('should dispatch a setGasPrice and setGasLimit action with the correct props', () => {
|
||||
it('should dispatch a updateGasPrice and updateGasLimit action with the correct props', () => {
|
||||
mapDispatchToPropsObject.setGasData('ffff', 'aaaa');
|
||||
expect(dispatchSpy.calledTwice).toStrictEqual(true);
|
||||
expect(setGasPrice).toHaveBeenCalled();
|
||||
expect(setGasLimit).toHaveBeenCalled();
|
||||
expect(setGasLimit).toHaveBeenCalledWith('ffff');
|
||||
expect(setGasPrice).toHaveBeenCalledWith('aaaa');
|
||||
expect(updateGasPrice).toHaveBeenCalled();
|
||||
expect(updateGasLimit).toHaveBeenCalled();
|
||||
expect(updateGasLimit).toHaveBeenCalledWith('ffff');
|
||||
expect(updateGasPrice).toHaveBeenCalledWith('aaaa');
|
||||
});
|
||||
});
|
||||
|
||||
@ -165,7 +167,7 @@ describe('gas-modal-page-container container', () => {
|
||||
};
|
||||
dispatchProps = {
|
||||
updateCustomGasPrice: sinon.spy(),
|
||||
hideGasButtonGroup: sinon.spy(),
|
||||
useCustomGas: sinon.spy(),
|
||||
setGasData: sinon.spy(),
|
||||
updateConfirmTxGasAndCalculate: sinon.spy(),
|
||||
someOtherDispatchProp: sinon.spy(),
|
||||
@ -194,7 +196,7 @@ describe('gas-modal-page-container container', () => {
|
||||
dispatchProps.updateConfirmTxGasAndCalculate.callCount,
|
||||
).toStrictEqual(0);
|
||||
expect(dispatchProps.setGasData.callCount).toStrictEqual(0);
|
||||
expect(dispatchProps.hideGasButtonGroup.callCount).toStrictEqual(0);
|
||||
expect(dispatchProps.useCustomGas.callCount).toStrictEqual(0);
|
||||
expect(dispatchProps.hideModal.callCount).toStrictEqual(0);
|
||||
|
||||
result.onSubmit();
|
||||
@ -203,7 +205,7 @@ describe('gas-modal-page-container container', () => {
|
||||
dispatchProps.updateConfirmTxGasAndCalculate.callCount,
|
||||
).toStrictEqual(1);
|
||||
expect(dispatchProps.setGasData.callCount).toStrictEqual(0);
|
||||
expect(dispatchProps.hideGasButtonGroup.callCount).toStrictEqual(0);
|
||||
expect(dispatchProps.useCustomGas.callCount).toStrictEqual(0);
|
||||
expect(dispatchProps.hideModal.callCount).toStrictEqual(1);
|
||||
|
||||
expect(dispatchProps.updateCustomGasPrice.callCount).toStrictEqual(0);
|
||||
@ -238,7 +240,7 @@ describe('gas-modal-page-container container', () => {
|
||||
dispatchProps.updateConfirmTxGasAndCalculate.callCount,
|
||||
).toStrictEqual(0);
|
||||
expect(dispatchProps.setGasData.callCount).toStrictEqual(0);
|
||||
expect(dispatchProps.hideGasButtonGroup.callCount).toStrictEqual(0);
|
||||
expect(dispatchProps.useCustomGas.callCount).toStrictEqual(0);
|
||||
expect(dispatchProps.cancelAndClose.callCount).toStrictEqual(0);
|
||||
|
||||
result.onSubmit('mockNewLimit', 'mockNewPrice');
|
||||
@ -251,7 +253,7 @@ describe('gas-modal-page-container container', () => {
|
||||
'mockNewLimit',
|
||||
'mockNewPrice',
|
||||
]);
|
||||
expect(dispatchProps.hideGasButtonGroup.callCount).toStrictEqual(1);
|
||||
expect(dispatchProps.useCustomGas.callCount).toStrictEqual(1);
|
||||
expect(dispatchProps.cancelAndClose.callCount).toStrictEqual(1);
|
||||
|
||||
expect(dispatchProps.updateCustomGasPrice.callCount).toStrictEqual(0);
|
||||
@ -278,7 +280,7 @@ describe('gas-modal-page-container container', () => {
|
||||
dispatchProps.updateConfirmTxGasAndCalculate.callCount,
|
||||
).toStrictEqual(0);
|
||||
expect(dispatchProps.setGasData.callCount).toStrictEqual(0);
|
||||
expect(dispatchProps.hideGasButtonGroup.callCount).toStrictEqual(0);
|
||||
expect(dispatchProps.useCustomGas.callCount).toStrictEqual(0);
|
||||
expect(dispatchProps.cancelAndClose.callCount).toStrictEqual(1);
|
||||
|
||||
expect(dispatchProps.createSpeedUpTransaction.callCount).toStrictEqual(1);
|
||||
|
@ -14,20 +14,21 @@ import {
|
||||
fetchBasicGasEstimates,
|
||||
} from '../../../../ducks/gas/gas.duck';
|
||||
import {
|
||||
hideGasButtonGroup,
|
||||
setGasLimit,
|
||||
setGasPrice,
|
||||
setGasTotal,
|
||||
updateSendAmount,
|
||||
updateSendErrors,
|
||||
} from '../../../../ducks/send/send.duck';
|
||||
getSendMaxModeState,
|
||||
getGasLimit,
|
||||
getGasPrice,
|
||||
getSendAmount,
|
||||
updateGasLimit,
|
||||
updateGasPrice,
|
||||
useCustomGas,
|
||||
getSendAsset,
|
||||
ASSET_TYPES,
|
||||
} from '../../../../ducks/send';
|
||||
import {
|
||||
conversionRateSelector as getConversionRate,
|
||||
getCurrentCurrency,
|
||||
getCurrentEthBalance,
|
||||
getIsMainnet,
|
||||
getSendToken,
|
||||
getPreferences,
|
||||
getIsTestnet,
|
||||
getBasicGasEstimateLoadingStatus,
|
||||
getCustomGasLimit,
|
||||
@ -35,13 +36,12 @@ import {
|
||||
getDefaultActiveButtonIndex,
|
||||
getRenderableBasicEstimateData,
|
||||
isCustomPriceSafe,
|
||||
getTokenBalance,
|
||||
getSendMaxModeState,
|
||||
isCustomPriceSafeForCustomNetwork,
|
||||
getAveragePriceEstimateInHexWEI,
|
||||
isCustomPriceExcessive,
|
||||
getIsGasEstimatesFetched,
|
||||
getIsCustomNetworkGasPriceFetched,
|
||||
getShouldShowFiat,
|
||||
} from '../../../../selectors';
|
||||
|
||||
import {
|
||||
@ -57,16 +57,15 @@ import {
|
||||
isBalanceSufficient,
|
||||
} from '../../../../pages/send/send.utils';
|
||||
import { MIN_GAS_LIMIT_DEC } from '../../../../pages/send/send.constants';
|
||||
import { calcMaxAmount } from '../../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils';
|
||||
import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction';
|
||||
import { GAS_LIMITS } from '../../../../../shared/constants/gas';
|
||||
import GasModalPageContainer from './gas-modal-page-container.component';
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const {
|
||||
metamask: { currentNetworkTxList },
|
||||
send,
|
||||
} = state;
|
||||
const gasLimit = getGasLimit(state);
|
||||
const gasPrice = getGasPrice(state);
|
||||
const amount = getSendAmount(state);
|
||||
const { currentNetworkTxList } = state.metamask;
|
||||
const { modalState: { props: modalProps } = {} } = state.appState.modal || {};
|
||||
const { txData = {} } = modalProps || {};
|
||||
const { transaction = {}, onSubmit } = ownProps;
|
||||
@ -74,15 +73,15 @@ const mapStateToProps = (state, ownProps) => {
|
||||
({ id }) => id === (transaction.id || txData.id),
|
||||
);
|
||||
const buttonDataLoading = getBasicGasEstimateLoadingStatus(state);
|
||||
const sendToken = getSendToken(state);
|
||||
const asset = getSendAsset(state);
|
||||
|
||||
// a "default" txParams is used during the send flow, since the transaction doesn't exist yet in that case
|
||||
const txParams = selectedTransaction?.txParams
|
||||
? selectedTransaction.txParams
|
||||
: {
|
||||
gas: send.gasLimit || GAS_LIMITS.SIMPLE,
|
||||
gasPrice: send.gasPrice || getAveragePriceEstimateInHexWEI(state, true),
|
||||
value: sendToken ? '0x0' : send.amount,
|
||||
gas: gasLimit || GAS_LIMITS.SIMPLE,
|
||||
gasPrice: gasPrice || getAveragePriceEstimateInHexWEI(state, true),
|
||||
value: asset.type === ASSET_TYPES.TOKEN ? '0x0' : amount,
|
||||
};
|
||||
|
||||
const { gasPrice: currentGasPrice, gas: currentGasLimit } = txParams;
|
||||
@ -116,20 +115,18 @@ const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
const balance = getCurrentEthBalance(state);
|
||||
|
||||
const { showFiatInTestnets } = getPreferences(state);
|
||||
const isMainnet = getIsMainnet(state);
|
||||
const showFiat = Boolean(isMainnet || showFiatInTestnets);
|
||||
const showFiat = getShouldShowFiat(state);
|
||||
|
||||
const isSendTokenSet = Boolean(sendToken);
|
||||
const isTestnet = getIsTestnet(state);
|
||||
|
||||
const newTotalEth =
|
||||
maxModeOn && !isSendTokenSet
|
||||
maxModeOn && asset.type === ASSET_TYPES.NATIVE
|
||||
? sumHexWEIsToRenderableEth([balance, '0x0'])
|
||||
: sumHexWEIsToRenderableEth([value, customGasTotal]);
|
||||
|
||||
const sendAmount =
|
||||
maxModeOn && !isSendTokenSet
|
||||
maxModeOn && asset.type === ASSET_TYPES.NATIVE
|
||||
? subtractHexWEIsFromRenderableEth(balance, customGasTotal)
|
||||
: sumHexWEIsToRenderableEth([value, '0x0']);
|
||||
|
||||
@ -194,9 +191,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
txId: transaction.id,
|
||||
insufficientBalance,
|
||||
isMainnet,
|
||||
sendToken,
|
||||
balance,
|
||||
tokenBalance: getTokenBalance(state),
|
||||
conversionRate,
|
||||
value,
|
||||
onSubmit,
|
||||
@ -213,12 +208,13 @@ const mapDispatchToProps = (dispatch) => {
|
||||
dispatch(hideModal());
|
||||
},
|
||||
hideModal: () => dispatch(hideModal()),
|
||||
useCustomGas: () => dispatch(useCustomGas()),
|
||||
updateCustomGasPrice,
|
||||
updateCustomGasLimit: (newLimit) =>
|
||||
dispatch(setCustomGasLimit(addHexPrefix(newLimit))),
|
||||
setGasData: (newLimit, newPrice) => {
|
||||
dispatch(setGasLimit(newLimit));
|
||||
dispatch(setGasPrice(newPrice));
|
||||
dispatch(updateGasLimit(newLimit));
|
||||
dispatch(updateGasPrice(newPrice));
|
||||
},
|
||||
updateConfirmTxGasAndCalculate: (gasLimit, gasPrice, updatedTx) => {
|
||||
updateCustomGasPrice(gasPrice);
|
||||
@ -231,14 +227,8 @@ const mapDispatchToProps = (dispatch) => {
|
||||
createSpeedUpTransaction: (txId, gasPrice, gasLimit) => {
|
||||
return dispatch(createSpeedUpTransaction(txId, gasPrice, gasLimit));
|
||||
},
|
||||
hideGasButtonGroup: () => dispatch(hideGasButtonGroup()),
|
||||
hideSidebar: () => dispatch(hideSidebar()),
|
||||
fetchBasicGasEstimates: () => dispatch(fetchBasicGasEstimates()),
|
||||
setGasTotal: (total) => dispatch(setGasTotal(total)),
|
||||
setAmountToMax: (maxAmountDataObject) => {
|
||||
dispatch(updateSendErrors({ amount: null }));
|
||||
dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -251,17 +241,12 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
isSpeedUp,
|
||||
isRetry,
|
||||
insufficientBalance,
|
||||
maxModeOn,
|
||||
customGasPrice,
|
||||
customGasTotal,
|
||||
balance,
|
||||
sendToken,
|
||||
tokenBalance,
|
||||
customGasLimit,
|
||||
transaction,
|
||||
} = stateProps;
|
||||
const {
|
||||
hideGasButtonGroup: dispatchHideGasButtonGroup,
|
||||
useCustomGas: dispatchUseCustomGas,
|
||||
setGasData: dispatchSetGasData,
|
||||
updateConfirmTxGasAndCalculate: dispatchUpdateConfirmTxGasAndCalculate,
|
||||
createSpeedUpTransaction: dispatchCreateSpeedUpTransaction,
|
||||
@ -269,7 +254,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
hideSidebar: dispatchHideSidebar,
|
||||
cancelAndClose: dispatchCancelAndClose,
|
||||
hideModal: dispatchHideModal,
|
||||
setAmountToMax: dispatchSetAmountToMax,
|
||||
...otherDispatchProps
|
||||
} = dispatchProps;
|
||||
|
||||
@ -305,17 +289,9 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
dispatchCancelAndClose();
|
||||
} else {
|
||||
dispatchSetGasData(gasLimit, gasPrice);
|
||||
dispatchHideGasButtonGroup();
|
||||
dispatchUseCustomGas();
|
||||
dispatchCancelAndClose();
|
||||
}
|
||||
if (maxModeOn) {
|
||||
dispatchSetAmountToMax({
|
||||
balance,
|
||||
gasTotal: customGasTotal,
|
||||
sendToken,
|
||||
tokenBalance,
|
||||
});
|
||||
}
|
||||
},
|
||||
gasPriceButtonGroupProps: {
|
||||
...gasPriceButtonGroupProps,
|
||||
|
@ -15,6 +15,7 @@ export default function TokenCell({
|
||||
string,
|
||||
image,
|
||||
onClick,
|
||||
isERC721,
|
||||
}) {
|
||||
const userAddress = useSelector(getSelectedAddress);
|
||||
const t = useI18nContext();
|
||||
@ -50,6 +51,7 @@ export default function TokenCell({
|
||||
warning={warning}
|
||||
primary={`${string || 0}`}
|
||||
secondary={formattedFiat}
|
||||
isERC721={isERC721}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -62,6 +64,7 @@ TokenCell.propTypes = {
|
||||
string: PropTypes.string,
|
||||
image: PropTypes.string,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
isERC721: PropTypes.bool,
|
||||
};
|
||||
|
||||
TokenCell.defaultProps = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { getIsMainnet, getPreferences } from '../../../selectors';
|
||||
import { getShouldShowFiat } from '../../../selectors';
|
||||
import { getNativeCurrency } from '../../../ducks/metamask/metamask';
|
||||
import { getHexGasTotal } from '../../../helpers/utils/confirm-tx.util';
|
||||
import { sumHexes } from '../../../helpers/utils/transactions.util';
|
||||
@ -11,8 +11,6 @@ const mapStateToProps = (state, ownProps) => {
|
||||
txParams: { gas, gasPrice, value } = {},
|
||||
txReceipt: { gasUsed } = {},
|
||||
} = transaction;
|
||||
const { showFiatInTestnets } = getPreferences(state);
|
||||
const isMainnet = getIsMainnet(state);
|
||||
|
||||
const gasLimit = typeof gasUsed === 'string' ? gasUsed : gas;
|
||||
|
||||
@ -22,7 +20,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
return {
|
||||
nativeCurrency: getNativeCurrency(state),
|
||||
showFiat: isMainnet || Boolean(showFiatInTestnets),
|
||||
showFiat: getShouldShowFiat(state),
|
||||
totalInHex,
|
||||
gas,
|
||||
gasPrice,
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
} from '../../../hooks/useMetricEvent';
|
||||
import { useTokenTracker } from '../../../hooks/useTokenTracker';
|
||||
import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount';
|
||||
import { updateSendToken } from '../../../ducks/send/send.duck';
|
||||
import { ASSET_TYPES, updateSendAsset } from '../../../ducks/send';
|
||||
import { setSwapsFromToken } from '../../../ducks/swaps/swaps';
|
||||
import {
|
||||
getAssetImages,
|
||||
@ -85,12 +85,19 @@ const TokenOverview = ({ className, token }) => {
|
||||
className="token-overview__button"
|
||||
onClick={() => {
|
||||
sendTokenEvent();
|
||||
dispatch(updateSendToken(token));
|
||||
history.push(SEND_ROUTE);
|
||||
dispatch(
|
||||
updateSendAsset({
|
||||
type: ASSET_TYPES.TOKEN,
|
||||
details: token,
|
||||
}),
|
||||
).then(() => {
|
||||
history.push(SEND_ROUTE);
|
||||
});
|
||||
}}
|
||||
Icon={SendIcon}
|
||||
label={t('send')}
|
||||
data-testid="eth-overview-send"
|
||||
disabled={token.isERC721}
|
||||
/>
|
||||
<IconButton
|
||||
className="token-overview__button"
|
||||
@ -145,6 +152,7 @@ TokenOverview.propTypes = {
|
||||
address: PropTypes.string.isRequired,
|
||||
decimals: PropTypes.number,
|
||||
symbol: PropTypes.string,
|
||||
isERC721: PropTypes.bool,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
|
@ -1,20 +1,19 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { ETH } from '../../../helpers/constants/common';
|
||||
import { getIsMainnet, getPreferences } from '../../../selectors';
|
||||
import { getShouldShowFiat } from '../../../selectors';
|
||||
import CurrencyInput from './currency-input.component';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
metamask: { nativeCurrency, currentCurrency, conversionRate },
|
||||
} = state;
|
||||
const { showFiatInTestnets } = getPreferences(state);
|
||||
const isMainnet = getIsMainnet(state);
|
||||
const showFiat = getShouldShowFiat(state);
|
||||
|
||||
return {
|
||||
nativeCurrency,
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
hideFiat: !isMainnet && !showFiatInTestnets,
|
||||
hideFiat: !showFiat,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -110,3 +110,12 @@
|
||||
'. actions actions actions actions mid mid mid mid right right right';
|
||||
}
|
||||
}
|
||||
|
||||
.list-item--single-content-row {
|
||||
grid-template-areas: 'icon head head head head head head head right right right right';
|
||||
align-items: center;
|
||||
|
||||
@media (min-width: 576px) {
|
||||
grid-template-areas: 'icon head head head head mid mid mid mid right right right';
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,11 @@ export default function ListItem({
|
||||
className,
|
||||
'data-testid': dataTestId,
|
||||
}) {
|
||||
const primaryClassName = classnames('list-item', className);
|
||||
const primaryClassName = classnames(
|
||||
'list-item',
|
||||
className,
|
||||
subtitle || children ? '' : 'list-item--single-content-row',
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -141,6 +141,7 @@ describe('TokenInput Component', () => {
|
||||
}}
|
||||
tokenExchangeRates={{ '0x1': 2 }}
|
||||
showFiat
|
||||
currentCurrency="usd"
|
||||
/>
|
||||
</Provider>,
|
||||
);
|
||||
@ -278,6 +279,7 @@ describe('TokenInput Component', () => {
|
||||
}}
|
||||
tokenExchangeRates={{ '0x1': 2 }}
|
||||
showFiat
|
||||
currentCurrency="usd"
|
||||
/>
|
||||
</Provider>,
|
||||
);
|
||||
|
@ -1,23 +1,17 @@
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
getIsMainnet,
|
||||
getTokenExchangeRates,
|
||||
getPreferences,
|
||||
} from '../../../selectors';
|
||||
import { getTokenExchangeRates, getShouldShowFiat } from '../../../selectors';
|
||||
import TokenInput from './token-input.component';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
metamask: { currentCurrency },
|
||||
} = state;
|
||||
const { showFiatInTestnets } = getPreferences(state);
|
||||
const isMainnet = getIsMainnet(state);
|
||||
|
||||
return {
|
||||
currentCurrency,
|
||||
tokenExchangeRates: getTokenExchangeRates(state),
|
||||
hideConversion: !isMainnet && !showFiatInTestnets,
|
||||
hideConversion: !getShouldShowFiat(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { removeLeadingZeroes } from '../../../pages/send/send.utils';
|
||||
|
||||
function removeLeadingZeroes(str) {
|
||||
return str.replace(/^0*(?=\d)/u, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that attaches a suffix or unit of measurement trailing user input, ex. 'ETH'. Also
|
||||
|
@ -15,10 +15,11 @@ import {
|
||||
getNumberOfAccounts,
|
||||
getNumberOfTokens,
|
||||
} from '../selectors/selectors';
|
||||
import { getSendToken } from '../selectors/send';
|
||||
import { getSendAsset, ASSET_TYPES } from '../ducks/send';
|
||||
import { txDataSelector } from '../selectors/confirm-transaction';
|
||||
import { getEnvironmentType } from '../../app/scripts/lib/util';
|
||||
import { trackMetaMetricsEvent } from '../store/actions';
|
||||
import { getNativeCurrency } from '../ducks/metamask/metamask';
|
||||
|
||||
export const MetaMetricsContext = createContext(() => {
|
||||
captureException(
|
||||
@ -31,7 +32,8 @@ export const MetaMetricsContext = createContext(() => {
|
||||
export function MetaMetricsProvider({ children }) {
|
||||
const txData = useSelector(txDataSelector) || {};
|
||||
const environmentType = getEnvironmentType();
|
||||
const activeCurrency = useSelector(getSendToken)?.symbol;
|
||||
const activeAsset = useSelector(getSendAsset);
|
||||
const nativeAssetSymbol = useSelector(getNativeCurrency);
|
||||
const accountType = useSelector(getAccountType);
|
||||
const confirmTransactionOrigin = txData.origin;
|
||||
const numberOfTokens = useSelector(getNumberOfTokens);
|
||||
@ -72,7 +74,10 @@ export function MetaMetricsProvider({ children }) {
|
||||
action: eventOpts.action,
|
||||
number_of_tokens: numberOfTokens,
|
||||
number_of_accounts: numberOfAccounts,
|
||||
active_currency: activeCurrency,
|
||||
active_currency:
|
||||
activeAsset.type === ASSET_TYPES.NATIVE
|
||||
? nativeAssetSymbol
|
||||
: activeAsset?.details?.symbol,
|
||||
account_type: accountType,
|
||||
is_new_visit: config.is_new_visit,
|
||||
// the properties coming from this key will not match our standards for
|
||||
@ -102,7 +107,8 @@ export function MetaMetricsProvider({ children }) {
|
||||
accountType,
|
||||
currentPath,
|
||||
confirmTransactionOrigin,
|
||||
activeCurrency,
|
||||
activeAsset,
|
||||
nativeAssetSymbol,
|
||||
numberOfTokens,
|
||||
numberOfAccounts,
|
||||
environmentType,
|
||||
|
200
ui/ducks/ens.js
Normal file
200
ui/ducks/ens.js
Normal file
@ -0,0 +1,200 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import ENS from 'ethjs-ens';
|
||||
import log from 'loglevel';
|
||||
import networkMap from 'ethereum-ens-network-map';
|
||||
import { isConfusing } from 'unicode-confusables';
|
||||
import { isHexString } from 'ethereumjs-util';
|
||||
|
||||
import { getCurrentChainId } from '../selectors';
|
||||
import {
|
||||
CHAIN_ID_TO_NETWORK_ID_MAP,
|
||||
MAINNET_NETWORK_ID,
|
||||
} from '../../shared/constants/network';
|
||||
import {
|
||||
CONFUSING_ENS_ERROR,
|
||||
ENS_ILLEGAL_CHARACTER,
|
||||
ENS_NOT_FOUND_ON_NETWORK,
|
||||
ENS_NOT_SUPPORTED_ON_NETWORK,
|
||||
ENS_NO_ADDRESS_FOR_NAME,
|
||||
ENS_REGISTRATION_ERROR,
|
||||
ENS_UNKNOWN_ERROR,
|
||||
} from '../pages/send/send.constants';
|
||||
import { isValidDomainName } from '../helpers/utils/util';
|
||||
import { CHAIN_CHANGED } from '../store/actionConstants';
|
||||
import {
|
||||
BURN_ADDRESS,
|
||||
isBurnAddress,
|
||||
isValidHexAddress,
|
||||
} from '../../shared/modules/hexstring-utils';
|
||||
|
||||
// Local Constants
|
||||
const ZERO_X_ERROR_ADDRESS = '0x';
|
||||
|
||||
const initialState = {
|
||||
stage: 'UNINITIALIZED',
|
||||
resolution: null,
|
||||
error: null,
|
||||
warning: null,
|
||||
network: null,
|
||||
};
|
||||
|
||||
export const ensInitialState = initialState;
|
||||
|
||||
const name = 'ENS';
|
||||
|
||||
let ens = null;
|
||||
|
||||
const slice = createSlice({
|
||||
name,
|
||||
initialState,
|
||||
reducers: {
|
||||
ensLookup: (state, action) => {
|
||||
// first clear out the previous state
|
||||
state.resolution = null;
|
||||
state.error = null;
|
||||
state.warning = null;
|
||||
const { address, ensName, error, network } = action.payload;
|
||||
|
||||
if (error) {
|
||||
if (
|
||||
isValidDomainName(ensName) &&
|
||||
error.message === 'ENS name not defined.'
|
||||
) {
|
||||
state.error =
|
||||
network === MAINNET_NETWORK_ID
|
||||
? ENS_NO_ADDRESS_FOR_NAME
|
||||
: ENS_NOT_FOUND_ON_NETWORK;
|
||||
} else if (error.message === 'Illegal Character for ENS.') {
|
||||
state.error = ENS_ILLEGAL_CHARACTER;
|
||||
} else {
|
||||
log.error(error);
|
||||
state.error = ENS_UNKNOWN_ERROR;
|
||||
}
|
||||
} else if (address) {
|
||||
if (address === BURN_ADDRESS) {
|
||||
state.error = ENS_NO_ADDRESS_FOR_NAME;
|
||||
} else if (address === ZERO_X_ERROR_ADDRESS) {
|
||||
state.error = ENS_REGISTRATION_ERROR;
|
||||
} else {
|
||||
state.resolution = address;
|
||||
}
|
||||
if (isValidDomainName(address) && isConfusing(address)) {
|
||||
state.warning = CONFUSING_ENS_ERROR;
|
||||
}
|
||||
}
|
||||
},
|
||||
enableEnsLookup: (state, action) => {
|
||||
state.stage = 'INITIALIZED';
|
||||
state.error = null;
|
||||
state.resolution = null;
|
||||
state.warning = null;
|
||||
state.network = action.payload;
|
||||
},
|
||||
disableEnsLookup: (state) => {
|
||||
state.stage = 'NO_NETWORK_SUPPORT';
|
||||
state.error = null;
|
||||
state.warning = null;
|
||||
state.resolution = null;
|
||||
state.network = null;
|
||||
},
|
||||
ensNotSupported: (state) => {
|
||||
state.resolution = null;
|
||||
state.warning = null;
|
||||
state.error = ENS_NOT_SUPPORTED_ON_NETWORK;
|
||||
},
|
||||
resetEnsResolution: (state) => {
|
||||
state.resolution = null;
|
||||
state.warning = null;
|
||||
state.error = null;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(CHAIN_CHANGED, (state, action) => {
|
||||
if (action.payload !== state.currentChainId) {
|
||||
state.stage = 'UNINITIALIZED';
|
||||
ens = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const { reducer, actions } = slice;
|
||||
export default reducer;
|
||||
|
||||
const {
|
||||
disableEnsLookup,
|
||||
ensLookup,
|
||||
enableEnsLookup,
|
||||
ensNotSupported,
|
||||
resetEnsResolution,
|
||||
} = actions;
|
||||
export { resetEnsResolution };
|
||||
|
||||
export function initializeEnsSlice() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const chainId = getCurrentChainId(state);
|
||||
const network = CHAIN_ID_TO_NETWORK_ID_MAP[chainId];
|
||||
const networkIsSupported = Boolean(networkMap[network]);
|
||||
if (networkIsSupported) {
|
||||
ens = new ENS({ provider: global.ethereumProvider, network });
|
||||
dispatch(enableEnsLookup(network));
|
||||
} else {
|
||||
ens = null;
|
||||
dispatch(disableEnsLookup());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function lookupEnsName(ensName) {
|
||||
return async (dispatch, getState) => {
|
||||
const trimmedEnsName = ensName.trim();
|
||||
let state = getState();
|
||||
if (state[name].stage === 'UNINITIALIZED') {
|
||||
await dispatch(initializeEnsSlice());
|
||||
}
|
||||
state = getState();
|
||||
if (
|
||||
state[name].stage === 'NO_NETWORK_SUPPORT' &&
|
||||
!(
|
||||
isBurnAddress(trimmedEnsName) === false &&
|
||||
isValidHexAddress(trimmedEnsName, { mixedCaseUseChecksum: true })
|
||||
) &&
|
||||
!isHexString(trimmedEnsName)
|
||||
) {
|
||||
await dispatch(ensNotSupported());
|
||||
} else {
|
||||
log.info(`ENS attempting to resolve name: ${trimmedEnsName}`);
|
||||
let address;
|
||||
let error;
|
||||
try {
|
||||
address = await ens.lookup(trimmedEnsName);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
const chainId = getCurrentChainId(state);
|
||||
const network = CHAIN_ID_TO_NETWORK_ID_MAP[chainId];
|
||||
await dispatch(
|
||||
ensLookup({
|
||||
ensName: trimmedEnsName,
|
||||
address,
|
||||
error,
|
||||
chainId,
|
||||
network,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function getEnsResolution(state) {
|
||||
return state[name].resolution;
|
||||
}
|
||||
|
||||
export function getEnsError(state) {
|
||||
return state[name].error;
|
||||
}
|
||||
|
||||
export function getEnsWarning(state) {
|
||||
return state[name].warning;
|
||||
}
|
14
ui/ducks/gas/gas-action-constants.js
Normal file
14
ui/ducks/gas/gas-action-constants.js
Normal file
@ -0,0 +1,14 @@
|
||||
// This file has been separated because it is required in both the gas and send
|
||||
// slices. This created a circular dependency problem as both slices also
|
||||
// import from the actions and selectors files. This easiest path for
|
||||
// untangling is having the constants separate.
|
||||
|
||||
// Actions
|
||||
export const BASIC_GAS_ESTIMATE_STATUS =
|
||||
'metamask/gas/BASIC_GAS_ESTIMATE_STATUS';
|
||||
export const RESET_CUSTOM_DATA = 'metamask/gas/RESET_CUSTOM_DATA';
|
||||
export const SET_BASIC_GAS_ESTIMATE_DATA =
|
||||
'metamask/gas/SET_BASIC_GAS_ESTIMATE_DATA';
|
||||
export const SET_CUSTOM_GAS_LIMIT = 'metamask/gas/SET_CUSTOM_GAS_LIMIT';
|
||||
export const SET_CUSTOM_GAS_PRICE = 'metamask/gas/SET_CUSTOM_GAS_PRICE';
|
||||
export const SET_ESTIMATE_SOURCE = 'metamask/gas/SET_ESTIMATE_SOURCE';
|
@ -10,6 +10,14 @@ import GasReducer, {
|
||||
fetchBasicGasEstimates,
|
||||
} from './gas.duck';
|
||||
|
||||
import {
|
||||
BASIC_GAS_ESTIMATE_STATUS,
|
||||
SET_BASIC_GAS_ESTIMATE_DATA,
|
||||
SET_CUSTOM_GAS_PRICE,
|
||||
SET_CUSTOM_GAS_LIMIT,
|
||||
SET_ESTIMATE_SOURCE,
|
||||
} from './gas-action-constants';
|
||||
|
||||
jest.mock('../../helpers/utils/storage-helpers.js', () => ({
|
||||
getStorageItem: jest.fn(),
|
||||
setStorageItem: jest.fn(),
|
||||
@ -61,13 +69,6 @@ describe('Gas Duck', () => {
|
||||
type: 'mainnet',
|
||||
};
|
||||
|
||||
const BASIC_GAS_ESTIMATE_STATUS = 'metamask/gas/BASIC_GAS_ESTIMATE_STATUS';
|
||||
const SET_BASIC_GAS_ESTIMATE_DATA =
|
||||
'metamask/gas/SET_BASIC_GAS_ESTIMATE_DATA';
|
||||
const SET_CUSTOM_GAS_LIMIT = 'metamask/gas/SET_CUSTOM_GAS_LIMIT';
|
||||
const SET_CUSTOM_GAS_PRICE = 'metamask/gas/SET_CUSTOM_GAS_PRICE';
|
||||
const SET_ESTIMATE_SOURCE = 'metamask/gas/SET_ESTIMATE_SOURCE';
|
||||
|
||||
describe('GasReducer()', () => {
|
||||
it('should initialize state', () => {
|
||||
expect(GasReducer(undefined, {})).toStrictEqual(initState);
|
||||
|
@ -10,6 +10,14 @@ import {
|
||||
} from '../../helpers/utils/conversions.util';
|
||||
import { getIsMainnet, getCurrentChainId } from '../../selectors';
|
||||
import fetchWithCache from '../../helpers/utils/fetch-with-cache';
|
||||
import {
|
||||
BASIC_GAS_ESTIMATE_STATUS,
|
||||
RESET_CUSTOM_DATA,
|
||||
SET_BASIC_GAS_ESTIMATE_DATA,
|
||||
SET_CUSTOM_GAS_LIMIT,
|
||||
SET_CUSTOM_GAS_PRICE,
|
||||
SET_ESTIMATE_SOURCE,
|
||||
} from './gas-action-constants';
|
||||
|
||||
export const BASIC_ESTIMATE_STATES = {
|
||||
LOADING: 'LOADING',
|
||||
@ -22,14 +30,6 @@ export const GAS_SOURCE = {
|
||||
ETHGASPRICE: 'eth_gasprice',
|
||||
};
|
||||
|
||||
// Actions
|
||||
const BASIC_GAS_ESTIMATE_STATUS = 'metamask/gas/BASIC_GAS_ESTIMATE_STATUS';
|
||||
const RESET_CUSTOM_DATA = 'metamask/gas/RESET_CUSTOM_DATA';
|
||||
const SET_BASIC_GAS_ESTIMATE_DATA = 'metamask/gas/SET_BASIC_GAS_ESTIMATE_DATA';
|
||||
const SET_CUSTOM_GAS_LIMIT = 'metamask/gas/SET_CUSTOM_GAS_LIMIT';
|
||||
const SET_CUSTOM_GAS_PRICE = 'metamask/gas/SET_CUSTOM_GAS_PRICE';
|
||||
const SET_ESTIMATE_SOURCE = 'metamask/gas/SET_ESTIMATE_SOURCE';
|
||||
|
||||
const initState = {
|
||||
customData: {
|
||||
price: null,
|
||||
|
@ -2,7 +2,8 @@ import { combineReducers } from 'redux';
|
||||
import { ALERT_TYPES } from '../../shared/constants/alerts';
|
||||
import metamaskReducer from './metamask/metamask';
|
||||
import localeMessagesReducer from './locale/locale';
|
||||
import sendReducer from './send/send.duck';
|
||||
import sendReducer from './send/send';
|
||||
import ensReducer from './ens';
|
||||
import appStateReducer from './app/app';
|
||||
import confirmTransactionReducer from './confirm-transaction/confirm-transaction.duck';
|
||||
import gasReducer from './gas/gas.duck';
|
||||
@ -16,6 +17,7 @@ export default combineReducers({
|
||||
activeTab: (s) => (s === undefined ? null : s),
|
||||
metamask: metamaskReducer,
|
||||
appState: appStateReducer,
|
||||
ENS: ensReducer,
|
||||
history: historyReducer,
|
||||
send: sendReducer,
|
||||
confirmTransaction: confirmTransactionReducer,
|
||||
|
1
ui/ducks/send/index.js
Normal file
1
ui/ducks/send/index.js
Normal file
@ -0,0 +1 @@
|
||||
export * from './send';
|
@ -1,142 +0,0 @@
|
||||
import SendReducer, {
|
||||
openToDropdown,
|
||||
closeToDropdown,
|
||||
updateSendErrors,
|
||||
showGasButtonGroup,
|
||||
hideGasButtonGroup,
|
||||
} from './send.duck';
|
||||
|
||||
describe('Send Duck', () => {
|
||||
const mockState = {
|
||||
mockProp: 123,
|
||||
};
|
||||
const initState = {
|
||||
toDropdownOpen: false,
|
||||
gasButtonGroupShown: true,
|
||||
errors: {},
|
||||
gasLimit: null,
|
||||
gasPrice: null,
|
||||
gasTotal: null,
|
||||
tokenBalance: '0x0',
|
||||
from: '',
|
||||
to: '',
|
||||
amount: '0',
|
||||
memo: '',
|
||||
maxModeOn: false,
|
||||
editingTransactionId: null,
|
||||
toNickname: '',
|
||||
ensResolution: null,
|
||||
ensResolutionError: '',
|
||||
gasIsLoading: false,
|
||||
};
|
||||
const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN';
|
||||
const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN';
|
||||
const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS';
|
||||
const RESET_SEND_STATE = 'metamask/send/RESET_SEND_STATE';
|
||||
const SHOW_GAS_BUTTON_GROUP = 'metamask/send/SHOW_GAS_BUTTON_GROUP';
|
||||
const HIDE_GAS_BUTTON_GROUP = 'metamask/send/HIDE_GAS_BUTTON_GROUP';
|
||||
|
||||
describe('SendReducer()', () => {
|
||||
it('should initialize state', () => {
|
||||
expect(SendReducer(undefined, {})).toStrictEqual(initState);
|
||||
});
|
||||
|
||||
it('should return state unchanged if it does not match a dispatched actions type', () => {
|
||||
expect(
|
||||
SendReducer(mockState, {
|
||||
type: 'someOtherAction',
|
||||
value: 'someValue',
|
||||
}),
|
||||
).toStrictEqual(mockState);
|
||||
});
|
||||
|
||||
it('should set toDropdownOpen to true when receiving a OPEN_TO_DROPDOWN action', () => {
|
||||
expect(
|
||||
SendReducer(mockState, {
|
||||
type: OPEN_TO_DROPDOWN,
|
||||
}),
|
||||
).toStrictEqual({ toDropdownOpen: true, ...mockState });
|
||||
});
|
||||
|
||||
it('should set toDropdownOpen to false when receiving a CLOSE_TO_DROPDOWN action', () => {
|
||||
expect(
|
||||
SendReducer(mockState, {
|
||||
type: CLOSE_TO_DROPDOWN,
|
||||
}),
|
||||
).toStrictEqual({ toDropdownOpen: false, ...mockState });
|
||||
});
|
||||
|
||||
it('should set gasButtonGroupShown to true when receiving a SHOW_GAS_BUTTON_GROUP action', () => {
|
||||
expect(
|
||||
SendReducer(
|
||||
{ ...mockState, gasButtonGroupShown: false },
|
||||
{ type: SHOW_GAS_BUTTON_GROUP },
|
||||
),
|
||||
).toStrictEqual({ gasButtonGroupShown: true, ...mockState });
|
||||
});
|
||||
|
||||
it('should set gasButtonGroupShown to false when receiving a HIDE_GAS_BUTTON_GROUP action', () => {
|
||||
expect(
|
||||
SendReducer(mockState, { type: HIDE_GAS_BUTTON_GROUP }),
|
||||
).toStrictEqual({ gasButtonGroupShown: false, ...mockState });
|
||||
});
|
||||
|
||||
it('should extend send.errors with the value of a UPDATE_SEND_ERRORS action', () => {
|
||||
const modifiedMockState = {
|
||||
...mockState,
|
||||
errors: {
|
||||
someError: false,
|
||||
},
|
||||
};
|
||||
expect(
|
||||
SendReducer(modifiedMockState, {
|
||||
type: UPDATE_SEND_ERRORS,
|
||||
value: { someOtherError: true },
|
||||
}),
|
||||
).toStrictEqual({
|
||||
...modifiedMockState,
|
||||
errors: {
|
||||
someError: false,
|
||||
someOtherError: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the initial state in response to a RESET_SEND_STATE action', () => {
|
||||
expect(
|
||||
SendReducer(mockState, {
|
||||
type: RESET_SEND_STATE,
|
||||
}),
|
||||
).toStrictEqual(initState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Send Duck Actions', () => {
|
||||
it('calls openToDropdown action', () => {
|
||||
expect(openToDropdown()).toStrictEqual({ type: OPEN_TO_DROPDOWN });
|
||||
});
|
||||
|
||||
it('calls closeToDropdown action', () => {
|
||||
expect(closeToDropdown()).toStrictEqual({ type: CLOSE_TO_DROPDOWN });
|
||||
});
|
||||
|
||||
it('calls showGasButtonGroup action', () => {
|
||||
expect(showGasButtonGroup()).toStrictEqual({
|
||||
type: SHOW_GAS_BUTTON_GROUP,
|
||||
});
|
||||
});
|
||||
|
||||
it('calls hideGasButtonGroup action', () => {
|
||||
expect(hideGasButtonGroup()).toStrictEqual({
|
||||
type: HIDE_GAS_BUTTON_GROUP,
|
||||
});
|
||||
});
|
||||
|
||||
it('calls updateSendErrors action', () => {
|
||||
expect(updateSendErrors('mockErrorObject')).toStrictEqual({
|
||||
type: UPDATE_SEND_ERRORS,
|
||||
value: 'mockErrorObject',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,382 +0,0 @@
|
||||
import log from 'loglevel';
|
||||
import { estimateGas } from '../../store/actions';
|
||||
import { setCustomGasLimit } from '../gas/gas.duck';
|
||||
import {
|
||||
estimateGasForSend,
|
||||
calcTokenBalance,
|
||||
} from '../../pages/send/send.utils';
|
||||
|
||||
// Actions
|
||||
const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN';
|
||||
const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN';
|
||||
const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS';
|
||||
const RESET_SEND_STATE = 'metamask/send/RESET_SEND_STATE';
|
||||
const SHOW_GAS_BUTTON_GROUP = 'metamask/send/SHOW_GAS_BUTTON_GROUP';
|
||||
const HIDE_GAS_BUTTON_GROUP = 'metamask/send/HIDE_GAS_BUTTON_GROUP';
|
||||
const UPDATE_GAS_LIMIT = 'UPDATE_GAS_LIMIT';
|
||||
const UPDATE_GAS_PRICE = 'UPDATE_GAS_PRICE';
|
||||
const UPDATE_GAS_TOTAL = 'UPDATE_GAS_TOTAL';
|
||||
const UPDATE_SEND_HEX_DATA = 'UPDATE_SEND_HEX_DATA';
|
||||
const UPDATE_SEND_TOKEN_BALANCE = 'UPDATE_SEND_TOKEN_BALANCE';
|
||||
const UPDATE_SEND_TO = 'UPDATE_SEND_TO';
|
||||
const UPDATE_SEND_AMOUNT = 'UPDATE_SEND_AMOUNT';
|
||||
const UPDATE_MAX_MODE = 'UPDATE_MAX_MODE';
|
||||
const UPDATE_SEND = 'UPDATE_SEND';
|
||||
const UPDATE_SEND_TOKEN = 'UPDATE_SEND_TOKEN';
|
||||
const CLEAR_SEND = 'CLEAR_SEND';
|
||||
const GAS_LOADING_STARTED = 'GAS_LOADING_STARTED';
|
||||
const GAS_LOADING_FINISHED = 'GAS_LOADING_FINISHED';
|
||||
const UPDATE_SEND_ENS_RESOLUTION = 'UPDATE_SEND_ENS_RESOLUTION';
|
||||
const UPDATE_SEND_ENS_RESOLUTION_ERROR = 'UPDATE_SEND_ENS_RESOLUTION_ERROR';
|
||||
|
||||
const initState = {
|
||||
toDropdownOpen: false,
|
||||
gasButtonGroupShown: true,
|
||||
errors: {},
|
||||
gasLimit: null,
|
||||
gasPrice: null,
|
||||
gasTotal: null,
|
||||
tokenBalance: '0x0',
|
||||
from: '',
|
||||
to: '',
|
||||
amount: '0',
|
||||
memo: '',
|
||||
maxModeOn: false,
|
||||
editingTransactionId: null,
|
||||
toNickname: '',
|
||||
ensResolution: null,
|
||||
ensResolutionError: '',
|
||||
gasIsLoading: false,
|
||||
};
|
||||
|
||||
// Reducer
|
||||
export default function reducer(state = initState, action) {
|
||||
switch (action.type) {
|
||||
case OPEN_TO_DROPDOWN:
|
||||
return {
|
||||
...state,
|
||||
toDropdownOpen: true,
|
||||
};
|
||||
case CLOSE_TO_DROPDOWN:
|
||||
return {
|
||||
...state,
|
||||
toDropdownOpen: false,
|
||||
};
|
||||
case UPDATE_SEND_ERRORS:
|
||||
return {
|
||||
...state,
|
||||
errors: {
|
||||
...state.errors,
|
||||
...action.value,
|
||||
},
|
||||
};
|
||||
case SHOW_GAS_BUTTON_GROUP:
|
||||
return {
|
||||
...state,
|
||||
gasButtonGroupShown: true,
|
||||
};
|
||||
case HIDE_GAS_BUTTON_GROUP:
|
||||
return {
|
||||
...state,
|
||||
gasButtonGroupShown: false,
|
||||
};
|
||||
case UPDATE_GAS_LIMIT:
|
||||
return {
|
||||
...state,
|
||||
gasLimit: action.value,
|
||||
};
|
||||
case UPDATE_GAS_PRICE:
|
||||
return {
|
||||
...state,
|
||||
gasPrice: action.value,
|
||||
};
|
||||
case RESET_SEND_STATE:
|
||||
return { ...initState };
|
||||
case UPDATE_GAS_TOTAL:
|
||||
return {
|
||||
...state,
|
||||
gasTotal: action.value,
|
||||
};
|
||||
case UPDATE_SEND_TOKEN_BALANCE:
|
||||
return {
|
||||
...state,
|
||||
tokenBalance: action.value,
|
||||
};
|
||||
case UPDATE_SEND_HEX_DATA:
|
||||
return {
|
||||
...state,
|
||||
data: action.value,
|
||||
};
|
||||
case UPDATE_SEND_TO:
|
||||
return {
|
||||
...state,
|
||||
to: action.value.to,
|
||||
toNickname: action.value.nickname,
|
||||
};
|
||||
case UPDATE_SEND_AMOUNT:
|
||||
return {
|
||||
...state,
|
||||
amount: action.value,
|
||||
};
|
||||
case UPDATE_MAX_MODE:
|
||||
return {
|
||||
...state,
|
||||
maxModeOn: action.value,
|
||||
};
|
||||
case UPDATE_SEND:
|
||||
return Object.assign(state, action.value);
|
||||
case UPDATE_SEND_TOKEN: {
|
||||
const newSend = {
|
||||
...state,
|
||||
token: action.value,
|
||||
};
|
||||
// erase token-related state when switching back to native currency
|
||||
if (newSend.editingTransactionId && !newSend.token) {
|
||||
const unapprovedTx =
|
||||
newSend?.unapprovedTxs?.[newSend.editingTransactionId] || {};
|
||||
const txParams = unapprovedTx.txParams || {};
|
||||
Object.assign(newSend, {
|
||||
tokenBalance: null,
|
||||
balance: '0',
|
||||
from: unapprovedTx.from || '',
|
||||
unapprovedTxs: {
|
||||
...newSend.unapprovedTxs,
|
||||
[newSend.editingTransactionId]: {
|
||||
...unapprovedTx,
|
||||
txParams: {
|
||||
...txParams,
|
||||
data: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return Object.assign(state, newSend);
|
||||
}
|
||||
case UPDATE_SEND_ENS_RESOLUTION:
|
||||
return {
|
||||
...state,
|
||||
ensResolution: action.payload,
|
||||
ensResolutionError: '',
|
||||
};
|
||||
case UPDATE_SEND_ENS_RESOLUTION_ERROR:
|
||||
return {
|
||||
...state,
|
||||
ensResolution: null,
|
||||
ensResolutionError: action.payload,
|
||||
};
|
||||
case CLEAR_SEND:
|
||||
return {
|
||||
...state,
|
||||
gasLimit: null,
|
||||
gasPrice: null,
|
||||
gasTotal: null,
|
||||
tokenBalance: null,
|
||||
from: '',
|
||||
to: '',
|
||||
amount: '0x0',
|
||||
memo: '',
|
||||
errors: {},
|
||||
maxModeOn: false,
|
||||
editingTransactionId: null,
|
||||
toNickname: '',
|
||||
};
|
||||
case GAS_LOADING_STARTED:
|
||||
return {
|
||||
...state,
|
||||
gasIsLoading: true,
|
||||
};
|
||||
|
||||
case GAS_LOADING_FINISHED:
|
||||
return {
|
||||
...state,
|
||||
gasIsLoading: false,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// Action Creators
|
||||
export function openToDropdown() {
|
||||
return { type: OPEN_TO_DROPDOWN };
|
||||
}
|
||||
|
||||
export function closeToDropdown() {
|
||||
return { type: CLOSE_TO_DROPDOWN };
|
||||
}
|
||||
|
||||
export function showGasButtonGroup() {
|
||||
return { type: SHOW_GAS_BUTTON_GROUP };
|
||||
}
|
||||
|
||||
export function hideGasButtonGroup() {
|
||||
return { type: HIDE_GAS_BUTTON_GROUP };
|
||||
}
|
||||
|
||||
export function updateSendErrors(errorObject) {
|
||||
return {
|
||||
type: UPDATE_SEND_ERRORS,
|
||||
value: errorObject,
|
||||
};
|
||||
}
|
||||
|
||||
export function resetSendState() {
|
||||
return { type: RESET_SEND_STATE };
|
||||
}
|
||||
|
||||
export function setGasLimit(gasLimit) {
|
||||
return {
|
||||
type: UPDATE_GAS_LIMIT,
|
||||
value: gasLimit,
|
||||
};
|
||||
}
|
||||
|
||||
export function setGasPrice(gasPrice) {
|
||||
return {
|
||||
type: UPDATE_GAS_PRICE,
|
||||
value: gasPrice,
|
||||
};
|
||||
}
|
||||
|
||||
export function setGasTotal(gasTotal) {
|
||||
return {
|
||||
type: UPDATE_GAS_TOTAL,
|
||||
value: gasTotal,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateGasData({
|
||||
gasPrice,
|
||||
blockGasLimit,
|
||||
selectedAddress,
|
||||
sendToken,
|
||||
to,
|
||||
value,
|
||||
data,
|
||||
}) {
|
||||
return (dispatch) => {
|
||||
dispatch(gasLoadingStarted());
|
||||
return estimateGasForSend({
|
||||
estimateGasMethod: estimateGas,
|
||||
blockGasLimit,
|
||||
selectedAddress,
|
||||
sendToken,
|
||||
to,
|
||||
value,
|
||||
estimateGasPrice: gasPrice,
|
||||
data,
|
||||
})
|
||||
.then((gas) => {
|
||||
dispatch(setGasLimit(gas));
|
||||
dispatch(setCustomGasLimit(gas));
|
||||
dispatch(updateSendErrors({ gasLoadingError: null }));
|
||||
dispatch(gasLoadingFinished());
|
||||
})
|
||||
.catch((err) => {
|
||||
log.error(err);
|
||||
dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' }));
|
||||
dispatch(gasLoadingFinished());
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function gasLoadingStarted() {
|
||||
return {
|
||||
type: GAS_LOADING_STARTED,
|
||||
};
|
||||
}
|
||||
|
||||
export function gasLoadingFinished() {
|
||||
return {
|
||||
type: GAS_LOADING_FINISHED,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateSendTokenBalance({ sendToken, tokenContract, address }) {
|
||||
return (dispatch) => {
|
||||
const tokenBalancePromise = tokenContract
|
||||
? tokenContract.balanceOf(address)
|
||||
: Promise.resolve();
|
||||
return tokenBalancePromise
|
||||
.then((usersToken) => {
|
||||
if (usersToken) {
|
||||
const newTokenBalance = calcTokenBalance({ sendToken, usersToken });
|
||||
dispatch(setSendTokenBalance(newTokenBalance));
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
log.error(err);
|
||||
updateSendErrors({ tokenBalance: 'tokenBalanceError' });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function setSendTokenBalance(tokenBalance) {
|
||||
return {
|
||||
type: UPDATE_SEND_TOKEN_BALANCE,
|
||||
value: tokenBalance,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateSendHexData(value) {
|
||||
return {
|
||||
type: UPDATE_SEND_HEX_DATA,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateSendTo(to, nickname = '') {
|
||||
return {
|
||||
type: UPDATE_SEND_TO,
|
||||
value: { to, nickname },
|
||||
};
|
||||
}
|
||||
|
||||
export function updateSendAmount(amount) {
|
||||
return {
|
||||
type: UPDATE_SEND_AMOUNT,
|
||||
value: amount,
|
||||
};
|
||||
}
|
||||
|
||||
export function setMaxModeTo(bool) {
|
||||
return {
|
||||
type: UPDATE_MAX_MODE,
|
||||
value: bool,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateSend(newSend) {
|
||||
return {
|
||||
type: UPDATE_SEND,
|
||||
value: newSend,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateSendToken(token) {
|
||||
return {
|
||||
type: UPDATE_SEND_TOKEN,
|
||||
value: token,
|
||||
};
|
||||
}
|
||||
|
||||
export function clearSend() {
|
||||
return {
|
||||
type: CLEAR_SEND,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateSendEnsResolution(ensResolution) {
|
||||
return {
|
||||
type: UPDATE_SEND_ENS_RESOLUTION,
|
||||
payload: ensResolution,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateSendEnsResolutionError(errorMessage) {
|
||||
return {
|
||||
type: UPDATE_SEND_ENS_RESOLUTION_ERROR,
|
||||
payload: errorMessage,
|
||||
};
|
||||
}
|
1508
ui/ducks/send/send.js
Normal file
1508
ui/ducks/send/send.js
Normal file
File diff suppressed because it is too large
Load Diff
1859
ui/ducks/send/send.test.js
Normal file
1859
ui/ducks/send/send.test.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -34,9 +34,10 @@ import {
|
||||
SWAPS_MAINTENANCE_ROUTE,
|
||||
} from '../../helpers/constants/routes';
|
||||
import {
|
||||
fetchSwapsFeatureLiveness,
|
||||
fetchSwapsFeatureFlags,
|
||||
fetchSwapsGasPrices,
|
||||
isContractAddressValid,
|
||||
getSwapsLivenessForNetwork,
|
||||
} from '../../pages/swaps/swaps.util';
|
||||
import { calcGasTotal } from '../../pages/send/send.utils';
|
||||
import {
|
||||
@ -223,9 +224,12 @@ export function shouldShowCustomPriceTooLowWarning(state) {
|
||||
|
||||
const getSwapsState = (state) => state.metamask.swapsState;
|
||||
|
||||
export const getSwapsFeatureLiveness = (state) =>
|
||||
export const getSwapsFeatureIsLive = (state) =>
|
||||
state.metamask.swapsState.swapsFeatureIsLive;
|
||||
|
||||
export const getUseNewSwapsApi = (state) =>
|
||||
state.metamask.swapsState.useNewSwapsApi;
|
||||
|
||||
export const getSwapsQuoteRefreshTime = (state) =>
|
||||
state.metamask.swapsState.swapsQuoteRefreshTime;
|
||||
|
||||
@ -373,16 +377,21 @@ export const fetchAndSetSwapsGasPriceInfo = () => {
|
||||
|
||||
export const fetchSwapsLiveness = () => {
|
||||
return async (dispatch, getState) => {
|
||||
let swapsFeatureIsLive = false;
|
||||
let swapsLivenessForNetwork = {
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
try {
|
||||
swapsFeatureIsLive = await fetchSwapsFeatureLiveness(
|
||||
const swapsFeatureFlags = await fetchSwapsFeatureFlags();
|
||||
swapsLivenessForNetwork = getSwapsLivenessForNetwork(
|
||||
swapsFeatureFlags,
|
||||
getCurrentChainId(getState()),
|
||||
);
|
||||
} catch (error) {
|
||||
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
||||
}
|
||||
await dispatch(setSwapsLiveness(swapsFeatureIsLive));
|
||||
return swapsFeatureIsLive;
|
||||
await dispatch(setSwapsLiveness(swapsLivenessForNetwork));
|
||||
return swapsLivenessForNetwork;
|
||||
};
|
||||
};
|
||||
|
||||
@ -395,15 +404,22 @@ export const fetchQuotesAndSetQuoteState = (
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const chainId = getCurrentChainId(state);
|
||||
let swapsFeatureIsLive = false;
|
||||
let swapsLivenessForNetwork = {
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
try {
|
||||
swapsFeatureIsLive = await fetchSwapsFeatureLiveness(chainId);
|
||||
const swapsFeatureFlags = await fetchSwapsFeatureFlags();
|
||||
swapsLivenessForNetwork = getSwapsLivenessForNetwork(
|
||||
swapsFeatureFlags,
|
||||
chainId,
|
||||
);
|
||||
} catch (error) {
|
||||
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
||||
}
|
||||
await dispatch(setSwapsLiveness(swapsFeatureIsLive));
|
||||
await dispatch(setSwapsLiveness(swapsLivenessForNetwork));
|
||||
|
||||
if (!swapsFeatureIsLive) {
|
||||
if (!swapsLivenessForNetwork.swapsFeatureIsLive) {
|
||||
await history.push(SWAPS_MAINTENANCE_ROUTE);
|
||||
return;
|
||||
}
|
||||
@ -600,15 +616,22 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
|
||||
const state = getState();
|
||||
const chainId = getCurrentChainId(state);
|
||||
const hardwareWalletUsed = isHardwareWallet(state);
|
||||
let swapsFeatureIsLive = false;
|
||||
let swapsLivenessForNetwork = {
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
try {
|
||||
swapsFeatureIsLive = await fetchSwapsFeatureLiveness(chainId);
|
||||
const swapsFeatureFlags = await fetchSwapsFeatureFlags();
|
||||
swapsLivenessForNetwork = getSwapsLivenessForNetwork(
|
||||
swapsFeatureFlags,
|
||||
chainId,
|
||||
);
|
||||
} catch (error) {
|
||||
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
||||
}
|
||||
await dispatch(setSwapsLiveness(swapsFeatureIsLive));
|
||||
await dispatch(setSwapsLiveness(swapsLivenessForNetwork));
|
||||
|
||||
if (!swapsFeatureIsLive) {
|
||||
if (!swapsLivenessForNetwork.swapsFeatureIsLive) {
|
||||
await history.push(SWAPS_MAINTENANCE_ROUTE);
|
||||
return;
|
||||
}
|
||||
@ -808,12 +831,13 @@ export function fetchMetaSwapsGasPriceEstimates() {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const chainId = getCurrentChainId(state);
|
||||
const useNewSwapsApi = getUseNewSwapsApi(state);
|
||||
|
||||
dispatch(swapGasPriceEstimatesFetchStarted());
|
||||
|
||||
let priceEstimates;
|
||||
try {
|
||||
priceEstimates = await fetchSwapsGasPrices(chainId);
|
||||
priceEstimates = await fetchSwapsGasPrices(chainId, useNewSwapsApi);
|
||||
} catch (e) {
|
||||
log.warn('Fetching swaps gas prices failed:', e);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import nock from 'nock';
|
||||
|
||||
import { MOCKS } from '../../../test/jest';
|
||||
import { setSwapsLiveness } from '../../store/actions';
|
||||
import { setStorageItem } from '../../helpers/utils/storage-helpers';
|
||||
import * as swaps from './swaps';
|
||||
@ -25,7 +26,7 @@ describe('Ducks - Swaps', () => {
|
||||
describe('fetchSwapsLiveness', () => {
|
||||
const cleanFeatureFlagApiCache = () => {
|
||||
setStorageItem(
|
||||
'cachedFetch:https://api.metaswap.codefi.network/featureFlag',
|
||||
'cachedFetch:https://api2.metaswap.codefi.network/featureFlags',
|
||||
null,
|
||||
);
|
||||
};
|
||||
@ -34,12 +35,12 @@ describe('Ducks - Swaps', () => {
|
||||
cleanFeatureFlagApiCache();
|
||||
});
|
||||
|
||||
const mockFeatureFlagApiResponse = ({
|
||||
active = false,
|
||||
const mockFeatureFlagsApiResponse = ({
|
||||
featureFlagsResponse,
|
||||
replyWithError = false,
|
||||
} = {}) => {
|
||||
const apiNock = nock('https://api.metaswap.codefi.network').get(
|
||||
'/featureFlag',
|
||||
const apiNock = nock('https://api2.metaswap.codefi.network').get(
|
||||
'/featureFlags',
|
||||
);
|
||||
if (replyWithError) {
|
||||
return apiNock.replyWithError({
|
||||
@ -47,9 +48,7 @@ describe('Ducks - Swaps', () => {
|
||||
code: 'serverSideError',
|
||||
});
|
||||
}
|
||||
return apiNock.reply(200, {
|
||||
active,
|
||||
});
|
||||
return apiNock.reply(200, featureFlagsResponse);
|
||||
};
|
||||
|
||||
const createGetState = () => {
|
||||
@ -58,61 +57,111 @@ describe('Ducks - Swaps', () => {
|
||||
});
|
||||
};
|
||||
|
||||
it('returns true if the Swaps feature is enabled', async () => {
|
||||
it('checks that Swaps for ETH are enabled and can use new API', async () => {
|
||||
const mockDispatch = jest.fn();
|
||||
const featureFlagApiNock = mockFeatureFlagApiResponse({ active: true });
|
||||
const isSwapsFeatureEnabled = await swaps.fetchSwapsLiveness()(
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: true,
|
||||
};
|
||||
const featureFlagsResponse = MOCKS.createFeatureFlagsResponse();
|
||||
const featureFlagApiNock = mockFeatureFlagsApiResponse({
|
||||
featureFlagsResponse,
|
||||
});
|
||||
const swapsLiveness = await swaps.fetchSwapsLiveness()(
|
||||
mockDispatch,
|
||||
createGetState(),
|
||||
);
|
||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(true);
|
||||
expect(isSwapsFeatureEnabled).toBe(true);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
||||
it('returns false if the Swaps feature is disabled', async () => {
|
||||
it('checks that Swaps for ETH are disabled for API v2 and enabled for API v1', async () => {
|
||||
const mockDispatch = jest.fn();
|
||||
const featureFlagApiNock = mockFeatureFlagApiResponse({ active: false });
|
||||
const isSwapsFeatureEnabled = await swaps.fetchSwapsLiveness()(
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
const featureFlagsResponse = MOCKS.createFeatureFlagsResponse();
|
||||
featureFlagsResponse.ethereum.extension_active = false;
|
||||
const featureFlagApiNock = mockFeatureFlagsApiResponse({
|
||||
featureFlagsResponse,
|
||||
});
|
||||
const swapsLiveness = await swaps.fetchSwapsLiveness()(
|
||||
mockDispatch,
|
||||
createGetState(),
|
||||
);
|
||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(false);
|
||||
expect(isSwapsFeatureEnabled).toBe(false);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
||||
it('returns false if the /featureFlag API call throws an error', async () => {
|
||||
it('checks that Swaps for ETH are disabled for API v1 and v2', async () => {
|
||||
const mockDispatch = jest.fn();
|
||||
const featureFlagApiNock = mockFeatureFlagApiResponse({
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
const featureFlagsResponse = MOCKS.createFeatureFlagsResponse();
|
||||
featureFlagsResponse.ethereum.extension_active = false;
|
||||
featureFlagsResponse.ethereum.fallback_to_v1 = false;
|
||||
const featureFlagApiNock = mockFeatureFlagsApiResponse({
|
||||
featureFlagsResponse,
|
||||
});
|
||||
const swapsLiveness = await swaps.fetchSwapsLiveness()(
|
||||
mockDispatch,
|
||||
createGetState(),
|
||||
);
|
||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
||||
it('checks that Swaps for ETH are disabled if the /featureFlags API call throws an error', async () => {
|
||||
const mockDispatch = jest.fn();
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
const featureFlagApiNock = mockFeatureFlagsApiResponse({
|
||||
replyWithError: true,
|
||||
});
|
||||
const isSwapsFeatureEnabled = await swaps.fetchSwapsLiveness()(
|
||||
const swapsLiveness = await swaps.fetchSwapsLiveness()(
|
||||
mockDispatch,
|
||||
createGetState(),
|
||||
);
|
||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(false);
|
||||
expect(isSwapsFeatureEnabled).toBe(false);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
||||
it('only calls the API once and returns true from cache for the second call', async () => {
|
||||
it('only calls the API once and returns response from cache for the second call', async () => {
|
||||
const mockDispatch = jest.fn();
|
||||
const featureFlagApiNock = mockFeatureFlagApiResponse({ active: true });
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: true,
|
||||
};
|
||||
const featureFlagsResponse = MOCKS.createFeatureFlagsResponse();
|
||||
const featureFlagApiNock = mockFeatureFlagsApiResponse({
|
||||
featureFlagsResponse,
|
||||
});
|
||||
await swaps.fetchSwapsLiveness()(mockDispatch, createGetState());
|
||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||
const featureFlagApiNock2 = mockFeatureFlagApiResponse({ active: true });
|
||||
const isSwapsFeatureEnabled = await swaps.fetchSwapsLiveness()(
|
||||
const featureFlagApiNock2 = mockFeatureFlagsApiResponse({
|
||||
featureFlagsResponse,
|
||||
});
|
||||
const swapsLiveness = await swaps.fetchSwapsLiveness()(
|
||||
mockDispatch,
|
||||
createGetState(),
|
||||
);
|
||||
expect(featureFlagApiNock2.isDone()).toBe(false); // Second API call wasn't made, cache was used instead.
|
||||
expect(mockDispatch).toHaveBeenCalledTimes(2);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(true);
|
||||
expect(isSwapsFeatureEnabled).toBe(true);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -5,3 +5,4 @@ export const TRANSACTION_NO_CONTRACT_ERROR_KEY = 'transactionErrorNoContract';
|
||||
export const ETH_GAS_PRICE_FETCH_WARNING_KEY = 'ethGasPriceFetchWarning';
|
||||
export const GAS_PRICE_FETCH_FAILURE_ERROR_KEY = 'gasPriceFetchFailed';
|
||||
export const GAS_PRICE_EXCESSIVE_ERROR_KEY = 'gasPriceExcessive';
|
||||
export const UNSENDABLE_ASSET_ERROR_KEY = 'unsendableAsset';
|
||||
|
@ -150,8 +150,11 @@ const conversionUtil = (
|
||||
conversionRate,
|
||||
invertConversionRate,
|
||||
},
|
||||
) =>
|
||||
converter({
|
||||
) => {
|
||||
if (fromCurrency !== toCurrency && !conversionRate) {
|
||||
return 0;
|
||||
}
|
||||
return converter({
|
||||
fromCurrency,
|
||||
toCurrency,
|
||||
fromNumericBase,
|
||||
@ -163,6 +166,7 @@ const conversionUtil = (
|
||||
invertConversionRate,
|
||||
value: value || '0',
|
||||
});
|
||||
};
|
||||
|
||||
const getBigNumber = (value, base) => {
|
||||
if (!isValidBase(base)) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user